This tutorial is going to cover the addition of some utilities to Engine and demonstrate the creation of a basic component. Then we are going to cover the creation of a camera and actor.

We are going to add two utilities to the Engine class. The first is a math helper that mainly generates matrices for drawing, cameras, model rendering, etc. The second is a graphics utility that helps with the creation of render targets and resolve textures. It’s OK if you don’t know that that means, we won’t be using them for a while.

First, here is the code for the MathUtil class:

using Microsoft.Xna.Framework;

namespace Innovation
{
    public static class MathUtil
    {
        // Generates a projection matrix for a draw call
        public static Matrix CreateProjectionMatrix()
        {
            return Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
                (float)Engine.GraphicsDevice.Viewport.Width
                / (float)Engine.GraphicsDevice.Viewport.Height,
                .01f, 1000000);
        }

        // 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);
        }

        // 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);
        }
    }
}

Second, here is the code for the GraphicsUtil class:

using Microsoft.Xna.Framework.Graphics;

namespace Innovation
{
    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);
        }

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

        // Creates a RenderTarget2D with the specified parameters
        public static RenderTarget2D CreateRenderTarget(int Width, int Height,
            SurfaceFormat Format, int MultiSampleQuality,
            MultiSampleType SampleType)
        {
            return new RenderTarget2D(Engine.GraphicsDevice, Width,
                Height, 1, Format, SampleType, MultiSampleQuality,
                RenderTargetUsage.DiscardContents);
        }

        // Creates a ResolveTexture2D
        public static ResolveTexture2D CreateResolveTexture()
        {
            return new ResolveTexture2D(Engine.GraphicsDevice,
                Engine.GraphicsDevice.Viewport.Width,
                Engine.GraphicsDevice.Viewport.Height, 1,
                Engine.GraphicsDevice.DisplayMode.Format);
        }
    }
}

Now we can get started with some basic components. Our demo component will do nothing more than clear the backbuffer. Before we get started on this component, we need to clean up the Game class in the demo project (in our example, “TestEnvironment”). We are going to remove the large amounts of comments and some of the methods and objects we won’t be using (in Game1.cs):

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Innovation;

namespace TestEnvironment
{
    public class Game1 : Game
    {
        // The IGraphicsDeviceService the engine will use
        GraphicsDeviceManager graphics;

        public Game1()
        {
            // Setup graphics
            graphics = new GraphicsDeviceManager(this);
        }

        protected override void LoadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);
        }
    }
}

Now we are going to add the code to initialize the engine. Add the followig to LoadContent(), Update(), and Draw(), respectively. Add the code before the base.Whatever() calls.

// Setup engine. We do this in the load method
// so that we know graphics will be ready for use
Engine.SetupEngine(graphics);
// Update the engine and game
Engine.Update(gameTime);
// Draw the engine and game
Engine.Draw(gameTime, ComponentType.All);

Now when you hit F5, you should see the following:

Blue Screen

Blue Screen

Now we can create our component. Create this one in the game project. Add a new class and add the following code:

using Innovation;
using Microsoft.Xna.Framework.Graphics;

namespace TestEnvironment
{
    class ClearScreen : Component
    {
        // Override the component's draw method
        public override void Draw()
        {
            // Simply clear the backbuffer to red
            Engine.GraphicsDevice.Clear(Color.Red);
        }
    }
}

Now, go back to the Game1 class and add the following at the end of the LoadContent() method to add an instance of it to the game:

// Create a new ClearScreen. It will automatically
// be set the engine's DefaultScreen, currently
// Engine.BackGroundScreen
ClearScreen clear = new ClearScreen();

You should now see the following:

Red Screen

Red Screen

Now we are going to start on some more complicated components. The first is our Camera class. This is just a base class, other cameras later on will inherit from it. This camera doesn’t have any fancy movement functions, but it does what we need it to, which, for now is just point at a target location and generate the matrices we need to draw. Here is the code, add this class to the engine’s project:

using Microsoft.Xna.Framework;
using System;

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();
        }
    }
}

The next component we need to make is called ‘Actor’. This class simply keeps track of a model, keeps track of Position, Rotation, Scale, etc, and draws the model with those properties. You will see that it looks for a Camera in the Engine’s service container to draw with. If it doesn’t find one, it will stop excecution and throw an exception. Here is the code:

using System;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace Innovation
{
    public class Actor : Component, I3DComponent
    {
        // The model to draw
        Model model;

        // 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)
                );
            }
        }

        // Constructors take a model to draw and a position
        public Actor(Model Model, Vector3 Position)
            : base()
        {
            Setup(Model, Position);
        }

        public Actor(Model Model, Vector3 Position, GameScreen Parent)
            : base(Parent)
        {
            Setup(Model, Position);
        }

        // Provide a method to setup the actor so we don't need to
        // write it in each constructor
        void Setup(Model Model, Vector3 Position)
        {
            this.model = Model;
            this.Position = Position;
            Scale = Vector3.One;
            EulerRotation = Vector3.Zero;
        }

        public override void Draw()
        {
            // Look for a camera in the service container
            Camera camera = Engine.Services.GetService();

            // Throw an exception if one isn't present
            if (camera == null)
            {
                throw new Exception("Camera not found in engine's"
                + "service container, cannot draw");
            }

            // Generate the world matrix (describes the objects movement in 3D)
            Matrix world = MathUtil.CreateWorldMatrix(Position, Rotation, Scale);

	    Matrix[] transforms = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(transforms);

            // Set some renderstates so the model will draw properly
            Engine.GraphicsDevice.RenderState.AlphaBlendEnable = true;
            Engine.GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
            Engine.GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
            Engine.GraphicsDevice.RenderState.DepthBufferEnable = true;

            // Loop through meshes and effects and set them up to draw
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    // Set effect parameters
                    effect.Parameters["World"].SetValue(transforms[mesh.ParentBone.Index] * world);
                    effect.Parameters["View"].SetValue(camera.View);
                    effect.Parameters["Projection"].SetValue(camera.Projection);

                    // Enable lighting
                    effect.EnableDefaultLighting();
                }

                // Draw the mesh
                mesh.Draw();
            }
        }
    }
}

Finally, we are going to add a camera and actor to our game. You will need to add the content files found here to the content project for the following code to work. Add this code at the end of the LoadContent() method in the Game1 class in the game project:

// Create a new Camera
Camera camera = new Camera();

// Setup its position and target
camera.Position = new Vector3(1, 1, 2);
camera.Target = new Vector3(0, 0, 0);

// Add it to the service container
Engine.Services.AddService(typeof(Camera), camera);

// Create a new actor
Actor actor = new Actor(Engine.Content.Load("Content/ig_box"), new Vector3(0, 0, 0));

Now, when you run the game, you should see the model being draw at the coordinates specified with a camera positioned at the specified coordinates.

Rendered Model

Rendered Model

That’s all for this tutorial, we’ll be making some more components next time!

Download the Code for this Chapter

Back to Game Engine Tutorial Series

« »