Overview
This tutorial shows how to create a very basic world using JigLibX. This world will consist of two boxes. The first box is a large cube that is stationary. The second box is a small cube that is high in the air. The second box falls down onto the immovable box and bounces away. This demonstrates both gravity and collision detection.
Starting the Project
The very first thing we need to do is create a new Windows game to put all of our code in. For simplicity change the contents of the default game class to look like this:
using System; using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using JigLibX.Physics; using JigLibX.Geometry; using JigLibX.Collision; public class BasicWorldGame : Game { GraphicsDeviceManager graphics; public BasicWorldGame() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; InitializePhysics(); } private void InitializePhysics() { PhysicsSystem world = new PhysicsSystem(); world.CollisionSystem = new CollisionSystemSAP(); } protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); base.Update(gameTime); } protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); base.Draw(gameTime); } static void Main(string[] args) { using (BasicWorldGame game = new BasicWorldGame()) { game.Run(); } } }
This is a basic game class with the default comments stripped out and the Main function moved into it. It also contains some "using" statements that we will need later. To debug the game at this point you will need to delete Program.cs and add a reference to the JigLibX assembly. Once you do those things debug the game to make sure it is working. At this point you will just see an empty blue window.
Next we will need a model of a cube. This cube should be centered at the origin and have a side length of 1; each side will range from -0.5 to 0.5. Place this model into the content pipeline so we can use it later.
Adding the Physics System
Now for the meat of this tutorial: adding the physics system. The physics system is what controls everything physics related in any simulation. Everything that will use JigLibX will require a physics system.
To create one is very easy. In the InitializePhysics method add this code:
PhysicsSystem world = new PhysicsSystem();
There we have it: our own physics system. Although, the setup is not complete. If we were to continue on from here leaving the physics system the way it is our two boxes would simply pass through each other. Therefore we need to add a collision system to our physics system. Add the following code right under the code we just added to achieve this.
world.CollisionSystem = new CollisionSystemSAP();
In that line of code we specified what collision system would be used. There are other types of collision systems but deciding which one to use is not important because our simulation is very small.
There is one final thing to do before we move on from creating the physics system; we must update the world every so often. This updating is called "integrating" in physics terminology. When a physics system is updated it basically applies gravity to all our objects and detects collisions between them. In reality it is far more complicated than that but the details are not important at this point.
Updating the physics system is easy. We simply make a call to the physics system's Integrate method. The Integrate method takes a parameter specifying how much time has gone by since the last call to integrate. Using the Integrate method is as easy as adding the following line to our Update method in the game class.
float timeStep = (float)gameTime.ElapsedGameTime.Ticks / TimeSpan.TicksPerSecond; PhysicsSystem.CurrentPhysicsSystem.Integrate(timeStep);
PhysicsSystem.CurrentPhysicsSystem is a static member of the PhysicsSystem class that is set to the last physics system created. This is the physics system we set up in the InitializePhysics method. We use this static member for convenience so we don't have to pass around a PhysicsSystem variable to whatever function that needs it.
For the parameter of the Integrate method we use the gameTime parameter passed to the Update method.
The physics system is now complete and ready to simulate a basic world. At this point though our world is rather bare. There is nothing in it yet. To populate the world with our boxes, we will create a class that automates the process.
Creating the BoxActor Class
The class we will create is called the BoxActor class. This class will handle setting up a box in our simulation and drawing it to the screen. To start out, create a new empty file in the project called BoxActor.cs. Put the following code into the file.
using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using JigLibX.Math; using JigLibX.Physics; using JigLibX.Geometry; using JigLibX.Collision; public class BoxActor : DrawableGameComponent { private Vector3 position; private Vector3 scale; private Body _body; public Body Body { get { return _body; } } private CollisionSkin _skin; public CollisionSkin Skin { get { return _skin; } } public BoxActor(Game game, Vector3 position, Vector3 scale) : base(game) { this.position = position; this.scale = scale; } }
This is the basic outline of our BoxActor class. It inherits from DrawableGameComponent because a BoxActor will need to be drawn. Any object that you want displayed on the screen will need to inherit from DrawableGameComponent. The position field holds the default position of the center of the box. The scale field holds the size of each side of the box. The _body field and the Body property are references to the body of the box. A body in the context of JigLibX is something that moves and rotates. It can't collide with anything though without the help of a CollisionSkin. That is the purpose of the _skin and Skin members. These are references to the collision skin of the box's body. A collision skin is what describes the physical volume of the body. It is what tells JigLibX how to detect collisions between bodies.
Creating a Body and Collision Skin
Right now neither the body or the skin is defined. This is the point where our project gets more involved. To start out, add the following code to the end of the BoxActor constructor.
_body = new Body(); _skin = new CollisionSkin(_body); _body.CollisionSkin = _skin;
This code is very straightforward. We use the constructor of the Body class to create a new body. We do the same thing for the collision skin except we pass the newly create body to it. The third line of code sets the body's collision skin to the new collision skin we just made. Now we have the basics of our body and skin done but they are far from finished.
The next thing to do is to add a primitive to the collision skin. Primitives are basic objects that make up the more complicated objects in a large simulation. Examples of primitives are spheres, boxes, and planes. Since the object we want to simulate is nothing more than a box, the only primitive we need is a box.
To create a box primitive, we create a new instance of the Box class. After creating a Box primitive, we use the AddPrimitive method in the CollisionSkin class. This method takes our new primitive and adds it to the collision skin so our body can now collide with other things. Put the following code at the bottom of the constructor.
Box box = new Box(Vector3.Zero, Matrix.Identity, scale); _skin.AddPrimitive(box, new MaterialProperties(0.8f, 0.8f, 0.7f));
The first line creates a new Box class. The first parameter of the constructor is the position of the box relative to the body. We want the box at the center of the body so the position is the origin in this case. The next parameter is orientation. This defines the rotation of the box relative to the box. We want the box to be aligned with body so we use the identity matrix, which means no rotation. The third parameter is the scale of the box. We use the scale given to this class through the constructor.
The second line adds the box to the collision skin so the body can collide with things. The first parameter is the primitive we wish to add which is our new box. The second parameter defines a custom material. A material in the context of Physics is what defines the surface properties of an object, such as bounciness and friction. The MaterialProperties class has three float parameters, the first is Elasticity, the second is StaticRoughness, and the third is DynamicRoughness. You can also use a predefined material by replacing the second parameter with a value from MaterialTable.MaterialID (make sure to cast as int).
Setting the Box's Mass
Now that we are done with adding the box we must make adjustments to the body because its mass has changed. These changes are best demonstrated by some code. Add this method to the BoxActor class.
private Vector3 SetMass(float mass) { PrimitiveProperties primitiveProperties = new PrimitiveProperties( PrimitiveProperties.MassDistributionEnum.Solid, PrimitiveProperties.MassTypeEnum.Mass, mass); float junk; Vector3 com; Matrix it; Matrix itCoM; Skin.GetMassProperties(primitiveProperties, out junk, out com, out it, out itCoM); Body.BodyInertia = itCoM; Body.Mass = junk; return com; }
This function will set the mass of our box. This function simply queries the collision skin for important information about the body and gives that information to the body. The function returns the center of mass of the body. We call this method right after adding the box primitive to the collision skin. Add the following code to the bottom of the constructor.
Vector3 com = SetMass(1.0f);
This sets the body's mass to 'one' and also stores the center of mass for later use.
Setting the Box's Position
Next we move the body to the position specified in the constructor's parameters and adjust the collision skin to match our model. Add the following to the bottom of the constructor.
_body.MoveTo(position, Matrix.Identity); _skin.ApplyLocalTransform(new Transform(-com, Matrix.Identity)); _body.EnableBody();
The body is moved to the position specified by the first parameter which is the BoxActor's position in this case. The second parameter sets the orientation of the box. We do not want that changed so we set that to the identity matrix. The second line moves the collision skin to the body's center of mass. This is there to align the model we will see on the screen to the body that JigLibX will be moving. The last line enables the body for the simulation because it is turned off by default.
Creating Our World
Now that all of the framework has been setup it is time to create our world. Go back to InitializePhysics method in our game class and add the following to the end of the method.
fallingBox = new BoxActor(this, new Vector3(0, 50, 0), new Vector3(1, 1, 1)); immovableBox = new BoxActor(this, new Vector3(0, -5, 0), new Vector3(5, 5, 5)); immovableBox.Body.Immovable = true; Components.Add(fallingBox); Components.Add(immovableBox);
You will also need to add the following code to the top of the class to declare the variables.
BoxActor fallingBox; BoxActor immovableBox;
The code we have just added is utilizing our BoxActor class to setup our world. The first line creates the falling box I mentioned in the overview. We create it just like any other game component except for the additional parameters. The first parameter is the game class, the second is the position. (The position we specify is really high up and therefore falling) The final parameter is the size of the box. We just want it to be a small cube.
The second line does the exact same thing except that the immovable box is much lower and larger. The third line accesses the immovable box's Body property. We set the Immovable property of the body to "true" making this box immovable. This will allow our falling box to collide with this box; otherwise the immovable box would fall just as fast as the falling box and they would never collide.
In the fourth and fifth lines we are adding the boxes to the components collection built into our game class. This allows them to be drawn automatically by the game class.
Now our simulation is complete. Everything in JigLibX is setup and ready to go (although we are not done just yet). If we were to build the game right now and run it, we would see an empty blue screen. We would not see our nice cubes colliding. This is because JigLibX is only doing part of the work in a game. It will do physics but it will never draw anything on the screen for you. We have to do this ourselves.
Drawing our World
Remember our cube model we made in the beginning? This is where we will make use of it. If the cube model was made correctly it's center should be at the origin and it must have a side length of 1.
Like any model, we must first load it from the content pipeline. Add this method to the BoxActor class.
protected override void LoadContent() { model = Game.Content.Load<Model>("boxModel"); }
*You should change "boxModel" to whatever you called the model in the content pipeline.
You will need to add this field to the beginning of the BoxActor class so we can use the model while drawing.
private Model model;
If you have any question about how to load or draw a model in XNA refer to the tutorial at MSDN.
Next add the following method to the BoxActor class.
private Matrix GetWorldMatrix() { return Matrix.CreateScale(scale) * _skin.GetPrimitiveLocal(0).Transform.Orientation * _body.Orientation * Matrix.CreateTranslation(_body.Position); }
This method creates the World matrix that will make the box model on screen match up with the box's body in JigLibX. The first matrix we use is a scale matrix to make the box the size specified in the constructor. The second matrix rotates the box to how the collision skin is rotated. The third matrix rotates the box according to the body's orientation. The final matrix moves the box to the body's position. We combine them to get the World matrix of the box.
In addition to the World matrix, drawing the box requires a View and Projection matrix. Since neither is specific to any given BoxActor we will store them in the game class. Add the following code to the top of the game class.
private Matrix _view; public Matrix View { get { return _view; } } private Matrix _projection; public Matrix Projection { get { return _projection; } }
This is basic code for storing a View and Projection matrix and allowing outside classes to access them. We must add code to set the View and Projection matrices. Since this code is covered in the tutorial mentioned above, I am not going to explain it here. Put the following at the bottom of the game class's constructor.
_projection = Matrix.CreatePerspectiveFieldOfView( MathHelper.ToRadians(45.0f), (float)graphics.PreferredBackBufferWidth / (float)graphics.PreferredBackBufferHeight, 0.1f, 1000.0f );
Copy the following code to the end of the Update method.
_view = Matrix.CreateLookAt( new Vector3(0, 5, 20), fallingBox.Body.Position, Vector3.Up );
This View matrix is set to look at the falling box's position so we can see it at all times. We also set the camera's position to good viewpoint a little above and behind the immovable box.
Now the game class is completely finished, we will add one more method to the BoxActor class and we will be done. Add the following method to the bottom of the BoxActor class.
public override void Draw(GameTime gameTime) { BasicWorldGame game = (BasicWorldGame)Game; Matrix[] transforms = new Matrix[model.Bones.Count]; model.CopyAbsoluteBoneTransformsTo(transforms); Matrix worldMatrix = GetWorldMatrix(); foreach (ModelMesh mesh in model.Meshes) { foreach (BasicEffect effect in mesh.Effects) { effect.EnableDefaultLighting(); effect.PreferPerPixelLighting = true; effect.World = transforms[mesh.ParentBone.Index] * worldMatrix; effect.View = game.View; effect.Projection = game.Projection; } mesh.Draw(); } }
The first line casts the Game class built into the game component to our application specific BasicWorldGame class. This is so we can access the View and Projection matrices we just added to the game class. The rest of the code is just basic model drawing code until we get down to the parts where we set the effect's matrices. We multiply the normal World matrix by the World matrix we got from the GetWorldMatrix method. We then set the View and Projection matrices to the ones from the game class.
The project is now done. Go ahead and run it and you will see the falling box smash into the immovable box and bounce a couple times. That does it for the basics of creating a world with JigLibX.
The completed code should look like this:
BasicWorldGame.cs
using System;
using System.Collections.Generic;
using System.Linq;
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.Media;
using JigLibX.Physics;
using JigLibX.Collision;
namespace Sample1
{
/// <summary>
/// This is the main type for your game
/// </summary>
public class BasicWorldGame : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
BoxActor fallingBox, immovableBox;
public Matrix View { get; private set; }
public Matrix Projection { get; private set; }
public BasicWorldGame()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
InitializePhysics();
this.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),
(float)graphics.PreferredBackBufferWidth / (float)graphics.PreferredBackBufferHeight,
0.1f,
1000.0f
);
}
private void InitializePhysics()
{
PhysicsSystem world = new PhysicsSystem();
world.CollisionSystem = new CollisionSystemSAP();
this.fallingBox = new BoxActor(this, new Vector3(0, 50, 0), new Vector3(1, 1, 1));
this.immovableBox = new BoxActor(this, new Vector3(0, -5, 0), new Vector3(5, 5, 5));
immovableBox.Body.Immovable = true;
this.Components.Add(fallingBox);
this.Components.Add(immovableBox);
}
/// <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();
// TODO: Add your update logic here
base.Update(gameTime);
float timeStep = (float)gameTime.ElapsedGameTime.Ticks / TimeSpan.TicksPerSecond;
PhysicsSystem.CurrentPhysicsSystem.Integrate(timeStep);
this.View = Matrix.CreateLookAt(
new Vector3(0, 5, 20),
this.fallingBox.Body.Position,
Vector3.Up
);
}
/// <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)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
base.Draw(gameTime);
}
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main(string[] args)
{
using (BasicWorldGame game = new BasicWorldGame())
{
game.Run();
}
}
}
}
BoxActor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using JigLibX.Physics;
using JigLibX.Collision;
using JigLibX.Geometry;
using JigLibX.Math;
using Microsoft.Xna.Framework.Graphics;
namespace Sample1
{
public class BoxActor : DrawableGameComponent
{
private Vector3 position, scale;
private Model model;
public Body Body { get; private set; }
public CollisionSkin Skin { get; private set; }
public BoxActor(Game game, Vector3 position, Vector3 scale)
: base(game)
{
this.position = position;
this.scale = scale;
this.Body = new Body();
this.Skin = new CollisionSkin(this.Body);
this.Body.CollisionSkin = this.Skin;
Box box = new Box(Vector3.Zero, Matrix.Identity, scale);
this.Skin.AddPrimitive(box, new MaterialProperties(
0.8f, // elasticity
0.8f, // static roughness
0.7f // dynamic roughness
) );
Vector3 com = SetMass(1.0f);
this.Body.MoveTo(this.position, Matrix.Identity);
this.Skin.ApplyLocalTransform(new Transform(-com, Matrix.Identity));
this.Body.EnableBody();
}
private Vector3 SetMass(float mass)
{
PrimitiveProperties primitiveProperties = new PrimitiveProperties(
PrimitiveProperties.MassDistributionEnum.Solid,
PrimitiveProperties.MassTypeEnum.Mass,
mass);
float junk;
Vector3 com;
Matrix it, itCom;
this.Skin.GetMassProperties(primitiveProperties, out junk, out com, out it, out itCom);
this.Body.BodyInertia = itCom;
this.Body.Mass = junk;
return com;
}
protected override void LoadContent()
{
this.model = Game.Content.Load<Model>("boxModel");
}
private Matrix GetWorldMatrix()
{
return Matrix.CreateScale(scale) * this.Skin.GetPrimitiveLocal(0).Transform.Orientation * this.Body.Orientation * Matrix.CreateTranslation(this.Body.Position);
}
public override void Draw(GameTime gameTime)
{
BasicWorldGame game = (BasicWorldGame)this.Game;
Matrix[] transforms = new Matrix[model.Bones.Count];
model.CopyAbsoluteBoneTransformsTo(transforms);
Matrix worldMatrix = this.GetWorldMatrix();
foreach (ModelMesh mesh in model.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.EnableDefaultLighting();
effect.PreferPerPixelLighting = true;
effect.World = transforms[mesh.ParentBone.Index] * worldMatrix;
effect.View = game.View;
effect.Projection = game.Projection;
}
mesh.Draw();
}
}
}
}