【游戏客户端开发】Unity3D 学习笔记4 —— UGUI+uLua游戏框架

  • 使用到的资料下载地址以及基础知识
  • 框架讲解
  • 拓展热更过程

在这里我们使用的是uLua/cstolua技术空间所以提供的UGUI+uLua的热更游戏框,我也只是把我学习和使用这个框架的笔记记录下来而已。

一.资料下载地址以及基础知识:

主要使用到的资料有:

  1. 客户端框架:LuaFramework_UGUI-master
  2. 服务端框架:ServerFramework-master
  3. toLua(uLua的升级版本)基本框架:tolua-master

学习本框架,首先需要了解lua脚本的基本语法,然后先学习tolua框架,明白C#与lua之间的交互之后,我们再对LuaFramework_UGUI框架进行学习。


二、框架讲解

1.toLua框架

关于toLua的学习我们可以直接借助框架中提供的Demo来帮助我们理解,用Unity打开tolua-master工程,在目录ToLua/Example中,就是框架作者为我们提供的学习demo: 
这里写图片描述 
例如案例一中就是在C#中调用Lua中的print打印方法打印日志:

<code class="hljs cs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">using</span> UnityEngine;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">using</span> System.Collections;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">using</span> LuaInterface;

<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> HelloWorld : MonoBehaviour 
{   
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> Start () 
    {        
        LuaState lua = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> LuaState();
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">string</span> hello =
            <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">@"                
                print('hello tolua, 广告招租')                
            "</span>;
        lua.DoString(hello, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"hello"</span>);
        lua.CheckTop();
        lua.Dispose();
    }
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li></ul>

声明hello这串字符串就是lua脚本的内容,具体调用的办法就是创建一个LuaState对象,相当于一个操作接口,从而在C#中对lua脚本进行操作。此外,关于toLua相较于原本的uLua,实际上就是对交互接口LuaScriptMgr.cs进行了功能拆分,变成了成 ToLua.cs 和 LuaState.cs , 一个为lua 回调C#服务,一个为C#调用lua 服务。

在这里,我们就不一一地对每个案例做赘述,大家自行了解学习,我们这里要讲解的重点是游戏客户端热更框架LuaFramework_UGUI-master。


2.ServerFramework-master框架

这个其实是我们用于测试客户端框架而使用到的工具,我们只需要如何使用它来进行测试就行了,至于具体实现,有兴趣的朋友自己研究一下吧。 
启动方式有两种,一是使用源码在VS中编译生成可执行文件,二是直接使用包里已编译好的.exe可执行文件,显然第二种办法更为简便一些。文件目录为:ServerFramework-master\Server\bin\Debug\SuperSocket.SocketService.exe,正常启动的结果如下图:这里写图片描述 
此状态为准备就绪等待客户端的登陆连接,而客户端正常连接时: 
这里写图片描述


3.LuaFramework_UGUI-master框架

打开框架,我们首先来看看其文件目录: 
这里写图片描述 
我们可以简单介绍下每个目录的用途。

Examples :框架自带的Demo例子,如果只需要框架的同学,里面的资源可以删除掉。去“疑难解答”里面查看方法。 
—Builds:里面都是一些NGUI/UGUI定义的图集啊、Prefab等资源。用于生成assetbundle而准备的资源。 
—Editor:里面是例子用到的一个新手引导步骤演示的编辑器脚本。 
—Editor Default Resource:目录是新手引导步骤对话框用的的图片资源。 
—Rsources:例子里面用于演示的一个内建的GUI容器的Prefab。 
—Textures:里面是Buidls目录里面图集的原图文件。

Scenes:里面一个login场景文件也是cstolua自带的性能测试场景文件。

Lua:框架自带的Lua源码目录,用户自定义的Lua脚本也就是放在这里面,最后打包的时候,打包脚本会将其按目录结构生成到StreaminAssets目录里面去,然后在将其上传到游戏的Web服务器上面,用于准备被每个游戏客户端下载更新他们本地的Lua脚本。达到热更目的。 
—3rd:里面是第三方的一些插件lua、实例源码文件,比如:cjson、pbc、pblua、sproto等。 
—Common:公用的lua文件目录,如define.lua文件,一些变量声明,全局配置等,functions.lua常用函数库,通讯的protocal.lua协议文件。 
—Controller:控制器目录,它不依赖于某一个Lua面板,它是独立存活在Luavm中的一个操作类,操作数据、控制面板显示而已。 
—Logic:目录里面存放的是一些管理器类,比如GameManager游戏管理器、NetworkManager网络管理器,如果你有新的管理器可以放到里面。 
—View:这是面板的视图层,里面都是一些被Unity调用的面板的变量,走的是Unity GameObject的生命周期的事件调用。

Plugins:ulua底层库所在的目录,里面存放的是不同平台的底层库,之所以ulua效率高,就是它是纯c的lua虚拟机,而不是c#解释型的。 
—Andriod:安卓lua虚拟机底层库,里面分为armv7-a与Intel x86平台。 
—iOS:里面就是苹果lua虚拟机底层库。 
—ulua.bundle:里面是Mac机器的底层库。 
—x86:里面是Win32/Linux32位机器的lua虚拟机底层库。 
—x86_64:里面是Win64/Linux64位机器的lua虚拟机底层库。

Scripts:框架的C#脚本层,之所以这个目录跟lua目录都放在最外层,为了让用户一眼都能找到,明白是什么。 
—Common:框架的公用定义类。LuaLoader(跟lua加载有关的类)、与luavm通知unity游戏对象的“LuaBehaviour”桥类。 
—ConstDefine:常量定义目录,AppConst(应用常量)ManagerName(管理器名称)NotiConst(通知常量,用于mvc消息通知)。 
—Controller:控制器目录,分为StartUpCommand启动控制器,跟常用逻辑控制器。框架接收到启动命令后,直接在启动命令里面注册所有的管理器类。 
—Framework:经过修改过的PureMVC的框架文件。 
—Manager:Unity提供基础功能的管理器类,音乐、面板、线程、资源等众多管理器。 
—Network:网络的常用辅助类,ByteBuffer字节操作封装类,网络协议类,转换器类。 
—Utility:常用工具类。 
—View:C#用的PureMVC的视图层。

ToLua(低版本的可能是uLua):ulua/cstolua的核心目录,里面还有经过我们修缮后ulua的基础使用例子,用户初学者最佳。 
—Core:顾名思义,ulua的核心目录,所有c#与lua的交互都是通过它进行调度的。 
—Editor:这是供cstolua去反射定义Wrap文件列表的工具类目录。 
—Examples:经过我们修改增加后ulua自带的例子。 
—Source:这个是cstolua的核心目录,里面有Base核心目录,与动态生成用于存放LuaWrap类的缓存目录。

原来版本的消息结构为PureMVC,而新版的改成了: 
AppFacade + Controller层通讯(修改版)+重写的View层通讯 = 0.3.8的框架

三、拓展热更过程

实际上,Unity中的C#部分是不能进行热更新的,只有Lua与ui资源能够进行热更新。在Web服务器上面部署最新的版本资源文件,我们就可以将Lua的代码编码后上传到Web服务器上面去,当游戏客户端启动的时候,它会启动解包流程、解包的资源一般都是当时做包时候的资源,到后面我吗修改以后,它内部的资源可能已经不是最新的了,但是大部分可能是新的,只有少部分需要更新,那紧接着就启动更新流程,从Web服务器上面的资源配置列表里面,通过MD5/CRC比较查询到最新的资源,下载更新本地的文件,达到更新最新版的目的,游戏顺利启动。 
这里写图片描述
这里写图片描述

具体的流程如下: 
1.将资源打包到StreamingAssets中,因为Unity打包生成Apk/Ipa的时候,此目录会原样地打进安装包中,游戏客户端框架可以通过代码读取到里面的资源,并且把里面的资源复制到玩家的手机本地存储里面,这叫做解包。 
那怎么打包呢?打包的资源分为素材资源与代码资源,这两部分的打包,框架都集成了,你可以直接修改里面的脚本逻辑适应自己的游戏项目,我们打开ulua/Editor/Packager.cs打包脚本。这里面根据不同的平台打包相应的资源。

<code class="hljs cs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">[MenuItem(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Game/Build iPhone Resource"</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11</span>)]
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">BuildiPhoneResource</span>() {
        BuildTarget target;
<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">#<span class="hljs-keyword" style="box-sizing: border-box;">if</span> UNITY_5</span>
        target = BuildTarget.iOS;
<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">#<span class="hljs-keyword" style="box-sizing: border-box;">else</span></span>
        target = BuildTarget.iPhone;
<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">#<span class="hljs-keyword" style="box-sizing: border-box;">endif</span></span>
        BuildAssetResource(target, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>);
    }

    [MenuItem(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Game/Build Android Resource"</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">12</span>)]
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">BuildAndroidResource</span>() {
        BuildAssetResource(BuildTarget.Android, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>);
    }

    [MenuItem(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Game/Build Windows Resource"</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">13</span>)]
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">BuildWindowsResource</span>() {
        BuildAssetResource(BuildTarget.StandaloneWindows, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>);
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li></ul>

重要的函数是下面这个,它打包了框架自带例子的素材文件后,继续处理Lua代码文件的打包。

<code class="hljs cs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">BuildAssetResource</span>(BuildTarget target, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">bool</span> isWin) {
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (AppConst.ExampleMode) {
            HandleExampleBundle(target);
        }
        HandleLuaFile(isWin);
        AssetDatabase.Refresh();
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li></ul>

HandleExampleBundle函数就是将Examples/Builds下面的素材统一按照Unity的规则进行打包成assetbundle文件。没什么好说的。

我们主要是看HandleLuaFile函数的操作流程。

(1)在StreamingAssets目录下面新建lua目录,用于存放编码后的lua文件。 
(2)遍历Lua目录下面所有的lua文件,并且根据目录结构创建相应目录树。 
(3)如果AppConst.LuaEncode 设置了编码开关,就启动编码操作,否则直接复制源码过去。 
(4)然后将前面的图片素材assetbundle跟相对应的lua文件目录,统一遍历计算出MD5/CRC,生成到files.txt里面。

这个代码就做了这几件事情,唯一需要展开介绍的只有怎么编码Lua文件了吧。这个函数其实并不长, 
他就是根据相应的平台配置其编码器,编码器存放的路径也就是前面工程外的LuaEncoder的目录,拼凑出它的路径后,我们开始拼凑其编码的参数命令行, 
需要注意的是luajit跟luavm的编码命令行不同: 
luajit的:luajit.exe -b srcfile outfile 
luavm的:luac -o outfile srcfile 
命令行的格式其实都很简洁,然后我们启动C#的调用外部shell程序的代码,调用他们来编码出2进制文件即可。那最后统一计算MD5的文件就变成了编码的2进制的值。 
//———————————————————————————————————— 
说完了打包流程,接下来说下怎么更新,只有能顺利更新下面后,才能够算作热更的最后一环。关于更新这一块,我们是用C#写成的,为啥?因为我们真实项目中,游戏的基础功能,比如下载、线程操作都建议在C#中完成,为了效率,而不是为了花架子都在lua中完成。因为这些基础功能,相对于游戏来说,更新的频率非常之低,用C#完成追求了效率,也没有任何损失。

我们打开Scripts/Manager/GameManager.cs文件,定位到IEnumerator OnUpdateResource()函数, 
(1)它一上来就做些初始化的操作,找到更新的地址URL等。 
(2)请求更新列表文件files.txt,因为里面存放了上面生成的目录结构及其MD5/CRC的信息。 
(3)分析Web服务器上的files.txt文件内容,然后遍历检查本地的文件结构是否完整、MD5是否匹配。 
(4)如果MD5不匹配,或者本地文件不存在,就开始创建一个下载请求,并且将它传递给线程,请求线程下载。 
(5)本地协程会每一帧查询线程下载完后是否将下载的文件名存放到下载文件列表中,如果找到,继续下载下一个,直至全部下载完成。 
其实当全部更新完成后,此时手机存储的文件结构及其内容已经是最新的了,客户端程序便可以顺利启动,完成了热更的最后一环。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值