AssebBundle是Unity引擎处理资源方式,一个AssetBundle可以包含一个或多个Unity识别的Asset,今天 就简单聊聊AssetBundle关键技术点。
使用BundleName方式构建AssetBundle。
加载AssetBundle.LoadFromFile(Async)加载资源只加载AssetBundle头信息,是在Unity5.4之后版本支持的。
熟悉3D渲染顺序,对于游戏开发来说非常重要,不仅仅是基础知识,更是一项非常重要的工作技能,相信很多 同学对于Unity引擎的渲染顺序还是没有没有搞清楚,如果你已经非常清楚了,完全跳过阅读,避免浪费你宝 贵的时间。
BlendOp Add | Min | Max | Sub | RevSub 混合的方式 |
备注: 顺序可能存在问题,随时更正。
Sprite组件和Canvas组件 默认使用的Shader未写入z缓冲,但是进行z缓冲测试 默认 RenderQueue 均为 Transfront=3000
解释:
lua作为一种脚本语言,主要的用途有两个方面,一方面是作为项目或应用的配置脚本使用,另一方面 作为应用的逻辑编写脚本使用,作为逻辑编写脚本主要提交游戏方便,今天主要讨论lua编写时的一些 影响性能的注意事项,希望对于热爱lua这门脚本语言的同学以启发。
为了进行性能,这里给出一个简单性能测试通用函数,下面的测试都将使用这个函数进行测试,废话不 多说,直接贴上代码。
function profiler(func, count)
if type(func) ~= "function" then
return
end
if count == nil then
count = 100
end
local total_time = 0
for i = 1, count do
local startTime = os.clock()
func()
local endTime = os.clock()
total_time = total_time + (endTime - startTime)
end
local average_time = total_time / count
print("function run times=" .. count .. ",average cost time=".. average_time)
end
table是Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数组、字典等。 下面给出两种方式看看性能数据
function func_table_1(c)
for i = 1, c do
local t = {}
t[1] = 1
t[2] = 2
t[3] = 3
end
end
profiler(function()
func_table_1(100000)
end)
测试结果: function run times=100,average cost time=0.0631
function func_table_2(c)
for i = 1, c do
local t = {0, 0, 0}
t[1] = 1
t[2] = 2
t[3] = 3
end
end
profiler(function()
func_table_2(100000)
end)
测试结果: function run times=100,average cost time=0.0279
上面两份测试结果可以看出第二种方式速度差不多是第一种的3倍,主要原因是: 默认创建出来的的表,都是空的,在插入元素的过程,逐渐翻倍扩大,从0到1, 1到2,2到4,…都会触发realloc,同时把旧元素拷贝到新申请的空间中,对于最终有成千上万个元素的table,扩张的开销可以接受,但是对于大量生成小的table的场景,会明显拖慢性能,可以通过lua的构造函数,让Lua的编译器预分配空间,Hash的扩展也是同样的原理。
全局变量和局部变量是编写代码时时刻需要涉及的,但是什么时候使用全局,什么时候使用局部,这些都是非常影响代码运行效率,先分析两种方式,看看性能结果。
function func_var_1(c)
r = 0
for i = 1, c do
r = r + i
math.type(i)
end
end
profiler(function()
func_var_1(1000000)
end)
测试结果:function run times=100,average cost 0.06832
function func_var_2(c)
local r = 0
local t = math.type
for i = 1, c do
r = r + i
t(i)
end
end
profiler(function()
func_var_2(1000000)
end)
测试结果:function run times=100,average cost time=0.03914
上面测试结果可以看出第二种方式也是第一种方式的2倍效率,其中原因主要是因为 全局变量涉及的到表的查询和修改,所以性能要显著差于local变量,对于函数也是 相同的道理。
lua5.0之后采用是3色标记的GC回收算法,相比较5.0的双色标记算法的有点是算法 可以分布进行计算,避免阻塞影响逻辑脚本性能,具体实现和影响性能测试改天单 独写一篇文章讲解,今天简单说如何避免GC的开销。
GC:Garbage Collection 垃圾收集
(1)GC:Garbage Collection 垃圾收集。这里所谓的垃圾指的是在系统运行过程当中所产生的一些无用的对象,这些对象占据着一定的内存空间,如果长期不被释放,可能导致OOM。
在C/C++里是由程序猿自己去申请、管理和释放内存空间,因此没有GC的概念。而在Java中,后台专门有一个专门用于垃圾回收的线程来进行监控、扫描,自动将一些无用的内存进行释放,这就是垃圾收集的一个基本思想,目的在于防止由程序猿引入的人为的内存泄露。
(2)事实上,GC的历史比Java久远,1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言。当Lisp还在胚胎时期时,人们就在思考GC需要完成的3件事情:哪些内存需要回收?什么时候回收?如何回收?
(3)内存区域中的程序计数器、虚拟机栈、本地方法栈这3个区域随着线程而生,线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈的操作,每个栈帧中分配多少内存基本是在类结构确定下来时就已知的。在这几个区域不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟着回收了。
概念
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
优点
实现比较简单,判断的效率也比较高,使用的地方也是比较多的。
缺点
概念
由于引用计数算法的缺陷,所以JVM一般会采用一种新的算法,叫做根搜索算法。它的处理方式就是,设立若干种根对象,当任何一个根对象到某一个对象均不可达时,则认为这个对象是可以被回收的。
算法分析
可达性分析
从根(GC Roots)的对象作为起始点,开始向下搜索,搜索所走过的路径称为“引用链”,当一个对象到GC Roots没有任何引用链相连(用图论的概念来讲,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
根对象(GC Roots)
一般来说根对象主要包含以下几个方便,当前不用语言的实现会有所不同。
概念
标记-清除算法是现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象;然后,在清除阶段,清除所有未被标记的对象。
算法分析
标记
标记的过程其实就是,遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。
清除
清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。
缺点
首先,它的缺点就是效率比较低(递归与全堆对象遍历),导致stop the world的时间比较长,尤其对于交互式的应用程序来说简直是无法接受。试想一下,如果你玩一个网站,这个网站一个小时就挂五分钟,你还玩吗?
第二点主要的缺点,则是这种方式清理出来的空闲内存是不连续的,这点不难理解,我们的死亡对象都是随即的出现在内存的各个角落的,现在把它们清除之后,内存的布局自然会乱七八糟。而为了应付这一点,JVM就不得不维持一个内存的空闲列表,这又是一种开销。而且在分配数组对象的时候,寻找连续的内存空间会不太好找。
概念
将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
优点
回收完成后对象的空间是连续的,解决内存碎片的问题,实现比较简单,效率也比较高。
缺点
空间的浪费。
概念
标记-压缩算法适合用于存活对象较多的场合,如老年代。它在标记-清除算法的基础上做了一些优化。和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记;但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端;之后,清理边界外所有的空间。
算法分析
标记
它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。
整理
移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。
优点
标记/整理算法不仅可以弥补标记/清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内存减半的高额代价。
缺点
效率:复制算法>标记/整理算法>标记/清除算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如此)。
内存整齐度:复制算法=标记/整理算法>标记/清除算法。
内存利用率:标记/整理算法=标记/清除算法>复制算法。
介绍
当前商业虚拟机的GC都是采用的“分代收集算法”,这并不是什么新的思想,只是根据对象的存活周期的不同将内存划分为几块儿。一般是把Java堆分为新生代和老年代:短命对象归为新生代,长命对象归为老年代。
概念
案例
热更新或热修复是现在手游必不可少的东西,这里不介绍怎么进行资源,采用什么脚本进行 游戏的开发,我们假定我们现在需要更新游戏的资源、脚本、配置表等数据资源,该如何来 控制资源的版本呢。
游戏版本一般推荐使用3位表示
获得服务器版本号,与客户端当前版本号作比较,如果是大版本更新,去商店下载,如果是小版本更新则去资源服务器下载。
拼接资源服务器地址,获得versionFile下载地址,根据大版本号,手机平台和资源版本号得到下载地址为:baseurl/ios/1.0/170513.1/VersionFile.txt。
下载VersionFile,并与上一次手机缓存的VersionFile作对比,生成从上一次更新到这次更新需要下载的文件列表,然后逐一去对应的地址下载下载,资源地址需要根据VersionFile里面的版本号去生成。例如:baseurl/ios/1.0/170512.1/test.txt,baseurl/ios/1.0/170403.1/test.txt。每一个资源去对应的版本文件夹下去下载。
下载完所有文件后,保存本次下载的VersionFile,等待下一次更新时与新的versionFile再做比较。
当有大版本更新时,更新结束后,应该删除之前缓存的所有更新,因为新包都是默认带着所有资源。这样既减少包的大小,也避免了游戏运行时加载错资源。因为VersionFile存放了我们所有的更新文件,所以游戏加载资源会根据VersionFile是否存在该资源来决定去加载下载目录里的资源还是去加载包里面自带的资源。因此游戏包更新后为了避免加载到上一个版本的资源,应该删除掉之前下载目录里的文件。