Introduction
This is an introductory article for XNA development, discussing tools and the minimum amount of code to start using the XNA Game Studio with simple models created in Blender.
Tools
To write applications (such as games) using the XNA Game Studio, you must download and install a variety of tools. Since Microsoft changes its website frequently, these links are not guaranteed to work. If you don't get to the download pages, use Google to search for the new links using the appropriate keywords.
This article is written using:
- Visual Studio C# 2005 Express Edition with SP1
- XNA Game Studio Express 1.0 Refresh
- XNA Framework 1.0 Refresh
- Blender 2.44
- Python 2.51
Visual Studio Express
XNA development, as of the writing of this article, can only be done using Visual Studio Express.
- Download Visual Studio C# 2005 Express Edition here
- Download SP1 for VS Expression Edition here (scroll down a bit)
XNA
XNA development requires both the Game Studio Express (GSE) and the XNA Framework:
- Download XNA Game Studio Express here
- Download XNA Framework here
Modeling Tool
If you're going to be doing anything "serious" with XNA, you will quickly discover that you need a modeling tool to create your 3D models. I recommend Blender (which is free) or AutoDesk Maya, which is $2000. Guess which tool I'll be mentioning in this and other articles?
- Download Blender here
- Drool over Maya using the Maya Personal Learning Edition here
The Maya PLE cannot be used for creating models for XNA (as far as I know) because it cannot export to the format that XNA requires.
Plug-Ins
You will probably want to take advantage of plug-ins and scripts in Blender, which use Python.
Blender automatically detects the Python installation, so no further work on your part is necessary.
Tutorials
Tutorials on XNA and Blender are quite abundant and the Microsoft and Blender sites respectively and on numerous third party sites. Simply Google and you will find many resources. I started by going through the Microsoft tutorial on displaying a 3D model. Be aware of the aspect ratio bug which I describe here. It's not fatal, but it will lead to stretched objects if you use the Microsoft tutorial as a basis for working with models created in Blender.
Creating a Windows XNA Project
Launch Visual Studio C# 2005 Express Edition.
Creating the Windows Game Project
Create a Windows Game project by selecting the Windows Game project icon:
Enter a name for the project and click on OK.
Create a Model Folder
Models (spaceships, people, objects, etc.) typically live in a Content\Models folder. Right click on the project and add the Content folder and Models sub-folder, so your project tree looks like this:
If you build and run this project, you will get a window with a blue field.
Create a Model in Blender
Launch Blender. Blender starts with a cube as an initial model, although since you're looking at the cube from the top, it looks like a square:
To see that it's actually a cube, click on the numpad 0 (with numlock on) and you will see:
Export the Model
XNA cannot load Blender files, so rather, you must export your Blender model to an FBX format. You can read a little bit about FBX here. Basically it's an open standard, platform independent 3D file format created by AutoDesk. You can also export to DirectX (.x) format, however this brings up a complicated options dialog, whereas the FBX export does not.
The Blender UI will be a bit weird to Windows users, so here's the walkthrough:
- On the menu bar at the top, click on File (do not try to use the keyboard with the typical Alt-F keystroke combo!)
- Move the mouse down to Export
- Move the mouse over to Autodesk FBX
- A dialog will appear with two textboxes, the first showing the path and the second for entering the filename. Click on the second edit box and type in "cube" (without the quotes).
- Click on the Export FBX button or press the Enter key twice.
Import the Model into XNA
The model is now ready for importing into XNA:
- Go back to the XNA Game Studio and right click on the Models folder and select "Add / Existing Item...".
- In the file types, select "Content Pipeline Files"
- Navigate to the Blender folder and select the cube.fbx file.
Add the Code to Render the Model
Rendering the model involves:
- Defining where the model is
- Defining where the camera is
- Defining the camera orientation
- Loading the model into the content pipeline
- Drawing the model
All of this work will be done in the game1.cs file and the Game1
class.
Defining Where the Model Is
For simplicity, the model is placed at world coordinates (0, 0, 0). Create a field for the model position:
protected Vector3 modelPosition = Vector3.Zero;
Defining Where the Camera Is
Create a field for the camera position:
protected Vector3 cameraPosition = new Vector3(0.0f, 0.0f, 10.0f);
How did I arrive at these values? The vector consists of the x, y, and z axis position of the model. X is left-right, Y is up-down, and Z is in-out. If you look at the screenshots of the cube above, you will notice a grid. Each grid line represents one unit, therefore the cube is a 2 units wide, 2 units long, and 2 units tall. The camera is position ten units away from the world center (0, 0, 0) which creates a reasonably sized rendering of the cube.
Loading the Model into the Content Pipeline
Create a field for your model:
protected Model myModel;
In the LoadGraphicsContent
method, load your cube model:
protected override void LoadGraphicsContent(bool loadAllContent)
{
if (loadAllContent)
{
myModel = content.Load<Model>("Content\\Models\\cube");
}
}
Note that you do not specify the file extension. The XNA Game Studio will load the appropriate file, as the model is actually compiled to a different file extension from the FBX file that you added to the Content\Models folders.
While we're here, let's also get the aspect ratio of the viewport (our window) as this is necessary for rendering the model so that it looks like a cube. Add the field:
protected float aspectRatio;
and, to the above method, calculate the aspect ratio:
protected override void LoadGraphicsContent(bool loadAllContent)
{
if (loadAllContent)
{
myModel = content.Load<Model>("Content\\Models\\cube1");
}
aspectRatio = ((float)graphics.GraphicsDevice.Viewport.Width) /
((float)graphics.GraphicsDevice.Viewport.Height);
}
Drawing the Model
Modify the Draw
method so that it iterates through the model meshes, translating and rotating them appropriately, etc.
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
Matrix[] transforms = new Matrix[myModel.Bones.Count];
myModel.CopyAbsoluteBoneTransformsTo(transforms);
foreach (ModelMesh mesh in myModel.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.EnableDefaultLighting();
effect.World = transforms[mesh.ParentBone.Index];
effect.View = Matrix.CreateLookAt(cameraPosition, Vector3.Zero,
Vector3.Up);
effect.Projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.ToRadians(45.0f), aspectRatio, 1.0f, 10000.0f);
}
mesh.Draw();
}
base.Draw(gameTime);
}
When you run the program, you will get a cube rendered on the blue field:
The following is a brief description of what is going on in the above method. Each of these subtopics deserves considerably more description that I have provided, but I feel that is outside the scope of this beginner article.
Bones
The code above is the basic rendering loop for all models in the game. XNA uses a skeletal animation technique which you can read about here.
<p<code>CopyAbsoluteBoneTransformsTo</code /> flattens the bone hierarchy and puts the bones into an array so that the parent bone matrix can be quickly acquired in this assignment:
<p />
<pre lang="cs">effect.World = transforms[mesh.ParentBone.Index];</pre>
<p>Alternatively, you could write this as:</p>
<pre lang="cs">effect.World = mesh.ParentBone.Transform;</pre>
<p>However, since each mesh in the child needs to access its parent bone transform (so that the mesh builds on the transform of the parent bone) indexing is ultimately faster than asking the parent bone to recalculate its transform every time.</p>
<p>How many bones do you think this model has? If you answer 1, because you think there's just one model, then you would be partly correct. However, there is always a root that defines the topmost parent of the bone tree:</p>
<img height="254" hspace="0" src="/KB/game/xna1/img8.png" width="262" border="0" />
<h4>Model Meshes</h4>
<p>A model consists of meshes (read about meshes in the Blender documentation). How many meshes do you think the cube has? The expected answer would be 6--one mesh for each face of the cube. However, the cube model only has one mesh! What is confusing is actually the local variable named <code>mesh, which instead should be called
modelMesh
, as there is a single mesh to represent the entire model. So one has to be clear about the difference between XNA
ModelMesh
instances and Blender meshes.
The XNA Mesh for the cube:
consists of 1 ModelMeshPart
object which has 8 vertices, which is the number of vertices in the cube (the above model has 51 vertices).
As a second example, an object like this:
(in which the quad--square--meshes have been converted to triangles for one face and one of the triangles has been extruded) still is represented as only one model mesh in the XNA model.
Effects
This line of code:
foreach (BasicEffect effect in mesh.Effects)
is an shorthand way of saying (again note we're dealing with the model mesh part):
foreach (ModelMeshPart part in mesh.MeshParts)
{
BasicEffect effect = (BasicEffect)part.Effect;
...
However the former is optimized to avoid redundant setup work if more than one ModelMeshPart
shares the same effect (reference).
Effect Properties
Each model mesh is rendered based on:
- its orientation
- the orientation of the camera
- the camera field of view
as expressed by these three statements:
effect.World = transforms[mesh.ParentBone.Index];
effect.View = Matrix.CreateLookAt(cameraPosition, Vector3.Zero, Vector3.Up);
effect.Projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.ToRadians(45.0f), aspectRatio, 1.0f, 10000.0f);
- The
World
property establishes the world matrix for this model mesh (from the perspective of the model's world), which is typically a combination of the parent bone transforms and any local transforms applied just to this bone. - The
View
property establishes the orientation of the camera--its position, where it's looking, and its orientation. - The
Projection
property establishes how the camera projects its view onto the screen--the field of view, the aspect ratio of the viewport, and the boundaries (in distance) of what the camera can see: the near plane and the far plane.
Each model mesh requires the World
property to be set to reflect any changes in the parent bone and any local changes. The View
and Projection
properties need to be set whenever the camera position and orientation changes. In this simple example, we could pre-calculate those values:
effect.World = transforms[mesh.ParentBone.Index];
effect.View = lookAt;
effect.Projection = projection;
where lookAt
and projection
are Matrix
objects initialized in the LoadGraphicsContent
method:
lookAt = Matrix.CreateLookAt(cameraPosition, Vector3.Zero, Vector3.Up);
projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),
aspectRatio, 1.0f, 10000.0f);
Animating the Model
The above screenshot of the cube looks utterly boring and not like a cube at all. Getting an object to rotate on its own is one of the simplest forms of animation. The following code will rotate the cube around the X and Y axis, giving it some nice spin. First, add:
protected float modelRotation = 0.0f;
to the Game1
class. This field is used for holding the rotational angle.
In the Update
method, add the line to increment the model rotation based on the elapsed time between updates:
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
modelRotation += (float)gameTime.ElapsedGameTime.TotalMilliseconds *
MathHelper.ToRadians(0.1f);
base.Update(gameTime);
}
In the Draw
method, modify the World
property assignment to rotate the model meshes:
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
Matrix[] transforms = new Matrix[myModel.Bones.Count];
myModel.CopyAbsoluteBoneTransformsTo(transforms);
foreach (ModelMesh mesh in myModel.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.EnableDefaultLighting();
effect.World = transforms[mesh.ParentBone.Index] *
Matrix.CreateRotationX(modelRotation) *
Matrix.CreateRotationY(modelRotation);
effect.View = Matrix.CreateLookAt(cameraPosition, Vector3.Zero,
Vector3.Up);
effect.Projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.ToRadians(45.0f), aspectRatio, 1.0f, 10000.0f);
}
mesh.Draw();
}
base.Draw(gameTime);
}
This creates a nice rotating cube:
Conclusion
This article encapsulates the research and experimentation that I've done to get a basic understanding of XNA and how to use a tool such as Blender to start developing models that XNA can render. I've been blogging about my trials and tribulations here, and I will continue to do so as I make progress on this concept that I'm developing. I'm primarily interested in using XNA as a rendering space rather than game development, but I may just for fun diverge and put together a simple game that I wrote years ago in Windows 3.1.