Hey there.
I started writing my platformer game in C++/SFML and inspired by your article I went with the self-attached component system route. So I started implementing and came with a working solution. Thing is its a "working solution", my C++ skills are rusty as hell after spending about 6+ years with C# and NeoAxis engine, so code could use some refactoring even at this early stage. So here are some relevant parts of the code:
BaseAttribute:
// HPP
#ifndef BASE_ATTRIBUTE_HPP_INCLUDED
#define BASE_ATTRIBUTE_HPP_INCLUDED
#include <string>
namespace ksg
{
class BaseAttribute
{
public:
virtual ~BaseAttribute() = 0;
const std::string& getName() { return m_Name; }
protected:
std::string m_Name;
};
}
#endif // BASE_ATTRIBUTE_HPP_INCLUDED
// CPP
#include "../include/BaseAttribute.hpp"
namespace ksg
{
BaseAttribute::~BaseAttribute()
{
}
}
Attribute:
#ifndef ATTRIBUTE_HPP
#define ATTRIBUTE_HPP
#include <string>
#include <memory>
namespace ksg
{
template <typename T>
class Attribute : public BaseAttribute
{
public:
Attribute(T value, const std::string& name)
{
m_Value = value;
m_Name = name;
}
T& getValue() { return m_Value; }
protected:
T m_Value;
};
}
#endif // ATTRIBUTE_HPP
BaseComponent:
//HPP
#ifndef BASE_COMPONENT_HPP
#define BASE_COMPONENT_HPP
#include "BaseAttribute.hpp"
#include <vector>
#include <memory>
namespace ksg
{
class GameObject;
class BaseComponent
{
public:
BaseComponent(std::shared_ptr<GameObject> parent);
virtual ~BaseComponent();
virtual std::unique_ptr<BaseComponent> clone(std::shared_ptr<GameObject> parent) = 0;
virtual void update(float dt) = 0;
bool checkRequiredAttributes(std::shared_ptr<GameObject> obj);
protected:
static std::vector<std::string> m_RequiredAttributes;
std::shared_ptr<GameObject> m_Parent;
};
}
#include "GameObject.hpp"
#endif // BASE_COMPONENT_HPP
//CPP
#include "../include/BaseComponent.hpp"
namespace ksg
{
std::vector<std::string> BaseComponent::m_RequiredAttributes;
BaseComponent::BaseComponent(std::shared_ptr<GameObject> parent)
{
m_Parent = parent;
}
BaseComponent::~BaseComponent()
{
}
bool BaseComponent::checkRequiredAttributes(std::shared_ptr<GameObject> obj)
{
if(!obj)
return false;
std::vector<std::string>::const_iterator iter;
for(iter = m_RequiredAttributes.begin(); iter != m_RequiredAttributes.end(); ++iter)
if(!obj->getAttribute(*iter))
return false;
return true;
}
}
GameObject:
//HPP
#ifndef GAME_OBJECT_HPP_INCLUDED
#define GAME_OBJECT_HPP_INCLUDED
#include "BaseAttribute.hpp"
#include "BaseComponent.hpp"
#include <unordered_map>
#include <vector>
#include <memory>
namespace ksg
{
class GameObjectFactory;
class GameObject
{
friend class GameObjectFactory;
public:
void update(float dt);
std::shared_ptr<BaseAttribute> getAttribute(std::string name) { return m_Attributes[name]; }
private:
std::unordered_map<std::string, std::shared_ptr<BaseAttribute>> m_Attributes;
std::vector<std::unique_ptr<BaseComponent>> m_Components;
};
}
#include "GameObjectFactory.hpp"
#endif // GAME_OBJECT_HPP_INCLUDED
//CPP
#include "../include/GameObject.hpp"
namespace ksg
{
void GameObject::update(float dt)
{
std::vector<std::unique_ptr<BaseComponent>>::const_iterator iter;
for(iter = m_Components.begin(); iter != m_Components.end(); ++iter)
(*iter)->update(dt);
}
}
GameObjectFactory:
//HPP
#ifndef GAME_OBJECT_FACTORY_HPP
#define GAME_OBJECT_FACTORY_HPP
#include "GameObject.hpp"
#include "BaseAttribute.hpp"
#include "BaseComponent.hpp"
#include <vector>
#include <memory>
namespace ksg
{
class GameObjectFactory
{
public:
GameObjectFactory();
~GameObjectFactory();
static bool initialize();
static std::unique_ptr<GameObjectFactory>& getInstance() { return m_Instance; }
void createGameObject(const std::vector<BaseAttribute*>& attributes);
void update(float dt);
private:
static std::unique_ptr<GameObjectFactory> m_Instance;
std::vector<std::unique_ptr<BaseComponent>> m_Components;
std::vector<std::shared_ptr<GameObject>> m_GameObjects;
};
}
#endif // GAME_OBJECT_FACTORY_HPP
//CPP
#include "../include/GameObjectFactory.hpp"
#include "../include/ScrollingBackgroundComponent.hpp"
namespace ksg
{
std::unique_ptr<GameObjectFactory> GameObjectFactory::m_Instance;
GameObjectFactory::GameObjectFactory()
{
ScrollingBackgroundComponent::initRequiredAttributes();
m_Components.push_back(std::unique_ptr<ScrollingBackgroundComponent>(new ScrollingBackgroundComponent(nullptr)));
}
GameObjectFactory::~GameObjectFactory()
{
}
bool GameObjectFactory::initialize()
{
m_Instance.reset();
m_Instance = std::unique_ptr<GameObjectFactory>(new GameObjectFactory());
if(!m_Instance)
return false;
return true;
}
void GameObjectFactory::createGameObject(const std::vector<BaseAttribute*>& attributes)
{
std::shared_ptr<GameObject> gameObject = std::shared_ptr<GameObject>(new GameObject);
for(std::vector<BaseAttribute*>::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter)
gameObject->m_Attributes.insert(std::make_pair((*iter)->getName(), std::shared_ptr<BaseAttribute>(*iter)));
for(std::vector<std::unique_ptr<BaseComponent>>::const_iterator iter = m_Components.begin(); iter != m_Components.end(); ++iter)
if((*iter)->checkRequiredAttributes(gameObject))
gameObject->m_Components.push_back(std::unique_ptr<BaseComponent>((*iter)->clone(gameObject)));
m_GameObjects.push_back(gameObject);
}
void GameObjectFactory::update(float dt)
{
std::vector<std::shared_ptr<GameObject>>::iterator iter;
for(iter = m_GameObjects.begin(); iter != m_GameObjects.end(); ++iter)
(*iter)->update(dt);
}
}
And finally something more specific - ScrollingBackgroundComponent:
//HPP
#ifndef SCROLLING_BACKGROUND_COMPONENT_HPP
#define SCROLLING_BACKGROUND_COMPONENT_HPP
#include "BaseComponent.hpp"
#include "Attribute.hpp"
#include "ScrollingBackground.hpp"
#include <SFML/Graphics.hpp>
#include <memory>
namespace ksg
{
class ScrollingBackgroundComponent : public BaseComponent
{
public:
ScrollingBackgroundComponent(std::shared_ptr<GameObject> parent);
~ScrollingBackgroundComponent();
std::unique_ptr<BaseComponent> clone(std::shared_ptr<GameObject> parent) { return std::unique_ptr<BaseComponent>(new ScrollingBackgroundComponent(parent)); }
void update(float dt);
static void initRequiredAttributes();
private:
std::shared_ptr<Attribute<ScrollingBackground*>> m_Visual;
};
}
#endif // SCROLLING_BACKGROUND_COMPONENT_HPP
//CPP
#include "../include/ScrollingBackgroundComponent.hpp"
namespace ksg
{
ScrollingBackgroundComponent::ScrollingBackgroundComponent(std::shared_ptr<GameObject> parent) : BaseComponent(parent)
{
if(parent)
{
m_Visual = std::dynamic_pointer_cast<Attribute<ScrollingBackground*>>(parent->getAttribute("sb_visual"));
std::shared_ptr<Attribute<sf::Vector2i>> position = std::dynamic_pointer_cast<Attribute<sf::Vector2i>>(parent->getAttribute("sb_position"));
std::shared_ptr<Attribute<sf::Vector2f>> scrollSpeed = std::dynamic_pointer_cast<Attribute<sf::Vector2f>>(parent->getAttribute("sb_scroll_speed"));
std::shared_ptr<Attribute<bool>> autoScroll = std::dynamic_pointer_cast<Attribute<bool>>(parent->getAttribute("sb_auto_scroll"));
if(position)
m_Visual->getValue()->setPosition(position->getValue());
if(scrollSpeed)
m_Visual->getValue()->setScrollSpeed(scrollSpeed->getValue());
if(autoScroll)
m_Visual->getValue()->setAutoScroll(autoScroll->getValue());
}
}
ScrollingBackgroundComponent::~ScrollingBackgroundComponent()
{
}
void ScrollingBackgroundComponent::update(float dt)
{
m_Visual->getValue()->update(dt);
}
void ScrollingBackgroundComponent::initRequiredAttributes()
{
m_RequiredAttributes.push_back("sb_visual");
}
}
Somewhere in the Game.cpp:
std::vector<BaseAttribute*> attr;
attr.push_back(new Attribute<ScrollingBackground*>(&back1, "sb_visual"));
attr.push_back(new Attribute<sf::Vector2f>(sf::Vector2f(1, .25f), "sb_scroll_speed"));
GameObjectFactory::getInstance()->createGameObject(attr);
attr.clear();
attr.push_back(new Attribute<ScrollingBackground*>(&back2, "sb_visual"));
attr.push_back(new Attribute<sf::Vector2i>(sf::Vector2i(0, 32), "sb_position"));
attr.push_back(new Attribute<sf::Vector2f>(sf::Vector2f(16, .5f), "sb_scroll_speed"));
attr.push_back(new Attribute<bool>(true, "sb_auto_scroll"));
GameObjectFactory::getInstance()->createGameObject(attr);
attr.clear();
attr.push_back(new Attribute<ScrollingBackground*>(&back3, "sb_visual"));
attr.push_back(new Attribute<sf::Vector2f>(sf::Vector2f(1.5f, .75f), "sb_scroll_speed"));
GameObjectFactory::getInstance()->createGameObject(attr);
attr.clear();
All runs fine so far, though as I said my C++ skills are rusty and I need code revision badly
For start I would like to change the way of storing available components into the game factory, having empty components for the sake of it is just meh to me.
Anyway thanks in advance for help, and thanks for your game design articles
P.S. Two links I found during my research that could be of some use:
Pitfalls of Object Oriented Programming Theory and Practice of Game Object Component ArchitectureEDIT: Is there like some spoiler tag or show hide code option? Too much code = unnecessary lengthy post