阅读:14612回复:0
Unity热更新技术研究
一、研究背景
在基于Unity实现功能动态组装研究中,发现单靠AssetBundle不能直接将代码打包出来,AssetBundle只是打包了其中的关联关系,而其中打包的代码没有经过编译。通过网上查找,发现Lua热更新对于代码更新有很大的帮助。深入研究后,更是发现Lua与C#结合能够很大程度的对Unity场景内容及功能逻辑进行直接控制。通过Lua能够间接的实现功能代码的加载。下面就介绍热更新概念和一些经常用到的Lua热更新框架。 二、热更新介绍 1.热更新定义 热更新是指在不停机的状态下对系统进行更新,例如Windows可以在不重启的状态下完成补丁的更新、Web服务器在不重启的前提下完成对数据和文件的替换等都是热更新的经典实例。 2.在Unity中热更新的作用 对于unity而言,如果开发的是Web游戏,每次游戏加载的过程中实现资源代码的更新就是热更新。如果发布的是一个客户端游戏,那么在重启客户端以后实现对资源代码的更新就是热更新。因为Web游戏需要保证玩家能够及时快速的进入游戏,因此在游戏加载完成之前,必须完成对游戏资源和代码的更新。而对于客户端游戏来说,玩家可以在等待本次更新结束后再进入游戏,而且大部分的客户端程序在更新后要求重启客户端,所以对于客户端而言,热更新并非严格意义上的热热更新。客户端之所以要进行热更新,是为了缩短客户获取新版本客户端所需的流程、改进用户体验。 3. 现有的热更新技术 Lua作为嵌入式解析型的语言,小到可以忽略不记。也能方便的嵌入各个程序。其功能比较全面,也能处理各种复杂的逻辑。而基于反射原理,运行效率较低。 UniLua、ULua、SLua、XLua,这些都是基于原生Lua代码作为Unity插件被调用。其中ULua和现在的NLua都是基于反射的原理,速度慢,gc alloc(垃圾回收,内存浪费)频繁,不支持直接代理,且现在功能还不完善。SLua是目前所有Unity+Lua方案中最快的,没有反射,gc alloc都很少,直接支持代理,文档简洁,更新快。而uniLua是C#的Lua实现,本身没有c代码效率高,同时还需要考虑导出unity接口(可以反射,也可以静态代码)。 三、UniLua 因为该插件能够很好的与unity结合,且容易上手,就从UniLua开始介绍。在Lua的C#接口中有一个DoString(string script)的方法,这个方法可以直接利用Lua虚拟机执行字符串中的脚本。因此可以通过本地读取Lua脚本来执行脚本中的命令。而有一个叫Require的方法可以将C#库引入到Lua脚本中并通过Lua来执行C#库中的方法。因此,Lua脚本就可以直接对unity3d场景内容及功能逻辑进行操作。 1、框架原理 在该原理图中,首先需要将unity Api封装成一个C#类库,这个类库中将涉及动态加载场景和动态创建场景,因为更新程序的逻辑的时候会用到这些方法。将这些方法封装后,就可以在Lua脚本中通过Require方式来引用,进而就可以通过Lua脚本来动态地进行设计。通过客户响应事件,动态的组装对应功能。根据原理图,通过WWW(Unity的网络访问类)来从远程服务器上下载最新的Lua脚本更新文件,并通过C#执行该脚本进行更新。 2、新增Lua交互操作步骤 (1)在类库中新增交互接口,在CSharpLib.cs类中增加两个方法,形成测试的类库 1. public static int SetPosition(ILuaState lua) 2. { 3. ULDebug.Log("将要对物体位置进行更新了!"); 4. string name = lua.L_CheckString(1); 5. float x = (float)lua.L_CheckNumber(2); 6. float y = (float)lua.L_CheckNumber(3); 7. float z = (float)lua.L_CheckNumber(4); 8. GameObject go = GameObject.Find(name); 9. Transform trans = go.transform; 10. trans.position = new Vector3(x, y, z); 11. lua.PushNumber(trans.position.x); 12. lua.PushNumber(trans.position.y); 13. lua.PushNumber(trans.position.z); 14. return 3; 15. } 16. public static int CreatGameObject(ILuaState lua) 17. { 18. Debug.Log("将要创建新的物体了!"); 19. string name = lua.L_CheckString(1); 20. GameObject go = (GameObject)Resources.Load(name); 21. float x = (float)lua.L_CheckNumber(2); 22. float y = (float)lua.L_CheckNumber(3); 23. float z = (float)lua.L_CheckNumber(4); 24. Object.Instantiate(go, new Vector3(x, y, z), Quaternion.identity); 25. lua.PushString(go.name); 26. return 1; 27. } 将这两个方法注册到函数表中。 (2)新建Lua脚本文件 1. SetPosition(“Cube”,2,1,0) 2. CreatGameObject(“Cube”,3,2,0) 3. CreatGameObject(“Sphere”,0,2,0) (4)在主程序中调用 1. private ILuaState Lua; 2. void Update () 3. { 4. Lua = LuaAPI.NewState(); 5. Lua.L_OpenLibs(); 6. } 7. public void UpdateScript() 8. { 9. StartCoroutine(Download()); 10. Debug.Log("开始更新"); 11. } 12. IEnumerator Download() 13. { 14. //下载新的Lua脚本文件,可以是本地,可以是服务器 15. WWW www = new WWW(path); 16. yield return www; 17. Debug.Log(www.text); 18. //Lua.L_DoString("CreatGameObject('Sphere',0,0,0)"); 19. //Lua.L_DoString("SetPosition('Cube',2,1,0)"); 20. Lua.L_DoString(www.text); 21. } 其中path为lua脚本的路径,,只需要调用UpdateScript函数即可完成更新操作。并且Cube是已经在场景中的物体,Cube和Sphere的预制体需要放在Resource文件夹下,因为创建物体是通过Resource.Load的方法加载预制体的。 (5)效果展示 Lua脚本调用前: Lua脚本调用后: 3、注意: 该方法是读取本地Lua脚本文件与预制体,而如果要想读取服务器上的,就需要用到AssetBundle载入远程资源,再用Lua实现更新。 四、ULua 1.基础介绍 在uLua 环境下本质是通过LuaInterface 来实现Lua 与C#语言交互的。 但是uLua 对LuaInterface 进行了二次的封装,所以在代码书写上和原生的会稍有不同。 2.C#代码中执行Lua 代码 1. LuaState lua = new LuaState(); 2. lua.DoString(); 这个框架比上个框架少了一个步骤, 说明: <1>同样需要在C#代码中引入LuaInterface; <2>原生的Lua 解析器对象“Lua”被二次封装成了“LuaState”; <3>LuaState 类存在的位置是:uLua\Core\Lua.cs。 3.Lua 代码中操作Unity 内的类[反射方式/ 原生方式] 创建一个游戏物体,名字叫wjg,有Box Colider : 1. using System.Collections; 2. using System.Collections.Generic; 3. using UnityEngine; 4. using LuaInterface; 5. 6. public class LuaAndUnityTest : MonoBehaviour{ 7. private string str = @" 8. luanet.load_assembly('UnityEngine') GameObject = 9. luanet.import_type('UnityEngine.GameObject') 10. BoxCollider = luanet.import_type('UnityEngine.BoxCollider') 11. local player = GameObject('wjg') 12. player:AddComponent(luanet.ctype(BoxCollider)) 13. "; 14. // Use this for initialization 15. void Start () { 16. //创建Lua解析器对象 17. LuaState luaState = new LuaState(); 18. luaState.DoString(str); 19. } 20. } 运行该文件后就会在Hierarchy 试图里创建一个叫wjg的物体带有BoxCollider组件 4.注意: <1>这种方式就是在VS 环境下演示的; <2>luanet 已经被封装到了LuaInterface 命名空间内,所以我们可以在Lua代码中直接使用luanet 这个对象; <3>在Lua 环境内操作C#中的类创建对象,不要写new 关键字!!! <4>在Lua 环境内操作C#中的类创建对象,访问对象中的方法使用分号(:); <5>这种“反射方式”在项目开发中并不常用 五、Slua 1. 基本信息 框架原理就是通常在Lua框架中所说的,开放一个C#的web接口,或者叫做在Slua框架中注册函数。 2. C#调用Lua方法 1. LuaState luaState = new LuaState(); //新建luaState 2. luaState.doFile("Lua脚本路径");//加载Lua脚本 3. LuaTable t = luaState.getFunction("函数名").call(...)//运行函数 4. t["字段名"] = "字段值" //操作table 3. 新建Lua脚本 1. import “UnityEngine” 2. Function main() 3. //创建一个Cube 4. Local cube = UnityEngine.GameObject.CreatePrimitive(UnityEngine.PrimitiveType.Cube) 5. 6. //对物体上的Transform组件进行操作 7. Local pos = cube.transform.position 8. pos.x = 10 9. cube.transform.position = pos 10. 11. //添加Rigidbody组件 12. cube:AddComponent(Rigidbody) 13. cube:AddComponent(BoxCollider) 14. 15. //获取BoxCollider组件 16. Local collider = cube:GetComponent(“BoxCollider”) 17. End 该插件能够直接在外部对unity组件、物体、脚本进行更新。 六、XLua 1.基本信息 xLua为Unity、 .Net、 Mono等C#环境增加Lua脚本编程的能力,借助xLua,这些Lua代码可以方便的和C#相互调用。 xLua在功能、性能、易用性都有不少突破,这几方面分别最具代表性的是: 可以运行时把C#实现(方法,操作符,属性,事件等等)替换成lua实 现; 出色的GC优化,自定义struct,枚举在Lua和C#间传递无C# gc alloc; 编辑器下无需生成代码,开发更轻量; 2.C#调用Lua 1. XLua.LuaEnv luaenv = new XLua.LuaEnv(); 2. luaenv.DoString("CS.UnityEngine.Debug.Log('hello world')"); 3. luaenv.Dispose(); 在DoString中可直接调用Lua脚本内容,一个luaenv示例一个Lua虚拟机,建议全局唯一。 七、Lua各个框架的比较 UniLua:该框架是C#的Lua实现,本身比c代码效率低,需要考虑导出unity接口(可以反射,也可以静态代码),只是一个C#实现Lua的虚拟机。 ULua: 是基于反射的解决方案, 劣势是速度慢,gc alloc频繁,不直接支持代理,。优势是不会产生静态代码,减少了app的尺寸。 SLua:是目前所有unity+lua方案里最快的,没有反射,很少gc alloc,功能比较强大。 XLua:是由腾讯维护的一个开源项目,除了常规的Lua绑定之外,还有一个比较有特色的功能就是代码热补丁。非常适合前期没有规划使用Lua进行逻辑开发,后期又需要在iOS这种平台获得代码热更新能力的项目。 |
|||||||