原创
以前写过几篇关于热更新的文章,但是我一直没有深入研究,就是公司用什么技术,我就根据公司的框架写代码。这回刚好在家闲着,我打算写一个系列的文章,深入研究一下Uua的热更新。最近几天有2家公司挖我去做游戏,开门问我第一句都是热更新框架你能不能搭建起来,COCOS做2D有LUA是不是比U3D强,搞的我腰板也是不太硬,都是因为热更新懂的不是很彻底,工资都没有到20K,虽然自信自己的学习能力,很快就能研究完,但是还是要从头搞一遍才行。
目前主流的ULua有两套框架,一个是SimpleFramework_UGUI ,16年初以后不维护了。一个是LuaFramework_UGUI。本来是当算学一下LuaFramework_UGUI毕竟是原作者的新东西。但是看一下,竟然打包资源还要手动加代码,再加上商用估计都还是老的比较多,我决定还是学习SimpleFramework_UGUI ,官方教程:http://doc.ulua.org/default.asp?cateID=4如果需要学LuaFramework_UGUI,可以看这系列博客:https://www.zhihu.com/people/pyluo/posts。
首先:需要了解这些知识点,编辑器扩展,Lua脚本语言,Unity5的AsstBundle打包,Lua和C#通信原理。我打算先上手直接做一遍流程再深入研究。我个人不喜欢一上来就是原理,这个那个的,咱们学东西就是要快速上手能用,用顺手了再去深挖!
我这里都是以热更新UI界面或者2D游戏为例子。我理解的ULUA热更新是这样的顺序:先导入SimpleFramework_UGUI框架到项目,登录界面一个Assetbundle包,大厅一个包,用户信息一个包,排行榜一个包,类型这种模块化的包,需要更新那个地方就更新哪一块,StreamingAssets文件夹下有个files文件是包的版本信息。我们打包了一个APK出去以后,如果有更新的模块,就放到服务器上,通过服务器下载远端的files和本地的files做比对,哪些资源包不一样,就会自动下载下来,覆盖原有的包,第一次打开游戏都是需要解包的,就是把StreamingAssets包的资源拷贝到你真机的持久化储存的地方,第二次就不需要了,直接读取。
我的U3D版本是Unity 5.3.1,SimpleFramework_UGUI版本是16年1月份的,Lua开发工具sublime。
首先,需要注意的是:
1.如果在Lua里创建的界面没有指定父物体,将会创建到Tag为GuiCamera的物体下。具体看PanelManager.cs这个类。
2.如果你修改了CS类,你要Lua菜单---Gen Lua Wrap Files,生成的文件在uLua/Source/LuaWrap文件夹,Lua菜单----Clean Wrap...可以清空这个文件夹。(生成的文件可以让Lua脚本去调用C#类)
3.修改和创建了LUA脚本就要Build Resource (如果把AppConst类中的 DebugMode = true; 设置为调试模式,就不会读取本地保存的Lua,而且是读取工程中的Lua。也就是可以一边修改一边看到修改后的内容,而不用Build,但是Prefab资源修改了就一定要Build)
制作Lua资源:就是修改3个Lua脚本(define,GameManager,CtrlManager,)和添加2个Lua脚本(Ctrl和Panle)和资源制作(比如名字叫LoginPanel,资源包叫Login.assetBundle)
第一步,打包成AssetBundle
1.比如你做好了一个登录界面,先把该Panel界面做成prefab,注意prefab命名为 xxxxxPanel (原因是PanelManager.cs 这个类里OnCreatePanel函数要求这么写,当然Scripts/Manager还有其他的管理器,有其他资源的加载方式。)
2.在右下角把该Panel的AssetBundle 命名为 xxxxx (不要Panle),后缀为 AssetBundle
3.Game菜单----build Windows Resource 会在StreamingAssets文件夹下生成的lua代码和资源包,正式项目打包最好先删除这个文件夹再Build
注意:如果你有Json,或者其他文本形式的文件想打包,直接放在StreamingAssets下,会打包进文件目录files.txt
第二步,用lua创建UI面板
1.新建一个物体叫GlobalGenerator,给他一个GlobalGenerator脚本,这个是Lua入口,再往下看GameManager类的OnResourceInited()方法,他会去调用GameManager.lua脚本里的LuaScriptPanel函数(CallMethod("LuaScriptPanel") ),如果返回 xxxxx,就回去执行 Lua/view/xxxxxPanel脚本。
2.接着会执行GameManager.Lua里的GameManager.OnInitOK()方法,会执行CtrlManager.Init(); 在CtrlManager.Lua脚本 require "Controller/xxxxxCtrl"然后再Init函数中加上 ctrlList[CtrlName.xxxxx] = xxxxxCtrl.New();
3.在Define.Lua脚本中的CtrlName表中加入 xxxxx="xxxxxCtrl"
4.添加一个UI模块,需要添加上面说到的有两个Lua脚本,一个是Lua \ View \ xxxxxPanel ,一个是Lua \ Controller \ xxxxxCtrl。 这两个脚本等会再说,可以先仿照官方的例子来写。
5.GameManager.Lua脚本在CtrlManager.Init();执行完后,会通过得到的Ctr脚本,执行ctrl:Awake(); 就是一句创建面板 PanelManager:CreatePanel('xxxxx', this.OnCreate); 前面一个参数是AssetBundle的包名(其实也是Prefab的名字,会自动加一个"Panel",在PanelManager.cs的CreatePanel函数中可以去看),后面一个参数是回调。 例子是是回调到LogCtrl.OnCreate(obj)这个函数,Obj是什么东西呢,其实就是创建的物体(在PanelManager.cs类里的CreatePanel函数,func.Call(go);) 创建的这个物体会先加载本地持久化目录的资源,然后创建一个UI物体,并给它挂载一个LuaBehaviour组件。LuaBehaviour里面就可以让xxxxxPanel.Lua脚本执行U3D里的Awake,Start函数,以及AddClick添加点击事件等。
6. 编写Ctrl和Panel的Lua文件
正式项目中,应该写一个LayerPanel,下面有前中后三层,就可以给生产的Panel指定父物体来,来改变显示的前后顺序。
Panel文件只负责UI界面的引用。
his.Btn_Ok = transform:FindChild("Btn_Ok").gameObject;
Ctrl文件的代码应该这么写(负责点击事件,创建关闭面板等):
LoginCtrl = {};
local this = LoginCtrl;
local Login;
local transform;
local gameObject;
--构建函数--
function LoginCtrl.New()
return this;
end
function LoginCtrl.Awake()
PanelManager:CreatePanel('Login', this.OnCreate);
end
--启动事件--
function LoginCtrl.OnCreate(obj)
gameObject = obj;
transform = obj.transform;
Login = transform:GetComponent('LuaBehaviour');
Login:AddClick(LoginPanel.gameObject, this.OnClick); --添加点击事件
end
--单击事件--
function LoginCtrl.OnClick(go)
logError("你点击了!"); this.Close();
end
Panel里负责面板的引用
--启动事件--
function GameFruitPanel.Awake(obj)
gameObject = obj;
transform = obj.transform;
transform:SetParent(LayerPanel.Layer_B); --设置父物体
local Rect = gameObject:GetComponent("RectTransform"); --设置位置,大小
Rect.localPosition=Vector3.zero; --Vector3是框架给我写好的,在Lua / System里,其实就是一个表。
Rect.sizeDelta=Vector3.one;
this.InitPanel();
end
另外,调用Update()
添加UpdateBeat:Add(PromptCtrl.Update) 移除UpdateBeat:Remove(PromptCtrl.Update) 具体还没研究,只知道这么用,用完要移除掉,很耗性能。
为什么要分开写,为什么要一个UI界面写2个Lua, 是因为Ulua的框架结构是PureMVC,基于模型、视图和控制器的三层MVC模式,这里xxxxxPanel.lua是界面展示和引用UI元素,也就是视图层,xxxxxCtrl.lua就是业务逻辑层,像define.lua就是数据层。当然你可以每个面板搞一个数据层存一下,我感觉没什么必要,只存全局数据就行了。
7.添加自定义组件。
打开 Ulua / Editor / WrapFile.cs文件, 添加: _GT( typeof(类名) ) , 然后打开菜单Lua---Gen lua Wrap Files,生成该C#类对应的Warp文件,有了这个文件才能给Lua脚本使用,生成的Wrap文件在uLua / Source/ LuaWrap里。
8.生成EXE或者APK。需要注意一下几点:
1.生成哪个平台,Game菜单下选择哪个平台的资源
2.选择生成的平台,PC要选择PC--- Architecture: X86_64 ,如果选择X86会找不到Ulua.DLL
3.生成选项:Development build ,会在游戏中打印日志信息,并且保存在一个 log.txt里。
生成选项:Autoconnect profiler,自动连接分析器,开启U3D性能分析工具的功能。(点Build And Run)
生成选项:Script debugging,开启脚本调试
在生成导出的时候,还报错: The nested type `AdvertisingIdentifierCallback' does not exist in the type `UnityEngine.Application'
这个问题我搞了好久,问了蛮多人,最后的解决方法:直接用U3D打开SimpleFramework_UGUI解压的工程,就不会报错,如果是Uua导入你原先的项目就会报错,有可能是我导入的时候少放了东西。
如果,报错:Push table failed 就是GameManager.LuaScriptPanel忘了添加了。
如果,编辑器没问题,但是到真机上报错:Loader lua filed System.Global,在AppConst设置调试模式DebugMode=false
--关闭事件--
function LoginCtrl.Close()
PanelManager:ClosePanel(CtrlName.Login);
end
Ctrl文件里的,我发现PanelManager.cs里没有ClosePanel这个方法,需要我们自己写
/// <summary>
/// 关闭面板
/// </summary>
/// <param name="name"></param>
public void ClosePanel(string name){
var panelName = name + "Panel";
// var panelObj = Parent.FindChild (panelName);
var panelObj = GameObject.Find(panelName);
if (panelObj == null) return;
Destroy(panelObj.gameObject);
}