There are some good examples around that show how to make animations in a 3D package and play them back in XNA, but what if you want your animations to be more than standard 'walk', 'run', 'jump', etc.? What if you want your 3D model to interact with its 3D environment? Well, then you need to be able to animate single parts of your 3D model through manipulating the model with your code. This tutorial attempts to show you how to develop a simple 3D Model using Blender, put in some armature bones, and finally how to manipulate them in XNA using C#.
In any 3D game, there are many items that interact with their environments, from trees swaying with the breeze to monsters with machine guns pawning noobs! All of these game assets or Models are moved through the use of Bones or Armatures. Bones in their simplest form can be compared to human bones: an animated human may have bones inside an arm that can be moved in relation to each other while staying attached to each other. Sometimes, however, this is where the similarity stops: Bones in the 3D animated world don't need to follow the same rules as for animals in the real world. Bones don't need to be inside a body as they are not visible in our 3D world. A human torso in our animated world may have one single bone, not the many complex sets of bones real humans have. The Bones only need to be as complicated as we we want the animations to be.
I've found documentation on manipulating single Bones in a Model in XNA difficult to find, especially in XNA 4.0 or Blender 2.5, so with this demonstration I'm attempting to bring together various sources of information into a single source. This article has been built on the shoulders of giants but hopefully will provide a useful reference for mere mortals.
What you will need
All the products below are free.
If you want to do some nice painting on your skin, you might want to use Paint.NET but it's not required: http://paint.net/.
This article is built on top of the Microsoft Skinned Model sample; you can get the original code here: http://create.msdn.com/en-US/education/catalog/sample/skinned_model.
First, what I hope to do here is to show you how to:
- Generate a simple Mesh in Blender (a tube)
- Add a Skin to the Mesh using UV unwrapping
- Add Armatures (Bones) to the Mesh
- Tell the Mesh how to deform when the Armatures/Bones move
- Export the Mesh, Armatures, and Skin in a format that XNA can use
- Bring the Model into XNA with a custom content pipeline
- Through code, manipulate the Mesh through moving the Armatures
To be able to consume the Model, we're about to generate in Blender, XNA. I'm going to be using a modified version of the
SkinningSampleContent pipeline, but before we can use this, our Mesh needs a few things so that the pipeline can process it correctly. It needs:
- A Mesh with at least one Bone
- Every vertex in our Mesh needs to be bound to at least one Bone (yes they can be bound to multiple Bones)
- A Skin on our Mesh (we're going to use UV unwrapping to do this)
Sounds simple, right? OK, let's get started. If you don't have it already, download Blender from Blender.org.
I wrote this based on Blender 2.57.1 - you will need version 2.5 or later; the user interface took a radical change at 2.5, and it's now a truly awesome tool that was being held back by a difficult-to-use UI.
Step 1: Create our Cylinder
- By default, when Blender starts, it makes a default scene with a cube, camera, and light. Select the cube that Blender generated for you (right click to select, then press 'x', then select 'Delete').
- Make sure your '3D cursor location' is set to (0,0,0); if it's not, press 'n' (while your mouse pointer is over the 3D view) to bring up the 3D window properties, scroll down to '3D Cursor Location', and while hovering your mouse over any of the fields, hit '0' to set the cursor to the origin (you can type them all in as zero if you like long-cuts).
- Now add a cylinder (Shift+A -> Mesh -> Cylinder).
- When you perform an operation in Blender, under the tool panel (see the image), you'll see the settings for our latest action (add cylinder in this case), change the depth to 16. Our cylinder needs to be large enough to be able to manipulate.
Figure 1: Our tube with various parts of the UI pointed out
Step 2: Add skin to our Mesh using UV unwrapping on our tube
Our tube needs to be wrapped with some skin before XNA can use it. To do this, we're going to do a 'UV unwrap' of our tube, then we wrap our tube with the skin this makes. As the name implies, UV unwrapping will peel our tube and allow us to paint on the skin.
- Make sure you have just the tube selected and then 'tab' into edit mode (you may already be in edit mode from Step 1).
- What UV unwrapping does is "flatten" our 3D surface into 2D so that we can edit it with a graphics tool, but before we can unwrap, we need to tell Blender how to unwrap our model. We do this by defining seams along our model that Blender will then use to pull the tube apart. You can define as many seams as you like and break it down into small pieces. Today we're going to define 3 seams, one around the top circumference, one around the bottom circumference, and one along one edge.
- Put Blender into 'edge select' mode ('ctrl+tab').
- 'Alt+right click' on an edge around the top of our model (in edit mode) to select a cycle (a very useful key combination to remember).
- 'Ctrl+e' and choose 'Mark Seam'.
- Repeat this for one edge as well as the other end of our tube (think of it as cutting the ends from a tin can then taking a pair of scissors and slicing down the length of the can).
- Right click on the top divide of the 3D window and choose 'Split', then drag the line across and split the screen in half, turn off the tools ('t') and properties ('n') on one of the windows, and then down in the mode select, change the window to 'UV/Image editor'.
- Back in the 3D view press, make sure you're in edit mode (tab) and the entire object is selected (a), then press 'u' and select 'Unwrap'. You should get the unwrapped image in the 'UV/Image editor' window.
- We now need to generate an image to hold the amazing art we can now paint onto our cylinder. Press the plus (+ new) under our UV Unwrap to generate a new image.
- Put our image into 'Image edit' mode and scribble on it (I did a few dots, I ain't no artist!).
- In the 'image' menu under our UV window, select 'Save image'. If you want to play along, call it "CylinderSkin.png". We'll need this in XNA.
- With our cylinder in texture mode, you should be able to see the dots, they are stretched as the UV is not proportional. I'm not going to cover this here, maybe in another tutorial, but it doesn't matter for now.
UV unwrapping can be tricky, and I would highly recommend that you search YouTube for "Blender 2.5 UV unwrap" for some fabulous tutorials that will help you a lot. It's difficult to describe the process in text.
Figure 2: Our tube showing red seams
Figure 3: UV unwrapped tube with various parts of the UI pointed out
Step 3: Subdivide our cylinder
Up to now, our cylinder is just a straight solid cylinder. Before we can bend it in the middle, it needs to have places where it can bend. A good analogy is that currently we have a metal cylinder, we can't bend it, and we want to put places in the metal cylinder where it can deform, perhaps effectively changing our metal cylinder into a flexible hose. There are several ways to do this with our cylinder: we can use 'w' to subdivide, but the problem with this is that it will subdivide all our sides. This would be fine for this simple model but would get quickly out of hand as our model gets larger, as we would have thousands of vertices we didn't need. So we're going to subdivide using the cut method. Essentially, we're going to slice up our cylinder up along its sides.
- Make sure our cylinder is in edit mode (tab) and that all the nodes are not selected (a), and make sure it's all black (not selected).
- Cut our cylinder (ctrl+r), click your left mouse button, and it will put a cut line half way along our cylinder.
- Rolling your mouse wheel will subdivide the cylinder, add about 10 subdivisions. The more subdivisions we do, the more smooth our animation will look, but the more vertices we will need to render (there are other ways to make our model appear more smooth, we could use a modifier on our mesh to subdivide it, but that is beyond this walk through).
Figure 4: Our cylinder showing seams and the subdivided sides
Step 4: Create our Bones
Find the bottom middle of the cylinder. If you followed the previous steps, you can do this by modifying the '3D cursor view' to -8 on the Z axis (in Blender, Z is up). (You'll need to be in object mode to do this, use 'tab' to swap back to it.)
- Now add an Armature (Shift 'a' -> Armature -> Single bone).
- Press the 'tab' button, this will put you in 'Edit' mode (on the Armature now, not the cylinder).
- It might look like nothing has happened but you will notice that the white circle jumps up a little bit; left click inside it and drag up the screen to about half way up the cylinder (if you like, click 'z' to restrict the movement of the Armature to the z axis).
- OK, you should have a single Bone. Press 'e' now to extrude another Bone and again, drag this up past the top of the cylinder (remember that Bones are just rigging for the model, they are not visual items and can actually be outside our mesh if we wanted).
- You should now have something like figure 2.
- We want to name our Bones, so find them in XNA and we can manipulate them.
- In edit mode, on our Armature, select the bottom Bone. In the Properties panel (n to make it appear), under item, set the name to be "BottomBone" (see image).
- Repeat this for the other bone, calling it "TopBone".
Figure 5: Naming our Bones
Step 5: Bind your Cylinder to your Bones
Before our Bones (Armature) can affect our Mesh, we need to tie them together. You can manipulate the Bone now if you like; you do this by pressing Ctrl-Tab to enter 'pose mode' (or select it from the mode selection drop down list at the bottom of the 3D view) - keep in mind that this will only work from object mode with the armature selected. So let's bind them together.
- Make sure you are in object mode (tab will bring you out of edit mode).
- Right click to select the cylinder, now hold the Shift key and right click your Armature (Bones) so both are selected.
- Now select 'Ctrl-p' - this is going to parent the two items together. The easiest option is now to choose 'With Automatic Weights' - this will assign weightings to our vertices proportional to their location in our Bones.
OK, hold on a minute! What's all that about? Remember, back at the beginning, one of the requirements for an animated model in XNA is that every vertex is assigned to at least one Bone. If you miss even one, you will get an error when you load your model into XNA. What we are doing here is letting Blender do our vertex weighting for us; we could do it manually by doing a 'weighted paint' (you can see this if you like by selecting the Cylinder and then changing the mode to 'weight paint' (better when in 'solid' for ViewPort shading)).
Figure 6: Parenting our cylinder to our bones with 'Automatic Weights'
Step 6: Play with dem Bones!
- All going well, we should have the Cylinder bound to the Bones, right click on just the Armature (not the Ccylinder).
- Control-tab should put you into 'pose' mode; in pose mode, you can manipulate your Bones to make the cylinder bend.
- Select the TopBone (right click); now at the bottom, a white circle will appear, left-click inside it and drag left and right, your tube should do a little dance!
- Even better, you can also set the view port shading back from wire frame to textured and you should also see our skin (image).
Figure 7: Moving the bones, notice the mouse pointer in the white circle
Step 7: Export to XNA
Before you can export, you need to turn on the XNA exporter; this is not enabled by default; the standard 'fbx' format that comes enabled with Blender won't work in XNA.
- In the 'File' menu at the top of the screen, select 'User Preferences...".
- In the 'Import-Export' section, enable the "Export XNA format (.fbx)".
- Back in our 3D windows, select 'file->export->XNA FBX. Call the model save file "AnimatedTube.fbx".
We are done with Blender, and we have two files that XNA will need:
- AnimatedTube.fbx, our model
- CylinderSkin.png, our skinning image
Figure 8: Adding XNA as an export option
So we now have a Model that we should be able to use in XNA. To get the Model into our game, we need to import it through the content pipeline. The content pipeline takes our content and normalises it so that it appears to our game as standard structured objects that we can use in our game. I'm basing the XNA part of this project on the sample provided by MS. The skinned model sample uses an existing model that has a built-in animation (walk) that the code runs. While this is great, I want to demonstrate how you might manipulate one joint on its own rather than run a pre-defined animation (incidentally, if you use the same code as I'm demonstrating with the 'dude' model, you can get the 'dude' to do so very unlikely gymnastics!).
The Skinned model sample loads it's 'dude.fdx' model through a custom content pipeline. We are going to make some modifications to the pipeline so that it doesn't load the animations from our 'tube' model because we don't have any. To do this, open the Skinned Model solution, go to the SkinnedModelPipeline project, and remove the methods
ProcessAnimations (both) and comment out the initialisation of the animation's dictionary.
And also pass in
null for our
animationClips in the following line:
model.Tag = new SkinningData(animationClips, bindPose, inverseBindPose, skeletonHierarchy);
model.Tag = new SkinningData(null, bindPose, inverseBindPose, skeletonHierarchy);
What we have done here is told the content pipeline to stop looking for animations in our model - of course, if you have some, leave this in. You could do a more thorough re-write but I wanted to keep the modifications to the Microsoft sample to a minimum. Interestingly, in the SkinnedModel example, Microsoft chose not to extend the Model class, rather they attach the extra skinning data in the tag field for a standard Model class.
Import our Model
Importing our model is simple: drag and drop the two files into the 'SkinnedSampleContent' project. After you've done that, select the model (AnimatedTube.fbx) and in the Properties, change its '
ContentProcessor' property to be '
SkinnedModelProcessor'. This way XNA knows that when it brings in our model, it will do it through the custom pipeline we just modified.
One little catch here, if your Model can't be imported for some reason, you can't debug the content pipeline. As it performs the import, the pipeline doesn't seem to be running in the debugger. I'm not sure how to fix this yet (if anyone else knows, please share!).
Figure 9: Make sure that the 'Content Processor' is set to use the 'SkinnedModelProcessor' pipeline
SkinnedModelWindows project AnimationPlayer class
The animation player that comes with the MS Skinning sample is used to read key frames from the animation in the fbx file and perform the animation they represent. Our fbx file doesn't have any so we can get rid of
UpdateBoneTransforms. The one thing that I have done is to make the
SkinTransforms public (you wouldn't do this in the long run but it will allow us to do a quick demo here).
public Matrix boneTransforms;
public Matrix worldTransforms;
public Matrix skinTransforms;
We'll need this later so we can get directly at the transforms.
Now we get down to actually manipulating our Bones. To do this, we need to apply a transform to one of our Bones. For that, we take the the initial
BindPose and store it, then when we want to manipulate our Bone, we do it in relation to our Bone's initial state. We need somewhere to store these initial states; we have an array to store the initial states in the fields section:
Now we modify the
LoadContent in the
SkinningSample class (this where our main code is running) thus:
protected override void LoadContent()
currentModel = Content.Load<Model>("AnimatedTube");
SkinningData skinningData = currentModel.Tag as SkinningData;
_originalBonesMatrix = new Matrix[skinningData.BindPose.Count];
int currentBone = 0;
while (currentBone < skinningData.BindPose.Count)
_originalBonesMatrix[currentBone] = skinningData.BindPose[currentBone];
if (skinningData == null)
throw new InvalidOperationException
("This model does not contain a SkinningData tag.");
animationPlayer = new AnimationPlayer(skinningData);
Now to understand XNA, it's important to understand the way it runs:
- First: It passes our content through the content pipeline to make it available to XNA.
- Second: it does a
LoadContent to bring our content into the current running game.
- Third: It now sits in a continual loop (until we exit or it dies), jumping from the
Update method to the
Draw method. It's our job to write what happens in these methods; traditionally,
Update will update cameras, meshes, bones, input, etc., where
Draw will simply write it all to the screen.
First, our modified
protected override void Update(GameTime gameTime)
float time = (float)gameTime.ElapsedGameTime.TotalMilliseconds;
bendY += time * 0.001f;
int boneId = currentModel.Bones["TopBone"].Index - 2;
Matrix.CreateFromYawPitchRoll(0, bendY, bendZ) * _originalBonesMatrix[boneId];
Points of interest
int boneId = currentModel.Bones["TopBone"].Index - 2;
This may seem strange, that I'm subtracting 2, but if you inspect the objects, it will all make sense, our Bind Pose is comprised of three sets of matrices:
- base cylinder position
But our Mesh is comprised of 5 objects:
- Root Node
This is where we actually perform the movement of our bone. We take the original location of our bone and multiply it by a rotational transform, we then copy this back into bon
Draw to apply.
Matrix.CreateFromYawPitchRoll(0, bendY, bendZ) * _originalBonesMatrix[boneId];
Finally, just for fun, let's add another keyboard key press handler to allow us to manipulate our Bone, so inside the
UpdateCamera, we add:
bendZ += 0.1f;
bendZ -= 0.1f;
Another point to note is that the axis in Blender has the 'Z' axis vertically up and down the screen while XNA has the 'Z' axis directly out of and into the screen. So to fix this, the following is added to the fields section of the
SkinningSampleGame class; this will rotate our model by 90 degrees.
float cameraArc = -90;
The code I've included in the download is only for the Windows version, feel free to run it in Phone 7 or Xbox 360. All going well, you should be able to run the solution now and press the 'v' and 'b' keys to bend the cylinder.
Glossary of terms
- Bone: a none rendered artificial construct to allow the easy manipulation of groups of vertices (meshes)
- BindPose: The initial position of all our bones when they are straight from Blender with no manipulation
- Model: the combination of our Meshes, Bones, Textures make up our complete model
- Weight Paint: an artificial coloration of our Mesh to display how the Mesh is bound to the bones
- Armature: a collection (1 or more) of Bones arranged hierarchically
Error messages you might get and how to fix them
"The mesh "Cylinder", using SkinnedEffect, contains geometry that is missing texture coordinates for channel 0"
You might get this when you first run your code. Your code won't even hit a break point, it just won't (seem) to compile. This is because the content pipeline has already looked at your Model and couldn't find the UV wrapped texture. Go back to Blender and make sure you completed Step 2 from the Blender section.
"Error normalizing vertex bone weights. BoneWeightCollection does not contain any weighting values."
You have nodes in your Mesh that are not bound to any Bones, this is illegal in XNA. You need to go back into Blender and make sure you bind your Mesh to your bones correctly. Check using weighted paint view to make sure all nodes are coloured.
I've always wanted to write a game from my first days with my Amstrad 6128, it is a shame it's taken me so long! I was surprised at how difficult it was to find resources to do this sort of stuff and plan on writing more if this is well received. Let me know how you go with this and if the Blender parts make sense. I did walk them through several times but it's difficult to explain some of the concepts. Drop me a line if you have any questions.
Right, now back to my game!