Hello once again! Welcome back to the dev blog of Blightmare. Today we’re going to do a little bit of a deeper dive into one specific feature of Blissa: her hair.
We have tried a couple of different approaches to animation of Blissa’s hair, but so far we haven’t settled on the final version. In this post I will present two of those methods: authored animations and physics based. The current state of hair is actually worse than either of these attempts because we decided to focus on the level design and mechanic tuning rather than the visual effects and somewhere along the way the hair simulation broke down. Eventually we will shift focus back on the visuals and I’ll be returning to this area which will be really exciting. I think the primary reason I got into games is the visual aspect and I always enjoy fiddling around with different visual effects. Let’s jump right in.
The simplest way to do hair as a programmer is to not do it at all. In this case, we rely entirely on our artists to make something that looks good and bake the effects into the regular character animations. This works just like the rest of the character animations that we discussed last week. Often there’s not even extra states to add to the state machine so this can just be dropped in without changing the code. I slowed down the video below to half speed so that you can see the hair animation a little better. It’s pretty subtle, but you can see a wind effect when Blissa is running.
When Blissa jumps, you can see the hair react, but when she falls off the edge at the end there isn’t quite the same reaction. This illustrates one of the big issues with relying on direct animation. It can be very difficult for an artist to anticipate all the things that the character model might be doing especially as new mechanics are being added. Even when the set of motions can be predicted, there’s an explosion of combinations that can happen when the various mechanics can interact with each other. For example, we have a “hang” pose for when Blissa is hanging from a cocoon. Her hair is still here of course because she isn’t moving. However, sometimes the thing she is hanging from is moving, like in the case of a caterpillar on the wall. This would mean that we would need multiple hanging animations. Additionally, the direction that a caterpillar is moving should affect the direction her hair moves, so we need many animations just for hanging and some complex logic now to figure out which one to play and how to blend between them. Yuck.
On a team as small as this one, we don’t have the time to be building so many animations. Additionally, it just makes everything more complicated: the amount of assets, the animation names, the animation code, etc. Maintaining such a system eventually becomes a significant burden on multiple team members which means we have less time to spend doing other things. Can we do better?
Of course we can! I wouldn’t have picked this topic to post about if we weren’t going to get to a solution where the programmers save the day.
Let’s think for a second about what the problem that we have is. What we want is for Blissa’s hair to react in a natural way to her movements. Furthermore, we want this to happen every time she moves, and we don’t want to have to tell the system that a movement happened. It should “Just Work.” Generating an animation at runtime is called “procedural animation” and it’s pretty common even in games that aren’t doing lots of things procedurally like Minecraft or No Man’s Sky.
Our task is to build a system that we can hook into the character animation to simulate hair. It turns out that hair is a really complex problem to solve correctly because of a number of issues like self-collision – you don’t want hair strands to intersect each other – and the sheer number of strands of hair to make a nice dense visual effect. For our purposes then, we will simplify things greatly and rely on our artists to help fill in the gaps. In particular, we’re only going to simulate a couple of regions of hair so that we can get the high impact effects from starting/stopping running or jumping and falling. In order to do this, we’re going to turn to a little physics for help.
These are some of the conditions that we want to be able to handle. The hair sections should be in some base position when Blissa is completely idle, they should move up and away from the direction she is moving in, but more when she’s going faster, and if there’s an abrupt stop, there should be some sway before coming back to idle. It turns out that this is exactly what might happen if you attached a slinky to the back of your head and then ran around. The term for such a system is “mass and spring” and that’s what we’re going to try to simulate here. Our case is a little more complex though because we have constraints to where our mass – or the end of the hair – can go. We don’t want really stretchy hair because that would look silly, and we don’t want the hair to fall straight down when we’re stopped. Additionally, the strand needs to stay connected to Blissa’s head at all times.
Now that we have a bit of an understanding of the problem, lets figure out how to build something that implements it. The first thing to try is what is built-in to Unity itself. There’s a complete 2D physics system with various constraints and rigid bodies. I took a night and tried to build a really simple test configuration to see if I could get something that looked like it could work. I ended up being really frustrated and didn’t get anywhere close to what I wanted. Being a programmer, I decided to take matters into my own fingers.
Implementing a physics simulation seems like it should be rather easy initially. We know the basic equation of motion:
Where t is time, a is acceleration, v is velocity, and p0 is the starting position. Implementing this into a game requires breaking it down into individual time steps – these are our frames – and pretending that nothing happens in between these points. This process is called discretization, and it’s the most common way to use computers to simulate physics. The obvious way to implement this is the direct way. Store a velocity and acceleration and then calculate a new position directly given some time step. The problem with this is that it introduces pretty significant error over time. I won’t go into the math here, but this implementation follows what is called the forward Euler method which is a first-order approximation and therefore has error proportional to the step size. This means that we can reduce the error by taking smaller steps, but that means we have to run the simulation more times for the same amount of simulation time and that doesn’t scale very well. We don’t want to do that, so we will use a slightly better method. This method is called Verlet Integration and it has second-order error, meaning the error is proportional to the square of the step size. The chart below shows what this means in practice.
Red is the correct trajectory, Green is the approximation using Verlet, and Blue is the approximation using Forward Euler. As you can see, we’re going to be a lot closer this way.
Let’s take a closer look at what Verlet Integration actually is. The formulation that we’re going to use doesn’t actually store the velocity directly at all! Instead, we store the current position and the previous position only. The velocity is then inferred by the distance between these locations divided by time. In programmer art math this looks like:
And that’s it! We don’t need to worry about storing (or maintaining) any velocity term which is actually pretty handy by itself, just the previous position along with the current one. Both methods need the acceleration term to be handled roughly the same, so we’ll ignore that for now. Okay, now that we can model the movement we have to take a look at the constraints we talked about earlier. This is where the Verlet formulation really shines for us. To see this, we first need to consider what a constraint actually is. For our purposes, we want to keep the length of a piece of hair about the same, and we want it to stay attached to Blissa’s head. Lastly, we want it to return to some initial shape. All of these can be thought of as specifying a distance that we want to maintain between pairs of points. For the length, this is easy to see because we’re already talking about the length in the constraint! Keeping something attached is just a distance of zero, and the shape case is slightly more complex, but it can be modeled by introducing anchor points that have distance constraints with the simulation points.
The next step then is to figure out how we would satisfy these constraints in our simulation. We can easily get the distance between two points with the Pythagorean Theorem, but that only tells us which points are violating their constraints. We want to just move the points then to make them satisfy their constraints. For simplicity, we will always move points along the line that the constraint forms. This is pretty easy so far. But now what do we do in our physics simulation to make this happen? Nothing extra! That’s the beauty of this equation. By moving the point’s current position, we automatically correct the velocity in the simulation to account for the constraint taking effect. But what if we have lots of constraints? Maybe solving one violates another? This happens all the time actually, and it’s really difficult to solve all of them at once. This problem is actually a system of equations with one equation for each constraint. As we remember from algebra class, it’s really annoying to try to solve more than a couple of these equations at the same time. This is also true when dealing with computer simulations if we try to solve the equations directly. Instead we can pretty easily take an iterative approach: solve each equation independently and then do it again and again until we aren’t moving the points very much on average. The number of times we iterate this system can be thought of as a convergence factor – the more times we iterate, the closer we will be to an actual solution. Being at the solution means that the points will be essentially at their rest position. So here’s the last trick: we do a single iteration each frame only. This means that for many frames, the points will not be in their constrained positions, but that’s actually okay! Solving the system over many frames actually gives us pretty nice visual results because the points appear to fall into place.
Enough words, let’s see this in action.
This is the most basic test setup that I had when trying to build out the system for Blissa. You can see here the constraints (in green). There’s distance and angle constraints in this video because I thought I needed both. It took quite some time for me to realize that this isn’t the case, and it greatly simplifies the code to not have to try to solve angular constraints, but it’s fun to do the math!
So now that we have something working, let’s see what happens when we integrate it with Blissa. Note that in this next video, the manual animations are in play, not the procedural ones. The white balls are the setup that is being driven by the physics simulation.
Now we shall see what it looks like when we override the manual animations with our procedural ones. It’s pretty easy to see how much tuning is required.
Here’s a tuning pass. Oops.
Yet another tuning pass. Maybe this is progress?
Things are settling down a bit now. Getting pretty close maybe?
In the end this system got rebuilt for one of the demos that we had. It was during that time that I realized there were some bugs in the implementation shown in these videos that made it harder to tune than it should have been. Such is the life of programming. Here’s a sneak peek ahead at what it looked like while I was tuning the fixed version – including some of the helpful debug drawing I had to add.
Whew. That was a long one. Thanks for sticking with me all the way! As always, if you like what you see here or are interested in the game, Wishlist Blightmare on Steam and follow us on Twitter to stay up to date!