在Unity中使用Lua脚本:语言层和游戏逻辑粘合层处理

来源:互联网 发布:淘宝客开通收费标准 编辑:程序博客网 时间:2024/05/22 23:37

http://blog.csdn.net/zhaoguanghui2012/article/details/46916705


点击打开链接

前言:为什么要用Lua

首先要说,所有编程语言里面,我最喜欢的还是C#,VisualStudio+C#,只能说太舒服了。所以说,为什么非要在Unity里面用Lua呢?可能主要是闲的蛋疼。。。。。另外还有一些次要原因:
  • 方便做功能的热更新;
  • Lua语言的深度和广度都不大,易学易用,可以降低项目成本。

C#与Lua互相调用的方案

坦白来将,我并没有对现在C#与Lua互相调用的所有库进行一个仔细的调研,大概搜了一下,找到这样几个:
  1. slua:https://github.com/pangweiwei/slua
  2. Nlua:http://nlua.org/
  3. UniLua:https://github.com/xebecnan/UniLua
  4. uLua插件
以上这些方案的具体内容,不是本文的重点,这里就不说了,感兴趣的同学,点开自己去看就行了。

最后我选用了uLua,主要原因是:uLua方案比较成熟,它并没有太多自己的代码,主要是把LuaInterface和Lua解释器整合了一下,都是比较成熟的代码,相对会稳定一些。另外,个人很欣赏LuaInterface这个库。接下来我们就看一下uLua。:)

uLua的基本使用

uLua插件的使用非常简单,基本上看一下他自带的几个例子就明白了。

游戏逻辑粘合层设计

uLua插件解决了语言层面的问题:C#与LUA两种语言代码互相调用,以及参数传递等相关的一系列底层问题。而我们游戏逻辑开发中,到底如何使用LUA是上层的一个问题。下面给出我摸索的一个方案,个人认为:够简单,够清晰,是很薄很薄的一层,不可能更薄了。

使用几个LuaState?

曾经看过一个网友的方案,每次运行脚本就new一个LuaState,个人认为这种方案十分不妥。整个游戏的Lua代码应该运行在一个LuaState之上,原因有二:
  1. 运行在同一LuaState的Lua代码才能互相调用啊。相信一个游戏总会有一定的代码量的,如果不同的lua文件之中的代码,完全独立运行,不能互相调用或者互相调用很麻烦,则游戏逻辑组织平添很多障碍;
  2. 混合语言编程中原则之一就是:尽量减少代码执行的语言环境切换,因为这个的代价往往比代码字面上看上去要高很多。我的目标是:既然用了Lua,就尽量把UI事件响应等游戏上层逻辑放到Lua代码中编写。
基于以上原因,我觉得游戏的Lua代码全都跑在一个LuaState之上。这也是本文方案的基础。

实现LuaComponent

首先说一下我的目标:
  • 既然C#对于Unity来说是脚本层了,那么Lua应该和C#脚本代码具有相同的逻辑地位;
  • Lua整合的代码应该很少,应尽量保持简单;
基于以上的目标,我实现了LuaComponet类,它的实现类似MonoBehavior,只不过我们没有C++源代码,只能由C#层的MonoBehavior来转发一下调用。这样,我们的Lua代码的实现方式就是写和写一个C#脚本组件完全一致了,可以说达到了和引擎天衣无缝的整合。:)OK,先上代码!
[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. using UnityEngine;  
  2. using System.Collections;  
  3. using LuaInterface;  
  4.   
  5. /// <summary>  
  6. /// Lua组件 - 它调用的Lua脚本可以实现类似MonoBehaviour派生类的功能  
  7. /// </summary>  
  8. [AddComponentMenu("Lua/LuaComponent")]  
  9. public class LuaComponent : MonoBehaviour  
  10. {  
  11.     private static LuaState s_luaState; // 全局的Lua虚拟机  
  12.   
  13.     [Tooltip("绑定的LUA脚本路径")]  
  14.     public TextAsset m_luaScript;  
  15.   
  16.     public LuaTable LuaModule  
  17.     {  
  18.         get;  
  19.         private set;  
  20.     }  
  21.     LuaFunction m_luaUpdate;    // Lua实现的Update函数,可能为null  
  22.   
  23.     /// <summary>  
  24.     /// 找到游戏对象上绑定的LUA组件(Module对象)  
  25.     /// </summary>  
  26.     public static LuaTable GetLuaComponent(GameObject go)  
  27.     {  
  28.         LuaComponent luaComp = go.GetComponent<luacomponent>();  
  29.         if (luaComp == null)  
  30.             return null;  
  31.         return luaComp.LuaModule;  
  32.     }  
  33.   
  34.     /// <summary>  
  35.     /// 向一个GameObject添加一个LUA组件  
  36.     /// </summary>  
  37.     public static LuaTable AddLuaComponent(GameObject go, TextAsset luaFile)  
  38.     {  
  39.         LuaComponent luaComp = go.AddComponent<luacomponent>();  
  40.         luaComp.Initilize(luaFile);  // 手动调用脚本运行,以取得LuaTable返回值  
  41.         return luaComp.LuaModule;  
  42.     }  
  43.   
  44.     /// <summary>  
  45.     /// 提供给外部手动执行LUA脚本的接口  
  46.     /// </summary>  
  47.     public void Initilize(TextAsset luaFile)  
  48.     {  
  49.         m_luaScript = luaFile;  
  50.         RunLuaFile(luaFile);  
  51.   
  52.         //-- 取得常用的函数回调  
  53.         if (this.LuaModule != null)  
  54.         {  
  55.             m_luaUpdate = this.LuaModule["Update"as LuaFunction;  
  56.         }  
  57.     }  
  58.   
  59.     /// <summary>  
  60.     /// 调用Lua虚拟机,执行一个脚本文件  
  61.     /// </summary>  
  62.     void RunLuaFile(TextAsset luaFile)  
  63.     {  
  64.         if (luaFile == null || string.IsNullOrEmpty(luaFile.text))  
  65.             return;  
  66.   
  67.         if (s_luaState == null)  
  68.             s_luaState = new LuaState();  
  69.   
  70.         object[] luaRet = s_luaState.DoString(luaFile.text, luaFile.name, null);  
  71.         if (luaRet != null && luaRet.Length >= 1)  
  72.         {  
  73.             // 约定:第一个返回的Table对象作为Lua模块  
  74.             this.LuaModule = luaRet[0] as LuaTable;  
  75.         }  
  76.         else  
  77.         {  
  78.             Debug.LogError("Lua脚本没有返回Table对象:" + luaFile.name);  
  79.         }  
  80.     }  
  81.   
  82.     // MonoBehaviour callback  
  83.     void Awake()  
  84.     {  
  85.         RunLuaFile(m_luaScript);  
  86.         CallLuaFunction("Awake"this.LuaModule, this.gameObject);  
  87.     }  
  88.   
  89.     // MonoBehaviour callback  
  90.     void Start()  
  91.     {  
  92.         CallLuaFunction("Start"this.LuaModule, this.gameObject);  
  93.     }  
  94.   
  95.     // MonoBehaviour callback  
  96.     void Update()  
  97.     {  
  98.         if (m_luaUpdate != null)  
  99.             m_luaUpdate.Call(this.LuaModule, this.gameObject);  
  100.     }  
  101.   
  102.     /// <summary>  
  103.     /// 调用一个Lua组件中的函数  
  104.     /// </summary>  
  105.     void CallLuaFunction(string funcName, params object[] args)  
  106.     {  
  107.         if (this.LuaModule == null)  
  108.             return;  
  109.   
  110.         LuaFunction func = this.LuaModule[funcName] as LuaFunction;  
  111.         if (func != null)  
  112.             func.Call(args);  
  113.     }  
  114. }  
  115.   
  116.   
  117. </luacomponent></luacomponent>  

这段代码非常简单,实现以下几个功能点:
  • 管理一个全局的LuaState;
  • 负责将MonoBehavior的调用转发到相应的LUA函数;
  • 提供了GetComponent()、AddComponent()对应的LUA脚本版本接口;这点非常重要。

LUA代码约定

为了很好的和LuaComponent协作,Lua脚本需要遵循一些约定:
  • LUA脚本应该返回一个Table,可以是LUA的Module,也可以是任何的Table对象;
  • 返回的Table对象应该含有MonoBehaviour相应的回调函数;
例如:
[plain] view plaincopy在CODE上查看代码片派生到我的代码片
  1. require "EngineMain"  
  2.   
  3. local demoComponent = {}  
  4.   
  5. function demoComponent:Awake( gameObject )  
  6.     Debug.Log(gameObject.name.."Awake")  
  7. end  
  8.   
  9. return demoComponent  
LuaComponent回调函数中,主动将GameObject对象作为参数传递给Lua层,以方便其进行相应的处理。

Lua组件之间的互相调用(在Lua代码中)

基于以上结构,就很容易实现Lua组件之间的互相调用。在Demo工程中,有一个“Sphere”对象,绑定了如下脚本:

[plain] view plaincopy在CODE上查看代码片派生到我的代码片
  1. require "EngineMain"  
  2.   
  3. local sphereComponent = {}  
  4.   
  5. sphereComponent.text = "Hello World"  
  6.   
  7. function sphereComponent:Awake( gameObject )  
  8.     Debug.Log(gameObject.name.."Awake")  
  9. end  
  10.   
  11. return sphereComponent  
还有另外一个“Cube”对象,绑定了如下脚本,用来演示调用上面这个Lua组件的成员:

[plain] view plaincopy在CODE上查看代码片派生到我的代码片
  1. require "EngineMain"  
  2.   
  3. local demoComponent = {}  
  4.   
  5. function demoComponent:Awake( gameObject )  
  6.     Debug.Log(gameObject.name.."Awake")  
  7. end  
  8.   
  9. function demoComponent:Start( gameObject )  
  10.     Debug.Log(gameObject.name.."Start")  
  11.   
  12.     --演示LuaComponent代码互相调用  
  13.     local sphereGO = GameObject.Find("Sphere")  
  14.     local sphereLuaComp = LuaComponent.GetLuaComponent(sphereGO)  
  15.     Debug.log("Sphere.LuaDemoB:"..sphereLuaComp.text)  
  16.   
  17. end  
  18.   
  19. return demoComponent  


完整版DEMO下载地址:

百度网盘链接: http://pan.baidu.com/s/1nt1eGPV 密码: 3g7b


最后,顺带总结一下:在设计上次游戏逻辑框架时,比较好的思路是:在透彻的理解Unity自身架构的前提下,在其架构下进行下一层设计,而不是想一种新的框架。因为Unity本身就是一个框架。
0 0
原创粉丝点击