XNA Game Engine Tutorial Series #6.5 – Engine Update

Note: This section is not necessary if you started with tutorial #1 after 12/10/2008

In this mini-tutorial, we will be making some minor updates to the engine. First, we will be changing the utility classes to static classes. Remove the instances of MathUtil and GraphicsUtil from the Engine class. Then, add the modifier ‘static’ before the class definition and methods for the two classes.

public static class GraphicsUtil
{
    // Creates a RenderTarget2D with the specified parameters
    public static RenderTarget2D CreateRenderTarget()
    {
        return CreateRenderTarget(Engine.GraphicsDevice.Viewport.Width,
            Engine.GraphicsDevice.Viewport.Height);
    }

    // Creates a RenderTarget2D with the specified parameters
    public static RenderTarget2D CreateRenderTarget(int Width, int Height)
    {
        return CreateRenderTarget(Width, Height,
            Engine.GraphicsDevice.DisplayMode.Format);
    }

    etc, etc...

Second, the Rectangle property in I2DComponent should be a Rectangle.

public interface I2DComponent
{
    Rectangle Rectangle { get; set; }
}

Now we will add a few more methods to the MathUtil class:

// Converts a rotation vector into a rotation matrix
public static Matrix Vector3ToMatrix(Vector3 Rotation)
{
    return Matrix.CreateFromYawPitchRoll(Rotation.Y, Rotation.X, Rotation.Z);
}

// Converts a rotation matrix into a rotation vector
public static Vector3 MatrixToVector3(Matrix Rotation)
{
    Quaternion q = Quaternion.CreateFromRotationMatrix(Rotation);
    return new Vector3(q.X, q.Y, q.Z);
}

Now change these methods in the MathUtil to use matrices for rotation:

// Creates a world matrix
public static Matrix CreateWorldMatrix(Vector3 Translation)
{
    return CreateWorldMatrix(Translation, Matrix.Identity);
}

// Creates a world matrix
public static Matrix CreateWorldMatrix(Vector3 Translation,
    Matrix Rotation)
{
    return CreateWorldMatrix(Translation, Rotation, Vector3.One);
}

// Creates a world matrix
public static Matrix CreateWorldMatrix(Vector3 Translation,
    Matrix Rotation, Vector3 Scale)
{
    return Matrix.CreateScale(Scale) *
        Rotation *
        Matrix.CreateTranslation(Translation);
}

In the Camera class:

Projection = Engine.Math.CreateProjectionMatrix();

becomes

Projection = MathUtil.CreateProjectionMatrix();

Now change the following in the Actor class:

Matrix world = Engine.Math.CreateWorldMatrix(Position, Rotation, Scale);

becomes

Matrix world = MathUtil.CreateWorldMatrix(Position, Rotation, Scale);
Rotation = Vector3.Zero;

becomes

EulerRotation = Vector3.Zero;

Third, representing rotations as a Vector3 is a bad way to do it, for several reasons. Change I3DComponent to look like this:

// Represents a 3D object. These objects will be drawn before
// 2D objects, and will have modifiers automatically provided
// in the editor.
public interface I3DComponent
{
    // Position in the Cartesian system (X, Y, Z)
    Vector3 Position { get; set; }

    // Rotation represented as a Vector3. This shouldn't
    // be used for calculations, it is left in so that
    // the rotation can be more easily modified by hand
    Vector3 EulerRotation { get; set; }
    // Rotation as a Matrix. This will give much smoother
    // and cleaner calculations that a Vector3
    Matrix Rotation { get; set; }

    // Scale for each axis (X, Y, Z)
    Vector3 Scale { get; set; }

    // BoundingBox to use for picking and pre-collision
    BoundingBox BoundingBox { get; }
}

Now we need to fix this in a few other classes. Change the following in the Actor class:

// I3DComponent values
public virtual Vector3 Position { get; set; }
public Vector3 EulerRotation
{
    get { return MathUtil.MatrixToVector3(Rotation); }
    set { Rotation = MathUtil.Vector3ToMatrix(value); }
}
public virtual Matrix Rotation { get; set; }
public virtual Vector3 Scale { get; set; }
public virtual BoundingBox BoundingBox
{
    get
    {
        return new BoundingBox(
            Position - (Scale / 2),
            Position + (Scale / 2)
        );
    }
}

Now change the following in PhysicsActor:

// Override the rotation of the base class to that
// of the physics object
public override Matrix Rotation
{
    get
    {
        if (PhysicsObject != null)
            return PhysicsObject.Rotation;
        else
            return Matrix.Identity;
    }
    set
    {
        if (PhysicsObject != null)
            PhysicsObject.Rotation = value;
    }
}

Change the Camera class to look like this:

using Microsoft.Xna.Framework;
using System;
using System.Runtime.Serialization;

namespace Innovation
{
    // Basic Camera class
    public class Camera : Component, I3DComponent
    {
        // Internal values
        Vector3 position = Vector3.Zero;
        Matrix rotationMatrix = Matrix.Identity;
        Vector3 target = new Vector3(0, 0, -1);
        Vector3 up = Vector3.Up;
        Matrix view;
        Matrix projection;

        // The point the camera is looking at
        public virtual Vector3 Target
        { get { return target; } set { target = value; } }

        // The View and Projection matrices commonly used for rendering
        public virtual Matrix View
        { get { return view; } set { view = value; } }
        public virtual Matrix Projection
        { get { return projection; } set { projection = value; } }

        public virtual Vector3 Up
        { get { return up; } set { up = value; } }

        // Public I3DComponent values
        public virtual Vector3 Position { get { return position; } set { position = value; } }
        public virtual Vector3 Scale { get { return Vector3.One; } set { } }
        public Vector3 EulerRotation
        {
            get { return MathUtil.MatrixToVector3(Rotation); }
            set { Rotation = MathUtil.Vector3ToMatrix(value); }
        }
        // The rotation matrix used by the camera and the current up vector
        public virtual Matrix Rotation
        { get { return rotationMatrix; } set { rotationMatrix = value; } }

        public virtual BoundingBox BoundingBox
        { get { return new BoundingBox(Position - Vector3.One, Position + Vector3.One); } }

        // Constructors
        public Camera(GameScreen Parent) : base(Parent) { }
        public Camera() : base() { }

        // Update the camera
        public override void Update()
        {
            // Calculate the direction from the position to the target, and normalize
            Vector3 newForward = Target - Position;
            newForward.Normalize();

            // Set the rotation matrix's forward to this vector
            Matrix rotationMatrixCopy = this.Rotation;
            rotationMatrixCopy.Forward = newForward;

            // Save a copy of "Up" (0, 1, 0)
            Vector3 referenceVector = Vector3.Up;

            // On the slim chance that the camera is pointed perfectly parallel with
            // the Y Axis, we cannot use cross product with a parallel axis, so we
            // change the reference vector to the forward axis (Z).
            if (rotationMatrixCopy.Forward.Y == referenceVector.Y
                || rotationMatrixCopy.Forward.Y == -referenceVector.Y)
                referenceVector = Vector3.Backward;

            // Calculate the other parts of the rotation matrix
            rotationMatrixCopy.Right = Vector3.Cross(this.Rotation.Forward,
                referenceVector);
            rotationMatrixCopy.Up = Vector3.Cross(this.Rotation.Right,
                this.Rotation.Forward);

            this.Rotation = rotationMatrixCopy;

            // Use the rotation matrix to find the new up
            Up = Rotation.Up;

            // Recalculate View and Projection using the new Position, Target, and Up
            View = Matrix.CreateLookAt(Position, Target, Up);
            Projection = MathUtil.CreateProjectionMatrix();
        }
    }
}

Change the following in the PhysicsObject class:

// The PhysicsObject's rotation as a Matrix
public Matrix Rotation
{
    get { return Body.Orientation; }
    set { Body.MoveTo(Body.Position, value); }
}
// The PhysicsObject's rotation as a Euler Vector
public Vector3 EulerRotation
{
    get { return MathUtil.MatrixToVector3(Rotation); }
    set { Rotation = MathUtil.Vector3ToMatrix(value); }
}

Remove the Orientation property from PhysicsObject. Now go through the various physics object, and change any references to Rotation to EulerRotation, and any references to Orientation to Rotation.

Download the code

10 Responses to “XNA Game Engine Tutorial Series #6.5 – Engine Update” »

  1. Comment by craig — December 8, 2008 @ 11:05 pm

    I’m guessing posting tut 7 was an accident? tut 7 showed in my rss then was gone on your site but now you posted this its back? oh well all the updates are great I’m learning a lot from this series and thanks for all of it.

  2. Comment by Sean James — December 8, 2008 @ 11:47 pm

    I posted it forgetting that there were a lot of updates to make, so I took it down, wrote this post, then reposted 7 so that it would work after people made the updates. I’m also in the middle of updating the older posts so new people wouldn’t have to do these ‘update’ posts.

  3. Comment by James — December 9, 2008 @ 11:56 pm

    Fantastic tutorials Sean, great to see someone who comments their code seems to be a bit of a lost art. Keep up the great work, very much enjoying reading your stuff, caused me to change the way I have been structuring parts of my own engine.

  4. Comment by James — December 10, 2008 @ 3:22 am

    Also just a quick typo above the projection that needs to be changed to use the MathUtil class is over in Camera not Actor.

  5. Comment by Ilya Ostrovskiy — December 11, 2008 @ 4:25 pm

    Errm, I think in the I2DComponent part, you need to change it to “The Rectangle property needs to be a Rectangle” instead of “needs to be a Vector2″.

    Great tuts, btw!

  6. Pingback by XNA Game Engine Tutorial Series #7 - First Person Camera | Innovative Games — December 11, 2008 @ 4:51 pm

    [...] XNA Game Engine Tutorial Series #6.5 – Engine Update [...]

  7. Pingback by [Updated] XNA Game Engine Tutorial Series #7 - First Person Camera | Innovative Games — December 11, 2008 @ 4:57 pm

    [...] XNA Game Engine Tutorial Series #6.5 – Engine Update [...]

  8. Comment by Markus Ewald — September 20, 2009 @ 1:31 pm

    I haven’t followed your series and only came across this article, but it seems you had the engine set up as dynamic classes and are now switching over to static classes.

    Especially the idea of keeping the GraphicsDevice in a static field. Is there a particular reason for your decision?

    I’m just interested in hearing your motivation :)

    I started out with some static classes, but ultimately went the other way. Mainly because I wanted to use an inversion of control container and because, well, a class which takes a graphics device in its constructor is more easily isolated from the other classes than one depending a static class named X in namespace Y with property Z to exist somewhere in order to compile.

    On the other hand, an all-static design might be easier to understand for people new to the topic, so maybe that’s the motivation?

  9. Comment by fused — November 27, 2010 @ 11:45 pm

    Hey man,

    very good tutorials you have here! They taught me a lot.

    Just a little optimization i would like to point out (contributing back and so forth :) )

    in GameScreen.cs

    public virtual void Update()
    {
    // Create a temporary list so we don’t crash if
    // a component is added to the collection while
    // updating
    List updating = new List();

    // Populate the temporary list
    foreach (Component c in Components)
    updating.Add(c);

    // Update all components that have been initialized
    foreach (Component Component in updating)
    if (Component.Initialized)
    Component.Update();
    }

    Could be changed to do this:

    public virtual void Update()
    {
    // Create a temporary list so we don’t crash if
    // a component is added to the collection while
    // updating
    List updating = Components.ToList();

    // Update all components that have been initialized
    foreach (Component Component in updating)
    if (Component.Initialized)
    Component.Update();
    }

    Not sure if it would actually faster(should hopefully be), but in terms of simplicity, its a win!

    Thanks again!
    fused

  10. Comment by fused — November 27, 2010 @ 11:46 pm

    It ate all the sharp brackets :|

RSS feed for comments on this post. TrackBack URI

Leave a comment