游戏服务器之lua脚本系统

本文介绍了游戏服务器中lua脚本系统的设计与实现,包括脚本系统初始化、关闭、调用lua函数、加载数据、lua接口应用实例及调试方法。内容涵盖了lua虚拟机、tolua++库的使用、NPC脚本加载、任务数据配置、复杂对象加载以及c++与lua的交互。
摘要由CSDN通过智能技术生成

现在做游戏的较多用脚本 ,可以热加载代码。

脚本系统的目的是方便c++和lua之间的相互调用,实现c++和lua的 交互。

脚本系统设计:

(1)脚本系统创建时初始化虚拟机和库,析构时销毁。

(2)脚本加载:

主体入口脚本NPCEntry.lua:

含所有npc的脚本的表,含聊天接口函数。选择调用接口控制脚本QC.lua的接口,或者npc脚本的接口。

接口控制脚本QC.lua:

含接任务和交任务的接口(会使用任务表NPCQuest),检查接任务条件,来选择接任务还是返回聊天响应;检查交任务的条件,来选择交任务还是返回聊天信息。

各个npc脚本:

各个npc脚本以npc的id来命名,加载时存储在主体入口脚本的NPCEntry.lua的npc的表。

(3)配置加载:

NPCQuest表是配置在配置表,在脚本系统初始化时热加载

(4)函数调用:

(4-1)c++调用lua:把脚本参数存储到自定义列表,调用lua函数时压入函数和自定义参数到运行时栈。

(4-2)lua调用c++:使用tolua++导出需要调用的类的接口和成员变量。

脚本结构设计:

(1)主体入口文件NPCEntry.lua
(1-1)表npcTable  会按npcId 索引对应的npc lua文件的代码

(1-2)点击npc 函数 clickNPC 会返回对应的该npcId的所有的可接任务和可提交任务组成的字符串

(1-3)与npc聊天函数 talkNPC 会调用接任务或交任务接口,并返回其对话的字符串

(2)任务控制文件QC.lua 含任务接受(或对话接口)、任务提交(或对话)接口。

(3)npc 脚本 npc1.lua (npc2.lua ......). 含指定npcId的默认对话(可接可交任务)组成的字符串的接口。可拓展npc与玩家交互接口。

1、脚本系统定义

为了可以传入自定义类型的变量和原子变量到一个列表,再一次性压栈(其实函数的调用就是压栈弹栈的过程,c++和lua交互的过程也是如此,就是通过栈来实现变量的访问)。

(1)脚本系统的初始化和关闭

脚本系统的析构函数里需要关掉虚拟机

if(m_pLua)
{
lua_close(m_pLua);
m_pLua = NULL;
}

在构造函数里打开虚拟机

m_pLua = lua_open();
luaL_openlibs(m_pLua);

初始化脚本系统:

1)加载npc脚本和npc数据到lua虚拟机

2)把tolua++导出的接口导入到lua虚拟机

bool ScriptSystemLoad::init()
{
	if (!initNPCScript())//加载npc脚本和npc数据到lua虚拟机
	{
		printf("initNPCScript error\n");
		return false;
	}
	bool res = loadLuaServerInterface();//加载tolua++导出的接口到lua虚拟机
	if (!res)
	{
		printf("loadLuaServerInterface error\n");
		return false;
	}
	
	return true;
}

 

(2)调用脚本函数

步骤如下:

1)调用函数需要传入函数指针和参数,先压入需要调用的函数,然后是压入各个参数。最终执行是使用交互栈底的lua函数,而参数则是函数以上的指定个数的交互栈数据。

2)返回函数的执行结果,在交互栈的栈顶(结果可能多个)。

3)  调用过后就需要清栈。这是个编程习惯也是符合语言设计理念的(想下c++函数调用也是遵守函数调用约定,调用过后就清交互栈的)。


c++调用lua函数使用到的lua c的api 是:

LUA_API int   (lua_pcall) (lua_State *L, int nargs, int nresults, int errfunc);(参数:lua 虚拟机对象  函数参数个数  返回值个数 错误处理函数)

执行lua函数调用(参数:函数名、需要压入的参数、需要返回的结果)

bool ScriptSystem::exec(const char* fname, const ScriptValueList* args, ScriptValueList *results )
{
//清除堆栈(获取堆栈的数据个数,弹出堆栈的数据)
#define cleanStack()  do {\
				int stackNum = lua_gettop(m_pLua);\
				if (stackNum)\
				{\
					lua_pop(m_pLua,stackNum);\
				}\
		}while(0)

	int i;
	int args_count = 0;
	if (args)args_count = args->count;
	
	lua_getglobal(m_pLua,fname);//获取需要调用的lua函数

	//压入参数列表
	for (i = 0;i < args_count;i++)
	{
		ScriptValue scriptValue = args->values[i];//根据不同参数类型来压入参数
		if (ScriptValue::vNumber == scriptValue.type)
		{
			lua_pushnumber(m_pLua,scriptValue.data.d);
		}
		else if (ScriptValue::vInterger == scriptValue.type)
		{
			lua_pushinteger(m_pLua,scriptValue.data.i);
		}
		else if (ScriptValue::vString == scriptValue.type)
		{
			lua_pushstring(m_pLua,scriptValue.data.str);
		}
		else if (ScriptValue::vBool == scriptValue.type)
		{
			lua_pushboolean(m_pLua,scriptValue.data.i);
		}
		else if (ScriptValue::vPointer == scriptValue.type)
		{
			lua_pushlightuserdata(m_pLua,scriptValue.data.ptr);
		}
		else if (ScriptValue::vBaseObject == scriptValue.type)
		{
			tolua_pushusertype(m_pLua,scriptValue.data.ptr,"CBaseObject");
		}
		else if (ScriptValue::vEntity == scriptValue.type)
		{
			tolua_pushusertype(m_pLua,scriptValue.data.ptr,"CEntity");
		}
		else if (ScriptValue::vActor == scriptValue.type)
		{
			tolua_pushusertype(m_pLua,scriptValue.data.ptr,"CDoer");
		}
		else if (ScriptValue::vPlayer == scriptValue.type)
		{
			tolua_pushusertype(m_pLua,scriptValue.data.ptr,"CPlayer");
		}
		else
		{
			lua_pushnil(m_pLua);//压入空指针
		}
	}
	
	if (!results)//不需要返回值
	{
		int err = lua_pcall(m_pLua,args_count,0,0);//调用lua函数
		if (err)
		{
			const char* result = lua_tostring(m_pLua, -1);
			logError("Script Err:lua_pcall result:%s,fname %s\n",result,fname);
			cleanStack();
			return false;
		}
	}
	else//需要一个返回值
	{
		int err = lua_pcall(m_pLua,args_count,1,0);
		const char* result = lua_tostring(m_pLua, -1);//取出栈顶的执行结果
		if (err)//lua函数调用错误
		{
			logError("Script Err:lua_pcall result:%s,fname %s\n",result,fname);
			cleanStack();
			return false;
		}
		if (result)
		{
			logDebug("lua_pcall result:%s,fname %s\n",result,fname);
			results->push(result);//加入返回结果
		}
	}
	cleanStack();//清除堆栈
	return true;
}


(3)脚本变量列表

定义一些接口用于传入变量到列表。

在脚本系统执行c++调用lua接口时传入的参数列表和获取返回结果列表。

class ScriptValueList : public CBaseObject
{
public:
	static const int MaxValueCount = 8;//脚本值列表最多值数量
	int count;
	ScriptValue values[MaxValueCount];
	public:
	ScriptValueList(){ count = 0; memset(values, 0, sizeof(values)); }
	~ScriptValueList(){ clear(); }
	//添加一个整数值到列表
	bool push(int v);
	//添加一个浮点值到列表
	bool push(double v);
	//添加一个布尔值到列表
	bool push(bool b);
	//添加一个字符串值到列表
	bool push(const char* str);
	//添加一个指针值到列表
	bool push(void* ptr);
	//添加一个CBaseObject值到列表
	bool push(CBaseObject* ptr);
	//添加一个CEntity值到列表
	bool push(CEntity* ptr);
	//添加一个CDoer值到列表
	bool push(CDoer* ptr);
	//添加一个CPlayer值到列表
	bool push(CPlayer* ptr);
	//添加一个脚本值到列表
	bool push(const ScriptValue &v);
	//清空列表数据
	inline void clear()
	{
		for (int i= count-1; i>-1; --i)
		{
			values[i].clear();
		}
		count = 0;
	}
};

(4)变量

参数列表的需要的自定义的类型变量。

重载等号操作符来对不同类型的赋值到对应的成员中。

class ScriptValue
{
public: 
	//脚本值类型
	enum ValueType
	{
		vNumber = 0,
		vInterger = 1,
		vString = 2,
		vBool = 3,
		vPointer = 4,
		vBaseObject = 5,
		vEntity = 6,
		vActor = 7,
		vPlayer = 8,
	};
	~ScriptValue() { clear(); }
	
	inline void operator = (const int v)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值