3.5Lua虚拟机
本项目的游戏流程逻辑实现使用lua
语言编写,使用xLua来实现lua和C#原生代码之间的相互调用。
游戏流程逻辑指如剧情、触发器管理、过场演出、调用战斗等等功能。
使用lua的好处在于不需要懂复杂的语法和项目的底层知识,即可对游戏进行开发和修改。面向游戏策划
友好。
本项目所有的lua文件提取自DOS版《金庸群侠传》,区别在于原版使用字节码的方式,使用由网友godka提供的工具自动转义为含逻辑的lua文件
lua调用游戏中的内容,均通过C#提供指令函数
来实现。一个典型的lua实现样例(jyx2/data/lua/jygame/ka11.lua)如下:
Talk(1, "有什么要我帮忙的,尽管说出来.", "talkname1", 0);
if AskJoin () == true then goto label0 end;
do return end;
::label0::
Talk(0, "胡大哥肯随我闯荡江湖否?", "talkname0", 1);
if TeamIsFull() == false then goto label1 end;
Talk(1, "你的队伍已满,我无法加入.", "talkname1", 0);
do return end;
::label1::
Talk(1, "好.我就随你一走.", "talkname1", 0);
Talk(0, "胡大哥肯随我闯荡江湖帮这个忙,那再好也不过了.", "talkname0", 1);
DarkScence();
ModifyEvent(-2, -2, 0, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2);
jyx2_ReplaceSceneObject("","NPC/胡斐","");
LightScence();
Join(1);
AddEthics(1);
do return end;
具体使用方法已经在 这里说明,在此不再赘述。
所有指令均在 Jyx2LuaBridge.cs中定义,每个static函数为一个指令。
xlua的机制保证在editor模式下如果没有生成Lua Wrap,则用反射的方式调用。如果有生成Lua Wrap,则用原生代码调用的方式。
Lua虚拟机在项目第一个场景启动前被初始化:BeforeSceneLoad.ColdBind()
中的LuaManager.Init()
方法
此初始化将载入main.lua文件(在lua虚拟机中注册所有的C#函数编写的指令)
在游戏过程中,所有的调用均通过LuaExecutor.cs
的ExecuteLua()
函数调用。
public static void ExecuteLua(string luaContent, Action callback = null, string path = "")
{
//BY CG:JYX2的特殊情况,有空文件
if (luaContent.Equals("do return end;"))
{
//Debug.Log("识别到空的lua文件,直接跳过:" + path);
callback?.Invoke();
return;
}
var luaEnv = LuaManager.GetLuaEnv() as LuaEnv;
Debug.Log("执行lua: " + path);
_executing = true;
Loom.RunAsync(() =>
{
try
{
luaEnv.DoString(luaContent);
Debug.Log("lua执行完毕: " + path);
}
catch (Exception e)
{
Debug.LogError("lua执行错误:" + e.ToString());
Debug.LogError(e.StackTrace);
}
currentLuaContext = null;
_executing = false;
if (callback != null)
{
Loom.QueueOnMainThread(_ => { callback(); }, null);
}
});
}
注意:
- 这里每次调用都使用了一个异步方法
Loom.RunAsync
来让lua执行在线程池里 - 使用
Loom.QueueOnMainThread
来将执行完毕的回调在UI线程(主线程)运行
这样做的原因是;我们在执行lua的过程中,可能会反复调用进行UI展示(比如呼出对话框、选择框等),而lua逻辑实际是需要阻塞在“呼出”逻辑的那一行。
所以需要有一个线程将lua的执行“挂起”。
而lua执行完毕的回调需要在主线程的原因是,后续的C#代码可能需要立刻访问UI元素,在Unity的体系下,如果不在主线程访问UI元素则会报错。
为了在C#实现的lua指令中实现“挂起”的功能,我们需要用到多线程编程的信号量
的概念。一个典型的例子如下
static public void ShowEthics()
{
RunInMainThrad(() => {
MessageBox.Create("你的道德指数为" + runtime.Player.Pinde, Next);
});
Wait();
}
这里我们封装了Wait()
和Next()
方法,可以进一步查看其实现:
static Semaphore sema = new Semaphore(0, 1);
static private void Wait()
{
sema.WaitOne();
}
static private void Next()
{
sema.Release();
}
补充说明:xlua的特性亦提供util.async_to_sync的方法,来将一个带回调的C#函数封装为一个可以同步方式来调用的lua函数。
如果有兴趣可以自行调整代码。