Nuts
Language: C++
Graphics: OGRE3D
Physics:
Havok Physics

Programming/Design/Modeling: Daniel Zeligman
Animation/Modeling: Katie Macias


Nuts is a casual 3D platformer where Quibble the squirrel must conquer dangerous obstacles in trees while seeking the ever elusive golden acorn.

This was an experiment in game system architecture. Objects are composed of multiple individual components. This project was developed in my spare time over the past three months.

Features:

  • Third person Spring Camera
  • Custom player animation and behavior
  • Complete 3D Environment
  • Data-driven levels
  • Havok powered physics
  • Dynamic triggers



Screenshots:

Main Menu   Those thorns will hurt!   Lets get to jumpin    The Golden acorn at last

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, and Mesh 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 have 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 through a game object template that their subsystem owns. The game initially traverses the xml files creating the templates and then makes many instances of each of those templates. Subsystems create their specific components, and the basic game object container "owns" them.

Levels are first created statically in 3D studio max. Next the physics side is exported using a Havok Plugin and the graphics side is exported using a Ogre plugin. I then merge the two with a custom tool.

Code Snippets:

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


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 PhysicsComponent, GameObject, and Component headers are below along with an example xml representation of a platform.

    1 //Author: Daniel Zeligman

    2 //GameObject.h : Header for component container

    3 #pragma once

    4 #include <string>

    5 #include <vector>

    6 #include <map>

    7 

    8 class Component;

    9 

   10 //Base GameObject Component holder

   11 class GameObject

   12 {

   13 public:

   14     /**

   15     * Base constructor

   16     * @param id the GO id

   17     */

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

   19 

   20     /**

   21     * Base destructor

   22     */

   23     ~GameObject(void);

   24 

   25     /**

   26     * Get the string ID of this Game Object

   27     * @return the std::string ID

   28     */

   29     const std::string& getID();

   30 

   31     /**

   32     * set the string ID of this Game Object

   33     * @param id std::string

   34     * @throws IllegalParameterException if id is empty

   35     */

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

   37 

   38     /**

   39     * Add a new component to this game object

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

   41     * @param component the new component to be added

   42     * @throws IllegalParameterException if component is NULL

   43     */

   44     void addComponent(Component* component);

   45 

   46     /**

   47     * Remove the component of the corresponding family

   48     * @param componentFamily the family type

   49     * @throws IllegalParameterException if componentFamily is empty

   50     */

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

   52 

   53 

   54     /**

   55     * Used for a few components that need a member pointer to another for efficient use

   56     * Get a component based off its family ID

   57     * This will return the component with its actual type

   58     * thus not requiring a dynamic downcast

   59     * @param id the component ID

   60     * @return the component index

   61     */

   62     template<class T>

   63     T* GetInterface(const std::string& familyID,const std::string& compID )

   64     {

   65         if(mComponents[familyID][compID] == 0)

   66         {

   67             return reinterpret_cast<T*>(NULL);

   68         }

   69         return reinterpret_cast<T*>( mComponents[familyID][compID] );

   70     }

   71 

   72     /**

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

   74     * @param id the family ID

   75     * @param compID

   76     * @param p the component

   77     */

   78     template<class T>

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

   80     {

   81         mComponents[id][compID]  = p;

   82     }

   83     /**

   84     * clear and delete all Components

   85     */

   86     void clearGOCs();

   87 

   88 

   89 private:

   90     //uncopyable

   91     explicit GameObject(const GameObject& go);

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

   93 

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

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

   96     typedef std::map<const std::string, std::map<const std::string,Component*> > ComponentMap;

   97     ComponentMap mComponents;

   98 

   99 

  100 };

  101 

  102 


    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     * TODO

   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         // Called before a contact point gets removed. We do not implement this for this demo.

   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     //keyframe stuff

   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>