编写Lua逻辑

为实现代码热更新,在Unity3D中使用lua,然而为此也需付出不少代价。其一,使代码结构混乱(尽管可以优化),其二降低了运行速度,其三增加学习成本(还要多学一门语言)。为了热更新,所有的逻辑都要用lua编写,那么怎样用lua编写游戏逻辑呢?

一、Lua 的 Update 方法

第一篇“代码热更新”演示了用lua打印HelloWorld的方法,第二篇“资源热更新”演示了加载坦克模型的方法。这一篇要把两者结合起来,用lua实现“用键盘控制坦克移动”的功能。用Lua和用c#编写的Unity3D程序大同小异,只需正确使用API即可。

1)、Update 方法

出于效率的考虑,tolua提供了名为UpdateBeat的对象,在LuaFramework中,只需给UpdateBeat添加回调函数,该函数便会每帧执行,相当于Monobehaviour的Update方法。Lua代码如下所示:

  1. function Main()
  2. UpdateBeat:Add(Update, self)
  3. end
  4. function Update()
  5. LuaFramework.Util.Log("每帧执行一次");
  6. end

除了UpdateBeat,tolua还提供了LateUpdateBeat和FixedUpdateBeat,对应于Monobehaviour中的LateUpdate和FixedUpdate。

2)、控制坦克

现在编写“用键盘控制坦克移动”的lua代码,加载坦克模型后,使用UpdateBeat注册每帧执行的Update方法,然后在Update方法中调用UnityEngine.Input等API实现功能。代码如下:

  1. local go; --加载的坦克模型
  2. --主入口函数。从这里开始lua逻辑
  3. function Main()
  4. LuaHelper = LuaFramework.LuaHelper;
  5. resMgr = LuaHelper.GetResManager();
  6. resMgr:LoadPrefab('tank', { 'TankPrefab' }, OnLoadFinish);
  7. end
  8. --加载完成后的回调--
  9. function OnLoadFinish(objs)
  10. go = UnityEngine.GameObject.Instantiate(objs[0]);
  11. LuaFramework.Util.Log("LoadFinish");
  12. UpdateBeat:Add(Update, self)
  13. end
  14. --每帧执行
  15. function Update()
  16. LuaFramework.Util.Log("每帧执行");
  17. local Input = UnityEngine.Input;
  18. local horizontal = Input.GetAxis("Horizontal");
  19. local verticla = Input.GetAxis("Vertical");
  20. local x = go.transform.position.x + horizontal
  21. local z = go.transform.position.z + verticla
  22. go.transform.position = Vector3.New(x,0,z)
  23. end

运行游戏,即可用键盘的控制坦克移动。

二、自定义API

框架中提供了数十个可供lua调用的c#类,但这些往往不够用,需要自己添加,本节将介绍添加自定义API的方法。

1)、编写C#类

例如,编写TestLuaFun.类,它包含一个静态方法Log,会打印出两行文本。

  1. using UnityEngine;
  2. using System.Collections;
  3. public class TestLuaFun
  4. {
  5. public static void Log()
  6. {
  7. Debug.Log("《Unity3D网络游戏实战》是一本好书!");
  8. Debug.Log("《手把手教你用c#制作rpg游戏》也是一本好书!");
  9. }
  10. }

2)、修改CustomSetting

打开CustomSetting.cs,在customTypeList中添加一句“_GT(typeof(TestLuaFun))”。

  1. //在这里添加你要导出注册到lua的类型列表
  2. public static BindType[] customTypeList =
  3. {
  4. // ...
  5. _GT(typeof(GameManager)),
  6. _GT(typeof(LuaManager)),
  7. _GT(typeof(PanelManager)),
  8. _GT(typeof(SoundManager)),
  9. _GT(typeof(TimerManager)),
  10. _GT(typeof(ThreadManager)),
  11. _GT(typeof(NetworkManager)),
  12. _GT(typeof(ResourceManager)),
  13. // 在这里添加导出注册到lua的类型类标
  14. _GT(typeof(TestLuaFunc)),
  15. }

3)、生成wrap文件

点击菜单栏的Lua→Clear wrap files和Lua→Generate All,重新生成wrap文件。由于刚刚在customTypeList添加了类,所以会生成TestLuaFun类的wrap文件TestLuaFunWrap.cs。

打开TestLuaFunWrap.cs,可以看到TestLuaFun注册了Log方法。

  1. //this source code was auto-generated by tolua#, do not modify it
  2. using System;
  3. using LuaInterface;
  4. public class TestLuaFuncWrap
  5. {
  6. public static void Register(LuaState L)
  7. {
  8. L.BeginClass(typeof(TestLuaFunc), typeof(System.Object));
  9. L.RegFunction("Log", Log);
  10. L.RegFunction("New", _CreateTestLuaFunc);
  11. L.RegFunction("__tostring", ToLua.op_ToString);
  12. L.EndClass();
  13. }
  14. [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
  15. static int _CreateTestLuaFunc(IntPtr L)
  16. {
  17. try
  18. {
  19. int count = LuaDLL.lua_gettop(L);
  20. if (count == 0)
  21. {
  22. TestLuaFunc obj = new TestLuaFunc();
  23. ToLua.PushObject(L, obj);
  24. return 1;
  25. }
  26. else
  27. {
  28. return LuaDLL.luaL_throw(L, "invalid arguments to ctor method: TestLuaFunc.New");
  29. }
  30. }
  31. catch (Exception e)
  32. {
  33. return LuaDLL.toluaL_exception(L, e);
  34. }
  35. }
  36. [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
  37. static int Log(IntPtr L)
  38. {
  39. try
  40. {
  41. ToLua.CheckArgsCount(L, 0);
  42. TestLuaFunc.Log();
  43. return 0;
  44. }
  45. catch (Exception e)
  46. {
  47. return LuaDLL.toluaL_exception(L, e);
  48. }
  49. }
  50. }

4)、测试API

修改main.lua,调用TestLuaFun.Log() ,即可看到效果。

  1. --主入口函数。从这里开始lua逻辑
  2. function Main()
  3. TestLuaFun.Log()
  4. end

三、原理

tolua实现了LuaInterface,抛开luaFramework,只需创建lua虚拟机,便能在c#中调用lua代码,如下所示。

  1. using UnityEngine;
  2. using System.Collections;
  3. using LuaInterface;
  4. public class test : MonoBehaviour
  5. {
  6. void Start ()
  7. {
  8. //初始化
  9. LuaState lua = new LuaState();
  10. LuaBinder.Bind(lua);
  11. //lua代码
  12. string luaStr =
  13. @"
  14. print('hello tolua#, 广告招租')
  15. LuaFramework.Util.Log('HelloWorld');
  16. TestLuaFun.Log()
  17. ";
  18. //执行lua脚本
  19. lua.DoString(luaStr);
  20. }
  21. }

实际上LuaFramework也是用了相似的方法,框架启动后,会创建LuaManager、LuaLooper的实例。LuaManager创建lua虚拟机并调用Main.lua的Main方法,LuaLooper处理了UpdateBeat相关的事情。如下所示:

  1. private LuaState lua;
  2. private LuaLoader loader;
  3. private LuaLooper loop = null;
  4. void Awake() {
  5. loader = new LuaLoader();
  6. lua = new LuaState();
  7. this.OpenLibs();
  8. lua.LuaSetTop(0);
  9. LuaBinder.Bind(lua);
  10. DelegateFactory.Init();
  11. LuaCoroutine.Register(lua, this);
  12. }
  13. public void InitStart() {
  14. InitLuaPath();
  15. InitLuaBundle();
  16. this.lua.Start(); //启动LUAVM
  17. this.StartMain();
  18. this.StartLooper();
  19. }
  20. void StartMain() {
  21. lua.DoFile("Main.lua");
  22. LuaFunction main = lua.GetFunction("Main");
  23. main.Call();
  24. main.Dispose();
  25. main = null;
  26. }
  27. void StartLooper() {
  28. loop = gameObject.AddComponent<LuaLooper>();
  29. loop.luaState = lua;
  30. }

?