Creating a Textured Box in XNA |
Microsoft has undoubtedly made some top notch developer tools over the years.Besides VisualStudio,.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). |
|
|
|
|
using System;
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;
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;
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: |
class BasicShape
{
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 Vectors 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: |
public BasicShape(Vector3 size, Vector3 position)
{
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: |
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(
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(
bottomNormal, textureTopRight);
// Left face.
hapeVertices[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: |
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);
} |
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;
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);
hapeVertices[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);
}
}
}
|
|