Friday, November 18, 2011

Let's Make a Game Engine for XNA 4.0r, Part 5, RenderDescriptions, Vertex Declaration, and everything else

In Part 4 of this series we covered the Render and Camera Components. RenderComponent uses RenderDescriptions to hold information about its mesh so the RenderManager can render it, and that RenderDescription holds either a Model (which is a standard XNA class), or a GeometricPrimitive (which is part of this engine).

GeometricPrimitive
GeometricPrimitive is a base class I've built into the engine, but it was written by Microsoft's XNA team as part of a sample, as such I'm not going to cover much about it other than the basics. There are 7 files that are part of this system, and I modified each one slightly to be part of the project, if you're creating your own project rather than using the downloaded one you'll want to just copy the GeometricPrimitives directory out of the download project into your project, and you'll need to make sure the namespaces match within each file as well.

GeometricPrimitive takes a GraphicsDevice as part of its constructor. Based on the shape of the primitive it constructs a VertexBuffer, a list of its verticies, an IndexBuffer, and a list of its indices. Based on those variables we will have enough information to render the primitive. The only changes I made to these classes after putting them into this engine was to remove the #regions, and I pulled the BasicEffect out of the class itself since the RenderManager handles the rendering. Most importantly, I added the enum GeometricPrimitiveType to the GeometricPrimitive.cs file to allow users to use an enum type to create a shape for them:
public enum GeometricPrimitiveType
{
    Cube,
    Sphere,
    Cylinder,
    Torus,
    Teapot
}
If you look in the downloaded version of the engine you'll notice a 6th primitive shape called a BezierPrimitive. This isn't used directly, it is used by the TeapotPrimitive.

Here is a link to the original Geometric Primitives sample if you would like to learn more:
http://create.msdn.com/en-US/education/catalog/sample/primitives_3d


VertexPositionNormal
This class is an IVertexType (which is a standard XNA type). When creating a list of vertices you need to have a vertex type, this is the vertex type used by the Geometric Primitives, this file is also from Microsoft's sample. Place VertexPositionNormal.cs in the RenderManager folder. Here's the entire file:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace SimpleGameEngine.Render
{
    /// 
    /// Custom vertex type for vertices that have just a
    /// position and a normal, without any texture coordinates.
    /// 
    public struct VertexPositionNormal : IVertexType
    {
        public Vector3 Position;
        public Vector3 Normal;

        /// 
        /// Constructor.
        /// 
        public VertexPositionNormal(Vector3 position, Vector3 normal)
        {
            Position = position;
            Normal = normal;
        }

        /// 
        /// A VertexDeclaration object, which contains information about the vertex
        /// elements contained within this struct.
        /// 
        public static readonly VertexDeclaration VertexDeclaration = new VertexDeclaration
        (
            new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
            new VertexElement(12, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0)
        );

        VertexDeclaration IVertexType.VertexDeclaration
        {
            get { return VertexPositionNormal.VertexDeclaration; }
        }

    }
}

RenderDescription
RenderDescription is a data class, it holds no functionality, just holds data that can be passed between a RenderComponent and the RenderManager. Create RenderDescription.cs and place it inside of the RenderManager folder. The data RenderDescription holds is:

A Model (XNA's Model Class), or a GeometricPrimitive. It shouldn't hold both, the RenderManager will check if the Model exists first, and if it doesn't it will then check for a GeometricPrimitive, there's no reason to use both.  This class also holds the worldTransform Matrix. And that's it. Here's the entire file:
using System;
using System.Collections.Generic;
using System.Text;

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

using SimpleGameEngine.GeometricPrimitives;

namespace SimpleGameEngine.Render
{
    public class RenderDescription
    {   
        // A render description must contain either a Model, or
        // a GeometricPrimitive

        public Model model;
        public GeometricPrimitive geoPrim;

        public Matrix worldTransform = Matrix.Identity;

        public RenderDescription()
        {

        }
    }
}

EngineCommon
This is a file that will contain common constants used throughout the project. Create a file called EngineCommon.cs and place it in the main GameEngine folder (same level as Main.cs). Right now this just contains a variable for the name of the default root entity. Here's the entire file:
using System;
using System.Collections.Generic;
using System.Text;

namespace SimpleGameEngine
{
    public class EngineCommon
    {
        public const String RootEntityName = "Root";
    }
}

And that's it for our first milestone. At this point you got a lot of good practice watching the engine come together, and if you made your own files and code as you went along you should know it pretty well. Along the way there was a good chance for a typo on either my or your part, if your project doesn't compile I recommend just using the downloaded version of the engine, and if you want to change things around a little feel free to alter it to your liking, the license on the engine is such that you can do whatever you want with it.

So let's run the engine (press F5 to build and run attached), and what we should get is two loaded entities, one is the root entity, and the other is a primitive cube in front of the camera.


And there we have it. Now lets reflect back on things a bit. We took an awfully long time to make a program that renders a cube, we could have done it in about 1/10th the time, with much less code. However, the goal wasn't really to make a cube rendering program, what we wanted was the start to a flexible game engine that is extensible and reusable. If you think about it we could very easily change the program to render a stack of cubes, so let's give that a shot. If you remember the place we're temporarily using to load entities into our scene is within the SceneManager, so open that up and look for LoadContent().

Add this at the bottom of the function (but within the temporary lines comments):
BaseEntity testCube2 = new BaseEntity(this, "Cube2");
testCube2.position = new Vector3(0, -1.01f, -10);

RenderComponent rendComp2 = new RenderComponent(testCube2, GeometricPrimitiveType.Cube);

AddEntityToScene(testCube2);

BaseEntity testCube3 = new BaseEntity(this, "Cube3");
testCube3.position = new Vector3(0, 1.01f, -10);

RenderComponent rendComp3 = new RenderComponent(testCube3, GeometricPrimitiveType.Cube);

AddEntityToScene(testCube3);

And now we see this:

After some new additions to the engine it'll be able to do a lot more than this. From here on I won't be going into as much detail about setting everything up line by line, I will publish new versions of the engine that you can download, and I will go into some detail about new functionality. The biggest reason for this is because it takes a lot longer to blog about making an engine than it does to actually make it. I made this entire engine as you see it now in 7 hours, and it took about 15 hours to blog about it. The idea behind this project is that I will create a simple but extensive framework from which I will be able to create demos and samples that I can post on my blog, and the posted sample and code will relate to each other without me having to describe the entire engine to the use. For example if I want to create a spline cinematic camera demo, and I did it from scratch, there would be a ton of code in there that most people do not care about, now the engine can take place of that code and sit hidden from view so people can concentrate on the math and features behind such a camera system.

I will keep you guys posted on new versions of the engine, as well as posting samples that use the engine. Thanks for reading!

No comments:

Post a Comment