|
是电影的剧本,负责安排场景、灯光、人员以及台词,而 C/C++ 是真正演戏的演员,要拍出好电影还是得靠他们的表现。以这个角度思考,的确是一个很不错的比喻。
一般来说,如何恰当的使用脚本在游戏中的应用,我们必须遵守这样一个原则:感知和行为在代码中完成(需编译),逻辑和参数的配置在脚本中完成(无需编译)。
Lua 的能力十分强大,似乎无所不能,可以完成任何的任务与功能。但是就实际应用层面的考量来说,哪些功能与系统才是真正适合使用 Lua 的部分呢?又要如何区分 Java语言与 Lua 语言所能达成的任务呢?
以两者擅长处理的事项来分类:
- Java:低阶核心、记忆体管理、资源管理、绘图底层、数学运算、讯息发送(也可以说是游戏引擎的功能)。
- Lua:使用者介面、人工智慧、资料管理、游戏逻辑、动态行为。
复杂的运算与低阶的底层核心,适合使用 Java;而因应设计需求,经常会变动的各种功能系统,例如使用者介面、人工智慧、游戏逻辑等等就适合使用 Lua 来完成。另外,有安全性考量的功能层面,也应该使用 Java撰写比较合适。
1.server端用Lua主要是处理AI,一些算法在代码中实现,然后把这些注册入lua脚本中以供调用,在lua脚本中,再对这些进行封装,当然,是包含具体数据的.
关卡设置:
以场景编辑为例,举个简单的应用示范 Lua 其中一种使用方式:程式开发者需要先在 C++ 端撰写好 Lua 端程式码可唿叫使用的函式,然后在 Lua 中就可以自由唿叫使用。首先在 C++ 端的主程式中写好: // 在场景上增加一个物件
static int AddObject(lua_State* _pkState)
{
string kObjectType;
// 取出 传来的第一个参数
g_Manager->GetValueAt(1, kObjectType);
if (kObjectType == "monster") {
// 取出后续的参数进行处理
}
else if (kObjectType == "light") {
// 取出后续的参数进行处理
}
// 把物件交由场景相关的介面处理
g_SceneManager->AddObject(kObjectType);
return 0;
}
以上这样的函式称为 Glue ,负责把 C++ 与 Lua 两端胶黏起来以便于相互沟通。把这个函式註册好之后,在 Lua 端里就可以这样使用: -- 这是 Lua 中的註解
-- 增加一只怪物在 (10, 20, 5) 的位置
AddObject("monster", 10, 20, 5)
-- 增加一个白色灯光在 (0, 50, 0) 的位置
AddObject("light", 0, 50, 0, 255, 255, 255)
-- 增加一个宝箱在 (-51, 512, 3) 的位置
AddObject("treasure", -51, 512, 3)
对于场景上各种物件的增加与删减,从此不用透过 C++ 端程式码的修改,能够直接利用 Lua 端的 做出更加动态与弹性的关卡设计。
2.client端用Lua主要是处理图形,一般是在lua脚本中注册入一些游戏引擎的函数,比如图像处理,然后在lua脚本中,调用实现。
比如,在java game engine中有如下几个方法:
int GetRandInRange ( int Min, int Max ) This returns a random integer value between Min and Max, inclusive. void ToggleRoomLights () Calling this will toggle the lights in the room, from either light to dark or dark to light. You’ll make use of this in the ambience .
void MoveEnemyDroid ( int DroidIndex, int Dir, int Dist ) Calling this will move the specified index in the specified direction with the specified distance. int GetEnemyDroidX ( int DroidIndex ) int GetEnemyDroidY ( int DroidIndex ) These s are used together to get the X, Y location of an enemy droid. int IsEnemyDroidAlive ( int DroidIndex ) This returns TRUE if the specified droid is alive, and FALSE otherwise. void FireEnemyDroidGun ( int DroidIndex ) Calling this causes the specified droid to fire its laser cannon in whatever direction it happens to be facing.
注册这些函数入Lua,
XS_RegisterHostAPIFunc ( XS_GLOBAL_FUNC, "GetRandInRange", HAPI_GetRandInRange ); XS_RegisterHostAPIFunc ( XS_GLOBAL_FUNC, "ToggleRoomLights", HAPI_ToggleRoomLights ); XS_RegisterHostAPIFunc ( XS_GLOBAL_FUNC, "MoveEnemyDroid", HAPI_MoveEnemyDroid ); XS_RegisterHostAPIFunc ( XS_GLOBAL_FUNC, "GetEnemyDroidX", HAPI_GetEnemyDroidX ); XS_RegisterHostAPIFunc ( XS_GLOBAL_FUNC, "GetEnemyDroidY", HAPI_GetEnemyDroidY ); XS_RegisterHostAPIFunc ( XS_GLOBAL_FUNC, "IsEnemyDroidAlive", HAPI_IsEnemyDroidAlive ); XS_RegisterHostAPIFunc ( XS_GLOBAL_FUNC, "FireEnemyDroidGun", HAPI_FireEnemyDroidGun );
在lua中进行二次封装
// ---- Host API Import -------------- host GetRandInRange (); host MoveEnemyDroid (); host GetEnemyDroidX (); host GetEnemyDroidY (); host GetEnemyDroidDir (); host IsEnemyDroidAlive (); host FireEnemyDroidGun (); host GetPlayerDroidX (); host GetPlayerDroidY (); host GetPlayerDroidDir ();
; ---- Directives ----------------------------------------------------------- SetPriority 20 ; ---- Main ----------------------------------------------------------------- Func _Main { ; Enter the main loop LoopStart: ; Get a random number between 0 and 50, inclusive Push 0 Push 50 CallHost GetRandInRange ; If the number was 1, flicker the lights JNE _RetVal, 1, SkipToggleLights CallHost ToggleRoomLights SkipToggleLights: Jmp LoopStart }
根据各个优先级,在游戏中载入脚本:
XS_Load ( "s/Ambient.xse", g_iAmbientThreadIndex, XS_THREAD_PRIORITY_USER ); XS_Load ( "s/Blue_Droid.xse", g_iBlueDroidThreadIndex, XS_THREAD_PRIORITY_USER ); XS_Load ( "s/Grey_Droid.xse", g_iGreyDroidThreadIndex, XS_THREAD_PRIORITY_USER ); XS_Load ( "s/Red_Droid.xse", g_iRedDroidThreadIndex, XS_THREAD_PRIORITY_USER );
在不同的关卡启动或关闭一些脚本:
switch ( iType ) { case ROOM_TYPE_NORMAL: XS_Start ( g_iBlueDroidThreadIndex ); XS_Stop ( g_iGreyDroidThreadIndex ); XS_Stop ( g_iRedDroidThreadIndex ); break; case ROOM_TYPE_GUARD: XS_Stop ( g_iBlueDroidThreadIndex ); XS_Start ( g_iGreyDroidThreadIndex ); XS_Stop ( g_iRedDroidThreadIndex ); break; case ROOM_TYPE_PEDESTAL: XS_Stop ( g_iBlueDroidThreadIndex ); XS_Stop ( g_iGreyDroidThreadIndex ); XS_Start ( g_iRedDroidThreadIndex ); break; }
具体的如何用lua呼叫图形游戏引擎呢?

Java端实做出一组完整的 UI Widget 类别,然后再将这组 Widget 的所有函式、甚至所有类别,註册给 Lua 端自行呼叫使用。
使用上述的架构,在 C++ 端只需要实现唯一一个类别:GuiManager,做为 Facade 介面与游戏引擎的其他系统沟通。然后在游戏主迴圈的更新程序中,唿叫 GuiManager::Update() 函式进行 GUI 系统相关的更新程序;而在游戏主迴圈的绘图程序中,唿叫 GuiManager::Render() 函式将控制权递交给 Lua 端程式,以进行 GUI 系统的绘图流程。其他与 GUI 系统相关的操作,例如键盘事件与滑鼠事件,同样是在移动滑鼠或按下键盘按键时,唿叫相对应的函式,并传入滑鼠的座标或是按下的按键,以供 Lua 端程式进行判断处理。
将整个 GUI 系统建立完成后,更进一步的功能加强与改进,可以考虑使用多执行绪模式,使 GUI 系统在游戏主迴圈外独自拥有一个执行绪的资源。这样就能够减少游戏程式的反应时间,即使是在进行漫长的 I/O 程序或复杂的绘图运算时,玩家也能够继续操作部分的 GUI 行为,而不会使游戏程式显得好像完全失去反应作用与回应能力一样
---------------------
PS:
1.脚本文件可编译成字节码,这样执行的速度会快一些。
2.脚本文件可以加密,这样就可以避免被人盗读。
数据驱动。
3.脚本载入在不同的thread,这样可以避免绘制图形时程序的假死
XS_initial()
xs_load()
xs_Run()
推荐一本好书:
《Game Development With LUA》
API的使用大致就这些:
lua_dofile:执行lua的文件,然后就可以通过C++去使用lua中的了 lua_register: 把C++函数注册到lua中给lua使用 lua_getglobal: 把lua中的函数注册给C++使用 lua_pushnumber, lua_pushstring: 把数据压栈 lua_pop: 弹栈(突然想到,还没封装了,哈哈,赶快加上) lua_optstring: 读命令行中的string lua_optnumber: 读命令行中的number lua_call: 调用lua函数
可以让robot用户在用lua写
|