Unity中的热更新:Lua和C#通信

发表于2017-10-18
评论0 2.1k浏览

想免费获取内部独家PPT资料库?观看行业大牛直播?点击加入腾讯游戏学院游戏程序行业精英群

711501594

热更新是所有做开发都避不开的,为了让不熟悉的开发者了解Unity中的热更新,下面就给大家介绍下Lua和C#通信。

准备:

1、了解Lua的语法

         推荐书籍《Lua程序设计 第二版》

2、使用ULua插件进行通信

 

尽量早上真机。因为Bug问题特别多。

 

大杂烩:

更新LUa其实也是更新资源。 Lua被看作一个资源么。Lua代码都是运行时才编译的,不运行的时候就如同一张图片、一段音频一样,都是文件资源;所以更新逻辑只需要更新脚本,不需要再编译,因而Lua能轻松实现“热更新”。运行效率由于使用反射,所以成为它与生俱来的诟病。目前有对他的改进,像C#ToLuaSlua。但是Ulua是很成熟的,这个对于项目开发我认为暂时比性能重要!很多人在用,很多坑都已经被人采完。个人比较看好Slua的发展。当然了还有李总的L#走的就不是Lua的路。

网上还有说nLua的,我是新手不了解。 看看权威的解释:

ulua包含两种c模式(luajit 原生luavm版),加之tolua c#提供了直接访问渠道。所以追求效率的,请选用ulua。但是ulua因为底层使用luajit,而luajit目前不能在WP平台使用,所以如果ulua支持WP平台需要第二种原生luavm的底层库。

nlua包含2种模式(KeraLua c版本)(KopiLuac#版本),它支持全平台,因为c版本底层用的原始的luavm(非luajit)。但是缺少tolua c#的支持,因此效率略低于ulua,但是支持WP平台(其他平台也支持)。

 

插件:

下载地址

链接:http://pan.baidu.com/s/1dDjINLn

密码: f29h

ULua = Lua LuaJit(解析器、解释器) LuaInterface(与C#通信用这个);

 

DLL放在 Plugins文件夹下,如果导入后报错,就重启Unity

 

主要内容:

先感谢这篇博文的分享者:

http://www.unitymanual.com/blog-27966-2568.html

Ulua的使用流程一般为:

实例化LuaState对象(newLuaState())  à  加载Lua代码(LuaState.DoString(string)à调用Lua代码中的方法(GetFunction(string),LuaFunction.callFunction(string))。其中加载Lua代码这一块,可以直接赋一段Lua代码字符串,也可以指定一个Lua脚本文件。为了热更新,应当采用第二种加载方法,即创建一个Lua脚本文件。由于Unity不支持扩展名为lua的文件,所以可将Lua脚本扩展名定为txt(纯文本文件),并用unityTextAsset列表负责记录所有脚本文件。建议列表中给每个脚本搭配一个string类型的ID,这样凭此ID即可加载正确的lua脚本;另外在LuaState类中新增一个String类型的public成员,赋值为该ID。这样一旦某个Lua脚本在运行时报错,可根据输出的ID值判断是哪个Lua脚本有错误。

关于Lua里的预处理:

  1. <strong>luanet.load_assembly("Assembly-CSharp")   
  2. luanet.load_assembly('UnityEngine')  
  3. Vector2 = luanet.import_type('UnityEngine.Vector2')  
  4. Vector3 = luanet.import_type('UnityEngine.Vector3')  
  5. GameObject = luanet.import_type('UnityEngine.GameObject')  
  6. luanet.import_type('System.Collections.Generic.List')  
  7. Debug = luanet.import_type('UnityEngine.Debug')  
  8. </strong>  
      这些都是常用的Lua预处理,建议单独写个Lua脚本记录这些,以后加载其他Lua脚本前都先加载一下这个脚本。自定义C#类也是可以import的,import操作是Lua调用C#的前提。当然在C#中实例化的LuaState也可以预定义一些Lua全局变量,这都是在C#里完成的,比如:
  1. <strong>void SetLuaData(LuaState lua)  
  2. {  
  3.        lua["transform"] = transform;  
  4.        lua["gameObject"] = gameObject;  
  5. }  
  6. </strong>  

预定义了两个变量,一个是transform,一个是gameObject


关于Lua里的全局变量:


Lua里所有的字符串,如果不是关键字或者运算符,就都是变量;这些变量中,凡是没用local关键字修饰的,就是全局变量,反之,则是局部变量。每个LuaState对象,当它加载过Lua代码以后,它里面定义的全局变量,在这个对象生命期内是一直存在的。如果两次调用这个LuaState的某方法,第一次将某全局变量进行了修改,那么第二次,这个全局变量会在第一次修改的基础上继续修改。


关于LuaUnity的适用范围:


虽然Lua可以负责Unity工程的任何模块,但是出于对游戏性能的考虑,尽量少的低频率的调用Lua,比如尽量少在Update函数中调用Lua、循环利用已经实例化过的LuaState避免浪费资源。对于那些不需要高效运算的模块,比如UI部分,就可以放心大胆的使用Lua


关于LuaNGUI的适配:


调用Lua的主要方式就是callFunction,而对于NGUI来说,一般都是按钮触发某C#脚本的函数,那么如何用按钮触发Lua的函数呢?这就需要有个C#脚本作为“中介”,这个“中介”需要有自己的LuaState实例,当执行“中介”的某个方法时,由该方法调用callFunction。至于具体调用Lua脚本的哪个方法,可以通过传参的方式告诉这个“中介”——修改NGUIButtonMessage类,加入新的public string LuaFunctionName成员,以后由它来制定要调用的Lua方法名就好了。而通过NGUIUIButton等调用C#的方法,也是同理的(NGUI3.5.6版以后,可以给UIButton等等组件的触发方法添加参数了)。


关于LuaSimpleJSON的适配:


SimpleJSON是一个开源的JSON库,这个详见之前的博客。由于Lua语言无法理解C#里面的属性,所以凡是SimpleJSON里的public A{getset}这种,要新写一些接口函数返回它们的取值,以供Lua调用。而且Lua调用C#里经过很多重载的函数,也经常判断错误(这是个bug?),所以干脆多弄一些函数名,避免调用错误。


关于LuaLua之间的调用:


Unity中使用Ulua,想要让两个Lua脚本彼此调用是很难的,需要通过C#作为“中介”,而且Lua不支持C#的泛型,所以不能用GetComponnet<类名>()的形式,只能用GetComponnet (“类名”)的形式去获取组件。两个Lua脚本相互传参也很麻烦,因为有了C#脚本作为“中介”,所以不能传tabel类型的参数,我的做法是将若干参数拼成一个json字符串传递过去,另一方再解json包。感觉有点蛋疼。不过这种情况并不多见,都是可以避免的(比如一个模块只用一个大Lua脚本,各自独立减少沟通)。


关于Lua编辑器:


个人使用的是notepad ,也有很多人用sublime,代码折叠这块notepad 更好一些,而sublime在功能上貌似更强大一些。   unity项目开发基本使用VS系列。所以


BabeLua是一款基于VS2012/2013(简称VS)的免费开源的Lua集成开发环境,在Lua编辑和调试方面,具有如下功能和特性:

Lua语法高亮

●语法检查

●自动补全

●一键注释

●格式化代码

●自定义代码折叠

●工程管理

●快速搜索和跳转

●文件大纲

●注入宿主程序内对Lua脚本进行调试

●设置断点观察变量值

●查看堆栈信息

版本更新日志

https://babelua.codeplex.com/


         或者 开源的Lua轻量级编辑器:ZeroBraneStudio


Unity编程的亲们,有没有在Unity运行状态下修改并保存了脚本,然后切回来Unity直接卡死的惨痛经历?使用Lua,你将告别这一现象,有木有心动呢?


接下来感谢“小阿哥”的视频分享,虽然内容讲的支离破碎。


  1. C#如何访问Lua中的属性

1) .C#如何访问LUA中的属性?

2) .C#如何访问LUA中的函数?

3) .C#如何访问LUA中的表?

test0.lua文件

  1. --[[  
  2.  @author:小阿哥  
  3.  @des:测试C#访问LUA的一些东东  
  4.  @date:2015-03-21  
  5. --]]  
  6. config={name="小阿哥",age=24,qq="247124629"};  
  7.   
  8. Name="小阿哥";  
  9. Age=24;  
  10. isBoy=true;  
  11.   
  12. function PrintFromLua(a)  
  13.   print("打印信息。我来息LUA...CS传的参数的值:",a);  
  14.   return 200;  
  15. end  
  16.   
  17. print("test0.lua执行完毕..");  

Main.cs文件     随便挂在一个对象上测试吧!

  1. using UnityEngine;  
  2. using System.Collections;  
  3. using LuaInterface;  
  4. /**  
  5.  * @author:小阿哥  
  6.  * @des:讲通信基础版  
  7.  * @date:2015-03-21  
  8.  */  
  9. public class Main : MonoBehaviour {  
  10.   
  11.     private static Main instance;  
  12.     public string ss;  
  13.     public TextAsset tt;  
  14.   
  15.     // Use this for initialization  
  16.     void Start () {  
  17.         instance=this;  
  18.     }  
  19.   
  20.     public static Main GetInstance()  
  21.     {  
  22.         return instance;  
  23.     }  
  24.   
  25.     //C#调用LUA  
  26.     void testCSharp_GoLua()  
  27.     {  
  28.         LuaState lua=new LuaState();  
  29.         lua.DoString("print 'hello world'");  
  30.     }  
  31.   
  32.     //C#调用LUAFile  
  33.     void testCSharp_GoLuaFile()  
  34.     {  
  35.         LuaState lua=new LuaState();  
  36.         lua.DoFileFromAge(this,"Test0.lua");  
  37.     }  
  38.   
  39.     //C#调用LUAInfor  
  40.     void testCSharp_GoLuaInfor()  
  41.     {  
  42.         LuaState lua=new LuaState();  
  43.         lua.DoFileFromAge(this,"Test0.lua",delegate(object[] obj) {  
  44.             //访问LUA中的表  
  45.             LuaTable configTable=lua.GetTable("config");  
  46.             Debug.Log("name:" configTable["name"]);  
  47.             Debug.Log("age:" configTable["age"]);  
  48.             Debug.Log("qq:" configTable["qq"]);  
  49.             //访问LUA中的基础属性   
  50.             Debug.Log("Name:" lua.GetString("Name"));  
  51.             Debug.Log("Age:" lua.GetNumber("Age"));  
  52.             Debug.Log("isBoy:" lua["isBoy"]);  
  53.             //访问Lua中的函数   
  54.             LuaFunction luaFun=lua.GetFunction("PrintFromLua");  
  55.             if(luaFun!=null)  
  56.             {  
  57.                 System.Object[] obResult=luaFun.Call(100);   
  58.                 Debug.Log("obResult:" obResult[0]);  
  59.             }  
  60.        });  
  61.     }  
  62.     void OnGUI()  
  63.     {  
  64.         if(GUILayout.Button("第一节.C#调用LUA"))  
  65.         {  
  66.             testCSharp_GoLua();  
  67.         }  
  68.   
  69.         if(GUILayout.Button("第二节.C#调用LUA File"))  
  70.         {  
  71.             testCSharp_GoLuaFile();  
  72.         }  
  73.   
  74.         if(GUILayout.Button("第三节.C#调用LUA 信息"))  
  75.         {  
  76.             testCSharp_GoLuaInfor();  
  77.         }  
  78.     }  
  79. }  


  1. Lua如何访问C#中的属性

1) .LUA如何访问Unity提供的对象?

  a.如何new系统对象?

  b.如何访问对象的属性?

  c.如何访问对象的函数?

2) .LUA如何访问在C#中自定义的对象?

  a.如何new自定义对象?

  b.如何访问对象的属性?

  c.如何访问对象的函数?

Hero.cs文件

  1. using UnityEngine;  
  2. using System.Collections;  
  3.   
  4. /**  
  5.  * @author:小阿哥  
  6.  * @des:讲通信基础版  
  7.  * @date:2015-03-21  
  8.  */  
  9. public class Hero{  
  10.   
  11.     private string _name;  
  12.     private int _hp,_ap,_mp;  
  13.   
  14.     public Hero(string name,int hp,int ap,int mp)  
  15.     {  
  16.         this._name=name;  
  17.         this._hp=hp;  
  18.         this._ap=ap;  
  19.         this._mp=mp;  
  20.     }  
  21.     public int Hp  
  22.     {  
  23.         get  
  24.         {  
  25.             return this._hp;  
  26.         }  
  27.     }  
  28.   
  29.     public int Ap  
  30.     {  
  31.         get  
  32.         {  
  33.             return this._ap;  
  34.         }  
  35.     }  
  36.   
  37.     public int Mp  
  38.     {  
  39.         get  
  40.         {  
  41.             return this._mp;  
  42.         }  
  43.     }  
  44.   
  45.   
  46.     public void PrintInfor()  
  47.     {  
  48.         Debug.Log("hp:" this._hp "___ap:" this._ap "___mp:" this._mp);  
  49.     }  
  50.   
  51.   
  52.     public int AddHp(int add)  
  53.     {  
  54.         Debug.Log("Lua调用CS的AddHp Lua传的参数 add:" add);  
  55.         this._hp =add;  
  56.         return _hp;  
  57.     }  
  58.   
  59.     public int AddAp(int add)  
  60.     {  
  61.         Debug.Log("Lua调用CS的AddAp Lua传的参数 add:" add);  
  62.         this._ap =add;  
  63.         return _ap;  
  64.     }  
  65.   
  66.     public int AddMp(int add)  
  67.     {  
  68.         Debug.Log("Lua调用CS的AddMp Lua传的参数 add:" add);  
  69.         this._mp =add;  
  70.         return _mp;  
  71.     }  
  72.   
  73.     public void desroy()  
  74.     {  
  75.         this._name=null;  
  76.     }  
  77. }  

在上一个Main.cs文件中添加代码:

  1. //lua调用C#  
  2. void testLua_GoCSharp()  
  3. {               
  4.     LuaState lua=new LuaState();  
  5.     lua.DoFileFromAge(this,"test1.lua");  
  6. }  
  7. void OnGUI()  
  8. {  
  9.     if(GUILayout.Button("第四节.LUA调用C# 信息"))  
  10.     {  
  11.         testLua_GoCSharp();     
  12.     }  
  13. }  

test1.cs文件

  1. --[[  
  2.  @author:小阿哥  
  3.  @des:测试LUA访问C#的一些东东  
  4.  @date:2015-03-21  
  5. --]]  
  6.   
  7. --访问C#中的系统的对象  
  8. luanet.load_assembly("UnityEngine");  
  9. GameObject=luanet.import_type("UnityEngine.GameObject");  
  10. Vector3=luanet.import_type("UnityEngine.Vector3");  
  11.   
  12. local newObj=GameObject('ageObj');  
  13. --访问C#中的系统的对象的函数  
  14. newObj:addComponent("Animation");  
  15. --访问C#中的系统的对象的属性  
  16. newObj.transform.position=Vector3(10,20,30);  
  17.   
  18.   
  19. --访问C#中的自定义的对象  
  20. Hero=luanet.import_type("Hero");  
  21. --newC#中的自定义的对象  
  22. local heroa=Hero("小阿哥",100,200,300);  
  23. --访问C#中的自定义的对象的函数  
  24. heroa:PrintInfor();  
  25.   
  26. --访问C#中的自定义的对象的函数 来回传值  
  27. print("LUA hp:",heroa:AddHp(10));  
  28. print("LUA ap:",heroa:AddAp(20));  
  29. print("LUA mp:",heroa:AddMp(30));  
  30.   
  31. print("test1.lua执行完毕..");  

3Demo例子


所有的业务逻辑放在Lua这面,CS那面通过LuaInterface对其进行调用。Lua是脚本语言,需要宿主语言(C#)。

1) .封装Lua版的MonoBehaviour

2) .接下来所有的业务逻辑均使用LUA进行编写

Demo.lua 文件:

  1. --[[  
  2.  @author:小阿哥  
  3.  @des:主要使用LUA的进行编码  
  4.  @date:2015-03-21  
  5. --]]  
  6. luanet.load_assembly("UnityEngine");  
  7. GameObject=luanet.import_type("UnityEngine.GameObject");  
  8. Vector3=luanet.import_type("UnityEngine.Vector3");  
  9. PrimitiveType=luanet.import_type("UnityEngine.PrimitiveType");  
  10.   
  11. local boxGameObj;  
  12.   
  13. function Start()    
  14.   boxGameObj=GameObject.CreatePrimitive(PrimitiveType.Cube);    
  15.   boxGameObj.name="ageBox100";  
  16. end  
  17.   
  18. function Update()  
  19. end  
  20.   
  21. function FixedUpdate()  
  22.   boxGameObj.transform:Rotate(Vector3.up,5);  
  23. end  
  24.   
  25. function OnGUI()  
  26.     --print("OnGUI..");  
  27. end  
  28.   
  29. function OnDestroy()  
  30.     --print("OnDestroy..");  
  31. end  
  32.   
  33. using UnityEngine;  
  34. using System.Collections;  
  35. using LuaInterface;  

Demo.cs文件       挂在摄像机上做测试就行!

  1. /**  
  2.  * @author:小阿哥  
  3.  * @des:测试小例子  
  4.  * @date:2015-03-21  
  5.  */  
  6. public class Demo : MonoBehaviour {  
  7.   
  8.     private LuaState lua;  
  9.   
  10.     private LuaFunction funStart;  
  11.     private LuaFunction funUpdate;  
  12.     private LuaFunction funFixedUpdate;  
  13.     private LuaFunction funOnGUI;  
  14.     private LuaFunction funOnDestroy;  
  15.   
  16.   
  17.     // Use this for initialization  
  18.     void Start () {  
  19.         lua=new LuaState();  
  20.         lua.DoFileFromAge(this,"Demo.lua",delegate(System.Object[] obj) {  
  21.             funStart=lua.GetFunction("Start");  
  22.             funUpdate=lua.GetFunction("Update");  
  23.             funFixedUpdate=lua.GetFunction("FixedUpdate");  
  24.             funOnGUI=lua.GetFunction("OnGUI");  
  25.             funOnDestroy=lua.GetFunction("OnDestroy");  
  26.               
  27.             if(funStart!=null)  
  28.             {  
  29.                 funStart.Call();  
  30.             }  
  31.         });  
  32.         
  33.   
  34.     }  
  35.       
  36.     // Update is called once per frame  
  37.     void Update () {  
  38.         if(funUpdate!=null)  
  39.         {  
  40.             funUpdate.Call();  
  41.         }  
  42.     }  
  43.   
  44.     void FixedUpdate()  
  45.     {  
  46.         if(funFixedUpdate!=null)  
  47.         {  
  48.             funFixedUpdate.Call();  
  49.         }  
  50.     }  
  51.   
  52.     void OnGUI()  
  53.     {  
  54.         if(funOnGUI!=null)  
  55.         {  
  56.             funOnGUI.Call();  
  57.         }  
  58.     }  
  59.   
  60.     void OnDestroy()  
  61.     {  
  62.         if(funOnDestroy!=null)  
  63.         {  
  64.             funOnDestroy.Call();  
  65.         }  
  66.     }  
  67. }  


最后感谢 骏擎网络 的突出贡献吧


游戏已经开发到后期,如何接入ulua

1)活动

2)计时器(单位秒)驱动luaupdate

3)网络管理留给lua能跟服务器交互的接口(现在未必用得到的)。活动这部分变数最大,很多问题上线前是无法预知的,比如上线如果发生数据不理想,或者非常火爆,这些情况无法预知,根据这些情况做活动调整,这些很容易有更新需求。而且未必前期都能想到坐进去。运营策划都是要根据在线运营情况做未知的活动调整。还有一部分我称之为,程序给自己留的后路,如果绝大部分都是c#的话,很有可能产生上线后产生bug,比如:新手引导,在什么地方卡住了等。客户端启动一个计时器,驱动lua的一个onTimer,在里面根据游戏运行情况,动态调整对游戏的控制。还有就是多给自己留一个协议的接口给lua备份用。


最新版ulua cstolua的性能速度超slua N

    最新版的ulua cstolua,效率已经火箭式提升,早已阔别三日当刮目相看了,大家关心的GC问题已经彻底消除,GC早已将为0。加上阿萌已经将众多使用频繁的Unity类用lua重写,这样完全消除了P/Invoke的代价(不知道slua会不会发现了,再次山寨),甚至cstolua都把string都做了优化,可以在lua中转换(极限了),综上所述,ulua已经是当前最快的,没有之一!!!


     请直接下载uLua_v1.08.zip,集成cstoluaprotobuf-lua-genios64Intel x86, cjson, pbc, lpeg, sqlite3等支持,从网络协议到正则表达式,再到数据库,ulua已经为在unity lua环境开发集成了一个既兼顾效率(都是成熟的原生C的库)又完善(一应俱全)的稳定开发平台,使您开发从小游戏到大型网游都有相应的方案支持!!这也是ulua所特有的哦~~


http://blog.csdn.net/u010019717/article/details/44905363

原文链接

著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

游戏学院公众号二维码
腾讯游戏学院
微信公众号

提供更专业的游戏知识学习平台