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:
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>





