Microsoft has undoubtedly made some top notch developer tools over the years. Besides Visual Studio, .Net Languages, and the new Sliverlight, Microsoft has also jumped into gaming. We all know and love the Halo series, but what is slightly lesser known are the XNA code libraries. Microsoft has made some pretty sweet games, but with the still fresh XNA you have the chance to grab the reins and make your own games.
The XNA libraries make creating games a snap by providing functions to load content and render it, with support functions to help you do anything from calculate view matrices to applying textures onto 3-Dimensional meshes. The best thing about XNA is that it allows you to develop games and content for the XBox 360, while at the same time developing in and for a Windows environment. But you must start at the basics, and so we will.
Most of the code we are writing today requires you to have the XNA library, which can be found here. Once you get XNA installed and set up, the first thing we need to do is start a new XNA project. Go to file, then new, finally click on project. Next you need to navigate to C# in the project types pane. Find XNA Game Studio under that node. Then select Windows game (for now we will stick to the Windows platform). I named my project "BoxTutorial", but you can name yours whatever you like.
Creating a Project in Visual Studio
When you click ok, it will create a basic game class for you, which looks something like this:
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace BoxTutorial
{
/// <summary>
/// This is the main type for your game
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
/// <summary>
/// Allows the game to perform any initialization
/// it needs to before starting to run.
/// This is where it can query for any required
/// services and load any non-graphic
/// related content. Calling base. Initialize will
/// enumerate through any components
/// and initialize them as well.
/// </summary>
protected override void Initialize()
{
// TODO: Add your initialization logic here
base.Initialize();
}
/// <summary>
/// LoadContent will be called once
/// per game and is the place to load
/// all of your content.
/// </summary>
protected override void LoadContent()
{
// Create a new SpriteBatch, which can
// be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
// TODO: use this.Content to load
// your game content here
}
/// <summary>
/// UnloadContent will be called once per game
/// and is the place to unload all content.
/// </summary>
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
/// <summary>
/// Allows the game to run logic
/// such as updating the world,
/// checking for collisions,
/// gathering input, and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
base.Update(gameTime);
}
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
base.Draw(gameTime);
}
}
}
Notice all the new XNA using statements? Those frameworks will be the heart and soul of our project. Now that we have our shiny new game project, let's go ahead and do something worthwhile. The first step is to create a "BasicShape" class, which will contain all the attributes and methods needed to draw and manipulate a cube.
Go ahead and create a new class. For the uninitiated, you can do this by right clicking the solution in the explorer. Go to add, then new item. Class will be the first choice, and it is most likely already chosen for you. Name this class "BasicShape" and then press ok.
Before we start filling in the class, look take at the using statements at the top:
using System.Collections.Generic;
using System.Text;
I don't see any of the XNA frameworks, do you? We need to copy and paste the using statements from our Game1.cs so we can take advantage of the XNA frameworks in our new class. Simple enough, but without the XNA frameworks, you wont get very far. So go ahead and copy the using statements for the Game1.cs file. Now your BasicShape class file should look like this:
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace BoxTutorial
{
class BasicShape
{
}
}
So we have our new class ready for some cool new XNA code. Every shape in 3D space needs 3 things to work properly, and I am not talking about the 3 dimensions. Every shape, no matter how simple or complex, needs a position, a size, and a set of vertices. With those three things you can make any shape imaginable anywhere needed. At the top of our class, lets add some variables that we will need:
{
public Vector3 shapeSize;
public Vector3 shapePosition;
private VertexPositionNormalTexture[] shapeVertices;
private int shapeTriangles;
private VertexBuffer shapeBuffer;
public Texture2D shapeTexture;
Now I mentioned 3 things, but I have 6 variables here. Well, in order to put something on the screen you need to know how many triangles you have, and you need a VertexBuffer to hold the vertices you want to display. We will also be texturing the box later on, so it easier for us to add the Texture variable now. So, now what about the top 3? Well you can see 2 Vector3s and a VertexPositionNormalTexture array. What the heck are those? A Vector3 is simply just a position is 3D space or even more simply a 3 number array. Vector3s are used a lot in XNA, for everything from position to color values. Now our VertexPositionNormalTexture array is an array made up of Vertex objects that expects to have 3 values: a physical position, a normal position, and a texture position. Now that we have some of the variables we need, it's time to build a class constructor:
{
shapeSize = size;
shapePosition = position;
}
Yup, that is it. All we need to do in our constructor is take in a size and a position, then set the corresponding variables. Now we use a Vector3 for size because you can multiply Vector3s together and with that we can control the size along each axis. Technically you can make a rectangle out of our box just by making one of the size numbers larger than the others. So what will we do with all these variables we have? Well, we are going to make a method called "BuildShape", which will set all these values so we can use them to draw a box. The BuildShape Method looks something like this:
{
shapeTriangles = 12;
shapeVertices = new VertexPositionNormalTexture[36];
Vector3 topLeftFront = shapePosition +
new Vector3(-1.0f, 1.0f, -1.0f) * shapeSize;
Vector3 bottomLeftFront = shapePosition +
new Vector3(-1.0f, -1.0f, -1.0f) * shapeSize;
Vector3 topRightFront = shapePosition +
new Vector3(1.0f, 1.0f, -1.0f) * shapeSize;
Vector3 bottomRightFront = shapePosition +
new Vector3(1.0f, -1.0f, -1.0f) * shapeSize;
Vector3 topLeftBack = shapePosition +
new Vector3(-1.0f, 1.0f, 1.0f) * shapeSize;
Vector3 topRightBack = shapePosition +
new Vector3(1.0f, 1.0f, 1.0f) * shapeSize;
Vector3 bottomLeftBack = shapePosition +
new Vector3(-1.0f, -1.0f, 1.0f) * shapeSize;
Vector3 bottomRightBack = shapePosition +
new Vector3(1.0f, -1.0f, 1.0f) * shapeSize;
Vector3 frontNormal = new Vector3(0.0f, 0.0f, 1.0f) * shapeSize;
Vector3 backNormal = new Vector3(0.0f, 0.0f, -1.0f) * shapeSize;
Vector3 topNormal = new Vector3(0.0f, 1.0f, 0.0f) * shapeSize;
Vector3 bottomNormal = new Vector3(0.0f, -1.0f, 0.0f) * shapeSize;
Vector3 leftNormal = new Vector3(-1.0f, 0.0f, 0.0f) * shapeSize;
Vector3 rightNormal = new Vector3(1.0f, 0.0f, 0.0f) * shapeSize;
Vector2 textureTopLeft = new Vector2(0.5f * shapeSize.X, 0.0f * shapeSize.Y);
Vector2 textureTopRight = new Vector2(0.0f * shapeSize.X, 0.0f * shapeSize.Y);
Vector2 textureBottomLeft = new Vector2(0.5f * shapeSize.X, 0.5f * shapeSize.Y);
Vector2 textureBottomRight = new Vector2(0.0f * shapeSize.X, 0.5f * shapeSize.Y);
// Front face.
shapeVertices[0] = new VertexPositionNormalTexture(
topLeftFront, frontNormal, textureTopLeft);
shapeVertices[1] = new VertexPositionNormalTexture(
bottomLeftFront, frontNormal, textureBottomLeft);
shapeVertices[2] = new VertexPositionNormalTexture(
topRightFront, frontNormal, textureTopRight);
shapeVertices[3] = new VertexPositionNormalTexture(
bottomLeftFront, frontNormal, textureBottomLeft);
shapeVertices[4] = new VertexPositionNormalTexture(
bottomRightFront, frontNormal, textureBottomRight);
shapeVertices[5] = new VertexPositionNormalTexture(
topRightFront, frontNormal, textureTopRight);
// Back face.
shapeVertices[6] = new VertexPositionNormalTexture(
topLeftBack, backNormal, textureTopRight);
shapeVertices[7] = new VertexPositionNormalTexture(
topRightBack, backNormal, textureTopLeft);
shapeVertices[8] = new VertexPositionNormalTexture(
bottomLeftBack, backNormal, textureBottomRight);
shapeVertices[9] = new VertexPositionNormalTexture(
bottomLeftBack, backNormal, textureBottomRight);
shapeVertices[10] = new VertexPositionNormalTexture(
topRightBack, backNormal, textureTopLeft);
shapeVertices[11] = new VertexPositionNormalTexture(
bottomRightBack, backNormal, textureBottomLeft);
// Top face.
shapeVertices[12] = new VertexPositionNormalTexture(
topLeftFront, topNormal, textureBottomLeft);
shapeVertices[13] = new VertexPositionNormalTexture(
topRightBack, topNormal, textureTopRight);
shapeVertices[14] = new VertexPositionNormalTexture(
topLeftBack, topNormal, textureTopLeft);
shapeVertices[15] = new VertexPositionNormalTexture(
topLeftFront, topNormal, textureBottomLeft);
shapeVertices[16] = new VertexPositionNormalTexture(
topRightFront, topNormal, textureBottomRight);
shapeVertices[17] = new VertexPositionNormalTexture(
topRightBack, topNormal, textureTopRight);
// Bottom face.
shapeVertices[18] = new VertexPositionNormalTexture(
bottomLeftFront, bottomNormal, textureTopLeft);
shapeVertices[19] = new VertexPositionNormalTexture(
bottomLeftBack, bottomNormal, textureBottomLeft);
shapeVertices[20] = new VertexPositionNormalTexture(
bottomRightBack, bottomNormal, textureBottomRight);
shapeVertices[21] = new VertexPositionNormalTexture(
bottomLeftFront, bottomNormal, textureTopLeft);
shapeVertices[22] = new VertexPositionNormalTexture(
bottomRightBack, bottomNormal, textureBottomRight);
shapeVertices[23] = new VertexPositionNormalTexture(
bottomRightFront, bottomNormal, textureTopRight);
// Left face.
shapeVertices[24] = new VertexPositionNormalTexture(
topLeftFront, leftNormal, textureTopRight);
shapeVertices[25] = new VertexPositionNormalTexture(
bottomLeftBack, leftNormal, textureBottomLeft);
shapeVertices[26] = new VertexPositionNormalTexture(
bottomLeftFront, leftNormal, textureBottomRight);
shapeVertices[27] = new VertexPositionNormalTexture(
topLeftBack, leftNormal, textureTopLeft);
shapeVertices[28] = new VertexPositionNormalTexture(
bottomLeftBack, leftNormal, textureBottomLeft);
shapeVertices[29] = new VertexPositionNormalTexture(
topLeftFront, leftNormal, textureTopRight);
// Right face.
shapeVertices[30] = new VertexPositionNormalTexture(
topRightFront, rightNormal,textureTopLeft);
shapeVertices[31] = new VertexPositionNormalTexture(
bottomRightFront, rightNormal, textureBottomLeft);
shapeVertices[32] = new VertexPositionNormalTexture(
bottomRightBack, rightNormal, textureBottomRight);
shapeVertices[33] = new VertexPositionNormalTexture(
topRightBack, rightNormal, textureTopRight);
shapeVertices[34] = new VertexPositionNormalTexture(
topRightFront, rightNormal, textureTopLeft);
shapeVertices[35] = new VertexPositionNormalTexture(
bottomRightBack, rightNormal, textureBottomRight);
}
There is a lot here, but it is mostly very repetitive. Let me start from the top. We need to set the number of triangles we have, which can really be done at any point in the method, but we will go ahead and set it at the top. Next we set our VertexPositionNormalTexture array with 36 objects, because in order to draw this cube correctly we will need 36 unique vertices. Then there is a block of declaring and setting values that will only be used in this method. The first block sets 8 values that correspond to the shape's points in 3D space. Now we actually take the shape's position and build around that point. This way the center of the cube is the position of the cube. Then we take each point and size it according to our shapeSize variable.
Next we have our normal positions, which helps the graphics device determine how to handle light on the cube's surfaces. We don't need to calculate the normals relation to the center, but we do need to scale them with the rest of the shape. Each surface needs it's own normal, so we have 6 of them. The last bit in this block is texture positioning information. We only need 4 variables here, one for each corner of any given side. Now each vertex in our shape has a unique combination of all of these variables, so we are forced to set each vertex by hand, one by one. It is easier just to split up the declarations into blocks according to which side we are setting, so that is what we do. Now we have all 36 vertices ready for rendering......kind of.
Technically we have all the information set for our cube, but right now we have no way of displaying that information on the screen. The last part of our BasicShape class is a "RenderShape" method that will take these variables, put them into a buffer, and barf them on the screen for us. The RenderShape method looks something like this:
{
BuildShape();
shapeBuffer = new VertexBuffer(device,
VertexPositionNormalTexture.SizeInBytes * shapeVertices.Length,
BufferUsage.WriteOnly);
shapeBuffer.SetData(shapeVertices);
device.Vertices[0].SetSource(shapeBuffer, 0,
VertexPositionNormalTexture.SizeInBytes);
device.VertexDeclaration = new VertexDeclaration(
device, VertexPositionNormalTexture.VertexElements);
device.DrawPrimitives(PrimitiveType.TriangleList, 0, shapeTriangles);
}
The first thing we need to do is build the shape of course, so that is what we do. Next, without getting into a long rant of how a Vertex Buffer works, I will just say that the next 2 lines are preparing the vertices to be drawn. Then they are put into a buffer. The last three lines takes the information we have set, such as the number of triangles and our vertex buffer, and renders them on the screen. In order to do this, the graphics device needs to know the size of all the information given, a vertex buffer, and it needs to know how many triangles it is expected to draw. Once it has all this information, it draws all of triangles, one vertex at a time, one line at a time. This method is the last one in our BasicShape class, and just for review purposes, it our whole class should look something like:
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace BoxTutorial
{
class BasicShape
{
public Vector3 shapeSize;
public Vector3 shapePosition;
private VertexPositionNormalTexture[] shapeVertices;
private int shapeTriangles;
private VertexBuffer shapeBuffer;
public Texture2D shapeTexture;
public BasicShape(Vector3 size, Vector3 position)
{
shapeSize = size;
shapePosition = position;
}
private void BuildShape()
{
shapeTriangles = 12;
shapeVertices = new VertexPositionNormalTexture[36];
Vector3 topLeftFront = shapePosition +
new Vector3(-1.0f, 1.0f, -1.0f) * shapeSize;
Vector3 bottomLeftFront = shapePosition +
new Vector3(-1.0f, -1.0f, -1.0f) * shapeSize;
Vector3 topRightFront = shapePosition +
new Vector3(1.0f, 1.0f, -1.0f) * shapeSize;
Vector3 bottomRightFront = shapePosition +
new Vector3(1.0f, -1.0f, -1.0f) * shapeSize;
Vector3 topLeftBack = shapePosition +
new Vector3(-1.0f, 1.0f, 1.0f) * shapeSize;
Vector3 topRightBack = shapePosition +
new Vector3(1.0f, 1.0f, 1.0f) * shapeSize;
Vector3 bottomLeftBack = shapePosition +
new Vector3(-1.0f, -1.0f, 1.0f) * shapeSize;
Vector3 bottomRightBack = shapePosition +
new Vector3(1.0f, -1.0f, 1.0f) * shapeSize;
Vector3 frontNormal = new Vector3(0.0f, 0.0f, 1.0f) * shapeSize;
Vector3 backNormal = new Vector3(0.0f, 0.0f, -1.0f) * shapeSize;
Vector3 topNormal = new Vector3(0.0f, 1.0f, 0.0f) * shapeSize;
Vector3 bottomNormal = new Vector3(0.0f, -1.0f, 0.0f) * shapeSize;
Vector3 leftNormal = new Vector3(-1.0f, 0.0f, 0.0f) * shapeSize;
Vector3 rightNormal = new Vector3(1.0f, 0.0f, 0.0f) * shapeSize;
Vector2 textureTopLeft = new Vector2(0.5f * shapeSize.X, 0.0f * shapeSize.Y);
Vector2 textureTopRight = new Vector2(0.0f * shapeSize.X, 0.0f * shapeSize.Y);
Vector2 textureBottomLeft = new Vector2(0.5f * shapeSize.X, 0.5f * shapeSize.Y);
Vector2 textureBottomRight = new Vector2(0.0f * shapeSize.X, 0.5f * shapeSize.Y);
// Front face.
shapeVertices[0] = new VertexPositionNormalTexture(
topLeftFront, frontNormal, textureTopLeft);
shapeVertices[1] = new VertexPositionNormalTexture(
bottomLeftFront, frontNormal, textureBottomLeft);
shapeVertices[2] = new VertexPositionNormalTexture(
topRightFront, frontNormal, textureTopRight);
shapeVertices[3] = new VertexPositionNormalTexture(
bottomLeftFront, frontNormal, textureBottomLeft);
shapeVertices[4] = new VertexPositionNormalTexture(
bottomRightFront, frontNormal, textureBottomRight);
shapeVertices[5] = new VertexPositionNormalTexture(
topRightFront, frontNormal, textureTopRight);
// Back face.
shapeVertices[6] = new VertexPositionNormalTexture(
topLeftBack, backNormal, textureTopRight);
shapeVertices[7] = new VertexPositionNormalTexture(
topRightBack, backNormal, textureTopLeft);
shapeVertices[8] = new VertexPositionNormalTexture(
bottomLeftBack, backNormal, textureBottomRight);
shapeVertices[9] = new VertexPositionNormalTexture(
bottomLeftBack, backNormal, textureBottomRight);
shapeVertices[10] = new VertexPositionNormalTexture(
topRightBack, backNormal, textureTopLeft);
shapeVertices[11] = new VertexPositionNormalTexture(
bottomRightBack, backNormal, textureBottomLeft);
// Top face.
shapeVertices[12] = new VertexPositionNormalTexture(
topLeftFront, topNormal, textureBottomLeft);
shapeVertices[13] = new VertexPositionNormalTexture(
topRightBack, topNormal, textureTopRight);
shapeVertices[14] = new VertexPositionNormalTexture(
topLeftBack, topNormal, textureTopLeft);
shapeVertices[15] = new VertexPositionNormalTexture(
topLeftFront, topNormal, textureBottomLeft);
shapeVertices[16] = new VertexPositionNormalTexture(
topRightFront, topNormal, textureBottomRight);
shapeVertices[17] = new VertexPositionNormalTexture(
topRightBack, topNormal, textureTopRight);
// Bottom face.
shapeVertices[18] = new VertexPositionNormalTexture(
bottomLeftFront, bottomNormal, textureTopLeft);
shapeVertices[19] = new VertexPositionNormalTexture(
bottomLeftBack, bottomNormal, textureBottomLeft);
shapeVertices[20] = new VertexPositionNormalTexture(
bottomRightBack, bottomNormal, textureBottomRight);
shapeVertices[21] = new VertexPositionNormalTexture(
bottomLeftFront, bottomNormal, textureTopLeft);
shapeVertices[22] = new VertexPositionNormalTexture(
bottomRightBack, bottomNormal, textureBottomRight);
shapeVertices[23] = new VertexPositionNormalTexture(
bottomRightFront, bottomNormal, textureTopRight);
// Left face.
shapeVertices[24] = new VertexPositionNormalTexture(
topLeftFront, leftNormal, textureTopRight);
shapeVertices[25] = new VertexPositionNormalTexture(
bottomLeftBack, leftNormal, textureBottomLeft);
shapeVertices[26] = new VertexPositionNormalTexture(
bottomLeftFront, leftNormal, textureBottomRight);
shapeVertices[27] = new VertexPositionNormalTexture(
topLeftBack, leftNormal, textureTopLeft);
shapeVertices[28] = new VertexPositionNormalTexture(
bottomLeftBack, leftNormal, textureBottomLeft);
shapeVertices[29] = new VertexPositionNormalTexture(
topLeftFront, leftNormal, textureTopRight);
// Right face.
shapeVertices[30] = new VertexPositionNormalTexture(
topRightFront, rightNormal, textureTopLeft);
shapeVertices[31] = new VertexPositionNormalTexture(
bottomRightFront, rightNormal, textureBottomLeft);
shapeVertices[32] = new VertexPositionNormalTexture(
bottomRightBack, rightNormal, textureBottomRight);
shapeVertices[33] = new VertexPositionNormalTexture(
topRightBack, rightNormal, textureTopRight);
shapeVertices[34] = new VertexPositionNormalTexture(
topRightFront, rightNormal, textureTopLeft);
shapeVertices[35] = new VertexPositionNormalTexture(
bottomRightBack, rightNormal, textureBottomRight);
}
public void RenderShape(GraphicsDevice device)
{
BuildShape();
shapeBuffer = new VertexBuffer(device,
VertexPositionNormalTexture.SizeInBytes * shapeVertices.Length,
BufferUsage.WriteOnly);
shapeBuffer.SetData(shapeVertices);
device.Vertices[0].SetSource(shapeBuffer, 0,
VertexPositionNormalTexture.SizeInBytes);
device.VertexDeclaration = new VertexDeclaration(
device, VertexPositionNormalTexture.VertexElements);
device.DrawPrimitives(PrimitiveType.TriangleList, 0, shapeTriangles);
}
}
}
So we have a nice BasicShape class that will do everything we need. But how do we use this class to display a cube? Well, the answer is not that easy, so let me go over what must be done. First we need to define a world and a way to view it. Then we must define a very basic effect, without which XNA would not know how to display anything. Finally we need to initialize and use our BasicShape class. Lets get started with defining a world, which we will do with a InitializeWorld method. Before we do this though, we need to add the following declarations to the top of our Game1.cs file. Right below the 'SpriteBatch spriteBatch' declaration, so it looks something like:
Matrix worldMatrix;
Matrix cameraMatrix;
Matrix projectionMatrix;
float angle = 0f;
BasicEffect cubeEffect;
So,you have some Matrices and a floating point number. The Matrices are used for exactly what they seem, the world's boundries, the camera, and the camera's projection angles. The floating point number angle will help us rotate our cube when we get ready to display it. Lastly, we declare a variable to hold a BasicEffect object, which allows us to modify our shape, world, camera, and even add some light. These variables will help us get the 3-Dimension world up and running. Lets move on to our InitializeWorld world method, which not surprisingly uses the variables we just declared. We add this method right below the Draw method at the bottom, and it will look something like so:
{
cameraMatrix = Matrix.CreateLookAt(
new Vector3(0, 30, 20), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
Window.ClientBounds.Width / Window.ClientBounds.Height, 1.0f, 50.0f);
float tilt = MathHelper.ToRadians(22.5f);
worldMatrix = Matrix.CreateRotationX(tilt) * Matrix.CreateRotationY(tilt);
cubeEffect = new BasicEffect(GraphicsDevice, null);
cubeEffect.World = worldMatrix;
cubeEffect.View = cameraMatrix;
cubeEffect.Projection = projectionMatrix;
cubeEffect.TextureEnabled = true;
}
Let's go over what exactly this method does for us. First off we fill our camera, projection, and world matrices with suitable values for each. The cameraMatrix sets the position of the camera and points it at a specific point. Our projectionMatrix sets the viewing angles of the camera, and how far the camera can see. Finally we create a worldMatrix that defines the dimensions of our world, and we tilt the entire world with a temporary variable called tilt of course. Next we take all this information and use our BasicEffect object to hold it. We also set our BasicEffect to accept texture information. And that is what we need to set up a basic 3-Dimensional world.
Now we have to call our new method. Believe it or not, XNA creates the perfect place to do this, an Initialize method. We are going to call the InitializeWorld method in the Initialize Method, right below the base.Initialize() call. The Initialize method will end up like so:
{
base.Initialize();
initializeWorld();
}
Now that our world is built and our camera is ready to look at our shape, we need to finally declare our cube variable. We are going to make this declaration right below our BasicEffect declaration, so it will we get:
BasicShape cube = new BasicShape(new Vector3(2, 2, 2), new Vector3(0, 0, 0));
Notice that we are creating an instance of our BasicShape class. Remember our construction method? We have to pass any new instance of our class a size and a position, which we set to twice the size of the base shape and we set the position to the origin of our 3-Dimensional space. Ok, so we have our cube, but how do we use our world and camera to get it on the screen? The first step we must take is loading in a texture, then setting our shapes texture variable to the newly loaded texture. In order to do this we must load an image from a file. You really can use any image you like, and the size doesn't matter at this point either.
You will notice in your solution explorer that you have, right below references, a place called Content.
The Content Container in Visual Studio
Right click on this content holder and select add, then folder. Name the new folder 'Textures'. Right click on this new Textures folder and go to add, then existing item. Browse to whatever image you want, just make sure it is either a BMP, JPEG, or PNG. Again the size doesn't matter, and the shape would be better as a square, but that really doesn't matter either. This will add an image to the folder, AKA our texture.
Now we need to get this image into our BasicShape object. How? Well, again, XNA has created the perfect place to do this. There is a method called LoadContent, which is conveniently right below the Initialize method. This is where we will declare our texture, kind of like so:
{
// Create a new SpriteBatch, which can
// be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
cube.shapeTexture = Content.Load<Texture2D>("Textures\\GameThumbnail");
}
Now there is no need to pay attention to the SpriteBatch object because it is actually generated by XNA when you create a new project, plus we don't use it in this tutorial. But we do use the Content.Load method, which is used when you want to load something from the Content container in the project. With the Content.Load method, you can load anything from 3D models to simple images, which is the case here. As we load the image we make it a Texture2D object, which is a 2-Dimensional image that is used to texture a 3-Dimensional object.
So let's do a quick review. We have built a world, set up a camera, declared a BasicShape object, and given it a texture. Now what? Well, there is one last thing. Remember that floating point number, angle? We are going to use it to rotate the whole world, and in effect the cube we make. But in order to do this we need to update the variable every time the world is rendered. We do this by using the, once again conveniently created, Update method. This method is called every time a frame is rendered on the screen, a cozy spot to update our angle variable. With this simple addition our Update method looks like this:
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
angle += 0.5f;
base.Update(gameTime);
}
Of course you are welcome to experiment with different values, the bigger the number the faster the rotation will be. Really the angle update is the only thing we adding here, the rest is generated on project creation. We can move on to the final and most important pre-built method, the Draw method. The Draw method is the heart of an XNA project. It is where everything is actually drawn in the 3D world and where that world is rendered to the screen. Without this method your project would be about as interesting as a blank monitor. Inside this method we will call the functions needed to finally put something on the screen. Our final filled Draw method will be:
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
cubeEffect.Begin();
worldMatrix = Matrix.CreateRotationY(MathHelper.ToRadians(angle)) *
Matrix.CreateRotationX(MathHelper.ToRadians(angle));
cubeEffect.World = worldMatrix;
foreach(EffectPass pass in cubeEffect.CurrentTechnique.Passes)
{
pass.Begin();
cubeEffect.Texture = cube.shapeTexture;
cube.RenderShape(GraphicsDevice);
pass.End();
}
cubeEffect.End();
base.Draw(gameTime);
}
Our final code doesn't actually do a lot on its own, rather it takes all the stuff we have been doing and connects it. First we begin to run through our BasicEffect object, which can be filled with multiple effects, if that makes any since. Then we set the angle of the X and Y axis to our angle variable, giving it the effect that the cube is spinning. Next we loop through all the effects in our BasicEffect object, which is just one in this case. For each pass we have to start and end the pass, and during the pass we set the effect texture then render the cube. The cube is given the texture we set because we set the effect texture to the cube's texture. If we didn't set the texture here, there would be no texture on the cube. Finally we stop the loop, then stop the run through the BasicEffect.
The final result, a nicely textured box.
If you use and want a Visual Studio solution , it can be found here
It takes a lot more than you would think at first to make a cube on the screen. In XNA there are better ways to do so, but it is always good practice to try and make one rather than import one. Basic geometic shapes in a 3-Dimensional space can get pretty complex, but XNA makes things slightly easier, and the power of XNA is more than just shapes. But now that you have a cube, you can try to manipulate it and add more effects. So go ahead and try it yourself, you can start by changing the size and position, then move on from there. Hope you enjoyed the tutorial and keep Switching on the Code.
05/16/2008 - 04:44
Thanks for this. I visited your sponsors.
05/23/2008 - 08:04
Nice Article.
10/01/2008 - 05:56
Thanks for the article. I have a special cube which has different textures or no texture on each side, so in worst case there a six textures for the cube. How to handle that?
11/08/2008 - 13:41
Thanks a lot for this great example. Very nice explained and easy enough even for a beginner like me ;)
Peace,
Philipp
05/15/2009 - 11:01
Im quite a nub and this tutorial has helped me a LOT! thanks for this. I tinkered around with it a bit, and i have two questions that are prolly piss easy but I cant figure them out since I suck ^^.
1) same question as Sven, i cant find where you actually load your textures onto the drawn triangles and so cant change it : / i want 6 different textures on this cube.
2) I actually use this model as a base for a SkyBox thingy, so I put my camera inside the box. (0,0,0) since thats where the middle of the box is also. So i want the textures to display on the inside of the drawn spaces, not the outside, how can I alter this?
Or am i just a nub and does a camera on position 0,0,0 and a LookAt of 0,0,0 look at itself and never displays textures..?
07/15/2009 - 23:21
CodeMonkey: flip the normal of the surfaces
Add Comment
[language] [/language]
Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]
See here for supported languages.
Javascript must be enabled to submit anonymous comments - or you can login.