ENTIERLY NEW APPROACH TO COMPONENTS POSTED IN CODE SNIPPETS FORUM
http://irrlicht.sourceforge.net/phpBB2/ ... hp?t=35666
Prerequisites:
- Singleton pattern
http://www.inquiry.com/techtips/cpp_pro ... in0200.asp
- Decorator pattern / Component pattern
http://en.wikipedia.org/wiki/Decorator_pattern
- Observer pattern
http://en.wikipedia.org/wiki/Observer_pattern
Third party dependencies:
- Sigslot event system
http://sigslot.sourceforge.net/
Lately I've been working on modular entities and to prevent deep hierarchies. I stumbled over the component based design in some of the Game Programming Gems books, and thought I'd give it a try. It turned out great for myself and my game's framework.
A component-based approach makes entities data-driven, which means that entities can be described in feks. XML. It's less prone to difficulties during optimization stages than the deep hierarchy approach, and you split up entity functionality in logical and manageable chunks of code.
In my approach, I decided to go with the observer pattern for communication. I wanted to use this approach to prevent many to many relationships where it could be helped, lessening unnecessary recompilation of files when coding and, in my opinion, cleaning up code and making it all more logical to interpret.
Thus the following HOWTO describes the approach for a component based entity system using sigslot for communication. Please note that the code here won't work off the bat, you can download a complete codebase from my game's repository over at sourceforge here:
http://sourceforge.net/projects/ravinescrpg/
Let's first start with the ComponentContainer. This is a baseclass who's only functionality is to hold on to a collection of components.
ComponentContainer.h
Code: Select all
#ifndef COMPONENTCONTAINER_H
#define COMPONENTCONTAINER_H
#include "../Managers/GameManager.h"
#include <sigslot/sigslot.h>
#include <map>
class Component;
class ComponentContainer
{
public:
void addComponent(const char* name, Component* component);
Component* getComponent(const char* name) const;
const size_t newComponentID() { return _componentIDs++; }
const size_t lastComponentID() const { return _componentIDs; }
protected:
ComponentContainer(const size_t entityID);
virtual ~ComponentContainer();
private:
std::map<const char*, Component*> _componentContainer;
const size_t _entityID;
size_t _componentIDs;
};
#endif
Code: Select all
#include "ComponentContainer.h"
#include "Component.h"
#include "HealthComponent.h"
#include "AnimatedNodeComponent.h"
#include <iostream>
ComponentContainer::ComponentContainer(const size_t entityID)
: _entityID(entityID), _componentIDs(0)
{
}
ComponentContainer::~ComponentContainer()
{
std::map<const char*, Component*>::iterator iter = _componentContainer.begin();
for(; iter != _componentContainer.end(); ++iter)
{
delete iter->second;
}
}
void ComponentContainer::addComponent(const char* name, Component* component)
{
if(_componentContainer.find(name) != _componentContainer.end())
{
delete _componentContainer.find(name)->second;
_componentContainer.find(name)->second = component;
}
else
{
_componentContainer[name] = component;
}
}
Component* ComponentContainer::getComponent(const char* name) const
{
if(!this)
return 0;
if(_componentContainer.find(name) != _componentContainer.end())
return _componentContainer.find(name)->second;
else
{
std::cout << "Could not find component " << name << std::endl;
return 0;
}
}
Note that components are stored into the map with const char* as key. We can do this because there will only be one instance of any component inside the container.
Let's now move on to the Entity class. The entity class is a ComponentContainer with a unique id.
Entity.h
Code: Select all
#ifndef ENTITY_H
#define ENTITY_H
#include "../Managers/GameManager.h"
#include "../Managers/ActionManager.h"
#include "../Managers/EntityManager.h"
#include "../Components/ComponentContainer.h"
class Entity : public ComponentContainer, public sigslot::has_slots<>
{
friend class EntityManager;
public:
virtual ~Entity();
virtual void initialize() {}
const unsigned int getID() const { return _entityID; }
const char* getName() const { return _name; }
bool isAlive() const { return _isAlive; }
void setAlive(bool alive = true) { _isAlive = alive; }
protected:
Entity(const size_t id, const char* name);
//Actions
void onUpdate(void* fDeltaTime, const size_t id);
const size_t _entityID;
const char* _name;
bool _isAlive;
};
#endif
Code: Select all
#include "Entity.h"
#include <iostream>
Entity::Entity(const size_t id, const char* name)
: ComponentContainer(id), _entityID(id), _name(name), _isAlive(true)
{
ActionManager::ptr()->getCoreAction("OnUpdate")->connect(this, &Entity::onUpdate);
}
Entity::~Entity()
{
this->disconnect_all();
}
void Entity::onUpdate(void* fDeltaTime, const size_t id)
{
float dt = ActionManager::ptr()->toFloat(fDeltaTime);
//std::cout << _name << " was updated" << std::endl;
ActionManager::ptr()->emitIngameAction(_entityID, "OnUpdate", &dt, _entityID);
static float tot = 0.0f;
if(tot > 150.0f)
{
ActionManager::ptr()->emitIngameAction(_entityID, "OnDying", " ", _entityID);
}
tot+=dt;
if(_entityID == 101)
std::cout << tot << std::endl;
}
Note that here's also a first example of how we use the observer pattern, using the Actionmanager. Here we have a functor onUpdate, which on construction is said to be the reaction to the event "OnUpdate" for the object Entity.
In the onUpdate functor, we tell all the components of this entity to update (if they are listening to the ingame update event, that is).
In addition, we're counting time here, saying that when the variable tot reaches 150, we'll emit the ingame action event "OnDying".
We'll dig deeper into the ActionManager and events later in this HOWTO, but for now, let's be content with what I've said and move on.
Let's take a look at the Component baseclass now. This is the base for all components which describes an entity.
Component.h
Code: Select all
#ifndef COMPONENT_H
#define COMPONENT_H
#include <sigslot/sigslot.h>
class Component : public sigslot::has_slots<>
{
public:
Component(const size_t entityID) : _name(0), _compID(0), _entityID(entityID) {}
virtual ~Component() {}
virtual const char* getName() const { return "N/A"; }
const size_t getID() const { return _compID; };
protected:
char* _name;
size_t _compID;
const size_t _entityID;
};
#endif
Before we move on to any examples for components, I'd like us to take a look at the different managers we have in place to manage this whole thing. I like to use managers to kind of have a mainframe of functionality that I can access from any other point in my project. It creates obvious dependency chains, as I try to link most of the communication in my code through the managers, which is clean and easy to maintain. For this HOWTO, I'll give example code to three managers:
- GameManager, ActionManager and EntityManager.
Why do I call it ActionManager instead of EventManager? It's quite simple, I didn't want it to intervene with Irrlicht's event system, thus it's easy to add in Irrlicht's event handling for input and gui, while the entities are handled via actions.
Let's start with the EntityManager.
EntityManager.h
Code: Select all
#ifndef ENTITYMANAGER_H
#define ENTITYMANAGER_H
#include "../Managers/GameManager.h"
#include "../Entities/Entity.h"
#include <sigslot/sigslot.h>
#include <map>
class Entity;
class EntityManager : public sigslot::has_slots<>, public Singleton<EntityManager>
{
friend class Singleton<EntityManager>;
public:
Entity* createEntity( const char* name);
Entity* getEntity( const char* name ) const;
Entity* getEntity( const size_t id) const;
const size_t getCount() const { return _ents.size(); }
protected:
EntityManager();
virtual ~EntityManager();
//Actions
void onUpdate(void* fDeltaTime, const size_t senderID);
std::map<const char*, Entity*> _ents;
};
#endif
Code: Select all
#include "EntityManager.h"
#include <iostream>
EntityManager::EntityManager()
{
//ActionManager::ptr()->getCoreAction("OnUpdate")->connect(this, &EntityManager::onUpdate);
}
EntityManager::~EntityManager()
{
this->disconnect_all();
std::map<const char*, Entity*>::const_iterator it = _ents.begin();
for(; it != _ents.end(); ++it)
{
delete (*it).second;
}
}
Entity* EntityManager::createEntity( const char* name )
{
this->_ents[name] = new Entity(GameManager::ptr()->newEntityID(), name);
return this->_ents.find(name)->second;
}
Entity* EntityManager::getEntity( const char* name ) const
{
if(_ents.find(name) != _ents.end())
return _ents.find(name)->second;
else
{
std::cerr << "Entity, " << name << ", does not exist!" << std::endl;
return 0;
}
}
Entity* EntityManager::getEntity( const size_t id) const
{
std::map<const char*, Entity*>::const_iterator it = _ents.begin();
for(; it != _ents.end(); ++it)
{
if((*it).second->getID() == id)
{
return (*it).second;
}
}
std::cerr << "Entity, with ID " << id << ", does not exist!" << std::endl;
return NULL;
}
void EntityManager::onUpdate(void* fDeltaTime, const size_t senderID)
{
//float dt = ActionManager::ptr()->toFloat(fDeltaTime);
//std::cout << "Updated EntityManager: " << dt << std::endl;
}
The most interesting part for this manager is the createEntity which takes a name as input. Note how we use the function newEntityID() from GameManager to give the entity a unique id, quite identical to how ComponentContainer holds the newComponentID(). This brings one obvious improvement of our ComponentContainer to mind. We could make it a factory instead of a container...
Let's move on to the ActionManager now. The Action Manager serves as a center point for all sigslot messages, and I must admit, this might be one of the messier classes of my code, so if someone on this forum as any ideas for improving the code and making it easier to read, I'd be very interested in that.
In this HOWTO, there are two maps in use, one that holds on to all core actions, and one who holds on to all ingame actions. The only core action in this code is the OnUpdate between managers and entities, while there are more diverse examples of using the ingame actions.
The difference between a core and an ingame action, is that the ingame action stores an entityID as a first key, and then stores another map as second that holds the name of the action (or the event ID) and then the sigslot signal as second. The core action only has action name as key and stores sigslot signals as second.
The sigslot signal I decided to use is the sigslot::signal2, which allows me to have two parameters inside the signal. This conveniently gives me one spot for data and one spot for passing the id of the entity who invoked the action. This is convenient since I can tell an entity who is attacking it, etc.
Code: Select all
typedef sigslot::signal2<void*, const size_t> actionSignal;
I decided to add a bunch of conversion functions inside the ActionManager as well, that convert the void* into the type you want, like float toFloat(void* v), etc
Let's take a look at the code:
ActionManager.h
Code: Select all
#ifndef ACTIONMANAGER_H
#define ACTIONMANAGER_H
#include "../Singleton.h"
#include "GameManager.h"
#include <sigslot/sigslot.h>
#include <map>
#include "../Containers/AnimationSequenceContainer.h"
typedef sigslot::signal2<void*, const size_t> actionSignal;
class ActionManager : public Singleton<ActionManager>
{
friend class Singleton<ActionManager>;
public:
void emitCoreAction(const char* action, void* ptr, const size_t senderID = 0);
void emitIOAction(const size_t entityID, const char* action, void* ptr, const size_t senderID = 0);
void emitIngameAction(const size_t entityID, const char* action, void* ptr, const size_t senderID = 0);
actionSignal* getCoreAction(const char* action);
actionSignal* getIOAction(const size_t entityID, const char* action);
actionSignal* getIngameAction(const size_t entityID, const char* action);
const bool toBool(void* v)
{
return (reinterpret_cast<const bool*>(v))[0];
}
const char toChar(void* v)
{
return reinterpret_cast<const char*>(v)[0];
}
const char* toCharArray(void* v)
{
return reinterpret_cast<const char*>(v);
}
const short toShort(void* v)
{
return reinterpret_cast<const short*>(v)[0];
}
const short* toShortArray(void* v)
{
return reinterpret_cast<const short*>(v);
}
const int toInt(void* v)
{
return (reinterpret_cast<const int*>(v))[0];
}
const unsigned int toUInt(void *v)
{
return (reinterpret_cast<const unsigned int*>(v))[0];
}
const float toFloat(void* v)
{
return (reinterpret_cast<const float*>(v))[0];
}
const double toDouble(void* v)
{
return (reinterpret_cast<const double*>(v))[0];
}
const Containers::AnimationSequence* toAnimationSequence(void* v)
{
return reinterpret_cast<const Containers::AnimationSequence*>(v);
}
const core::vector3df to3DVec(void* v)
{
return (reinterpret_cast<const core::vector3df*>(v))[0];
}
const core::matrix4 to4Matrix(void* v)
{
return (reinterpret_cast<const core::matrix4*>(v))[0];
}
protected:
ActionManager() {}
virtual ~ActionManager() {}
private:
std::map<const char*, actionSignal> _coreAction;
std::map<const size_t, std::map<const char*, actionSignal>> _ioAction;
std::map<const size_t, std::map<const char*, actionSignal>> _ingameAction;
};
#endif
Code: Select all
#include "ActionManager.h"
#include <iostream>
void ActionManager::emitCoreAction(const char* action, void* ptr, const size_t senderID)
{
if(_coreAction.find(action) != _coreAction.end())
_coreAction.find(action)->second.emit(ptr, senderID);
else
std::cout << action << " does not exist in _coreAction" << std::endl;
}
void ActionManager::emitIngameAction(const size_t entityID, const char* action, void* ptr, const size_t senderID)
{
if(_ingameAction.find(entityID) != _ingameAction.end())
{
std::map<const char*, actionSignal>* actionMap = &_ingameAction.find(entityID)->second;
if(actionMap->find(action) != actionMap->end())
actionMap->find(action)->second.emit(ptr, senderID);
else
{
//std::cout << action << " does not exist in _ingameAction" << std::endl;
}
}
else
std::cout << entityID << " does not exist in _ingameAction" << std::endl;
}
void ActionManager::emitIOAction(const size_t entityID, const char* action, void* ptr, const size_t senderID)
{
if(_ioAction.find(entityID) != _ioAction.end())
{
std::map<const char*, actionSignal>* actionMap = &_ioAction.find(entityID)->second;
if(actionMap->find(action) != actionMap->end())
actionMap->find(action)->second.emit(ptr, senderID);
else
std::cout << action << " does not exist in _ioAction" << std::endl;
}
else
std::cout << entityID << " does not exist in _ioAction" << std::endl;
}
actionSignal* ActionManager::getCoreAction(const char* action)
{
if(_coreAction.find(action) != _coreAction.end())
return &_coreAction.find(action)->second;
else
{
actionSignal sig;
_coreAction[action] = sig;
return &_coreAction.find(action)->second;
}
}
actionSignal* ActionManager::getIOAction(const size_t entityID, const char* action)
{
if(_ioAction.find(entityID) != _ioAction.end())
{
std::map<const char*, actionSignal> &actionMap = _ioAction.find(entityID)->second;
if(actionMap.find(action) != actionMap.end())
return &_ioAction.find(entityID)->second.find(action)->second;
else
{
actionSignal sig;
actionMap[action] = sig;
return &_ioAction.find(entityID)->second.find(action)->second;
}
}
else
{
std::map<const char*, actionSignal> actionMap;
actionSignal sig;
actionMap[action] = sig;
_ioAction[entityID] = actionMap;
return &_ioAction.find(entityID)->second.find(action)->second;
}
}
actionSignal* ActionManager::getIngameAction(const size_t entityID, const char* action)
{
if(_ingameAction.find(entityID) != _ingameAction.end())
{
std::map<const char*, actionSignal> &actionMap = _ingameAction.find(entityID)->second;
if(actionMap.find(action) != actionMap.end())
return &_ingameAction.find(entityID)->second.find(action)->second;
else
{
actionSignal sig;
actionMap[action] = sig;
return &_ingameAction.find(entityID)->second.find(action)->second;
}
}
else
{
std::map<const char*, actionSignal> actionMap;
actionSignal sig;
actionMap[action] = sig;
_ingameAction[entityID] = actionMap;
return &_ingameAction.find(entityID)->second.find(action)->second;
}
}
Now, let's move on to the GameManager. This is the heart of our game's framework. I use this manager to initialize resource and to update and render every frame. I haven't added a shutdown() function to this manager, even though it would probably be convenient to have it.
GameManager.h
Code: Select all
#ifndef GAMEMANAGER_H
#define GAMEMANAGER_H
#include <Irrlicht/irrlicht.h>
using namespace irr;
#include "../Singleton.h"
class GameManager : public Singleton<GameManager>
{
friend class Singleton<GameManager>;
public:
void run();
IrrlichtDevice* device;
video::IVideoDriver* driver;
scene::ISceneManager* smgr;
gui::IGUIEnvironment* gui;
const size_t newEntityID() { return _entityIDs++; }
const size_t lastEntityID() const { return _entityIDs; }
const size_t getPlayerID() const { return 9999999; }
protected:
GameManager();
virtual ~GameManager() {}
void _init();
private:
size_t _entityIDs;
};
#endif
Code: Select all
#include "GameManager.h"
#include "StateManager.h"
#include "ActionManager.h"
#include "inputManager.h"
#include <iostream>
GameManager::GameManager()
{
_entityIDs = 100;
}
void GameManager::run()
{
_init();
u32 time = 0;
u32 lastTime = 0;
float deltaTime = 0.0f;
float fps = 0.0f;
while(device->run())
{
driver->beginScene(true, true, video::SColor(255, 10, 10, 10));
fps = (float)driver->getFPS();
time = device->getTimer()->getTime();
deltaTime = (float)time - (float)lastTime;
lastTime = time;
deltaTime *= 0.01f;
if(fps > 0.0f && deltaTime > 0.0f)
{
ActionManager::ptr()->emitCoreAction("OnUpdate", &deltaTime);
}
smgr->drawAll();
driver->endScene();
}
device->drop();
}
void GameManager::_init()
{
device = createDevice( video::EDT_DIRECT3D9, core::dimension2d<s32>(1028, 960), 32, false, false, false, InputManager::ptr());
driver = device->getVideoDriver();
smgr = device->getSceneManager();
gui = device->getGUIEnvironment();
if(!device->getFileSystem()->addZipFileArchive("../../../Data/Models/IrrMesh.zip"))
std::cout << "Failed to load zip archive!" << std::endl;
else
std::cout << "Loaded zip archive!" << std::endl;
if(!device->getFileSystem()->addZipFileArchive("../../../Data/Textures/IrrTex.zip"))
std::cout << "Failed to load zip archive!" << std::endl;
else
std::cout << "Loaded zip archive!" << std::endl;
StateManager::ptr()->init();
}
Just for those who's not following on the GameManager, here's my main.cpp:
Code: Select all
#include "Managers/GameManager.h"
void main()
{
GameManager::ptr()->run();
}
We've now gone over the managers I thought was important for this HOWTO. We now have to foundation we need to start taking advantage of component based entities and observer communication. Let's give a couple of examples of components:
The Health Component:
HealthComponent.h
Code: Select all
#ifndef HEALTH_H
#define HEALTH_H
#include "Component.h"
class Entity;
namespace Components
{
class Health : public Component
{
public:
Health();
Health(const size_t entityID);
virtual ~Health();
virtual const char* getName() const;
bool isInitialized() const;
void init(const size_t id, float startHealth, float maxHealth);
//Useful for such things as level increase, etc
void setMaxHealth(float maxHealth);
protected:
//Actions
void onUpdate(void* fDeltaTime, const size_t senderID);
void onDamage(void* fDamage, const size_t attackerID);
void onRegenerate(void* fAmount, const size_t healerID);
void onDead(void* cVoid, const size_t killerID);
void onDying(void* cvoid, const size_t killerID);
//void onWounded(void* cvoid, const size_t attackerID);
bool _isInitialized;
float _health;
float _lastHealth;
float _maxHealth;
Entity* _owner;
};
}
#endif
Code: Select all
#include "HealthComponent.h"
#include "../Managers/ActionManager.h"
#include "../Managers/EntityManager.h"
#include "../Entities/Entity.h"
#include <iostream>
using namespace Components;
Health::Health()
: Component(NULL)
{
}
Health::Health(const size_t entityID)
: Component(entityID)
{
_name = "HP";
_isInitialized = false;
_maxHealth = -1;
_health = _maxHealth;
_lastHealth = _health;
_owner = EntityManager::ptr()->getEntity(_entityID);
//Actions
//ActionManager::ptr()->getIngameAction(_entityID, "OnUpdate")->connect(this, &Health::onUpdate);
ActionManager::ptr()->getIngameAction(_entityID, "OnDamage")->connect(this, &Health::onDamage);
ActionManager::ptr()->getIngameAction(_entityID, "OnRegenerate")->connect(this, &Health::onRegenerate);
ActionManager::ptr()->getIngameAction(_entityID, "OnDying")->connect(this, &Health::onDying);
ActionManager::ptr()->getIngameAction(_entityID, "OnDead")->connect(this, &Health::onDead);
}
Health::~Health()
{
this->disconnect_all();
}
const char* Health::getName() const
{
return _name;
}
bool Health::isInitialized() const
{
return _isInitialized;
}
void Health::init(const unsigned int id, float startHealth, float maxHealth)
{
_compID = id;
_health = startHealth;
_lastHealth = _health;
_maxHealth = maxHealth;
_isInitialized = true;
}
//Useful for such things as level increase, etc
void Health::setMaxHealth(float maxHealth)
{
if(_isInitialized)
_maxHealth = maxHealth;
}
void Health::onUpdate(void* fDeltaTime, const size_t senderID)
{
}
void Health::onDamage(void* fDamage, const size_t attackerID)
{
float damage = ActionManager::ptr()->toFloat(fDamage);
std::cout << _owner->getName() << " took " << damage << " damage" << std::endl;
}
void Health::onRegenerate(void* fAmount, const size_t healerID)
{
float amount = ActionManager::ptr()->toFloat(fAmount);
std::cout << _owner->getName() << " was healed for " << amount << " hp" << std::endl;
}
void Health::onDead(void* cVoid, const size_t killerID)
{
if(!_owner->isAlive())
{
ActionManager::ptr()->getCoreAction("OnUpdate")->disconnect(_owner);
}
}
void Health::onDying(void* cVoid, const size_t killerID)
{
if(_owner->isAlive())
{
_owner->setAlive(false);
std::cout << _name << " was killed by " << EntityManager::ptr()->getEntity(killerID)->getName() << std::endl;
ActionManager::ptr()->emitIngameAction(_entityID, "OnChangeAnimation", "ASEQ_DIE1", _entityID);
ActionManager::ptr()->getIngameAction(_entityID, "OnDying")->disconnect(_owner);
}
}
The Animated Node component:
AnimatedNodeComponent.h
Code: Select all
#ifndef ANIMATEDNODECOMPONENT_H
#define ANIMATEDNODECOMPONENT_H
#include "Component.h"
#include "../Managers/GameManager.h"
#include "../Containers/AnimationSequenceContainer.h"
#include <map>
class Entity;
namespace Components
{
class AnimatedNode : public Component
{
public:
AnimatedNode();
AnimatedNode(const size_t entityID);
virtual ~AnimatedNode();
virtual const char* getName() const;
bool isInitialized() const;
void init(const unsigned int id, const char* name, scene::ISceneNode* parent, core::vector3df position, const c8* mesh, const c8* texture, f32 size);
scene::IAnimatedMeshSceneNode* get() const;
const char* getCurrentAnimation() const;
const char* getLastAnimation() const;
void setCurrentAnimation(char* currentAnim);
bool animationChanged() const;
void addAnimationSequence(Containers::AnimationSequence* seq);
protected:
//Actions
void onUpdate(void* fDeltaTime, const size_t senderID);
void onChangeAnimation(void* caCurrentAnim, const size_t senderID);
void onRotateNode(void* fY, const size_t senderID);
void onMoveForward(void* fSpeed, const size_t senderID);
bool _isInitialized;
scene::IAnimatedMeshSceneNode* _anode;
char* _currentAnim;
char* _lastAnim;
bool _isCurrentAnimLooping;
Entity* _owner;
std::map<const char*, Containers::AnimationSequence*> _seqMap;
};
}
#endif
Code: Select all
#include "AnimatedNodeComponent.h"
#include "../Managers/ActionManager.h"
#include "../Managers/EntityManager.h"
#include "../Entities/Entity.h"
#include <iostream>
using namespace Components;
AnimatedNode::AnimatedNode()
: Component(NULL)
{
}
AnimatedNode::AnimatedNode(const size_t entityID)
: Component(entityID)
{
_name = "ANODE";
_isInitialized = false;
_anode = NULL;
_currentAnim = "ASEQ_IDLE";
_lastAnim = " ";
_owner = EntityManager::ptr()->getEntity(_entityID);
//Actions
ActionManager::ptr()->getIngameAction(_entityID, "OnUpdate")->connect(this, &AnimatedNode::onUpdate);
ActionManager::ptr()->getIngameAction(_entityID, "OnChangeAnimation")->connect(this, &AnimatedNode::onChangeAnimation);
ActionManager::ptr()->getIngameAction(_entityID, "OnRotateNode")->connect(this, &AnimatedNode::onRotateNode);
ActionManager::ptr()->getIngameAction(_entityID, "OnMoveForward")->connect(this, &AnimatedNode::onMoveForward);
}
AnimatedNode::~AnimatedNode()
{
this->disconnect_all();
}
const char* AnimatedNode::getName() const
{
return _name;
}
bool AnimatedNode::isInitialized() const
{
return _isInitialized;
}
void AnimatedNode::init(const unsigned int id, const char* name, scene::ISceneNode* parent, core::vector3df position, const c8* mesh, const c8* texture, f32 size)
{
_compID = id;
_anode = GameManager::ptr()->smgr->addAnimatedMeshSceneNode(GameManager::ptr()->smgr->getMesh(mesh), parent, id);
_anode->setName((const c8*)name);
if(texture)
_anode->setMaterialTexture(0, GameManager::ptr()->driver->getTexture(texture));
_anode->setPosition(position);
_anode->addShadowVolumeSceneNode();
_anode->setScale(core::vector3df(size,size,size));
_anode->setMaterialFlag(video::EMF_LIGHTING, false);
_anode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true);
ActionManager::ptr()->emitIngameAction(_entityID, "OnRotateCamera", (void*)(&_anode->getAbsoluteTransformation()), _entityID);
//ActionManager::ptr()->emitIngameAction(_entityID, "OnTargetMoved", (void*)(&_anode->getPosition()), _entityID);
_isInitialized = true;
}
scene::IAnimatedMeshSceneNode* AnimatedNode::get() const
{
return _anode;
}
const char* AnimatedNode::getCurrentAnimation() const
{
return _currentAnim;
}
const char* AnimatedNode::getLastAnimation() const
{
return _lastAnim;
}
bool AnimatedNode::animationChanged() const
{
return _lastAnim != _currentAnim;
}
void AnimatedNode::onUpdate(void* fDeltaTime, const size_t senderID)
{
if(!_isInitialized)
return;
if(!_isCurrentAnimLooping)
{
//If animation is done playing
s32 currFrame = (s32)_anode->getFrameNr();
s32 endFrame = _anode->getEndFrame()-1;
if(currFrame >= endFrame)
{
if(animationChanged())
{
_anode->setTransitionTime(0.3f);
}
else
{
_anode->setFrameLoop(endFrame-2, endFrame-2);
if(!_owner->isAlive())
{
ActionManager::ptr()->emitIngameAction(_entityID, "OnDead", " ", _entityID);
}
return; //if we're here, we're not going to animate our node at all
}
}
else
{
_anode->animateJoints();
_lastAnim = _currentAnim;
}
}
else
{
if(animationChanged())
{
_anode->setTransitionTime(0.3f);
}
_anode->animateJoints();
}
}
void AnimatedNode::onChangeAnimation(void* caCurrentAnim, const size_t senderID)
{
const char* currentAnim = ActionManager::ptr()->toCharArray(caCurrentAnim);
if(_currentAnim != currentAnim)
{
_lastAnim = _currentAnim;
_currentAnim = const_cast<char*>(currentAnim);
if(_seqMap.find(_currentAnim) != _seqMap.end())
{
const Containers::AnimationSequence* seq = _seqMap.find(_currentAnim)->second;
_anode->setFrameLoop(seq->startFrame, seq->endFrame);
_isCurrentAnimLooping = seq->looping;
}
}
std::cout << EntityManager::ptr()->getEntity(_entityID)->getName() << " changed animation from " << _lastAnim << " to " << _currentAnim << std::endl;
}
void AnimatedNode::onRotateNode(void* fY, const size_t senderID)
{
float y = ActionManager::ptr()->toFloat(fY);
_anode->setRotation(core::vector3df(_anode->getRotation().X, y*3, _anode->getRotation().Z));
core::matrix4 m;
m.setRotationDegrees(_anode->getRotation());
m.setTranslation(_anode->getPosition());
m.setScale(_anode->getScale());
ActionManager::ptr()->emitIngameAction(_entityID, "OnRotateCamera", (void*)(&m), _entityID);
//ActionManager::ptr()->emitIngameAction(_entityID, "OnTargetMoved", (void*)(&_anode->getPosition()), _entityID);
}
void AnimatedNode::onMoveForward(void* fSpeed, const size_t senderID)
{
core::vector3df facing = core::vector3df(sin(_anode->getRotation().Y*core::DEGTORAD), 0, cos(_anode->getRotation().Y*core::DEGTORAD));
facing *= 1.01f;
_anode->setPosition((_anode->getPosition()+facing));
core::matrix4 m;
m.setRotationDegrees(_anode->getRotation());
m.setTranslation(_anode->getPosition());
m.setScale(_anode->getScale());
ActionManager::ptr()->emitIngameAction(_entityID, "OnRotateCamera", (void*)(&m), _entityID);
}
void AnimatedNode::addAnimationSequence(Containers::AnimationSequence* seq)
{
_seqMap[seq->name] = seq;
}
At the end of this HOWTO, I'd like to display some simple examples for how to initialize an entity using the described approach:
Code: Select all
Entity* ent = EntityManager::ptr()->createEntity("Chest");
Components::Health* comp = new Components::Health(ent->getID());
comp->init(ent->newComponentID(), 100.0f, 100.0f);
ent->addComponent("HP", comp);
Entity* ent2 = EntityManager::ptr()->createEntity("Dwarf");
Components::CameraController* comp5 = new Components::CameraController(ent2->getID());
comp5->init(ent2->newComponentID());
ent2->addComponent("CAMERACONTROLLER", comp5);
Components::AnimatedNode* comp3 = new Components::AnimatedNode(ent2->getID());
comp3->init(ent2->newComponentID(), ent2->getName(), 0, core::vector3df(), "dwarf2.x", 0, 2.5f);
comp3->addAnimationSequence(new Containers::AnimationSequence("ASEQ_DIE1", 212, 227, false));
comp3->addAnimationSequence(new Containers::AnimationSequence("ASEQ_IDLE1", 292, 325, true));
ent2->addComponent("ANODE", comp3);
ActionManager::ptr()->emitIngameAction(ent2->getID(), "OnChangeAnimation", "ASEQ_IDLE1", ent2->getID());
Components::Health* comp2 = new Components::Health(ent2->getID());
comp2->init(ent2->newComponentID(), 100.0f, 100.0f);
ent2->addComponent("HP", comp2);
Components::InputReceiver* comp4 = new Components::InputReceiver(ent2->getID());
comp4->init(ent2->newComponentID());
ent2->addComponent("INPUTRECEIVER", comp4);
Now, if the XMLManager would handle adding components to entities and giving entities names, while the entityManager gives the entities unique ids, imagine, with a little bit of expanding on my example, how simple it really will be to add a new entity into your scene. One line of code would be enough:
Code: Select all
EntityManager::getEntity("Barrel")->spawnCopy(position, rotation, size);
Happy programming!