Lua 组件

基于组件的编程模式是Unity3D的核心思想之一,然而使用纯lua编程,基本就破坏了这一模式。那么有没有办法做一些封装,让Lua脚本也能挂载到游戏物体上,作为组件呢?

一、设计思想

在需要添加Lua组件的游戏物体上添加一个LuaComponent组件,LuaComponent引用一个lua表,这个lua表包含lua组件的各种属性以及Awake、Start等函数,由LuaComponent适时调用Lua表所包含的函数。

  1. /*
  2. * created by shenjun
  3. */
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using UnityEngine;
  7. using LuaInterface;
  8. using LuaFramework;
  9. public class LuaComponent : MonoBehaviour {
  10. // Lua 表
  11. public LuaTable table;
  12. // 添加Lua组件
  13. public static LuaTable Add(GameObject go, LuaTable tableClass)
  14. {
  15. LuaFunction fun = tableClass.GetLuaFunction("New");
  16. if (fun == null) return null;
  17. object[] rets = fun.LazyCall(tableClass);
  18. if (rets.Length != 1) return null;
  19. LuaComponent cmp = go.AddComponent<LuaComponent>();
  20. cmp.table = (LuaTable)rets[0];
  21. cmp.CallAwake();
  22. return cmp.table;
  23. }
  24. public static LuaTable Get(GameObject go, LuaTable table)
  25. {
  26. LuaComponent[] cmps = go.GetComponents<LuaComponent>();
  27. foreach (LuaComponent cmp in cmps)
  28. {
  29. string mat1 = table.ToString();
  30. string mat2 = cmp.table.GetMetaTable().ToString();
  31. if (mat1 == mat2) return cmp.table;
  32. }
  33. return null;
  34. }
  35. void CallAwake()
  36. {
  37. LuaFunction fun = table.GetLuaFunction("Awake");
  38. if (fun != null)
  39. fun.Call(table, gameObject);
  40. }
  41. void Start () {
  42. LuaFunction fun = table.GetLuaFunction("Start");
  43. if (fun != null)
  44. fun.Call(table, gameObject);
  45. }
  46. void Update () {
  47. // 效率问题有待测试和优化
  48. // 可在lua中调用UpdateBeat替代
  49. LuaFunction fun = table.GetLuaFunction("Update");
  50. if (fun != null)
  51. fun.Call(table, gameObject);
  52. }
  53. }

下面列举lua组件的文件格式,它包含一个表(如Component),这个表包含property1 、property2 等属性,包含Awake、Start等方法。表中必须包含用于派生对象的New方法,它会创建一个继承自Component的表o,供LuaComponent调用。

  1. Component= --组件表
  2. ​{
  3. property1 = 100,
  4. property2 = helloWorld
  5. }
  6. function Component:Awake()
  7. print("TankCmp Awake name = "..self.name );
  8. end
  9. function Component:Start()
  10. print("TankCmp Start name = "..self.name );
  11. End
  12. --更多方法略
  13. function Component:New(obj)
  14. local o = {}
  15. setmetatable(o, self)
  16. self.__index = self
  17. return o
  18. end

二、LuaComponent 组件

LuaComponent主要有Get和Add两个静态方法,其中Get相当于UnityEngine中的GetComponent方法,Add相当于AddComponent方法,只不过这里添加的是lua组件不是c#组件。每个LuaComponent拥有一个LuaTable(lua表)类型的变量table,它既引用上述的Component表。

Add方法使用AddComponent添加LuaComponent,调用参数中lua表的New方法,将其返回的表赋予table。

Get方法使用GetComponents获取游戏对象上的所有LuaComponent(一个游戏对象可能包含多个lua组件,由参数table决定需要获取哪一个),通过元表地址找到对应的LuaComponent,返回lua表。

三、调试LuaComponent

现在编写名为TankCmp的lua组件,测试LuaCompomemt的功能,TankCmp会在Awake、Start和Update打印出属性name。TankCmp.lua的代码如下:

  1. TankCmp =
  2. {
  3. --里面可以放一些属性
  4. Hp = 100,
  5. att = 50,
  6. name = "good tank",
  7. }
  8. function TankCmp:Awake()
  9. print("TankCmp Awake name = "..self.name );
  10. end
  11. function TankCmp:Start()
  12. print("TankCmp Start name = "..self.name );
  13. end
  14. function TankCmp:Update()
  15. print("TankCmp Update name = "..self.name );
  16. end
  17. --创建对象
  18. function TankCmp:New(obj)
  19. local o = {}
  20. setmetatable(o, self)
  21. self.__index = self
  22. return o
  23. end

编写Main.lua,给游戏对象添加lua组件。

  1. require "TankCmp"
  2. --主入口函数。从这里开始lua逻辑
  3. function Main()
  4. --组件1
  5. local go = UnityEngine.GameObject ('go')
  6. local tankCmp1 = LuaComponent.Add(go,TankCmp)
  7. tankCmp1.name = "Tank1"
  8. --组件2
  9. local go2 = UnityEngine.GameObject ('go2')
  10. LuaComponent.Add(go2,TankCmp)
  11. local tankCmp2 = LuaComponent.Get(go2,TankCmp)
  12. tankCmp2.name = "Tank2"
  13. end

运行游戏,即可看到lua组件的运行结果。

四、坦克组件

下面代码演示用lua组件实现“用键盘控制坦克移动”的功能,TankCmp.lua的代码如下:

  1. TankCmp =
  2. {
  3. name = "good tank",
  4. }
  5. function TankCmp:Update(gameObject)
  6. print("TankCmp Update name = "..self.name );
  7. local Input = UnityEngine.Input;
  8. local horizontal = Input.GetAxis("Horizontal");
  9. local vertical = Input.GetAxis("Vertical");
  10. local x = gameObject.transform.position.x + horizontal
  11. local z = gameObject.transform.position.z + vertical
  12. gameObject.transform.position = Vector3.New(x,0,z)
  13. end
  14. --创建对象
  15. function TankCmp:New(obj)
  16. local o = {}
  17. setmetatable(o, self)
  18. self.__index = self
  19. return o
  20. end

Main.lua先加载坦克模型,然后给他添加lua组件,代码如下:

  1. require "TankCmp"
  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. LuaComponent.Add(go,TankCmp)
  12. end

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

?