Engine Structure

This is for all those geeks out there! Like me! There have been lots of revisions in this engine as can be expected from a home-brew game engine – most revisions have been in an effort to make adding new stuff seamless. This is important because we need the freedom of having a complete product that can accept new parts. Build and Battle is our first game using the engine, which means the main parts of the engine are tailored towards this product. However, with the current engine structure we can add new parts without changing any existing code.

How is this possible? Well the main ingredients are template programming and composition. The engine has four main organizational tools that are all extendable; Components, Systems, Resources, and Managers. In coding Build and Battle I have created the necessary components, systems, resources and managers that allow for the things we need from this game. The nice thing is that most of them will be usable for any game – the other nice thing is that new components/systems/resources/managers can be added at any time.

Let me now define what these things are, what they do, and how they work. In the most basic terms, a manager manages resources and a system manages components. But there is a bit more to it than that. Let me start off with some examples of resources and managers.

A resource is anything that can be loaded from file. This currently includes meshes, materials, textures, animations, shaders, and sound-bytes. In order to make a new type of resource in the engine you need to create a class that inherits NSResource and includes the functions “bool serialize(NSFileStream & pStream)” and “bool deserialize(NSFileStream & pStream)”. The function “serialize” must write the contents of the resource to the file stream passed in, and the function “deserialize” must read in the contents of the resource from a file stream.

Anytime you make a new resource type, you need to create a manager for that resource type. This can be done by creating a class that inherits “NSResManager” and writing the functions “bool saveToFile(NSString & pResourceName)” and “ResourceType * loadFromFile(NSString & pFileName)”. Lets say we have written to classes; MyResource and MyResourceManager. We can now add this resource to the engine like this:

MyResourceManager * myManager = engine->createManager();

//To add a new resource to the manager:

MyResource * myResource = myManager->create("ResourceName");

Now we have our own resource type included in the engine – why include it at all? The engine hashes the name of the resource you give and stores an ID for that resource that you can then use anywhere in your code to keep track of the resource. This means if you create a component that uses your resource type – you can give the component the resource ID rather than a pointer to the actual resource. If the resource gets deleted, the component will know when it uses the ID to get the resource and can handle the situation easily. If it stored a pointer to the resource and the resource was deleted, the pointer would be left dangling.

The resource can be retrieved by its name or its ID by the manager, and the manager can be retrieved by its type from the engine:

MyResourceManager * myManager = engine->getManager();

MyResource * resource = myManager->get("ResourceName");

MyResource * resource = myManager->get(resourceID);

This is, in a nutshell, how the resource/manager system works in the engine. It allows for new resource types as needed and they are easily integrated.

Now lets talk about systems and components. Systems are really where the game logic is held, and components are where the data used by the systems is held. We can make new systems and new components at any time – much the same way as resources.

To be considered a system we must inherit from NSSystem and write the functions “void update(const NSScene * pScene, NSTimer * pTimer)” and “void init()”. We also need to write a static function called getPriority() that returns a float which defines the update order of the systems. Higher value for priority means the system updates sooner than other systems.

The systems included with the engine all have defined priorities – by making your system return a priority in between the defined priorities the engine will update your system between the two corresponding engine systems. For example, renderSystem has a priority of 1.0 and moveSystem has a priority of 2.0; by making your system have a priority of 1.5 the engine will update them like:

moveSystem.update(scene,timer);
yourSystem.update(scene, timer);
renderSystem.update(scene, timer);

//To add your system to the engine you call the following function:

engine->addSystem();

When adding the system the engine automatically calls the “init()” function that you defined in your system class. Now every frame the engine will call your system’s update function passing it the scene and a timer – the timer contains the current time, last time, and has a function dt which gives the time since the last frame. I will get in to what the scene contains after explaining components.

A component is a set of data and possibly logic that can be added to and removed from and game object at any time. For example, a RenderComponent contains an ID for a mesh and a map of materials to submeshes for that mesh. If a game object has a RenderComponent then it will be rendered to the screen by the RenderSystem. If it does not – then it wont.

Another example is the TransformComponent – if the game object has a transform component then the object will have an orientation, position, and scaling. If it doesn’t contain it then it wont have these things. Note that the RenderSystem actually renders a game object to screen if the game object has both a RenderComponent and a TransformComponent.

I keep saying the word “game object” – in the engine this “game object” is actually defined by the class NSEntity. It is possible to add components to an entity by using the following:

NSRenderComp * renderComp = entity->addComponent();

You can remove components in the same fashion. Notice that there can only be one component of each type – this is on purpose. We don’t want any entities floating around with two render components: If we want the entity to have two meshes for some reason, we can create another component such as SecondMeshComp and make it inherit from NSRenderComp.

So we know that you can add components to an entity, but how do you make an entity? And how do you make the engine know about the entity? This is where the scene comes in.

The scene is nothing more than a container for entities – it contains an array of all entities plus it has an array of entities for each component type. You remember that in each system’s update call you pass a const pointer to the scene. You can use this pointer to get the array of all entities by writing:

NSEntPtrMap & allEnts = pScene->getEntities();

//Or you can get all entities with a certain component only:

NSEntPtrMap & renderEnts = pScene->getEntitiesWithComp();

//To add a new entity to the scene you can either create an entity yourself with:

NSEntity * ent = new NSEntity("EntityName");

pScene->addEntity(ent); or ent->setScene(pScene);

//Or you can use the scene to create the entity, which is easier.

pScene->createEntity("EntityName");

Note that this create function is not templated – this means that if you want to subclass NSEntity for some reason you need to create the entity yourself and add it to the scene. In all cases of adding an entity to the scene, the scene takes ownership of that entity and will take care of deleting it correctly. Also, the scene will automatically add the entity to the correct arrays as you add and delete components.

When making a new type of component you must inherit from the NSComponent class and define update and init functions. You can also choose to write serialize, deserialize, and handleEvent functions. Serialize and deserialize allow you to read/write the contents of the component to/from a file stream (just like a resource) and the handleEvent function allows you to – you guessed it – handle events. More on events later. Also, you need to write two static functions called getTypeString and create that return the type of your component in the form of a string and a pointer to your component. For ease of explanation, take a look at the example.


// header file

class NSAnimComp : public NSComponent

{

public:

// Other code left out

static NSString getTypeString();

static NSComponent * create(NSEntity * pOwner);

};

// cpp file

NSString NSAnimComp::getTypeString()
{
return "NSAnimComp";
}

NSComponent * NSAnimComp::create(NSEntity * pOwner)
{
return pOwner->createComponent();
}

Before adding your custom component to an entity, you then need to register your component with the engine. You do this with the following function call:

engine->registerCompType(MyComponent::getTypeString(), MyComponent::create);

Then engine can now save and load scenes with entities containing your custom component type. To add your component to an entity just write

entity->createComponent();

The scene will now have another array containing only entities with your custom component type which you can use in your custom system.

By making resources, managers, systems, and components completely extendable we are allowing the engine to tailor to any game imaginable.

The last part of the engine I am going to talk about is the input handler. I mentioned before that you can write your own handleEvent function when implementing a custom component. This function is passed every event in an event que and by writing the handleEvent function you can choose to act on that event.

How are events created? Well – components can create an event as they are given access to the EventSystem – they contain a pointer to it called mEventSystem. But the most basic type of event comes from the input handler.

You can assign keys, mouse presses, mouse movements, etc to an input event name. Every key/mouse input can also be assigned modifiers so that the event only triggers if that modifier is held down. Below is an example of creating an event that should be triggered whenever the left mouse button is clicked.


mInput->addMouseTrigger("DefualtContext",NSInput::LeftButton, NSInput::Trigger("LeftMouseClick"));

NSInput::Trigger is a structure that contains the name of the event trigger and any modifiers that you might want. The field “DefaultContext” specifies the input context. An input context is basically a qualifier for that event to trigger if the key/mouse button is pressed. This is necessary because you don’t always want the mouse to do the same thing when the left button is pressed.

For example, if you are in the game menu you don’t want the left mouse to trigger “ShootGun” event – you would create a separate context for the menu and game play. You would have “MenuContext” where LeftButton triggers an event handled in the UI, while the “GameContext” might trigger and event handled in the Animation, Sound, and Gun components of the entity shooting.

To handle some certain event, you write your own handleEvent function in the component and handle the event with the name you assigned to the input trigger. Using the “LeftMouseClick” example from above, you could handle this event by doing the following:


bool NSControllerComp::handleEvent(NSEvent * pEvent)
{
if (pEvent->mID == NSEvent::InputMouseButton)
{
        NSInputMouseButtonEvent * mE = (NSInputMouseButtonEvent*)pEvent;
        if (mE->mName == "LeftMouseClick")
        {
        // Do stuff
        return true;
        }
}
return false;
}

Now your component will do something every time the left mouse button is pressed and the context is set to “DefaultContext”.

This is the basic overview of the engine – the main point is that we are comfortable with the idea that it can easily be extended at anytime to do just about anything that we want. An overview of the structure is shown here enginestructure As Alex mentioned in his article, we are nearing the alpha stage. As he works on getting more characters finished I will be working on getting the UI for the toolkit setup the way that we want it. When the UI is near completion, we will release an alpha version of it. As it is play tested and finalized we will be working and getting the alpha version of the game released. Since the game is dependent on the toolkit (the toolkit generates the files needed by the game) we will first get the toolkit in working order.

Stay tuned – we will be updating periodically with UI shots and possibly more character shots.