原创

以前写过几篇关于热更新的文章,但是我一直没有深入研究,就是公司用什么技术,我就根据公司的框架写代码。这回刚好在家闲着,我打算写一个系列的文章,深入研究一下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);
        }