
Intro :
Dans la conception d’un jeu il est très important d’énumérer tous les mécanismes intrinsèques au comportement du jeu, c’est-à-dire des événements du gameplay, du son, de l’interface, du réseau, de la gestion des collisions et de l’IA.
Ceci est une première approche au problème.
J’ai écrit un article sur une deuxième approche plus juste et mieux adaptée.
Ici on parle des événements, nommés aussi Events.
Prérequis :
La compréhension de l’utilisation de la classe DataParameters.
Explications / exemples :
On se sert de deux macros.
Voici des macros permettant de simplifier la définition de vos Events :
#define DECLARE_GAME_EVENT(eventName) class eventName : public Event \
{ \
public: \
eventName() : \
Event(#eventName, 0) \
{} \
virtual bool performTask(); \
static const int GameEventID = Event::eventName; \
};
#define DECLARE_GAME_EVENT_WITH_PARAMETERS(eventName, argsNumber, ...) class eventName : public Event \
{ \
public: \
eventName() : \
Event(#eventName, argsNumber, __VA_ARGS__)\
{} \
virtual bool performTask(); \
static const int GameEventID = Event::eventName; \
};
#define IMPLEMENT_GAME_EVENT(eventName) bool eventName::performTask()
Je vous fournis deux macros : une pour un Event sans paramètres et une autre pour les Events
avec paramètres.
Voici le fichier .h de la classe Event :
class Event
{
public:
static const enum GameEvents
{
/* Exemples d'events à titre indicatif */
OnTakeScreenShot,
OnMobAggro,
OnMobUnAggro,
OnMobIdle,
OnMobAttack,
OnConsoleScrollUp,
OnConsoleScrollDown,
OnHeal,
OnEnemyDamage,
OnFocusTarget,
OnSpellCast,
OnMount,
OnUnMount,
OnGUIButtonClick,
OnGUIButtonHover,
OnAddItem,
OnRemoveItem,
OnKill,
OnTextPaste,
OnToggleCameraMode
};
Event(const char* sName, unsigned int iArgsNumber, ...);
virtual ~Event();
virtual void trigger();
const std::string& getName() const;
time_t getTimeLastTriggered() const;
bool hasLastTriggerSucceed() const;
// Associe un autre event à invoquer en cascade
void pushChildEvent(Event* pEvent);
// Paramètre associé à l'Event invoqué
void setParameter(DataParameters& params);
// Paramètre associé à l'Event invoqué
DataParameters* getParameters();
bool getLogWriting();
void setLogWriting(bool bWrite);
void getParametersName(std::vector<std::string>& paramsNames);
protected:
virtual bool performTask() = 0;
void setName(const std::string& sName);
private:
std::string m_sName;
time_t m_timeLastTriggered;
bool m_bLastTriggerSucceed;
std::vector<Event*> m_childrenEvents;
// Une liste des noms de paramètres de l'event
std::vector<std::string> m_parametersNames;
DataParameters m_params;
bool m_bLogWriting;
};
Voici le fichier .cpp
Event::Event(const char* sName, unsigned int iArgsNumber, ...) :
m_sName(sName),
m_timeLastTriggered(0),
m_bLastTriggerSucceed(false),
m_bLogWriting(true)
{
va_list args;
va_start(args, iArgsNumber);
for (unsigned i = 0; i < iArgsNumber; i++)
{
std::string sArgName = va_arg(args, char*);
m_parametersNames.push_back(sArgName);
}
va_end(args);
}
Event::~Event()
{
}
void Event::trigger()
{
time(&m_timeLastTriggered);
m_bLastTriggerSucceed = performTask();
if (getLogWriting())
{
//SYSTEM_LOG->log(Logger::MessageType::MSG_TYPE_EVENT, std::string("Event effectué : " + getName()).c_str());
}
// Invoque aussi les events enfants et associés (en cascade) :
for (unsigned int i = 0; i < m_childrenEvents.size(); i++)
{
Event* pEvent = m_childrenEvents[i];
if (pEvent)
pEvent->trigger();
}
}
const std::string& Event::getName() const
{
return m_sName;
}
time_t Event::getTimeLastTriggered() const
{
return m_timeLastTriggered;
}
bool Event::hasLastTriggerSucceed() const
{
return m_bLastTriggerSucceed;
}
void Event::setName(const std::string& sName)
{
m_sName = sName;
}
void Event::pushChildEvent(Event* pEvent)
{
if (pEvent)
{
m_childrenEvents.push_back(pEvent);
}
}
void Event::setParameter(DataParameters& params)
{
m_params = params;
}
DataParameters* Event::getParameters()
{
return &m_params;
}
bool Event::getLogWriting()
{
return m_bLogWriting;
}
void Event::setLogWriting(bool bWrite)
{
m_bLogWriting = bWrite;
}
void Event::getParametersName(std::vector<std::string>& paramsNames)
{
paramsNames = m_parametersNames;
}
On définit les Events comme suit :
DECLARE_GAME_EVENT( OnTakeScreenShot );
DECLARE_GAME_EVENT( OnMobAggro );
DECLARE_GAME_EVENT( OnMobUnAggro );
DECLARE_GAME_EVENT( OnMobIdle );
DECLARE_GAME_EVENT( OnMobAttack );
DECLARE_GAME_EVENT( OnConsoleScrollUp );
DECLARE_GAME_EVENT( OnConsoleScrollDown );
DECLARE_GAME_EVENT( OnHeal );
DECLARE_GAME_EVENT( OnEnemyDamage );
DECLARE_GAME_EVENT( OnFocusTarget );
DECLARE_GAME_EVENT( OnSpellCast );
DECLARE_GAME_EVENT( OnMount );
DECLARE_GAME_EVENT( OnUnMount );
DECLARE_GAME_EVENT( OnGUIButtonClick );
DECLARE_GAME_EVENT( OnGUIButtonHover );
DECLARE_GAME_EVENT( OnAddItem );
DECLARE_GAME_EVENT( OnRemoveItem );
DECLARE_GAME_EVENT( OnKill );
DECLARE_GAME_EVENT( OnTextPaste );
DECLARE_GAME_EVENT( OnToggleCameraMode );
Et on implémente les Events comme suit :
IMPLEMENT_GAME_EVENT( OnTextPaste )
{
if (OpenClipboard(NULL))
{
HANDLE hData = GetClipboardData(CF_TEXT);
if (hData)
{
char* pszText = static_cast<char*>(GlobalLock(hData));
// Release the lock
GlobalUnlock( hData );
// Release the clipboard
CloseClipboard();
if (strlen(pszText) > 100)
{
return true;
}
switch (GAME_STATE_MANAGER->getState())
{
case GameState::ON_CONSOLE:
CONSOLE->appendCommandLineText(pszText);
break;
case GameState::ON_LOADING_MENU:
break;
}
}
}
return true;
}
IMPLEMENT_GAME_EVENT( OnCameraMovementUpwardRight )
{
switch (GAME_STATE_MANAGER->getState())
{
case GameState::ON_WORLD:
getGlobalSystems()->
getCameraManager()->setCameraMovement(CameraManager::CAMERA_MOVE_UPWARD |
CameraManager::CAMERA_STRAFE_RIGHT);
break;
}
return true;
}
Voici le fichier EventManager.h :
class Event;
class DataParameters;
class EventManager
{
public:
EventManager();
virtual ~EventManager();
void registerEvent(Event* const pEvent);
// Invoque un event avec spécification de paramètres
void triggerEvent(const std::string& sEventName);
// Invoque un event
void triggerEvent(const std::string& sEventName, DataParameters& p);
// On invoque la fonction callback depuis un fichier script lua
void triggerLuaCallback(const std::string& sEventName, DataParameters& p);
void triggerLuaCallback(const std::string& sEventName);
// On peut parenter un event
void attachEventTo(Event* pEventToAttach, Event* pChildEvent);
Event* getEvent(const std::string& sEventName);
void setParametersToEvent(std::string sEventName, DataParameters pParam);
void getEventsNames(std::vector<std::string>& paramNames);
// On peut invoquer des events spécicifiques à travers une pile d'appel
void pushEvent(const std::string& sName);
void triggerPushedEvents();
void clearPushedEvent();
private:
// La liste des events
std::map<std::string, Event*> m_events;
std::map<std::string, Event*> m_pushedEvents;
};
Voici le fichier EventManager.cpp
EventManager::EventManager()
{
}
EventManager::~EventManager()
{
}
void EventManager::registerEvent(Event* const pEvent)
{
SKIP_IF_FAILED_AssertMsg(pEvent != NULL, "pEvent NULL")
{
std::string sName = pEvent->getName();
SKIP_IF_FAILED_AssertMsg(m_events.count(sName) == 0, "Event déjà enregistré")
{
m_events[sName] = pEvent;
}
}
}
// On déclenche sans paramètre
void EventManager::triggerEvent(const std::string& sEventName)
{
if (m_events.count(sEventName) > 0)
{
Event* pEvent = m_events[sEventName];
pEvent->trigger();
triggerLuaCallback(sEventName);
}
}
// On déclenche avec un paramètre
void EventManager::triggerEvent(const std::string& sEventName, DataParameters& p)
{
if (m_events.count(sEventName) > 0)
{
Event* pEvent = m_events[sEventName];
pEvent->setParameter(p);
pEvent->trigger();
triggerLuaCallback(sEventName, p);
}
}
void EventManager::triggerLuaCallback(const std::string& sEventName)
{
// todo
}
void EventManager::triggerLuaCallback(const std::string& sEventName, DataParameters& p)
{
// todo
}
void EventManager::attachEventTo(Event* pEventToAttach, Event* pChildEvent)
{
if (pEventToAttach && pChildEvent)
{
pEventToAttach->pushChildEvent(pChildEvent);
}
}
Event* EventManager::getEvent(const std::string& sEventName)
{
if (m_events.count(sEventName) > 0)
{
Event* pEvent = m_events[sEventName];
return pEvent;
}
else
{
return NULL;
}
}
void EventManager::setParametersToEvent(std::string sEventName, DataParameters pParam)
{
Event* pEvent = getEvent(sEventName);
SKIP_IF_FAILED_AssertMsg(pEvent == NULL, "pEvent est NULL")
{
pEvent->setParameter(pParam);
}
}
void EventManager::getEventsNames(std::vector<std::string>& paramNames)
{
std::map<std::string, Event*>::iterator it;
for (it = m_events.begin(); it != m_events.end(); it++)
{
paramNames.push_back((*it).first);
}
}
void EventManager::pushEvent(const std::string& sName)
{
Event* pEvent = getEvent(sName);
AssertNULLPointer(pEvent);
std::string sEventName = pEvent->getName();
m_pushedEvents[sEventName] = pEvent;
}
void EventManager::clearPushedEvent()
{
m_pushedEvents.clear();
}
void EventManager::triggerPushedEvents()
{
std::map<std::string, Event*>::iterator it;
for (it = m_pushedEvents.begin(); it != m_pushedEvents.end(); it++)
{
Event* pEvent = it->second;
pEvent->trigger();
}
}
On enregistres les Events comme ci :
EVENT_MANAGER->registerEvent( new OnTextPaste() );
EVENT_MANAGER->registerEvent( new OnCameraMovementUpwardRight() );
Résumé :
L’implémentation d’un système d’Event est cruciale dans toute conception de jeu vidéo. Elle permet en effet de modulariser les mécanismes de jeu et rend le code plus élégant et plus facile à comprendre.