About

I'm developing my first game, Age of Goblins. I develop this part time, and work at a "real" (read: paying) job full time. Age of Goblins is a three dimensional goblin empire building game. Inspired by Dwarf Fortress and Minecraft. Age of Goblins gives a player control over a small band of goblins in a cube-based sandbox world. The player can instruct the goblins to add or remove different types of cubes, build various structures, make elaborate traps, and craft a multitude of items.

Wednesday, December 12, 2012

The Blender connection (part 2)

I previously introduced loading models and rendering them in-game. Now I'll go over implementing animation for Blender models.

First we have to add bones to the model. If you know pretty much anything about skeletons, this is fairly simple. Your mesh is the flesh. You want to add bones in places where you want the flesh to move. If you want this bit of the flesh to be able to move independently of that bit of flesh, you need to add multiple bones. Take, for example, the simple AutoCannon T230:





To create the bones, we first have to know what we want it to do. Well the AutoCannon T230 can sweep and pitch creating an decent kill zone in front of it. It can also, of course, accurately fire multiple rounds per minute with it's recoiling barrel. So knowing all that, we want to place bones for rotating around the vertical axis and the horizontal axes, as well as the ability to recoil the barrel. Something like this:


The bottom one controls the rotation around the vertical axis, the itty bitty one controls the rotation around the horizontal axes and the larger horizontal bone controls the recoil and twist of the barrel. Learning how to properly rig a model is probably best left to a proper tutorial site. The model needs to have bones and vertices assigned to those bones with weights.

Since I wanted to be able to animate some things dynamically, I do some special things in Blender when it comes to animation. First of all, I add tags to bones that I want to flag when importing. For example, I'll add the tag (LookY) to the base rotation bone. Since it's responsible for rotating the "head" around the Y axis, it's what I would use to dynamically apply a "look at" pose to have the turret track an entity in game. I additionally have named the itty bitty bone with the tag (LookX). It's going to be rotating the "head" around the X axis, so now we can look up, down, left and right.

But introducing dynamic animation means we need constraints. Unfortunately the .x format does not export the bone constraints I can enter into Blender for it's IK solver. So what to do? Well I decided that key frames could serve that purpose well enough.

First a bit of info on keyframes and animations. There wasn't a simple way to export multiple animations as discreet animations from Blender. There's not really even an easy way create separate animations in Blender, let alone export them that way. So every animation is packed into one animation. One after the other. Then I have a text file that describes where one animation starts and ends, as well as what frames are the loop frames. Loop frames are the start & end of the loop cycle for animations, for example, the walk cycle has a start walking from stop, walk and stop walking animation. If I want the animation to play longer than the single cycle I animated it for, I define the frames to use to wrap around the walk portion of it. This makes it dead simple to: start walking -> loop walk until we're near the stopping point -> seamlessly play through to the end of the walk animation with the stop.

Anyway, so all my animations are packed into a single animation with the text file defining them. For example, the AutoCannon animation description file looks like this:


0:0:6:6=SPECIAL_SETUP
7:7:22:22=ATTACK_1_FireTurret
23:23:44:44=IDLE_ANIMATION_1_Sweep
45:45:55:55=IDLE_ANIMATION_30_Stationary

Where each line is defined like this:
[Start frame]:[Loop start]:[Loop end]:[End frame]=Name of animation
The names also include tags for their purpose. Where each name is defined like:

PURPOSE_WeightedChance_ReadableName

The weighted chance is taken into consideration when there are multiple animations for a given purpose. The animations will be chosen with a weighted random. For example, the Stationary animation is much more likely to be played than the Sweep animation when idle.

As you can see the turret doesn't take advantage of any animation loops. Boring. The goblin walk animation does, and it's defined like this:
145:151:163:165=WALK_1_Walk
It's a fairly short animation. Goblins had to walk really fast. Since their legs are short and I don't want the player waiting for-ev-er to watch the goblins walk around. But the start walking goes from 145 to 151, then I can loop 151 to 163 forever for a perfectly looped walk cycle, then when I'm ready to stop, I just keep stepping through frames to get to 165.

Now, what's that first "Setup" animation all about? That gets back to the constraints for the dynamic animations. In those first 6 key frames I pose the model in all it's various extents. X, Y and Z extents get posed in their minimum then maximum rotations (or the maximum in both negative and positive directions) Those poses look like this:


With the last two frames being the same, because there is no rotation around the Z axis.

I like to create my animations so that they start and end on a standard "default pose". This is probably common in the animation world, but I admit I don't know a lot about the animation world. For example, we'll take the "Fire" animation for the AutoCannon.

That bit sticking out the back is the bone (won't be rendered in game)

That's a total of five keyframes:

Including the default pose at start and end

Now the setup is done and we have an animation to test out. We now know how to do pretty much everything we need to in Blender. The last step is to export. When exporting the animations, we just need to ensure that we check the boxes marked "Export Armatures" and makes sure the drop down box labeled "Animations:" is set to "Keyframes Only".

The first thing we'll do is read in the animation definitions. Use those to generate some empty animation sets that have their animation ranges defined. Now we know what to look for when we read in the animation data from Blender.

The animation data from Blender will come in the same .x file that we know and love from reading in our meshes. We actually already have a pretty good idea of how to import the armature. It's very similar to the nested format of the meshes that we found before. This time though there'll be some additional information. We'll have lists of which vertices that define which vertices are controlled by which bones, and how much they influence them (commonly called "skin weights").

Those lists look like this:


SkinWeights {
        "Armature_TurretTop_LookZ_";
        228; //number of indices to be read
        156, //indices
        157,
        .......
 Each one corresponds to the order in which the vertices were read in from the mesh, or the index of the vertex controlled. Those should be saved so we know which vertices to manipulate for each bone. I like to just stuff those values into some vertex attribute arrays and store them in the VBO for quick access on the graphics card. I have a maximum of three bones controlling each vertex. Those are sorted from strongest to weakest, so if I have too many, the weakest bones are ignored. So the VBOs have two additional `vec3` components per vertex. They have a one for the bone indices and one for the bone weights. But, getting into the gritty details isn't much fun here.

All together we have the animation definition, the skeleton and the mesh. Next up we need to get the actual animation data. Once again, the easy readability of .x animations helps us out a good deal.

AnimationSet {
  Animation {
    {Armature_BaseRotator_LookY_}
    AnimationKey { //Position
      2; //Defines the type of key, 2=Position
      7; //The number of key frames we're about to read.
      6;3;     0.000000, 0.000000, 0.000000;;,
      7;3;     0.000000, 0.000000, 0.000000;;,
      22;3;    0.000000, 0.000000, 0.000000;;,
      23;3;    0.000000, 0.000000, 0.000000;;,
      44;3;    0.000000, 0.000000, 0.000000;;,
      45;3;    0.000000, 0.000000, 0.000000;;,
      54;3;    0.000000, 0.000000, 0.000000;;;
    }
The animation starts with AnimationSet and the defines the keyframes for each bone. Each bone will have a set of key frames for their position, rotation and scale. The comments above show what the numbers mean.

The keyframe lines are in the format :

keyframeNumber;number of components to read;     component1, component2, component3;;,

For position that's the x, y, z position. For something like rotation (below) that's the w,x,y,z components of a quaternion. Position is not a very interesting set of key frames for the baseRotator however. Lets look at rotation:



AnimationKey { //Rotation
      0; //Defines the type of key, 0=Rotation
      15; //The number of key frames we're about to read
      0;4;    -0.707107, 0.707107, 0.000000, 0.000000;;,
      1;4;    -0.707107, 0.707107, 0.000000, 0.000000;;, 
      //frames 2 and 3 can be seen in the above animation showing the min/max rotations around the Y axis
      2;4;    -0.653281, 0.653281,-0.270598,-0.270598;;,
      3;4;    -0.653282, 0.653281, 0.270598, 0.270598;;,
      4;4;    -0.707107, 0.707107, 0.000000, 0.000000;;,
      5;4;    -0.707107, 0.707107, 0.000000, 0.000000;;,
      6;4;    -0.707107, 0.707107, 0.000000, 0.000000;;,
      7;4;    -0.707107, 0.707107, 0.000000, 0.000000;;,
      22;4;   -0.707107, 0.707107, 0.000000, 0.000000;;,
      23;4;   -0.707107, 0.707107, 0.000000, 0.000000;;,
      30;4;   -0.684901, 0.684901, 0.175814, 0.175814;;,
      37;4;   -0.689064, 0.689064,-0.158715,-0.158715;;,
      44;4;   -0.707107, 0.707107, 0.000000, 0.000000;;,
      45;4;   -0.707107, 0.707107, 0.000000, 0.000000;;,
      54;4;   -0.707107, 0.707107, 0.000000, 0.000000;;;
    }

OK, now we're getting somewhere. We read in all that data. We have the animations defined and filled. We have the meshes and armatures constructed. Now we just need to calculate the transforms for each bone and apply those transforms to the correct vertices.

Each keyframe needs to have the bones processed in order from root to tip. Each child bone depends on the transform of its parent bone. So when processing each bone we need to know:

BindMatrix - This is the root frame matrix multiplied by the model frame matrix
BoneMatrix - This is dependent on the bone. This is a recursive get. So getBoneMatrix() returns (BindMatrix * ArmatureRootMatrix * CombinedMatrix) if it's the root bone, otherwise it returns (getBoneMatrix() * CombinedMatrix). I'll talk about the CombinedMatrix in a bit.
LookMatrix - Derived from the direction the entity is looking. Applied only to the "look bones", ignore if not ready for dynamic animation.
SkinMatrix - This is from the SkinWeight frame, found at the end of the list of weighted vertices.

Now that we have all that, we need to calculate the new pose. The CombinedMatrix is the transform calculated from the rotation, scale and position of the keyframe. That information needs to be stored in the bone, so it can be accessed by all it's children bones when they are calculating their BoneMatrix as we see above. When there is no keyframe, the CombinedMatrix is set to the default matrix, which is found in the bone frame inside the armature definition. Each bone calculates their final transform with the following:

CombinedMatrix * BoneMatrix * SkinMatrix * InvertedBindMatrix

Luckily, after we compute this once, we can save it and just retrieve the stored value whenever something asks for the transform for that bone and that frame. Interpolating key frames is fairly simple too. I just do linear transformation between the surrounding position and scale keyframes. For the rotation I Slerp between the two quaternions. Then create a new frame transform using those interpolated values. Again, once those are calculated once we save them and all further calls for that frame and animation just return the pre-calculated value.

All in all, it's kind of complex, but breaking it down into its component parts can make the task achievable. Remember if you're implementing your own system, take it steps. Get a model with a single bone showing, then two bones. Then add a single animation with two frames, both keyed. And so on until you've reached full interpolated animation with complex armatures.

Thanks for reading! Probably a somewhat boring post for those who aren't interested in making their own game. But it is a development blog, so I get to talk nerdy every once-in-a-while. I'll be making a video to show off animations soon.

2 comments:

  1. Looking really good so far ;)
    I have a suggestion for the auto-cannon (nothing to do with mechanics, just asthetics): It says the bit sticking out at the end will not be rendered, as it's the bone. You could attach something (at a proper height) that IS rendered and will come out the back everytime a shot is fired. Just thought that would make the recoil animation look even better than it already is ;)

    ReplyDelete
  2. Thanks! I'll look into updating the animation. I was going to add a shell casing that was ejected out the back as well. Since it's just a draft, I'm sure it'll look somewhat different in the future. Thanks for your suggestion and for your interest.

    ReplyDelete