Thread: From script to C to C++

Threaded View

Previous Post Previous Post   Next Post Next Post
  1. #1
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607

    From script to C to C++

    Shakti and myself have come up with what we think is a powerful system for doing scripting. A lot of this system was developed by tearing MFC's message system apart piece by excruciating piece. The final product is something I think is invaluable to game development for many of us here.

    First we have decided the script is not going to be constantly sent to the game objects during game time. A script in itself defines what every object is going to do and when, so why keep sending stuff that either has been done or is going to be done. So basically everyone is given their script and then they execute it as it is layed out. So each object is jumping through it's own script at run time and the script class doesn't even exist anymore. After all, the script will never change once it has been written.

    The second major problem we had to address was going from text files to actual C functions and C++ functions. This problem has been addressed quite nicely in the heart of MFC but it is not pretty code. MFC's example uses hashing to find the message handlers but our method uses an STL container.


    The main header file for our scripts. I didn't say the code was pretty but it works just fine.

    ScriptCmn.h
    Code:
    #pragma once
    
    #include <map>
    //#include "CGameObject.h"
    
    /////////////////////////////////
    //Script message map structures//
    /////////////////////////////////
    
    //Forward delcaration of CGameObject;
    class CGameObject;
    
    //SCRIPT_MSG typedef
    typedef void (CGameObject::*SCRIPT_MSG)(void);
    
    //SCRIPT_MSGMAP_ENTRY
    
    struct SCRIPT_MSGMAP_ENTRY
    {
      UINT nCommandID;
      UINT nFuncSig;
      SCRIPT_MSG pfn;
    
    };
    
    //SCRIPT_MSGMAP
    struct SCRIPT_MSGMAP
    {
      SCRIPT_MSGMAP *pBaseMap;
      std::map<UINT,SCRIPT_MSGMAP_ENTRY *> mapEntries;
    
      SCRIPT_MSGMAP():pBaseMap(NULL) { }
    
      SCRIPT_MSGMAP(SCRIPT_MSGMAP *S_BaseMap,std::map<UINT,SCRIPT_MSGMAP_ENTRY *> S_mapEntries)
      {
         pBaseMap=S_BaseMap;
    
         mapEntries.reserve(S_mapEntries.size());
    
         for (UINT i=0;i<static_cast<UINT>(mapEntries.size());i++)
         {
            mapEntries[i]=S_mapEntries[i];
         }
      }
    
    };
    
    enum SFuncSigs
    {
      SFSig_Param0_n =0,
      SFSig_Param1_n,  //int fxn(DWORD)
      SFSig_Param2_n,  //int fxn(DWORD,DWORD)
      SFSig_Param3_n   //int fxn(DWORD,DWORD,DWORD)
    };
    
    union ScriptMsgMapFuncs
    {
      SCRIPT_MSG pfn;
      UINT (CGameObject::*pfnParam1_n)(DWORD);
      UINT (CGameObject::*pfnParam2_n)(DWORD,DWORD);
      UINT (CGameObject::*pfnParam3_n)(DWORD,DWORD,DWORD);
    };
    
    ///////////////////////////////
    //Message map macros//
    ///////////////////////////////
    
    #define DECLARE_SCRIPT_MSGMAP() \
    private: \
      static const std::map<UINT,SCRIPT_MSGMAP_ENTRY *> m_mapEntries; \
    protected: \
      static const SCRIPT_MSGMAP m_MsgMap; \
      virtual const SCRIPT_MSGMAP *GetMessageMap() const;
    
    #define BEGIN_SCRIPT_MSGMAP(theClass,baseClass) \
    const SCRIPT_MSGMAP *GetMessageMap() const \
    { return &theClass::m_MsgMap; } \
    const SCRIPT_MSGMAP theClass::m_MsgMap = \
    { &baseClass::m_MsgMap, &theClass::m_mapEntries }; \
    theClass::m_MsgMap. 
    
    #define ON_SCRIPT_CMD1(id,memberFxn) \
    Add(id,SCRIPT_MSGMAP_ENTRY(id,SSig_Param0_n,(SCRIPT_MSG)memberFxn); 
    
    #define ON_SCRIPT_CMD1(id,memberFxn) \
    Add(id,SCRIPT_MSGMAP_ENTRY(id,SSig_Param1_n,(SCRIPT_MSG)memberFxn); 
     
    #define ON_SCRIPT_CMD2(id,memberFxn) \
    Add(id,SCRIPT_MSGMAP_ENTRY(id,SSig_Param2_n,(SCRIPT_MSG)memberFxn); 
    
    #define ON_SCRIPT_CMD3(id,memberFxn) \
    Add(id,SCRIPT_MSGMAP_ENTRY(id,SSig_Param3_n,(SCRIPT_MSG)memberFxn); 
    
    
    /////////////////////
    //Script structures//
    /////////////////////
    
    //Function structure filled out by script class
    
    struct SCRIPTFUNC
    {
      UINT nCommandID;  //Script command ID
      UINT nParam1;
      UINT nParam2;
      UINT nParam3;
      CString strDebugID;
    };
      
    
    //One command from script
    struct ScriptCommand
    {
      bool bExecuted;
      SCRIPTFUNC *pFuncInfo;
      ScriptCommand *pNext;
    };
    
    //One if block from script
    struct ScriptIfBlock
    {
      bool bExecuted;
      ScriptCommand *pCommands;
      ScriptIfBlock *pNestedIfs;
      ScriptIfBlock *pNext;
    };
    
    //One complete event from script
    struct ScriptEvent
    {
      bool bExecuted;
      ScriptIfBlock *pBlocks;
      ScriptEvent *pNestedEvents;
      ScriptEvent *pNext;
    };
    
    //One trigger from script
    struct ScriptTrigger
    {
      bool bExecuted;
      ScriptEvent *pEvents;
      ScriptTrigger *pNestedTriggers;
      ScriptTrigger *pNext;
    };
    
    //One quest from script
    struct ScriptQuest
    {
      ScriptTrigger *pTriggers;
      ScriptQuest *pNestedQuests;
      ScriptQuest *pNext;
    };
    
    //One type of attack from script
    //An attack is a list of commands to be executed
    struct AttackScript
    {
      ScriptEvent *pAttack;
      AttackScript *pNestedAttacks;
      AttackScript *pNext;
    };

    The workings of this little beasty are quite complex but the result is that to respond to a message you do this:

    Code:
    class CMyObject::public CGameObject
    {
      DECLARE_SCRIPT_MSGMAP();
      ....
    };
    Code:
    #include "stdafx.h"
    #include "CMyObject.h"
    
    BEGIN_SCRIPT_MSGMAP(CMyObject,CObject)
      ON_SCRIPT_CMD1(ID_MOVETOWPL,MoveToWpList)
    END_SCRIPT_MSGMAP
    
    ...
    ...
    
    UINT CMyObject::MoveToWpList(UINT nID)
    {
      CWaypointList *pList=GetWayPointList(nID);
    
      D3DXVECTOR2 vecPos2;
      pList->GetPos(0,&vecPos2);
    
      D3DXVECTOR vecDiff2=m_vecPos2-vecPos2;
    
      float fLength=D3DXVec2Length(&vecDiff2);
      
      m_vecNextPos2.x=vecDiff2.x/fLength;
      m_vecNextPos2.y=vecDiff2.y/fLength;
    }

    The main MsgProc of CGameObject calls OnScriptMsg if the msgID is indeed an object message. If not, some other processing is done to determine if it's a button, engine command, etc, etc. OnScriptMsg() finds the handler in the std::map when given the ID of the command. If a handler exists, the message is processed. If not, control is passed to the default MsgProc which calls the default OnScriptMsg() to handle the message. If OnScriptMsg() does not have a handler for the message, the message is ignored.

    So far the setup seems to work just fine. Also note that if a handler does not exist, we can easily trap for this and write the information to a file for easy script debugging. The file could read something like this:

    00:00:23 - No handler for object "Human1" at 125,200 for aMoveToWPL().

    We would like comments on the system so we can see all angles because it's easy to miss something along the way. If you can see any major pitfalls, let us know.
    Last edited by VirtualAce; 09-10-2006 at 07:08 PM.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Script in games
    By Shakti in forum Game Programming
    Replies: 7
    Last Post: 09-27-2006, 12:27 AM
  2. In a game Engine...
    By Shamino in forum Game Programming
    Replies: 28
    Last Post: 02-19-2006, 11:30 AM
  3. how to implementate a registration script
    By TJa in forum C++ Programming
    Replies: 0
    Last Post: 10-28-2005, 02:33 AM
  4. Passing arguments to script.....
    By suwie in forum C Programming
    Replies: 5
    Last Post: 09-25-2004, 11:10 PM
  5. Game structure, any thoughts?
    By Vorok in forum Game Programming
    Replies: 2
    Last Post: 06-07-2003, 01:47 PM