新华电脑技校学院主要教什么?

Unity3D基础(216)
Unity应用的iOS热更新
作者:丁治宇
Unity TechnologiesChina
? 什么是热更新
? 为何要热更新
? 如何在iOS 上对Unity 应用进行热更新
? 支持Unity iOS 热更新的各种Lua 插件的对比
什么是热更新
? 广义定义
? 无需关闭服务器,不停机状态下修复,更新资源等,重点是更新逻辑代码。
? 狭义定义( iOS热更新)
? 无需将代码重新打包提交至AppStore,即可更新客户端的执行代码,即不用app而自动更新程序。
? 苹果禁止了C#的部分反射操作,禁止JIT(即时编译,程序运行时创建并运行新代码),不允许逻辑热更新,只允许使用AssetBundle进行资源热更新。
为何要热更新
? 缩短用户获取新版应用的客户端的流程,改善用户体验。
? 具体到iOS平台的应用上,有以下几个原因
? App Store的审核周期难控制。
? 手机应用更新频繁。
? 对于大型应用,更新成本太大。
? 终极状态
? 不重新下载、不停机状态下完全变换一个应用的内容。
如何在iOS 上对Unity 应用进行热更新
? Android 应用的热更新
? 将执行代码预编译为assemblydll。
? 将代码作为TextAsset打包进Assetbundle。
? 运行时,使用Reflection机制实现代码的功能。
? 更新相应的Assetbundle即可实现热更新。
? Android 与iOS 热更新的 异同
? 苹果官方禁止iOS下的程序热更新;JIT在iOS下无效。
? 热更新方案:Unity+Lua插件。
? 使用Lua 插件进行iOS 热更新的 原理
? Unity 热更新的注意点
? 需要更新的代码、资源,都必须打包成AssetBundle(建议使用未压缩的格式打包)
? 熟悉Unity的几个重要的路径
? Resources(只读)
? StreamingAssets(只读)
? Application.dataPath(只读)
? Application.persistentDataPath(可读写)
? 重要路径之 之Resources
? Resources文件夹下的资源无论使用与否都会被打包
? 资源会被压缩,转化成二进制
? 打包后文件夹下的资源只读
? 无法动态更改,无法做热更新
? 使用Resources.Load加载
? 重要路径之StreamingAssets
? 流数据的缓存目录
? 文件夹下的资源无论使用与否都会被打包
? 资源不会被压缩和加密
? 打包后文件夹下的资源只读,主要存放二进制文件
? 无法做热更新
? WWW类加载(一般用CreateFromFile ,若资源是AssetBundle,依据其打包方式看是否是压缩的来决定)
? 相对路径,具体路径依赖于实际平台
?Application.streamingAssetsPath
? IOS: Application.dataPath + “/Raw” 或Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxx.app/Data/Raw
? 重要路径之Application.dataPath
? 游戏的数据文件夹的路径(例如在Editor中的Assets)
? 很少用到
? 无法做热更新
? IOS路径: Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxx.app/Data
? 重要路径之Application.persistentDataPath
? 持久化数据存储目录的路径( 沙盒目录,打包之前不存在 )
? 文件夹下的资源无论使用与否都会被打包
? 运行时有效,可读写
? 无内容限制,从StreamingAsset中读取二进制文件或从AssetBundle读取文件来写入PersistentDataPath中
? 适合热更新
? IOS路径: Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/Documents
? 使用Lua 插件进行iOS 热更新的总体流程
支持Unity iOS 热更新的各种Lua 插件的对比
? uLua(asset store)
? uLua插件原生版本,开山鼻祖
? 不会产生静态代码
? 反射机制,效率低下,速度慢,gcalloc频繁
? 已停止更新维护,不支持Unity5.x,淡出主流
? uLua & cstoLua
? 开发平台成熟稳定,Bug修复迅速
? 开发者众多,资源丰富
? 静态方法,性能优
?有成功商业产品案例(啪啪三国、超神战队、酷鱼吧捕鱼、绝地战警、这不是刀塔等) 鱼、绝地战警、这不是刀塔等)
? 都是基于原生版本的改进;未来,两者会合并成一个插件
? 静态方法,性能优
? 核心代码简洁
? 资源较少,开发平台不够成熟稳定
? 无 无成功商业产品案例 成功商业产品案例
? 基于原生版本的改进
支持Unity iOS 热更新的各种Lua 插件的对比
? C#Light(L#)
? 淡出主流
? c#实现的Lua,非完整方案
? 淡出主流
支持Unity iOS 热更新的各种Lua 插件的对比
然后就是 uLua 和 sLua的测试代码。
综合来看 肯定是 uLua 会更好一些。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:666561次
积分:11637
积分:11637
排名:第1099名
原创:433篇
转载:475篇
评论:82条
(6)(2)(4)(3)(12)(4)(4)(3)(26)(12)(6)(14)(5)(1)(14)(18)(26)(37)(24)(29)(19)(9)(14)(17)(54)(20)(53)(127)(93)(49)(31)(44)(28)(56)(21)(5)(9)(12)(3)(1)热门关键词:
热门关键词:
上海Unity3d培训之U3D游戏代码为什么热更新
17:29:14 点击: 次&
全新升级 火热来袭,欢迎各同学咨询????????
热更新对于开发者来说是一件麻烦事,特别对于看重效率,便捷性和结构的程序员来说,然而,对于上线才是刚刚开始的网络游戏,特别是手游来说,热更新是极为重要的基础功能。那么U3D游戏代码为什么要热更新呢?下面来看上海Unity3d培训中心的分析
1、适应上线需求
对于手游客户端来说,受到苹果审核的约束, 一次审核提交需要10~20天不等的等待时间。而这段时间,开发进度依然会推进很多。
一旦手游上线,第一个版本在玩家疯狂行为下,出点问题是必然的,所以”上线更”就成了家常便饭。如果你要说,必须大包,无法热更。那么10~20多天后,游戏估计就没啥人了。 更别说渠道,发行投入巨大资金进行推广之下让玩家迎来的一堆bug的版本以及所谓程序员的傲慢和清高。
2、热调试,热开发,热发布
除了线上问题之外,由于Unity3D为了适应64位应用需求,将C#编译出的IL代码利用il2cpp第三方库编译成为C++。效率提升了倒是好,但工程编译和发布时间变得相当感人,没个1~2个小时完全搞不定。即便加装ssd,为了修改一个bug,也不知道要等多少根烟的时间...
只要核心功能不变化的情况下,完全可以让热更新成为开发期间的好工具,lua代码修改后,马上可以在手机上看效果,没有编译,发布的时间损耗,其实反而提升了开发效率。
对于服务器来说,常见游戏类型的玩家一般在半夜的在线人数会急速下降。但是对于比较热门的MMO,以沟通为基础的游戏,半夜也会有很多人在线,因此传统的停服更新对于玩家的热情秒杀很大的。想想看《守望先锋》公测停15天各位是什么感受?所以为了玩家体验,同时保证服务器稳定的前提下,修复一些轻微bug,用热更新再合适不过了。所以老服务器程序员,千万不能以服务器稳定为借口而忽略了玩家体验。技术是用来解决问题的,不是用来装X的。
以上就是上海Unity3d培训之U3D游戏代码为什么热更新的全部内容,更多Unity知识可以登录蓝鸥上海网站了解学习。
上海市松江区泗泾镇九干路168号丽德创业园附1楼蓝鸥科技
电话:021-
阅读上一篇:阅读下一篇:
找课114全国免费课程咨询热线,为您推荐口碑最好的中小学课外辅导、少儿英语、成人英语、出国留学机构(其他课程咨询恕不接待)
北京 400-001-&上海 400-001-&广州 400-001- 深圳 400-001-&天津 400-001-&杭州 400-001- 南京 400-001-&苏州 400-001-&济南 400-001- 青岛 400-001-&武汉 400-001-&重庆 400-001-
成都 400-001-&西安 400-001-&宁波 400-001-大连 400-001-
温馨提示:拨打招生免费咨询电话方法,先拨通400-001-9911,再根据语音提示按分机号,等待接通,欢迎您的来电。(电话接听时间段:周一至周日上午9:00―晚上20:00)
??????????
????????????????????
??????????
??????????
???????????????
音频公开课
9587
3971
4867
3665
4143
4180
5900
3755
14964
4328
6403
6014
5765
11535
9985
9882
8986
16071
13955
13865
三百六十行,欢迎各培训机构加盟【Unity3D】让你的游戏支持热更新
我的图书馆
【Unity3D】让你的游戏支持热更新
&作者:蝎子马戏团& &来源:
1、在Unity中引入lua的主要原因:& &&&1.1,Unity不支持代码热更新& &&&C#在ios平台不支持热更新,在Android平台热更新也比较麻烦,而lua支持热更新。& & 1.2,为了能够前后台复用关键逻辑代码& &&&Unity前台使用C#,后台使用C或C++,属性计算和战斗系统代码无法复用,通过lua可以方便实现前后台关键逻辑代码共用。 2、lua插件的选择:& & 2.1 UniLua& & 云风团队的作品,纯C#实现,但使用起来很麻烦,比较鸡肋。& &&&2.2 uLua& & & &&&uLua是 Lua 、LuaJIT 、LuaInterface的集合,Unity主流的lua插件, AssertStore上售价40美刀。& & C#调用lua脚本和从lua脚本中访问C#对象都方便,接下来我们简单了解一下uLua,然后通过一个简单的Demo来深入了解uLua的使用。 3、C#调用lua脚本& && && &基本保持了和C++相同的调用方式,对于用过tolua++的同学非常亲切& && & // 初始化lua脚本 //FLuaMgr.Instance.Init();
// 调用Test.lua中的 lua脚本函数//FLuaMgr.Instance.PCall("Test.ChangeMyClassProperty");
4、lua访问C#对象属性及函数& && && &uLua利用.Net的反射机制,直接从dll中导出C#类定义。& && && &这一块功能是通过Lua.cs实现的,具体实现机制比较复杂,我们看一个简答的例子:& && && &4.1 导入C#类UnityEngine& &&&= luanet.UnityEngineSystem& && && & = luanet.System
--- UnityEngine Classes---Debug& && && &&&= UnityEngine.DebugGameObject& && &= UnityEngine.GameObjectCamera& && && & = UnityEngine.CameraTransform& && & = UnityEngine.Transform
--- Our Classes ---luanet.load_assembly('Assembly-CSharp')MyClass = luanet.import_type('MyClass') & && & 通过这几句代码,我们就将UnityEngine的Debug、GameObject、Camera、Transform类导入进来了,此外将我们自定义的C#类MyClass也导入进了lua。这比用tolua++导出C++对象简单多了。& && &&&4.2 在lua使用使用C#导入类local myClass = GameObject.Find("MyClass"):GetComponent("MyClass");
myClass.DisplayName = "ChangedName From Lua";local box1 = myClassrawBox(1); & && && &GameObject .Find和GetComponent是Unity常用函数,通过uLua可以在lua中直接访问,这是uLua的重要优势。& && && &此外我们定义类MyClass的属性和函数也可以同样方便的调用,这样扩展起来非常方便。
5、加载lua脚本的优化& && && &创建一个LuaState, 然后使用LuaState的DoString就可以加载lua脚本;LuaState luaState = new LuaState();luaState.DoString("print('hello world')"); & && && &这样虽然可以加载lua,但是这并不能满项目需求,笔者封装了一个FLuaMgr& && &&&在FLuaMgr的Init函数中加载lua文件下的所有lua文件: // 直接读取TextAsset//private bool DoDirFilesByTextAsset(string path){& & TextAsset[] luaCodes = Resources.LoadAll(path);& & for (int i = 0; i & luaCodes.L i++)& & {& && &&&string fileName = luaCodes.& && &&&//Debug.Log("FileName=" + fileName);& && &&&int prefabEnd = fileName.LastIndexOf(GenLuaFileEnding); // 判断是否是lua文件 //& && &&&if (prefabEnd &= 0)& && &&&{& && && && &FDebug.LogWarning("FileName=" + fileName + " is not a lua file!");& && && && && && &&&}
& && &&&DoString(luaCodes.text, fileName);& & }& & // 卸载lua脚本文件 //& & Resources.UnloadUnusedAssets();& &} & && &5.1 Unity 只能识别.txt文件文件的解决办法& && & 在Assets/Lua 文件夹编辑lua脚本,然后通过ConvertToLuaTxt 转化到Assets/LuaGen文件夹下。具体内容参考Demo中的ConvertToLuaTxt.cs 6、Unity中嵌入lua的Demo介绍& && && &将附件中的uLuaExample.unitypackage 导入到一个空的Unity项目,然后打开LuaTest.unity,直接运行,lua脚本调用MyClass的函数绘制若干个矩形、球和圆柱体。-- 调用C#对象的函数function DrawSomeBoxes()& && && && &&&-- 直接调用GameObject的导出函数Find,类导出查看ImportClass.lua& && && && &&&local myClass = GameObject.Find("MyClass"):GetComponent("MyClass");
& && && && &&&-- 调用我们的C#类MyClass.DrawBox(float size)& && && && &&&local box1 = myClassrawBox(1);& && && && &&&box1.transform.position = Vector3(-5,-2.5,0);
& && && && &&&local box2 = myClassrawBox(1.25);& && && && &&&box2.transform.position = Vector3(-5,-1.0,0);
& && && && &&&local box3 = myClassrawBox(1.5);& && && && &&&box3.transform.position = Vector3(-5,1.5,0);end & && &&&执行效果图:& && && &
& & & & & & & & & & & & & & & & & && && &创建对象和位置排列逻辑由Test.lua实现。7、lua执行效率& && && &7.1 加载所有lua脚本的消耗& && && && && && & 笔者所在的项目有13个lua文件,lua脚本总行数1127行。& && && && && && &
& & & & & & & & & & & & & & & & & && && && && && & 在华为荣耀3手机上初始化lua平均耗时270ms。& && && &7.2 执行简单的lua函数的效率:function SimpleLuaFunc()& && && && &&&local id = 1;& && && && &&&if (id & 1)& && && && &&&then& && && && && && && && && & print("id & 1");& && && && &&&endend & && && &
& & & & & & & & & & & & & & & &
一次简单的lua调用平均耗时9微秒,性能在可接受的范围内。范围内。
馆藏&21135
TA的推荐TA的最新馆藏[转]&[转]&[转]&[转]&[转]&[转]&[转]&[转]&[转]&[转]&[转]&[转]&由于我本人是做游戏开发工作的,所以平时也常常加一些玩家的群。而一些困扰玩家的问题,同样也困扰着我们这些手机游戏开发者。这不最近我看自己加的一些群,常常会有人问为啥这个游戏一更新就要重新下载,而不能游戏内更新呢?作为游戏开发者,或者说Unity3D程序员,我们都清楚Unity3D不支持热更新,甚至于在iOS平台上生成新的代码都会导致游戏报错崩溃(我之所以在此处强调生成新的代码这几个字,就是提醒各位不要混淆Reflection.Emit和反射)。但我们是否和普通的玩家一样,看到的仅仅是“不能”的现象,而不了解“不能”背后的原因呢?那今天我就抛砖引玉,写写自己对这个问题的想法,聊聊到底是谁偷了玩家的热更新。
0x01 从一个常见的报错说起
不知道各位看官中的Unity3D程序员在开发iOS版本的时候是否也曾经碰到过这样的报错:
ExecutionEngineException: Attempting to JIT compile method 'XXXX'while running with --aot-only.
这个报错的意思很明确,说的也很具体,翻译成中文的大意就是在使用--aot-only这个选项的前提下,又试图去使用JIT编译器编译XXX方法。
那么不知道是否会有看官觉得这个问题兴许是程序跑在iOS平台上时,不小心犯了iOS的忌讳,使用了JIT(假设此时我们还不知道为何使用JIT是iOS的忌讳)去动态编译代码导致的iOS的报错呢?
答案是否定的。
又或者更进一步,看到“ExecutionEngineException”,似乎和iOS平台的异常没什么太大的关联,那就把责任定位在Unity3D的引擎上好了。一定是游戏引擎此时不支持JIT编译了。也不全对,不过离真相很近了。
各位想想,能涉及到编译的被怀疑的对象还能有谁呢?
好了,不卖关子了。这个异常其实是Mono的异常。换言之,Unity3D使用了Mono来编译,所以Unity3D的嫌疑被排除。而iOS并没有因为生成或者运行动态生成的代码而报错,换言之这个异常发生在触发iOS异常之前,所以说Mono在iOS平台上进行JIT编译之前就先一步让程序崩溃了。
说到这里,就绕不过Mono是如何编译代码这个话题了。如果我们去Mono的托管页面看它的源码,就可以简单对它的目录结构做一个简单的分析,我就简单总结一下Mono编译部分的目录结构:
好啦,具体到咱们要聊的JIT编译,我们需要看的就是mono目录下的mini文件夹中的文件了,这个文件夹中的.c文件们实现了JIT编译。
不过这里我想来一个倒叙,也就是先直接定位这个报错 “ExecutionEngineException: Attempting to JIT compile method 'XXXX' while running with --aot-only.” 的位置,然后再探明它究竟是如何被触发的。
这样,我们就来到了mono的JIT编译器目录mini下的mini.c文件。这里就是JIT的逻辑实现。而那段报错呢?在mini.c文件中是这样处理的:
if (mono_aot_only) {
char *fullname = mono_method_full_name (method, TRUE);
char *msg = g_strdup_printf ("Attempting to JIT compile method '%s' while
running with --aot-only. See /ios/about/limitations for more information.\n", fullname);
*jit_ex = mono_get_exception_execution_engine (msg);
g_free (fullname);
g_free (msg);
return NULL;
mono_aot_only?没错,只要我们设定mono的编译模式为full-aot(比如打iOS安装包的时候),则在运行时试图使用JIT编译时,mono自身的JIT编译器就会禁止这种行为进而报告这个异常。JIT编译的过程根本还没开始,就被自己扼杀了。
那么JIT究竟是什么洪水猛兽?为何iOS这么忌讳它呢?那就不得不聊聊JIT本尊了。
0x02 美丽的JIT
名如其特点,JIT——just in time,即时编译。
什么?这就是你要告诉大家伙的?这不是人人都知道的嘛?而且网上一搜也全都是JIT=just in time了事。好吧好吧,我知错啦。那就认真的定义一下JIT:
一个程序在它运行的时候创建并且运行了全新的代码,而并非那些最初作为这个程序的一部分保存在硬盘上的固有的代码。就叫JIT。
有几点需要注意:
程序需要运行
生成的代码是新的代码,并非作为原始程序的一部分被存在磁盘上的那些代码
不光生成代码,还要运行。
需要提醒的是第3点,也就是JIT不光是生成新的代码,它还会运行新生成的代码。之后我们会就这个话题展开。不过在之前我还是要解释一下,为何称JIT是美丽的。
举个例子:
比如你某一天突然穿越成为了一个优秀的学者(好吧好吧,这个貌似不是必须要穿越),现在要去一个语言不通的国家做一系列讲座。面对语言不通的窘境,如何才不出丑呢?
我有三条方案:
在家的时候雇人把所有的讲稿全部翻译一遍。这是最省事的做法,但却缺乏灵活性。比如临时有更好的话题或者点子,也只能恨自己没有好好学外语了。
雇一个翻译和你一起出发,你说啥他就翻译成啥。这样就不存在灵活性的问题,因为完全是同步的。不过缺点同样明显,翻译要翻译很多话,包括你重复说的话。所以需要的时间要远远高于方案1。
雇一个翻译和你一起出发,但不是你说啥他就翻译啥,而是记录翻译过的话,遇到曾经翻译过的就不会再翻译了。你自己就可以根据之前的翻译记录和别人交流了。
看完这三条方案,各位看官心中更喜欢哪个呢?
我个人的答案是方案3,因为这便是JIT的道。所以说JIT的美丽,就在于即保留了对代码优化的灵活性,也兼具对热点代码进行重复利用的功能。
模拟一下JIT的过程
JIT这么好,那它是如何实现既生成新代码,又能运行新代码的呢?
编译器如何生成代码很多文章都有涉及,我就不多在此着墨了。下面我就着重和各位聊聊,如何运行新生成的代码。
首先我们要知道生成的所谓机器码到底是神马东西。一行看上去只是处理几个数字的代码,蕴含着的就是机器码。
unsigned char[] macCode = {0x48, 0x8b, 0x07};
macCode对应的汇编指令就是:
(%rdi),%rax
其实可以看出机器码就是比特流,所以将它加载进内存并不困难。而问题是应该如何执行。
好啦。下面我们就模拟一下执行新生成的机器码的过程。假设JIT已经为我们编译出了新的机器码,是一个求和函数的机器码:
long add(long num) {
return num + 1;
//对应的机器码
0x48, 0x83, 0xc0, 0x01,
首先,动态的在内存上创建函数之前,我们需要在内存上分配空间。具体到模拟动态创建函数,其实就是将对应的机器码映射到内存空间中。这里我们使用c语言做实验,利用mmap函数来实现这一点。因为我们想要把已经是比特流的“求和函数”在内存中创建出来,同时还要运行它。所以mmap有几个参数需要注意一下。
代表映射区域的保护方式,有下列组合:
映射区域可被执行; PROT_READ
映射区域可被读取; PROT_WRITE
映射区域可被写入;
#include&stdio.h&
#include &stdlib.h&
#include &string.h&
#include &unistd.h&
#include &sys/mman.h&
//分配内存
void* create_space(size_t size) {
void* ptr = mmap(0, size,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANON,
这样我们就获得了一块分配给我们存放代码的空间。下一步就是实现一个方法将机器码,也就是比特流拷贝到分配给我们的那块空间上去。使用memcpy即可。
//在内存中创建函数
void copy_code_2_space(unsigned char* m) {
unsigned char macCode[] = {
0x48, 0x83, 0xc0, 0x01,
memcpy(m, macCode, sizeof(macCode));
然后我们在写一个main函数来处理整个逻辑:
#include&stdio.h&
#include &stdlib.h&
#include &string.h&
#include &unistd.h&
#include &sys/mman.h&
//分配内存
void* create_space(size_t size) {
void* ptr = mmap(0, size,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANON,
//在内存中创建函数
void copy_code_2_space(unsigned char* addr) {
unsigned char macCode[] = {
0x48, 0x83, 0xc0, 0x01,
memcpy(addr, macCode, sizeof(macCode));
//main 声明一个函数指针TestFun用来指向我们的求和函数在内存中的地址
int main(int argc, char** argv) {
const size_t SIZE = 1024;
typedef long (*TestFun)(long);
void* addr = create_space(SIZE);
copy_code_2_space(addr);
TestFun test =
int result = test(1);
printf("result = %d\n", result);
编译并且运行看一下结果:
gcc testFun.c
留给我们的难题
OK,到此为止,一切都很顺利。这个例子模拟了动态代码在内存上的生成,和之后的运行。似乎没有什么问题呀?可不知道各位是否忽略了一个前提?那就是我们为这块区域设置的保护模式可是:可读,可写,可执行的啊!如果没有内存可读写可执行的权限,我们的实验还能成功吗?
让我们把create_space函数中的“可执行”PROT_EXEC权限去掉,看看结果会是怎样的一番景象。
修改代码,同时将刚才生成的可执行文件a.out删除重新生成运行。
vim testFun.c
gcc testFun.c
结果。。。报错了!
所以,iOS并非把JIT禁止了。或者换个句式讲,iOS封了内存的可执行权限,相当于变相的封锁了JIT这种编译方式。
你可能感兴趣的文章
2 收藏,1k
10 收藏,1.3k
2 收藏,712
本作品采用 署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可
两张图木有显示
分享到微博?
技术专栏,帮你记录编程中的点滴,提升你对技术的理解收藏感兴趣的文章,丰富自己的知识库
明天提醒我
我要该,理由是:
扫扫下载 App

我要回帖

更多关于 新华电脑技校 的文章

 

随机推荐