Components and Subsystems - Karclan/Games-Programming-Engine GitHub Wiki
A subsystem is a class that handles the functionality of various components. When a component is added to a game object, the object manager will also send a shared pointer to it to any subsystem that handles it. For example, the RenderSystem class handles all components to do with rendering (renderers, cameras, lights etc). It will store references to these components in various data structures and call required functions in them each frame.
When a new component or subsystem is created it needs to be integrated into the engine properly. This page of the wiki explains how, using the Physics system as an example.
###Creating a new subsystem
Creating a new subsystem is easy. Create the class as normal. Then make an instance of the class in the Engine class as a member variable (usually on the stack unless there is a good reason to use dynamic memory, which there probably isn't!). In the case of the physics system, I just added this to the engine.h:
PhysicsSystem _physicsSys;
The object manager class is going to need a reference to this as well as it will be the one that adds new components to it when new game objects are created. E.g. create a variable of type pointer to PhysicsSystem in the ObjectManager class
PhysicsSystem* _physicsSys;
Then hook it up through the startUp function
ObjectManager* ObjectManager::startUp(RenderSystem &rendSys, PhysicsSystem &physicsSys)
{
ObjectManager* inst = ObjectManager::get();
inst->_rendSys = &rendSys;
inst->_physicsSys = &physicsSys;
return inst;
}
Then note in the startup function in the Engine class, where it calls ObjectManager::startUp
_objMngr = ObjectManager::startUp(_rendSys, _physicsSys);
Now it's all linked up! You just need to call the appropriate functions in the new subsystem from the main game loop in the engine class. For example, all physics will be done with a fixed time step. So I want a fixedUpdate function in the PhysicsSystem class that is called by the engine every fixed time step (look at the Engine::update() function, each part of the game loop is commented to show what it does!).
###Creating a new component
Creating new components is a little more complicated because the engine needs to know how to add them to game objects based on info from an XML file. Create the class like normal and inherit from Component.
class PhysicsBody : public Component {}
Component is an abstract class - it requires that you implement the functions getType() and isOnePerObject(). Simply implement these as public variables with hard-coded return values. The type is the type of component, you will need to update the enum in component.h to include your new component.
namespace ComponentType
{
enum Type { TRANSFORM, MODL_REND, CAMERA, ROB_REND, PHY_BODY };
}
The isOnePerObject() function should return a bool, true if there is only ever one of these allowed on a game object. I'm gonna say true for this as I have no idea how to resolve collisions for something with multiple colliders on it! So the two mandatory component functions for PhysicsBody are implemented as follows:
ComponentType::Type PhysicsBody::getType()
{
return ComponentType::PHY_BODY;
}
bool PhysicsBody::isOnePerObject()
{
return true;
}
Finally, you can define a share pointer to the component at the bottom of the header for ease-of-use.
typedef std::shared_ptr<PhysicsBody> SPtr_PhysBody;
###Adding the Component to a subsystem
Subsystems store shared pointers to the components they handle in data structures. For now, I've just made a vector of SPtr_PhysBody types in the PhysicsSystem class and created a public function so the engine can add new Sptr_PhysBodys to it, e.g.
void PhysicsSystem::addPhysBody(SPtr_PhysBody physBody)
{
_physBodys.push_back(physBody);
}
Next, we need to get the engine to add this component to our subsystem and a game object when it is read from XML. This is taken case of by the ObjectManager class (for adding objects etc) and the SceneManager class (for loading scenes from XML etc). Updating the ObjectManager class is easy. Simply alter the switch statement in the addComponent method to accommodate your new component, e.g.
case ComponentType::PHY_BODY:
_physicsSys->addPhysBody(std::static_pointer_cast<PhysicsBody>(addedComponent));
break;
This casts the component to the correct type and adds to physics subsystem. Actually adding the component to the game object is already taken care of.
Updating SceneManager is a little more complex because the system holds the XML data as data in C++ so it can reset things back to their initial states without having to read the XML again (not perfect yet, bare with it for now!).
You will need to create 3 new functions in the header. The first will be used to define what info should be written to xml when saving a scene.
void xmlAddPhysBody(TiXmlElement* go);
The implementation will look something like this
void SceneManager::xmlAddPhysBody(TiXmlElement* go)
{
TiXmlElement* physBodyElmnt = new TiXmlElement("COMP"); // Component Element
physBodyElmnt->SetAttribute("type", ComponentType::PHY_BODY); // Set type attrib
go->LinkEndChild(physBodyElmnt); // Add element to file, this auto cleans up pointer as well
}
This will get more complex if you need to define initial variable values for the component later (see other funcs for info, e.g. xmlAddTransform has a lot of initial data).
The second function to create reads XML data and creates a CompData instance, which is a class that holds the initialization data for the component as C++ code.
CompData newPhysBodyData(TiXmlElement* compElmnt);
The implementation looks something like
CompData SceneManager::newPhysBodyData(TiXmlElement* compElmnt)
{
CompData physData(SPtr_PhysBody(new PhysicsBody())); // new component and data
return physData;
}
Again, this will be more complex if it needs variable initializers. Finally, we need a function that will use the CompData to initialize the actual component.
void initPhysBody(CompData &comp);
This can just be a blank function for now - later, you put any code here for initializing it back to the state it is in when created (e.g. starting position for transform).
You can now set up the SceneManager to call the functions you just made at the relevant time. xmlAddPhysBody would be called when saving the scene (currently in writeDemoXML function, which generates the test scene). newPhysBodyData is called from loadFromXML. There is simply a switch statement where you can add something like this:
case ComponentType::PHY_BODY: goData->components.push_back(newPhysBodyData(compElmnt)); break;
initPhysBody is called from initFromInitTable(), although obviously it won't do anything at the moment as there are no initial variable values for this component.