I wanted to create an easily extensible system, so that it would be easy to dynamically define new interactions for every object in the game world. First words that came to my mind were: structure, list and callback: each action is represented by a data structure holding an information about its name, callback, and some additional stuff, like for example animation played by the game engine when the action is being performed. For the prototype I stripped the structure down to the two basic elements: name and callback, and decided to use lua scripts instead of hard-coded callbacks. The basic concept was that an actor (called agens), which works as an "emitter" of action, performs the action on an object (patiens) which is the "receiver" of action. The program checks if the interaction by that name is available for it, and if it is, it fires the script defined for that action. To make it clearer, some code. First the header file:
#include <list> #include <lua.h> #include <lualib.h> #include <lauxlib.h> struct Action { const char* name; const char* callback_script; }; //the base for Agens and Patiens classes class ActBase { private: std::list<Action> AvailableActions; public: void Append(const char* name, const char* script); std::list<Action> GetList() { return AvailableActions; } }; //action receiver class Patiens: public ActBase { private: lua_State* lVM; public: Patiens(lua_State* l) { this->lVM=l; } ~Patiens(); void OnAction(Action act); }; //action emitter class Agens: public ActBase { public: Agens(); ~Agens(); void Perform(const char* name, Patiens* pat); };
The class structure sucks and I know it. However, the concept evolved when I was coding, so the final structure will probably mirror the concept in a more accurate manner. The implementation looks like that:
#include <iostream> #include <list> extern "C" { #include <lua.h> #include <lualib.h> #include <lauxlib.h> } #include "as.h" //empty constructor and equally empty destructor for the Agens class Agens::Agens() { } Agens::~Agens() { } Patiens::~Patiens() { } // append an action to the list of actions that can be performed on the object void ActBase::Append(const char* name, const char* script) { Action act; act.name = name; act.callback_script = script; AvailableActions.push_back(act); } //check out if the action is available for an object. If yes, fire OnAction method //and run the callback script void Agens::Perform(const char* name, Patiens* pat) { if (pat==NULL) return; //we don't like null pointers std::list<Action> list = pat->GetList(); std::list<Action>::iterator i; for(i=list.begin(); i!=list.end(); i++) { if(i->name==name) pat->OnAction(*i); break; } } void Patiens::OnAction(Action act) { lua_dofile(lVM, act.callback_script); } int main() { // create the lua virtual machine lua_State* mLVM; mLVM = lua_open(); lua_baselibopen(mLVM); lua_iolibopen(mLVM); lua_strlibopen(mLVM); //create one instance of Agens, and one of the Patiens class Agens* agens = new Agens(); Patiens* patiens = new Patiens(mLVM); patiens->Append("example","f:/lua-5.0/test/hello.lua"); //perform two actions on patiens. Only one of them will provoke response. agens->Perform("example",patiens); agens->Perform("bad_example",patiens); // clean up after yourself, young man! delete agens; delete patiens; std::cout << std::endl << "That's all, folks!" << std::endl; lua_close(mLVM); return 0; }
"as.h" stands for "action system" and refers to the header file I copied and pasted above. I've written the thing on WinXP, with Code::Blocks, compiled with Windows gcc port, and it ran fine. What's the point with posting it on Devmaster, you ask? For no other reason than your comments. What do you think of the system? Do you think it might be improved? Or should it be entirely discarded? Or should author of this post should abandon his interest in computer programming and spend the rest of his life waiting tables?
Thanks for reading this somewhat bloated and confusing post.