找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 8857|回复: 6

Lua与C++的逻辑舞步

[复制链接]
发表于 2011-6-24 11:33:21 | 显示全部楼层 |阅读模式
以前写过一篇Lua的基础文章,属于基础的技术普及贴,具体请参阅精华区。以下的文章,希望你能稍微了解一些Lua的基础知识,再来阅读更有益处。
在网上,很多人都在说Lua适合做游戏,但是怎么做?说的却知之甚少,更多的是长篇累牍的基础贴。脚本与C++的结合,如何能达到较高的效率?什么样的数据适合Lua去表现?什么样的数据适合C++去处理?怎么利用两者的关系?在这里我给出一种解决方法。
很多人说,你可以把C++的对象封装到Lua里面去。在Lua里面提供对C++类操作的各种方法。这样的手段见仁见智,但是我个人却并不赞同,原因是,Lua是一个面向过程的语言,它和python,js是完全不同的,就像C和C++,为什么要把面向过程和面向过程完全分开,是因为它们服务于不同的需求,看过一些朋友把C++的对象模式做了一个Lua的粘合层,很是感觉有些繁复。在我看来,面向过程就要体现出面向过程的精华,不要把过多对象的概念引入其中,这样,代码简洁,易于维护管理,同时也能发挥各自最高的价值。C++是面向对象的,就让它去处理它擅长的对象,Lua是面向过程的,那么我就让lua去处理它擅长的过程组织,两个互不干扰,这样,双方组合后的能量才能真正爆发出来。

还是那句话,在我看来,代码只是一种表现形式,更重要的是思想,如何思考问题的方法。如果你是Lua初学者,我希望更多的是去学会如何思考问题,并将问题化解到你的思维模式中,形成流。这样,你才不会过分的依赖语言本身,毕竟,基础的语法在谷歌上我们还是能大量获得的。但是好的想法,却需要日积月累的总结。
先来讲个故事,我以前做游戏,经常碰到这样的策划:
"你来帮我实现这么一个东东,玩家到一个NPC那里,NPC展现出一个列表,上面写着若干任务描述,玩家选择一个,然后找到另外一个NPC或者到野外打够任务需要的素材和条件,回来交给这个NPC,完成任务。","玩家找到一个NPC,直接展开一个副本,里面有很多怪物啊关卡啊等等,玩家进去就可以乒乒乓乓。做完任务就可以退出来,并获得奖励。"
这样的需求我在游戏开发中会经常遇到,有时候我很纠结,为什么这么说?因为更多的时候,策划是不会特别考虑细节的。而程序实现的时候,往往面对的是非常细节的东西。好不容易实现了策划的想法,保不齐哪天策划要变化,你难道再跟着策划改一遍吗?要知道,就算你写程序再牛,你也赶不上策划天马行空的速度。在"我觉得应该是这样..."这样的语句面前,你的任何技术积淀都可能瞬间化为浮云。东西做不出来,策划肯定认为是你的能力不行,而程序呢,认为策划太BT。最后双方变成了敌人,在很多游戏团队中都存在这样的问题。
那么能不能有一种模式,策划策划的过程,让他们自己去实现?而且可以满足他们自己去不断纠正,同时不影响我们的程序实现?方法是有的,就是让策划写一个剧本,这个剧本可以加载到程序里面去执行。不过,不要指望策划会用C++,这是不现实的。你可以把他们想象成一个厨师,你是一个配料师,按照厨师的要求,你给他准备好各种配料,至于做什么菜,那是厨师的事情了。对了,这就是脚本语言带给策划和程序开发模式的改变。也正是源于此,我们需要找到一种能够清晰的描述事件过程的语言,来配合我C++为厨师们准备灶台。炒菜的东西我给你准备好了,都是按照厨师要求的,如果厨师做不出来,那么是厨师自己的手艺问题了。如果配料你做不出来,那么就是你配料师水平不行。责任明确,问题容易找。
那么好了,我们先以lua来说,怎么做这样的架构?
首先,既然我们是配料师,那么一定要提供一系列的配料才行。我再强调一次,对于面向过程的语言,尽量不要扯入对象的概念。那么好,看看我们手里有什么材料?
玩家的数据资料,比如:血量,名称,身上背负的任务编号,身上的装备,材料,格子。。。。等等等等。
我们要做的是,给策划提供一系列的方法,让他们可以随意修改这个值。这是第一步。光有这个还是不够的,我们还要提供一些事件的触发,可以让某些事件发生的时候,调用到策划所写的脚本里面去。完成这两项,我们作为配料师的责任就完成了。简单吧。
策划呢,拿到这些材料,来组织你的故事吧。当然,你得会基本的lua语法,lua本身并不复杂,很适合轻松上手。首先,你需要处理程序给你的事件,并决定你对这些事件的反应。然后,组织你的数据过程,完成你对数据流动的预想。
好了,说道理简单,让我们看看如何去做这样的架构吧,用代码来实现一个高弹性,可扩展的逻辑舞步,嘿嘿,很兴奋吧。
我先建立一个简单的CPlayer对象,姑且这就是最简单的玩家属性吧,当然,你可以扩展之,我只是展现这样的思维流程。这里包含了一个对所有CPlayer的数据管理对象。
  1. #include <string>
  2. #include <map>
  3. using namespace std;
  4. class CPlayer
  5. {
  6. public:
  7.         CPlayer(void) {};
  8.         ~CPlayer(void) {};
  9. //创建一个玩家信息
  10.         void CreatePlayer(int nPlayerID, string strPlayerName)
  11.         {
  12.                 m_nPlayerID      = nPlayerID;                   //玩家的ID
  13.                 m_nMessionID     = 0;                           //玩家的任务
  14.                 m_nItemID        = 0;                                //玩家所持有的物品ID
  15.                 m_strPlayerName  = strPlayerName;     //玩家的昵称
  16.                 m_strMessionDesc = "";                     //任务的描述
  17.         };
  18.         int GetPlayerID()
  19.         {
  20.                 return m_nPlayerID;
  21.         };
  22.         bool SetMessionID(int nMessionID)
  23.         {
  24.                 if(m_nMessionID != 0)
  25.                 {
  26.                         return false;
  27.                 }
  28.                 m_nMessionID = nMessionID;
  29.                 return true;
  30.         };
  31.         int GetMessionID()
  32.         {
  33.                 return m_nMessionID;
  34.         }
  35.         void FinishMessionID()
  36.         {
  37.                 m_nMessionID = 0;
  38.         };
  39.         void SetMessionDesc(const char* pDesc)
  40.         {
  41.                 m_strMessionDesc = pDesc;
  42.         };
  43.         void SetItemID(int nItemID)
  44.         {
  45.                 m_nItemID = nItemID;
  46.         };
  47.         int GetItemID()
  48.         {
  49.                 return m_nItemID;
  50.         }
  51. private:
  52.         int    m_nPlayerID;        //玩家ID
  53.         string m_strPlayerName;    //玩家姓名
  54.         int    m_nMessionID;       //玩家任务ID
  55.         string m_strMessionDesc;   //玩家任务描述
  56.         int    m_nItemID;          //玩家所持物品ID
  57. };
  58. class CPlayerManager
  59. {
  60. private:
  61.         CPlayerManager() {};
  62. public:
  63.         static CPlayerManager& Instance()
  64.         {
  65.                 if(m_pPlayerManager == NULL)
  66.                 {
  67.                         m_pPlayerManager = new CPlayerManager();
  68.                 }
  69.                 return *m_pPlayerManager;
  70.         };
  71.         ~CPlayerManager()
  72.         {
  73.                 Close();
  74.         };
  75.         bool AddPlayer(CPlayer* pPlayer)
  76.         {
  77.                 if(NULL == pPlayer)
  78.                 {
  79.                         return false;
  80.                 }
  81.                 mapPlayerManager::iterator f = m_mapPlayerManager.find(pPlayer->GetPlayerID());
  82.                 if(f != m_mapPlayerManager.end())
  83.                 {
  84.                         return false;
  85.                 }
  86.                 m_mapPlayerManager.insert(mapPlayerManager::value_type(pPlayer->GetPlayerID(), pPlayer));
  87.                 return true;
  88.         };
  89.         CPlayer* GetPlayer(int nPlayerID)
  90.         {
  91.                 mapPlayerManager::iterator f = m_mapPlayerManager.find(nPlayerID);
  92.                 if(f != m_mapPlayerManager.end())
  93.                 {
  94.                         return (CPlayer* )f->second;
  95.                 }
  96.                 else
  97.                 {
  98.                         return NULL;
  99.                 }
  100.         };
  101.         void Close()
  102.         {
  103.                 mapPlayerManager::iterator b = m_mapPlayerManager.begin();
  104.                 mapPlayerManager::iterator e = m_mapPlayerManager.end();
  105.                 for(b; b != e; b++)
  106.                 {
  107.                         CPlayer* pPlayer = (CPlayer* )b->second;
  108.                         if(NULL == pPlayer)
  109.                         {
  110.                                 delete pPlayer;
  111.                                 pPlayer = NULL;
  112.                         }
  113.                 }
  114.         };
  115. private:
  116.         typedef map<int, CPlayer*> mapPlayerManager;
  117.         mapPlayerManager m_mapPlayerManager;
  118.         static CPlayerManager* m_pPlayerManager;
  119. };
复制代码

呵呵,这个类很简单,里面包含了一些玩家的基本信息,并提供了一个CPlayerManager()的管理类,管理所有已经注册的CPlayer对象,这些CPlayer对象提供给我了修改玩家种种属性的方法。
好了,我还需要一个让Lua能够方便调用我的C++这些玩家的修改方法的类。来完成和lua的互联互通。这里,既然Lua是面向过程的语言,那么我就让我的方法全部是面向过程的。
  1. #ifndef LUAFNREGISTER_H
  2. #define LUAFNREGISTER_H
  3. //lua下的C++注册类,负责处理lua中用到的C++函数
  4. //add by freeeyes
  5. #include "LuaDefine.h"
  6. #include <vector>
  7. #include <string>
  8. using namespace std;
  9. typedef int (*FuncExecute)(lua_State* plua_State);
  10. struct _FuncInfo
  11. {
  12.         string      m_strFuncName;
  13.         FuncExecute m_FuncExecute;
  14. };
  15. //这个类负责管理所有注册的lua对C++函数类
  16. class CLuaFnRegister
  17. {
  18. public:
  19.         CLuaFnRegister() {};
  20.         ~CLuaFnRegister() {};
  21.         void Close()
  22.         {
  23.                 m_vecLuaFnRegCollection.clear();
  24.         };
  25.         bool AddFunc(const char* pFuncName, FuncExecute pFn)
  26.         {
  27.                 _FuncInfo objFuncInfo;
  28.                 objFuncInfo.m_strFuncName = pFuncName;
  29.                 objFuncInfo.m_FuncExecute = pFn;
  30.                
  31.                 m_vecLuaFnRegCollection.push_back(objFuncInfo);
  32.                 return true;
  33.         };
  34.         int GetSize()
  35.         {
  36.                 return (int)m_vecLuaFnRegCollection.size();
  37.         };
  38.         _FuncInfo* GetFuncInfo(int nIndex)
  39.         {
  40.                 if(nIndex >= (int)m_vecLuaFnRegCollection.size())
  41.                 {
  42.                         return NULL;
  43.                 }
  44.                 else
  45.                 {
  46.                         return (_FuncInfo* )&m_vecLuaFnRegCollection[nIndex];
  47.                 }
  48.         };
  49. private:
  50.         typedef vector<_FuncInfo> vecLuaFnRegCollection;
  51.         vecLuaFnRegCollection m_vecLuaFnRegCollection;
  52. };
  53. #endif
复制代码

首先,我用一个类,来管理所有我的修改方法在lua中的注册。一个函数名外加一个函数指针,就完成了一个注册。这个类用于管理所有已知我需要让lua调用我的方法。
好了,再来看看,如果策划给我们一个任务的需求,我们需要给予策划什么样的接口呢?
  1. #ifndef _LUAFN_MESSION_H
  2. #define _LUAFN_MESSION_H
  3. #include "LuaDefine.h"
  4. #include "Player.h"
  5. //执行远程函数
  6. static int LuaExecute_Func(lua_State* pState, const char* pFuncName, int nPlayerID)
  7. {
  8.         lua_getglobal(pState, pFuncName);
  9.         lua_pushnumber(pState, nPlayerID);
  10.         int nRet = lua_pcall(pState, 1, 0, 0);
  11.         if (nRet != 0)
  12.         {
  13.                 printf("[LuaExecute_Func]call function(%s) error(%s).\n", pFuncName, lua_tostring(pState, -1));
  14.                 return false;
  15.         }
  16.         lua_settop(pState, -1);
  17.         return 0;
  18. }
  19. //输出文字
  20. static int LuaFn_Mession_Print(lua_State* pState)
  21. {
  22.         char* pGlobal = (char* )lua_tostring(pState, -1);
  23.         printf_s("[LuaFn_Mession_Print]Data=%s.\n", pGlobal);
  24.         return 0;
  25. }
  26. //输入描述信息
  27. static int Lua_Mession_Desc(lua_State* pState)
  28. {
  29.         int nPlayerID  = (int)lua_tonumber(pState, -2);
  30.         char* pDesc    = (char* )lua_tostring(pState, -1);
  31.         //printf_s("[Lua_Mession_Desc]nPlayerID=%d, pDesc=%s.\n", nPlayerID, pDesc);
  32.         CPlayer* pPlayer = CPlayerManager::Instance().GetPlayer(nPlayerID);
  33.         if(NULL == pPlayer)
  34.         {
  35.                 lua_pushboolean(pState, (int)false);
  36.         }
  37.         else
  38.         {
  39.                 pPlayer->SetMessionDesc(pDesc);
  40.                 lua_pushboolean(pState, (int)true);
  41.         }
  42.         return 1;
  43. }
  44. //输入描述信息
  45. static int Lua_Mession_Error(lua_State* pState)
  46. {
  47.         int nPlayerID  = (int)lua_tonumber(pState, -2);
  48.         char* pDesc    = (char* )lua_tostring(pState, -1);
  49.         CPlayer* pPlayer = CPlayerManager::Instance().GetPlayer(nPlayerID);
  50.         if(NULL == pPlayer)
  51.         {
  52.                 lua_pushboolean(pState, (int)false);
  53.         }
  54.         else
  55.         {
  56.                 pPlayer->SetMessionDesc(pDesc);
  57.                 lua_pushboolean(pState, (int)true);
  58.         }
  59.         return 1;
  60. }
  61. //当前用户是否存在任务,如果有则返回False
  62. static int Lua_Mession_IsHave(lua_State* pState)
  63. {
  64.         int nPlayerID  = (int)lua_tonumber(pState, -1);
  65.         CPlayer* pPlayer = CPlayerManager::Instance().GetPlayer(nPlayerID);
  66.         if(NULL == pPlayer)
  67.         {
  68.                 lua_pushboolean(pState, (int)false);
  69.         }
  70.         else
  71.         {
  72.                 if(pPlayer->GetMessionID() != 0)
  73.                 {
  74.                         lua_pushboolean(pState, (int)true);
  75.                 }
  76.                 else
  77.                 {
  78.                         lua_pushboolean(pState, (int)false);
  79.                 }
  80.         }
  81.         return 1;
  82. }
  83. //得到用户任务ID
  84. static int Lua_Mession_GetID(lua_State* pState)
  85. {
  86.         int nPlayerID  = (int)lua_tonumber(pState, -1);
  87.         CPlayer* pPlayer = CPlayerManager::Instance().GetPlayer(nPlayerID);
  88.         if(NULL == pPlayer)
  89.         {
  90.                 lua_pushnumber(pState, 0);
  91.         }
  92.         else
  93.         {
  94.                 int nMessionID = pPlayer->GetMessionID();
  95.                 lua_pushnumber(pState, nMessionID);
  96.         }
  97.         return 1;
  98. }
  99. //得到用户任务ID
  100. static int Lua_Mession_SetID(lua_State* pState)
  101. {
  102.         int nPlayerID  = (int)lua_tonumber(pState, -2);
  103.         int nMessionID = (int)lua_tonumber(pState, -1);
  104.         CPlayer* pPlayer = CPlayerManager::Instance().GetPlayer(nPlayerID);
  105.         if(NULL != pPlayer)
  106.         {
  107.                 pPlayer->SetMessionID(nMessionID);
  108.         }
  109.         return 0;
  110. }
  111. //给与玩家道具ID
  112. static int Lua_Mession_SetItemID(lua_State* pState)
  113. {
  114.         int nPlayerID  = (int)lua_tonumber(pState, -3);
  115.         int nMessionID = (int)lua_tonumber(pState, -2);
  116.         int nItemID    = (int)lua_tonumber(pState, -1);
  117.         CPlayer* pPlayer = CPlayerManager::Instance().GetPlayer(nPlayerID);
  118.         if(NULL != pPlayer)
  119.         {
  120.                 pPlayer->SetItemID(nItemID);
  121.         }
  122.         return 0;
  123. }
  124. //得到玩家道具ID
  125. static int Lua_Mession_GetItemID(lua_State* pState)
  126. {
  127.         int nPlayerID  = (int)lua_tonumber(pState, -2);
  128.         int nMessionID = (int)lua_tonumber(pState, -1);
  129.         CPlayer* pPlayer = CPlayerManager::Instance().GetPlayer(nPlayerID);
  130.         if(NULL != pPlayer)
  131.         {
  132.                 int nItemID = pPlayer->GetItemID();
  133.                 lua_pushnumber(pState, nItemID);
  134.         }
  135.         else
  136.         {
  137.                 lua_pushnumber(pState, 0);
  138.         }
  139.         return 1;
  140. }
  141. //完成任务
  142. static int Lua_Mession_Done(lua_State* pState)
  143. {
  144.         int nPlayerID  = (int)lua_tonumber(pState, -2);
  145.         int nMessionID = (int)lua_tonumber(pState, -1);
  146.         CPlayer* pPlayer = CPlayerManager::Instance().GetPlayer(nPlayerID);
  147.         if(NULL != pPlayer)
  148.         {
  149.                 pPlayer->FinishMessionID();
  150.         }
  151.         return 0;
  152. }
  153. //调用远程事件处理函数
  154. static int Lua_Mession_Events(lua_State* pState)
  155. {
  156.         int nPlayerID  = (int)lua_tonumber(pState, -2);
  157.         int nMessionID = (int)lua_tonumber(pState, -1);
  158.         //lua_pop(pState, -1);
  159.         //调用远程接口
  160.         char szFuncName[100] = {'\0'};
  161.         sprintf(szFuncName, "Event_%d_Request", nMessionID);
  162.         LuaExecute_Func(pState, szFuncName, nPlayerID);
  163.         return 0;
  164. }
  165. #endif
复制代码

好了,我提供了一批方法给策划,这就是作料,如果策划觉得不够,我可以随意添加。这里你可以看到,我所有传给lua的方法,都是以基础类型为参数的,避免了设计中的类传递,这也是我抵触把类给Lua的实现,因为在实现细节上,任何修改都应该可以转变成对基础类型的修改。所以,你没有必要把类给策划,让他自己去找繁复的接口。你给他们的,都是清晰可见的,而且是独立的。这样,减少了策划误用错用你的接口的可能性。
还记得我在Lua基础贴中的那个CLuaFn类吗?如果你读了我以前的Lua的文章,对它应该不陌生,好的,那么让我扩展一下它吧。让它支持我所需要的接口。
  1. bool CLuaFn::InitClass()
  2. {
  3.         if(NULL == m_pState)
  4.         {
  5.                 printf("[CLuaFn::InitClass]m_pState is NULL.\n");
  6.                 return false;
  7.         }
  8.         tolua_open(m_pState);
  9.         tolua_module(m_pState, NULL, 0);
  10.         tolua_beginmodule(m_pState, NULL);
  11.         m_LuaFnRegister.AddFunc("LuaFn_Mession_Print", LuaFn_Mession_Print);
  12.         m_LuaFnRegister.AddFunc("Lua_Mession_Error", Lua_Mession_Error);
  13.         m_LuaFnRegister.AddFunc("Lua_Mession_Desc", Lua_Mession_Desc);
  14.         m_LuaFnRegister.AddFunc("Lua_Mession_IsHave", Lua_Mession_IsHave);
  15.         m_LuaFnRegister.AddFunc("Lua_Mession_GetID", Lua_Mession_GetID);
  16.         m_LuaFnRegister.AddFunc("Lua_Mession_SetID", Lua_Mession_SetID);
  17.         m_LuaFnRegister.AddFunc("Lua_Mession_SetItemID", Lua_Mession_SetItemID);
  18.         m_LuaFnRegister.AddFunc("Lua_Mession_GetItemID", Lua_Mession_GetItemID);
  19.         m_LuaFnRegister.AddFunc("Lua_Mession_Done", Lua_Mession_Done);
  20.         m_LuaFnRegister.AddFunc("Lua_Mession_Events", Lua_Mession_Events);
  21.         for(int i = 0; i < m_LuaFnRegister.GetSize(); i++)
  22.         {
  23.                 _FuncInfo* pFuncInfo = m_LuaFnRegister.GetFuncInfo(i);
  24.                 if(NULL != pFuncInfo)
  25.                 {
  26.                         tolua_function(m_pState, pFuncInfo->m_strFuncName.c_str(), pFuncInfo->m_FuncExecute);
  27.                 }
  28.         }
  29.         tolua_endmodule(m_pState);
  30.         return true;
  31. }
复制代码

呵呵,看到了吧,一点也不复杂。
好了,让我们来看看策划要做什么。
策划需要实现两个lua。
第一个NPC_freeeyes.lua
  1. --NPC的ID,在程序里面对应NPC
  2. NPC_1001_NAME="自由之眼"
  3. function NPC_1001_OnDefault(nMapID, nPlayerID)
  4.     blMessionState = Lua_Mession_IsHave(nPlayerID)
  5.     if blMessionState == true then
  6.         --如果任务有,则判断是否需要完成
  7.         nMessionID = Lua_Mession_GetID(nMapID, nPlayerID)
  8.         NPC_1001_OnSubmit(nMapID, nPlayerID, nMessionID)
  9.     else
  10.         Lua_Mession_SetID(nPlayerID, 200001)
  11.         Lua_Mession_Desc(nPlayerID, "请选择你要接受的任务信息,1是要土豆,2是要黄铜。")
  12.     end
  13. end
  14. function NPC_1001_OnEvents(nMapID, nPlayerID)
  15.     nMessionID = Lua_Mession_GetID(nMapID, nPlayerID)
  16.     --print("[NPC_1001_OnEvents]nMessionID="..nMessionID)
  17.     if nMessionID ~= 0 then
  18.         Lua_Mession_Events(nPlayerID, nMessionID)
  19.     end
  20. end
  21. function NPC_1001_OnSubmit(nMapID, nPlayerID, nMessionID)
  22.                 nItemID = Lua_Mession_GetItemID(nPlayerID, nMessionID)
  23.                 if nMessionID == 200001 then
  24.                                 if nItemID == 300 then
  25.                                                 Lua_Mession_Done(nPlayerID, nMessionID)
  26.                         --LuaFn_Mession_Print("200001 完成了。")
  27.                                 end
  28.                 end
  29.                 if nMessionID == 200002 then
  30.                                 if nItemID == 301 then
  31.                                                 Lua_Mession_Done(nPlayerID, nMessionID)
  32.                         --LuaFn_Mession_Print("200002 完成了。")
  33.                                 end
  34.                 end
  35. end
复制代码

呵呵,看看,我把自己做成了一个NPC,当然,如果你愿意,你可以建立无数个这样的NPC在这个游戏中。这里面提供了两个最简单的方法,当玩家碰触这个NPC,我们调用NPC_1001_OnDefault()方法,这里会验证玩家身上有没有任务,有,则会调用是否完成的函数。我准备了两个任务,200001和200002
好了,那么,我们再看看第二个lua完成了什么?
  1. function Event_200001_Request(nPlayerID)   
  2.     --LuaFn_Mession_Print("200001 给你一个土豆。")
  3.     Lua_Mession_SetItemID(nPlayerID, 200001, 300)
  4. end
  5. function Event_200002_Request(nPlayerID)
  6.     --LuaFn_Mession_Print("200002 给你一个黄铜。")
  7.     Lua_Mession_SetItemID(nPlayerID, 200002, 301)
  8. end
复制代码

我们姑且命名为Mession_Event_200001.lua(其实这个名字并不是很合适,呵呵,例子嘛)
为什么这么设计,因为我需要策划也需要建立一个完整的思维体系。任务的创建和任务的接收,是在很多的lua文件中完成的,一个任务体系一个lua,这样很清晰。关于任务中条件的达成,都是各个Event的lua去做的。这样,你可以让策划写起来很舒服,而且从程序的角度来讲,也会很舒服。事件的触发过程,由策划在另一个lua里面完成。
比如,我创建一个获取黄铜的任务,我在Event被触发的时候,就给他一个黄铜。具体可以参考我的源代码。
这样,一个基于过程的逻辑实现体系就完成了,当然,这是一个很简单的。那么如何在游戏中和事件绑定呢?这又是另一个话题了,等我有时间好好的讲讲,如何在服务器上控制事件的触发,这些事件会根据条件,调用不同的lua形成结果。
最后,作为程序员的我,自然要对这个架构测试一下性能,看看能否满足我的要求,于是,我让一个玩家接受100万次任务,获得道具,交还任务。看看花费了多少时间。
结果如下: (windowsXP,CPUE5300,2G内存的的条件下),运行时间是13.3秒。也就是每秒可以执行7.5万次任务交接,获得道具和任务完成的流程。呵呵,这个速度,我还是比较满意的。
以下代码在windowsXP和VS2005下测试通过。


如果你喜欢或者对你有帮助,我很高兴,当然,也欢迎你多多提问。
还有,代码永远是基础的,思想才是最有价值的东西。不要把自己的思路固定在代码实现的层面上。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?用户注册

×
发表于 2011-6-27 20:40:36 | 显示全部楼层
针对lua面向对象有些不同的见解:目的之一是让策划写起来容易理解些: '玩家:获得任务(任务id)'  要比 '获得任务(玩家id,任务id) ' 亲民一些 ;
目的之二是让程序扩展接口容易些:
class_add<CUnit>(LuaState, "UNIT");
class_def<CUnit>(LuaState, "getID", &CUnit::getID);
简单2句就把 CUnit 类和 getID 的方法给注册到 lua了,相比AddFunc和实现一个functor应该还是容易不少
 楼主| 发表于 2011-6-28 13:27:02 | 显示全部楼层
恩,我觉得各有各的好处。我说的还是一些偏颇了,不过我个人依旧倾向面向过程的方法。
我们在做网游开发的时候,有时候很难让策划理解类结构的概念,于是改变方法,告诉它那些接口可以用,传入几个数,出来几个数。这样后来在设计几百个脚本的时候,出错的概率较低。
发表于 2011-7-11 00:05:26 | 显示全部楼层
楼主说的跟我现在做的差不多,  npc上的显示的文字分两种,一种是可以点击的,一种是不可以点击的,可以点击的那种,脚本对它进行编号,客户端点击时,通知服务器点的是哪个编号的话就可以了,脚本中种的函数根据传过来的编号就可以调相应的事件函数, 其实想如何在游戏中用脚本, 天龙八部的代码中有详细的实现, 比楼主的详细多了, 而且天龙八部的代码网上到处都有下....
发表于 2011-7-11 00:07:53 | 显示全部楼层
客户端用lua可以实现对UI的定制操作和代码与数据的分离
发表于 2011-7-11 00:17:20 | 显示全部楼层
本帖最后由 iiyyp 于 2011-7-11 00:21 编辑



以下两个脚本一个是npc脚本,另一个是任务脚本, 来自天龙八部
npc脚本
  1. --阮诚
  2. --脚本号
  3. x020000_g_scriptId = 020000
  4. --所拥有的事件ID列表
  5. x020000_g_eventList={211105,211106,211108}       
  6. --**********************************
  7. --事件列表
  8. --**********************************
  9. function x020000_UpdateEventList( sceneId, selfId,targetId )
  10.         BeginEvent(sceneId)
  11.         local  PlayerName=GetName(sceneId,selfId)
  12.         AddText(sceneId,"  你好,你也是宋人吧。#r#r  你见过我的母亲和弟弟了?#r#r  你见过郡主了?#r#r  呵呵,看来你已经成为这片草原最受欢迎的客人了。进到毡帐里暖和一下吧,我想母亲和弟弟也会希望盛情款待你的。#r")
  13.         for i, eventId in x020000_g_eventList do
  14.                 CallScriptFunction( eventId, "OnEnumerate",sceneId, selfId, targetId )
  15.                
  16.         end
  17.         EndEvent(sceneId)
  18.         DispatchEventList(sceneId,selfId,targetId)
  19. end
  20. --**********************************
  21. --事件交互入口
  22. --**********************************
  23. function x020000_OnDefaultEvent( sceneId, selfId,targetId )
  24.         x020000_UpdateEventList( sceneId, selfId, targetId )
  25. end
  26. --**********************************
  27. --事件列表选中一项
  28. --**********************************
  29. function x020000_OnEventRequest( sceneId, selfId, targetId, eventId )
  30.         for i, findId in x020000_g_eventList do
  31.                 if eventId == findId then
  32.                         CallScriptFunction( eventId, "OnDefaultEvent",sceneId, selfId, targetId )
  33.                         return
  34.                 end
  35.         end
  36. end
  37. --**********************************
  38. --接受此NPC的任务
  39. --**********************************
  40. function x020000_OnMissionAccept( sceneId, selfId, targetId, missionScriptId )
  41.         for i, findId in x020000_g_eventList do
  42.                 if missionScriptId == findId then
  43.                         ret = CallScriptFunction( missionScriptId, "CheckAccept", sceneId, selfId )
  44.                         if ret > 0 then
  45.                                 CallScriptFunction( missionScriptId, "OnAccept", sceneId, selfId )
  46.                         end
  47.                         return
  48.                 end
  49.         end
  50. end
  51. --**********************************
  52. --拒绝此NPC的任务
  53. --**********************************
  54. function x020000_OnMissionRefuse( sceneId, selfId, targetId, missionScriptId )
  55.         --拒绝之后,要返回NPC的事件列表
  56.         for i, findId in x020000_g_eventList do
  57.                 if missionScriptId == findId then
  58.                         x020000_UpdateEventList( sceneId, selfId, targetId )
  59.                         return
  60.                 end
  61.         end
  62. end
  63. --**********************************
  64. --继续(已经接了任务)
  65. --**********************************
  66. function x020000_OnMissionContinue( sceneId, selfId, targetId, missionScriptId )
  67.         for i, findId in x020000_g_eventList do
  68.                 if missionScriptId == findId then
  69.                         CallScriptFunction( missionScriptId, "OnContinue", sceneId, selfId, targetId )
  70.                         return
  71.                 end
  72.         end
  73. end
  74. --**********************************
  75. --提交已做完的任务
  76. --**********************************
  77. function x020000_OnMissionSubmit( sceneId, selfId, targetId, missionScriptId, selectRadioId )
  78.         for i, findId in x020000_g_eventList do
  79.                 if missionScriptId == findId then
  80.                         CallScriptFunction( missionScriptId, "OnSubmit", sceneId, selfId, targetId, selectRadioId )
  81.                         return
  82.                 end
  83.         end
  84. end
复制代码
任务脚本
  1. --草原狼的逆袭
  2. --MisDescBegin
  3. --脚本号
  4. x211100_g_ScriptId = 211100
  5. --上一个任务的ID
  6. --g_MissionIdPre =
  7. --任务号
  8. x211100_g_MissionId = 540
  9. --任务目标npc
  10. x211100_g_Name        ="阮实"
  11. --任务归类
  12. x211100_g_MissionKind = 31
  13. --任务等级
  14. x211100_g_MissionLevel = 50
  15. --是否是精英任务
  16. x211100_g_IfMissionElite = 0
  17. --下面几项是动态显示的内容,用于在任务列表中动态显示任务情况**********************
  18. --任务是否已经完成
  19. x211100_g_IsMissionOkFail = 0                --变量的第0位
  20. --任务需要杀死的怪
  21. x211100_g_DemandKill = {{id=1810,num=5}}                --变量第1位
  22. --以上是动态**************************************************************
  23. --任务文本描述
  24. x211100_g_MissionName="草原狼的逆袭"
  25. x211100_g_MissionInfo="俺们兰陵郡是辽国运送粮草的必经之地,现在路上多出好多草原狼来,得有人杀点狼俺们才敢出去运草料。"  --任务描述
  26. x211100_g_MissionTarget="杀死5只草原狼"                --任务目标
  27. x211100_g_ContinueInfo="你已经杀了5只草原狼吗?它们就在村子南边,走走就看见了。小心点啊,被一群狼围攻了那你就白瞎了。"                --未完成任务的npc对话
  28. x211100_g_MissionComplete="谢谢啊,俺们终于敢出门了"                                        --完成任务npc说话的话
  29. --任务奖励
  30. x211100_g_MoneyBonus=100
  31. x211100_g_ItemBonus={{id=30002002,num=1}}
  32. x211100_g_RadioItemBonus={{id=10100001,num=1},{id=10210001,num=1}}
  33. x211100_g_DemandTrueKill = {{name="草原狼",num=5}}       
  34. --MisDescEnd
  35. --**********************************
  36. --任务入口函数
  37. --**********************************
  38. function x211100_OnDefaultEvent( sceneId, selfId, targetId )        --点击该任务后执行此脚本
  39.         --如果玩家完成过这个任务(实际上如果完成了任务这里就不会显示,但是再检测一次比较安全)
  40.     --if IsMissionHaveDone(sceneId,selfId,x211100_g_MissionId) > 0 then
  41.         --        return
  42.         --end
  43.         --如果已接此任务
  44.         if IsHaveMission(sceneId,selfId,x211100_g_MissionId) > 0 then
  45.                 --发送任务需求的信息
  46.                 BeginEvent(sceneId)
  47.                         AddText(sceneId,x211100_g_MissionName)
  48.                         AddText(sceneId,x211100_g_ContinueInfo)
  49.                         --for i, item in g_DemandItem do
  50.                         --        AddItemDemand( sceneId, item.id, item.num )
  51.                         --end
  52.                 EndEvent( )
  53.                 bDone = x211100_CheckSubmit( sceneId, selfId )
  54.                 DispatchMissionDemandInfo(sceneId,selfId,targetId,x211100_g_ScriptId,x211100_g_MissionId,bDone)
  55.         --满足任务接收条件
  56.         elseif x211100_CheckAccept(sceneId,selfId) > 0 then
  57.                 --发送任务接受时显示的信息
  58.                 BeginEvent(sceneId)
  59.                         AddText(sceneId,x211100_g_MissionName)
  60.                         AddText(sceneId,x211100_g_MissionInfo)
  61.                         AddText(sceneId,"#{M_MUBIAO}")
  62.                         AddText(sceneId,x211100_g_MissionTarget)
  63.                         AddMoneyBonus( sceneId, x211100_g_MoneyBonus )
  64.                         for i, item in x211100_g_ItemBonus do
  65.                                 AddItemBonus( sceneId, item.id, item.num )
  66.                         end
  67.                         for i, item in x211100_g_RadioItemBonus do
  68.                                 AddRadioItemBonus( sceneId, item.id, item.num )
  69.                         end
  70.                 EndEvent( )
  71.                 DispatchMissionInfo(sceneId,selfId,targetId,x211100_g_ScriptId,x211100_g_MissionId)
  72.         end
  73. end
  74. --**********************************
  75. --列举事件
  76. --**********************************
  77. function x211100_OnEnumerate( sceneId, selfId, targetId )
  78.     --如果玩家完成过这个任务
  79.     if IsMissionHaveDone(sceneId,selfId,x211100_g_MissionId) > 0 then
  80.             return
  81.         end
  82.     --如果已接此任务
  83.     --else
  84.     if IsHaveMission(sceneId,selfId,x211100_g_MissionId) > 0 then
  85.                 AddNumText(sceneId,x211100_g_ScriptId,x211100_g_MissionName);
  86.     --满足任务接收条件
  87.     elseif x211100_CheckAccept(sceneId,selfId) > 0 then
  88.                 AddNumText(sceneId,x211100_g_ScriptId,x211100_g_MissionName);
  89.     end
  90. end
  91. --**********************************
  92. --检测接受条件
  93. --**********************************
  94. function x211100_CheckAccept( sceneId, selfId )
  95.         --需要2级才能接
  96.         if GetLevel( sceneId, selfId ) >= 1 then
  97.                 return 1
  98.         else
  99.                 return 0
  100.         end
  101. end
  102. --**********************************
  103. --接受
  104. --**********************************
  105. function x211100_OnAccept( sceneId, selfId )
  106.         --加入任务到玩家列表
  107.         AddMission( sceneId,selfId, x211100_g_MissionId, x211100_g_ScriptId, 1, 0, 0 )                --添加任务
  108.         misIndex = GetMissionIndexByID(sceneId,selfId,x211100_g_MissionId)                        --得到任务的序列号
  109.         SetMissionByIndex(sceneId,selfId,misIndex,0,0)                                                --根据序列号把任务变量的第0位置0
  110.         SetMissionByIndex(sceneId,selfId,misIndex,1,0)                                                --根据序列号把任务变量的第1位置0
  111. end
  112. --**********************************
  113. --放弃
  114. --**********************************
  115. function x211100_OnAbandon( sceneId, selfId )
  116.         --删除玩家任务列表中对应的任务
  117.     DelMission( sceneId, selfId, x211100_g_MissionId )
  118. end
  119. --**********************************
  120. --继续
  121. --**********************************
  122. function x211100_OnContinue( sceneId, selfId, targetId )
  123.         --提交任务时的说明信息
  124.     BeginEvent(sceneId)
  125.                 AddText(sceneId,x211100_g_MissionName)
  126.                 AddText(sceneId,x211100_g_MissionComplete)
  127.                 AddMoneyBonus( sceneId, x211100_g_MoneyBonus )
  128.                 for i, item in x211100_g_ItemBonus do
  129.                         AddItemBonus( sceneId, item.id, item.num )
  130.                 end
  131.                 for i, item in x211100_g_RadioItemBonus do
  132.                         AddRadioItemBonus( sceneId, item.id, item.num )
  133.                 end
  134.     EndEvent( )
  135.     DispatchMissionContinueInfo(sceneId,selfId,targetId,x211100_g_ScriptId,x211100_g_MissionId)
  136. end
  137. --**********************************
  138. --检测是否可以提交
  139. --**********************************
  140. function x211100_CheckSubmit( sceneId, selfId )
  141.         misIndex = GetMissionIndexByID(sceneId,selfId,x211100_g_MissionId)
  142.     num = GetMissionParam(sceneId,selfId,misIndex,1)
  143.     if num == x211100_g_DemandTrueKill[1].num then
  144.        return 1
  145.     end
  146.         return 0
  147. end
  148. --**********************************
  149. --提交
  150. --**********************************
  151. function x211100_OnSubmit( sceneId, selfId, targetId,selectRadioId )
  152.         if x211100_CheckSubmit( sceneId, selfId, selectRadioId ) then
  153.             BeginAddItem(sceneId)
  154.                         for i, item in x211100_g_ItemBonus do
  155.                                 AddItem( sceneId,item.id, item.num )
  156.                         end
  157.                        
  158.                         for i, item in x211100_g_RadioItemBonus do
  159.                                 if item.id == selectRadioId then
  160.                                         AddItem( sceneId,item.id, item.num )
  161.                                 end
  162.                         end
  163.                 ret = EndAddItem(sceneId,selfId)
  164.                 --添加任务奖励
  165.                 if ret > 0 then
  166.                         AddMoney(sceneId,selfId,x211100_g_MoneyBonus );
  167.                         --扣除任务物品
  168.                         --for i, item in g_DemandItem do
  169.                         --        DelItem( sceneId, selfId, item.id, item.num )
  170.                         --end
  171.                         ret = DelMission( sceneId, selfId, x211100_g_MissionId )
  172.                         if ret > 0 then
  173.                                 MissionCom( sceneId,selfId, x211100_g_MissionId )
  174.                                 AddItemListToHuman(sceneId,selfId)
  175.                         end
  176.                 else
  177.                 --任务奖励没有加成功
  178.                         BeginEvent(sceneId)
  179.                                 strText = "背包已满,无法完成任务"
  180.                                 AddText(sceneId,strText);
  181.                         EndEvent(sceneId)
  182.                         DispatchMissionTips(sceneId,selfId)
  183.                 end      
  184.         end
  185. end
  186. --**********************************
  187. --杀死怪物或玩家
  188. --**********************************
  189. function x211100_OnKillObject( sceneId, selfId, objdataId )
  190.         if GetName(sceneId,objId) == x211100_g_DemandTrueKill[1].name          then
  191.                 misIndex = GetMissionIndexByID(sceneId,selfId,x211100_g_MissionId)
  192.                 num = GetMissionParam(sceneId,selfId,misIndex,1)
  193.                         if num < x211100_g_DemandTrueKill[1].num then
  194.                         --把任务完成标志设置为1
  195.                         if num == x211100_g_DemandTrueKill[1].num - 1 then
  196.                                 SetMissionByIndex(sceneId,selfId,misIndex,0,1)
  197.                         end
  198.                         --设置打怪数量+1
  199.                         SetMissionByIndex(sceneId,selfId,misIndex,1,num+1)
  200.                           BeginEvent(sceneId)
  201.                                   strText = format("已杀死草原狼 %d/5", GetMissionParam(sceneId,selfId,misIndex,1) )
  202.                                   AddText(sceneId,strText);
  203.                           EndEvent(sceneId)
  204.                         DispatchMissionTips(sceneId,selfId)
  205.                 end
  206.         end
  207. end
  208. --**********************************
  209. --进入区域事件
  210. --**********************************
  211. function x211100_OnEnterArea( sceneId, selfId, zoneId )
  212. end
  213. --**********************************
  214. --道具改变
  215. --**********************************
  216. function x211100_OnItemChanged( sceneId, selfId, itemdataId )
  217. end
  218. --**********************************
  219. --死亡事件
  220. --**********************************
  221. function x020000_OnDie( sceneId, selfId, killerId )
  222. end
复制代码

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?用户注册

×
 楼主| 发表于 2011-7-12 09:14:50 | 显示全部楼层
呵呵,是的,天龙八部的代码写的确是不错。
不过可惜很多网上的版本并不完整,有部分代码是缺失的,并有部分代码揉进了错误的分支。
天龙的程序就是以过程来做的,这个我以前也写过。不过,天龙服务器代码的精髓在于对场景信息的管理,NPC列表和AI的控制,并且在NPC的继承接口中提供了ExecuteScript()的接口,方便程序从外部调用事件,不过,对于NPC的AI,天龙八部并没有采用Lua的支持,这是非常正确的,因为对于非常频繁调用的事件,脚本支持虽然快速但是还不及C++编译,所以天龙采用了一种折衷的方法,就是写一套AI脚本解析器,用于解析简单的AI命令,并在C++中映射成条件树,根据条件树来完成NPC对某些事件所做的更改,不过这样的改进可以说还是有利有弊,条件树增加了代码的复杂度,并且依然会有些性能问题,不过最新版本的天龙已经改进了这部分。
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )

GMT+8, 2024-5-6 00:39 , Processed in 0.044837 second(s), 6 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表