Z Component Engine
Language: C++
Graphics: OGRE3D
Physics:
Havok Physics

Programming/Design/Menu Art: Daniel Zeligman


The Z Component Engine is an experiment in game architecture for me. I have taken zero courses related to games and have been mostly self-taught. This system is an alterantive to a deep traditional inheritance hiearchy.

The end goal of using a component design is to have increased modularity, greater flexibility, easier maintenance and overall a more polished code base.

The system I have created using Havok Physics and Ogre3D allows for a creation of a simple 3D game with all the key features one would expect: Collision detection response, Skeletal animation, dynamic textures, sound triggers, phantom triggers and its fully data driven.


Architecture Decisions


I first chose to apply this approach in order to achieve greater flexibility when making design changes as well as give me an easier time when I need to refactor. In a Component-based system game objects are usually just a collection of the components that they need in order to function. Each component exists to provide a single piece of functionality to their parent game object. For instance, a simple Box would have a Render component, Physics component, AI component and maybe a Position component.

A Component-based architecture also allows easier data-driven approaches to the gameplay. Logic or specific values can be easily written in XML files to tie directly to individual components which can be loaded at run-time.

My Component and GameObject headers can be seen below in the Code Snippet section of this page. Each GameObject contains a map of its components. GameObjects can have multiple instances of a single component as long as they have unique IDs. I first only allowed a single instance of a family, but more complex objects such as a player may need many AnimationComponents and SoundComponents. Components usually do not know anything about other components. They do most contact with the environment by sending events to their parent subsystem which then sends events to the other subsystems who ahve registered to listen for that type of event. However, objects do have public accessors to their components for some cases where components require direct access for efficient use.

Individual objects are created initially through prototype xml files. The game will traverse the xml files feeding data about each component to the components corresponding subsystem. The subsystem will create the specific component, store a reference to it and then return it to be added to the GameObject.

Screenshots:

Nature   Nature       

Code Snippets:

GameObject.h
GameObject.cpp
Component.h
PhysicsComponent.h
XML Keyframed Platform Example

Some brief discussions on a few of the classes are below.

Below are my interfaces for a simple GameObject container for various components. It is simple and can represent a variety of objects, ranging from Door's with triggers, static and dynamic platforms , scriptable physics crates, a dynamic light or anything else than can be made up of individual components. The Component header, an example xml representation of a platform and something else are shown below.

    1 #pragma once

    2 #include <string>

    3 #include <vector>

    4 #include <map>

    5 

    6 class Component;

    7 

    8 //Base GameObject Component holder

    9 class GameObject

   10 {

   11 public:

   12     /**

   13     * Base constructor

   14     * @param id the GO id

   15     */

   16     GameObject(const std::string& id);

   17 

   18     /**

   19     * Base destructor

   20     */

   21     ~GameObject(void);

   22 

   23     /**

   24     * Get the string ID of this Game Object

   25     * @return the std::string ID

   26     */

   27     const std::string& getID();

   28 

   29     /**

   30     * set the string ID of this Game Object

   31     * @param id std::string

   32     * @throws IllegalParameterException if id is empty

   33     */

   34     void setID( const std::string& id );

   35 

   36     /**

   37     * Add a new component to this game object

   38     * Will replace a old component of the same family if that family already exists

   39     * @param component the new component to be added

   40     * @throws IllegalParameterException if component is NULL

   41     */

   42     void addComponent(Component* component);

   43 

   44     /**

   45     * Remove the component of the corresponding family

   46     * @param componentFamily the family type

   47     * @throws IllegalParameterException if componentFamily is empty

   48     */

   49     void removeComponent(const std::string& componentFamily,const std::string& compID);

   50 

   51     /**

   52     * Get a component based off its familyID key

   53     * @param familyID the family string ID

   54     * @param compID the comp string ID

   55     * @return the component if it exists, otherwise NULL

   56     * @throws IllegalParameterException if familyID is empty

   57     */

   58     Component* getComponentByFamilyID( const std::string& familyID,const std::string& compID  );

   59 

   60     /**

   61     * Create a new gameobject with the same type of components as this Game Object

   62     * @param name the new GameObject name/id

   63     * @return the newly created GameObject

   64     * @throws IllegalParameterException if name is empty

   65     */

   66     GameObject* clone(const std::string& name);

   67 

   68     /**

   69     * ----USED for a few components that need a member pointer to another for efficient use

   70     * Get a component based off its family ID

   71     * This will return the component with its actual type

   72     * thus not requiring a dynamic downcast

   73     * @param id the component ID

   74     * @return the component index

   75     */

   76     template<class T>

   77     T* GetInterface(const std::string& id,const std::string& compID )

   78     {

   79         if(mComponents[id].size() == 0)

   80         {

   81             return reinterpret_cast<T*>(NULL);

   82         }

   83         return reinterpret_cast<T*>( mComponents[id][compID] );

   84     }

   85 

   86     /**

   87     * Register a component of this object based off its ID

   88     * @param id the family ID

   89     * @param compID

   90     * @param p the component

   91     */

   92     template<class T>

   93     void RegisterInterface(const std::string& id,const std::string& compID, T* p )

   94     {

   95         mComponents[id][compID]  = p;

   96     }

   97     /**

   98     * clear and delete all Components

   99     */

  100     void clearGOCs();

  101 

  102 

  103 private:

  104     //uncopyable

  105     explicit GameObject(const GameObject& go);

  106     GameObject& operator=(const GameObject& go);

  107 

  108     std::string mOID;            //unique identifier for this object

  109     //Key 1: Family ID, Key 2:Component ID

  110     std::map<const std::string, std::map<const std::string,Component*> > mComponents;

  111 

  112 

  113 };

  114 

  115 


    1 #pragma once

    2 #include <crtdbg.h>

    3 #include "EventListener.h"

    4 #include "Event.h"

    5 

    6 class GameObject;

    7 class SubSystem;

    8 

    9 /// Base class for all components, contains pointers to its owner and subsystem

   10 class Component : public EventListener

   11 {

   12 public:

   13     /**

   14     * Base constructor

   15     */

   16     Component(void);

   17 

   18     Component(const std::string& id);

   19 

   20     /**

   21     * Base destructor

   22     */

   23     virtual ~Component(void);

   24 

   25     /**

   26     * virtual familyID to represent the component family as a key value in a hash

   27     * @return the family ID

   28     */

   29     virtual const std::string& familyID() = 0;

   30 

   31     /**

   32     * set the GameObject owner

   33     * @param owner The GO component owner

   34     * @throws IllegalParametersException If owner is NULL

   35     */

   36     void setOwner(GameObject* owner);

   37 

   38     /**

   39     * Get the Owner of this component

   40     * @return the GO owner

   41     */

   42     GameObject* getOwner() const;

   43 

   44     /**

   45     * Set the subsystem

   46     * @param subSystem The subsystem of this component

   47     * @throws IllegalParametersException If subSystem is NULL

   48     */

   49     void setSubSystem(SubSystem* subSystem);

   50 

   51     /**

   52     * Get the Subsystem of this component

   53     * @return the component subsystem

   54     */

   55     SubSystem* getSubSystem();

   56 

   57     const std::string getID();

   58 

   59     /**

   60     * Duplicates this components members

   61     * Creates new Ogre objects when required

   62     * @param name the new name of the component

   63     * @return the new Cloned component

   64     * @throws IllegalParametersException If name is NULL

   65     */

   66     virtual Component* clone(const std::string& name) = 0;

   67 

   68     /**

   69     * initialization function

   70     * implemented by components that require secondary construction

   71     */

   72     virtual void init() = 0;

   73 

   74     /**

   75     * Used to receive events sent from other components/subsystems

   76     * @param event the Event to Handle

   77     */

   78     virtual void handleEvent(Event* event){}

   79 

   80 protected:

   81     GameObject* mOwner; //Parent GameObject owner

   82     SubSystem* mSubSystem; //Parent SubSystem

   83     std::string mID;

   84 private:

   85     //Uncopyable

   86     explicit Component(const Component& co);

   87     Component& operator= (const Component& co);

   88 

   89 };

   90 

    1 #pragma once

    2 #include <crtdbg.h>

    3 #include <Ogre.h>

    4 #include "PhysicsData.h"

    5 #include "Component.h"

    6 

    7 #include <Physics/Dynamics/Entity/hkpRigidBody.h>   

    8 #include <Physics/Dynamics/Collide/hkpCollisionListener.h>

    9 

   10 // Represents a base level Havok Rigid Body

   11 class PhysicsComponent :

   12     public Component , public hkpCollisionListener

   13 {

   14 public:

   15     /**

   16     * Base physics constructor

   17     * @param data the PhysicsData to populate the initial body / col

   18     */

   19     PhysicsComponent(PhysicsData data);

   20 

   21     /**

   22     *

   23     * Base destructor

   24     */

   25     virtual ~PhysicsComponent(void);

   26 

   27     /**

   28     * Duplicates this components members

   29     * Creates new Ogre objects when required

   30     * @param name the new name of the component

   31     * @return the new Cloned component

   32     * @throws IllegalParametersException If name is NULL

   33     */

   34     Component* clone(const std::string& name);

   35 

   36     /**

   37     * initialization function

   38     */

   39     virtual void init();

   40 

   41     /**

   42     * virtual familyID to represent the component family as a key value in a hash

   43     * @return the family ID, MeshComponent

   44     */

   45     virtual const std::string& familyID();

   46 

   47     /**

   48     * Get the physics body

   49     * @return the Havok Rigid Body

   50     */

   51     hkpEntity* getBody();

   52 

   53     //individual customized steps performed here

   54     virtual void preUpdate(float dt);

   55     virtual void postUpdate(float dt);

   56 

   57     //helper method for player control

   58     virtual void setTranslate(const Ogre::Vector3& translate,float angle);

   59 

   60 

   61     // Called after a contact point was added

   62     virtual void contactPointAddedCallback(    hkpContactPointAddedEvent& event );

   63 

   64     virtual void contactPointConfirmedCallback( hkpContactPointConfirmedEvent& event);

   65 

   66        

   67     virtual void contactPointRemovedCallback( hkpContactPointRemovedEvent& event );

   68 

   69         // Called just before the collisionResult is passed to the constraint system (solved).

   70     virtual void contactProcessCallback( hkpContactProcessEvent& event );

   71 

   72     void setMotionType(int motionType);

   73 

   74 private:

   75     //Uncopyable

   76     /**

   77     * Explicit private copy constructor making class uncopyable

   78     */

   79     explicit PhysicsComponent(const PhysicsComponent& co);

   80     PhysicsComponent& operator=(const PhysicsComponent& co);

   81 

   82 protected:

   83     /**

   84     * Base physics constructor

   85     * @see clone()

   86     */

   87     PhysicsComponent();

   88 

   89 

   90     static std::string mFamilyID;

   91     hkpRigidBody* mBody;

   92 

   93     //keyframed physics members

   94     std::vector<Ogre::Vector3> mOffsets;

   95     float mMoveSpeed;

   96     float mWaitTime;

   97     float mCurrentWaitTime;

   98     int mCurrentIndex;

   99     int mMotionType;

  100 };

  101 

  102 

  103 

84   <MovingPlatform>

   85     <MeshComponent>

   86       <MeshData name="platform0" meshName="cube.mesh" materialName="Simple/RockWall" entName="platform0"  castShadows="true"/>

   87     </MeshComponent>

   88     <RenderComponent>

   89       <RenderData name="platform0"  sizeX ="8" sizeY=".17" sizeZ ="8"  posX="40" posY="25" posZ="10" visible="1" />

   90     </RenderComponent>

   91     <PhysicsComponent>

   92       <PhysicsData

   93         name="platform0"

   94         collisionShape="Box"

   95         mass ="50"

   96         motionType="1"

   97         posX="40" posY="25" posZ="10"

   98         friction="1.0f"

   99         restitution="0.0f"

  100         colX="8" colY=".4" colZ="8"

  101         moveSpeed="226.0f"

  102         waitTime=".1f"

  103         >

  104         <offset  posX="40" posY="5" posZ="10" />

  105 

  106       </PhysicsData>

  107     </PhysicsComponent>

  108   </MovingPlatform>