UWA每周推送的知识型栏目《厚积薄发 | 技术分享》曾经随同大家走过了252个工作周。精选了2020年十大精彩问答分享给大家,期待2021年UWA问答持续有您的陪伴。

UWA 问答社区:answer.uwa4d.com
UWA QQ群2:793972859(原群已满员)


Q1:IL2CPP的内存问题

最近看问答下面有个对于IL2CPP和Mono的比照,看到IL2CPP内存冲高会降落。对于这个,我问了Unity的官网技术,答复是:你好,Unity有本人的GC机制,为了防止频繁向操作系统申请/开释内存,Reserved Mono值会放弃在肯定区间内,达到某些条件或在某些非凡状况才会触发GC。 有人说是:“内存池治理逻辑都是一样的,属于下层治理一样。它们只是两头语言不一样而已,也是只涨不降。”也有其余大佬说是IL2CPP冲高会降落。当初很困惑,求解答。

A:看下来题主说的内存冲高不降,波及两个指标,一个是Profiler里的Reserved Mono,一个是设施内存(PSS)。目前的确没有权威的文档阐明这一点,所以上面通过实在数据来阐明一下。

先说第一个(Reserved Mono)。

  1. 在Script Backend是Mono的状况下,如果抉择的是旧版本里的Mono 2.x,或者新版本里的 .Net 3.5(Runtime Version),那么这个值是只升不降的。比方这个数据,Unused曾经很高了,但也不会降落:

  2. 同样在Script Backend是Mono的状况下,如果抉择的是.Net 4.x (Runtime Version),那么这个值是能够降落的(但不确定具体是从哪个版本开始的)。比方这个数据,能够看出尽管会降落,也并不是频繁执行降落操作的:

  3. 最初Script Backend是IL2CPP的状况下,那么这个值也是能够降落的。比方这个数据,看上去和下面的状况相差不是太大:

而对于第二个,设施内存。这个就和安卓零碎的内存管理机制无关了,即便Unity把Reserved Mono升高了,缩小了本身的内存占用,零碎也不肯定会立刻会把这块内存开释,所以这里的行为就很难说分明了。

该答复由UWA提供


Q2:加载配置内存过大问题

配置表太多占用内存过大时,除了采纳Sqlite,还有什么好的解决办法没有,有没有大佬是否指导下。FlatBuffer不必全副进内存吗?如果不全副进内存,访问速度如何呢?

A1:第一问题参考如下:

  1. 能够针对反复数据进行剔除,尤其是一些字符串的配置。在配置导出时把这样的数据提取一份,其余用到的中央只是援用,会节俭不少。
  2. 数据类型要正当。
  3. 能够应用相似FlatBuffer/ZeroFormatter的提早加载的思路,在真正应用时再去反序列化。一次游戏过程中理论用到的配置量比拟无限,应用这种策略能够尽可能的缩小不必要数据的加载。

第二个问题参考如下:
咱们上个我的项目也是到前期优化时遇到相似问题,只是参考了这种思路,并没有进行齐全替换。咱们过后在打包时,会对配置以行为单位,进行Offset和Length的计算,在Runtime阶段,初始加载只会加载每行的ID,对应的这一行的Offset和Length,而后后续逻辑调用配置表接口拿数据的时候,如果发现没有反序列化过,就依据Offset和Length再去构建一下相应的数据提供给下层。访问速度的话必定不如开始间接全副加载好,但咱们测下来影响不大。

感激范君@UWA问答社区提供了答复

A2:字符串吃内存不说了,尽量少用或者复用。表格中比拟多的会是那种:攻打-1000;进攻-2000;血量-3000,每个int都是4个字节,数量多了会顶不住。这种能够思考用一个int32/int64/uint32/uint64 去存多个数值。

感激萧小俊@UWA问答社区提供了答复


Q3:Instruments如何看Mono内存调配

例如在调配了一个10MB数组,对应在Unity Profiler中会看到开拓了至多10MB大小的Mono内存。

那么在Instruments中,如何查看调配的内存信息呢?Allocations中的信息是此过程中调配的所有内存信息吗,尝试调配过100MB内存,Allocations中的统计没有任何增长。

A:我这边也做了测试:

创立了100MB大小的int数组,Size理论应该是400MB。

而后到Profiler察看:

能够看到ManagedHeap正确调配了这400MB的空间。

而后打包iOS后到xCode运行,运行前首先吧Run这个Scheme的Malloc Stack勾上:

Run当前点选Memory并导出Memory Graph来察看:

因为应用程序的内存都是在VirtualMemory空间调配的,因而查看VM Regions的VM_ALLOCATE局部。

于是就可已发现128X3+16刚好400MB的调配。
调用堆栈也很好确定:

正式咱们的测试代码。

而后咱们来看Instruments。
首先是Allocations局部,有一点要留神,该栏的下部有一些选项:

留神最初一个选项,如果抉择第一个:
All Heap & Anonymous VM,All Heap对应App理论调配的物理空间,不蕴含VM,

Anonymous VM的官网解释是:
interesting VM regions such as graphics- and Core Data-related. Hides mapped files, dylibs, and some large reserved VM regions。

因而一些比拟大的预留调配空间是不会显示的。
将这个选项切换为All VM Regions,就能看到调配的400MB了:

并且左边详情页面也正确显示了调用堆栈:

另外咱们还能够从VM Tracker来察看,关上VMTracker的Snapshots:

于是就能看到这400MB的具体调配信息:

能够发现,Virutal Size略大于400MB,因为程序其余局部也要申请一些内存。而这400MB又别离保留在Resident和Swapped内,其中Resident局部又根本等于Dirty Size,阐明这部分大小的空间被标记了Dirty是不能被替换进来的,剩下240MB左右空间是Clean空间,能够临时被替换进来以保障有足够的物理空间能应用。这也是因为咱们只是申请了这部分空间,并没有进行具体的赋值初始化和应用。

那如果赋值应用了呢?批改代码测试:

运行Instruments后再察看:

能够分明的发现这400MB都在Dirty Size内。这种状况真正会给该App和iOS以内存压力。

举荐浏览:
《写给Unity开发者的iOS内存调试指南》
《Understanding iOS Memory (WiP)》

感激黄程@UWA问答社区提供了答复


Q4:URP对于多个摄相机的性能优化

URP7.4.3,除开主相机外,还有一个子相机,用于将照到的模型渲到游戏主界面UI上,在Profiler中看到以下状况:

能够看到,在子相机中也进行了包含对LOD的计算,但子相机的Cullingmask只开了一个名为RTModel的Layer,在这一层里只有一个3D对象。按说子相机CullScriptable这块开销不应该有才对。

目前狐疑可能的起因是URP会对每个Base Camera都进行这部分的计算,但如果用Overlay相机,又无奈用原来的形式将相机的targetTexture渲到一张RawImage上了,有人遇到过么?

A:题主的纳闷是:子相机的CullScriptable这块的开销不应该有那么大对吧(毕竟只有一个物件)?

这里有两个问题:

  1. Culling到底做了什么,只有一个物件为什么要Culling那么久(难道只有一个物件也要做很多的筹备工作)?
  2. 在Profiler外面看到的数据真的是实在数据吗?也就是说,子相机的Culling真的做了1.68ms吗?

抛开这两个问题,也能够有更好的做法:
咱们一共两个相机,主相机和UI相机,那么UI上显示的3D物件怎么办呢?
咱们有个虚构相机,所谓相机,其实就是做一个VP矩阵,做一个RT,绘制可见的物件就能够了。应用Unity的SRP,随机选一个中央,设置VP矩阵,设置RT,接着绘制指定的物件(UI中所有的3D物件都会挂在这个物件上面),而后这个RT就能够随便应用了。

如果一个UI上有两个3D物件,尽量都放在一个RT上;如果不行,就放在两个或更多的RT上,只是会多几个绘制命令。几个RT(还不须要是全屏的),而且会多几个Swap RT的操作。因为咱们我的项目没需要须要若干RT,所以假如一下,在这种须要若干RT的状况下,也能够用一个RT加多个Viewport来解决的。这个代码都是现成的,参考一下Cascade Shadow Map的做法,这样Swap RT也就省了。

综上所述,既然你都晓得本人要绘制什么,就不要给Unity Culling的机会了。

在Development Build中连真机看到的性能数据,是实在数据吗?目前在应用相似于HLOD的形式来缩小掉这个LOD的微小开销。楼上说的“设置一下VP矩阵,设置RT”,还不太分明这个VP矩阵的操作具体是个什么,可否详解下或者举荐些相干材料?

A:你提的HLOD和LOD和下面的Culling没关系。VP矩阵就是view矩阵和projection矩阵。相机的作用就是提供这俩矩阵的。

如果你在管线外面设置了相应的矩阵,而后绘制指定的物件,就能够齐全不必多一个相机,毕竟多一个相机就多一个Culling。

如果你对VP矩阵不相熟,不分明怎么实现,也简略。仍然用一个额定相机,关上这个相机的Culling,而后在渲染pass中,不要绘制cullingresult.visibleobject,而间接用Graphics.DrawMesh或者CommandBuffer.DrawMesh绘制你要显示的那个3D Object的物件就好了。

感激王烁@UWA问答社区提供了答复


Q5:对于_CameraDepthTexture的纳闷

如果开启_CameraDepthTexture,Camera就须要渲染一遍场景内所有带有ShadowCaster的可见物体的Pass来实现深度图。

然而场景中的物体在开启ZWrite的时候就把深度写进了Depth Buffer中了,间接取得这个Depth Buffer是不是比近乎DrawCall翻倍的形式更有效率呢?还是Unity在这方面有什么思考?

另外,问一个更理论的问题:
咱们的我的项目须要渲染场景的中湖水的深度成果,所有不通明的场景物体的材质都是关联同样一个Shader,这个Shader是带有ShadowCaster的。然而只有个别插入水中的物体须要去渲染ShadowCaster的Pass,有没有办法在不减少Shader的状况下,让没有插入水里的物体不渲染Shadow Caster Pass呢?咱们用的是Built-in的渲染管线。

A1:第一个问题,能够参考这个问题中Unity官网人员的回复。
外面讲了两个起因,第一是对于非全屏渲染的状况,原本是想拿对应相机渲染的深度,然而Depth Buffer是全屏的。第二个起因是因为很多平台不反对间接拿Depth Buffer的数据。

参考网页:
https://forum.unity.com/threads/poor-performance-of-updatedepthtexture-why-is-it-even-needed.197455/

另外查FrameBufferFetch相干问题的时候看到Unity论坛上另外一个贴子外面的答复。外面说到Unity反对了FrameBufferFetch,然而不反对DepthBuffer的获取。

参考网页:
https://forum.unity.com/threads/pixel-local-storage-and-frame-buffer-fetch-on-mobile-devices.604186/

第二个问题,如果不减少Shader,目前没想到其余好的办法。
如果能够减少Shader,能够将原来的Shader复制一份,只在ShadowCaster的局部加一个“NeedDepth”这样的Tag,将水下的物体的材质球换成这个Shader,另外做一个只有ShadowCaster并带有“NeedDepth”这个Tag的Shader,这个Shader用来做Replace操作。

额定减少一个Camera,这个Camera追随主相机,或者作为主相机的子节点,创立一个RT,让这个Camera渲染到这个RT,在Update外面应用ReplaceShader去画一下,那么只有有那个Tag的ShadowCaster会进行深度渲染,后续能够对这个RT进行编码等操作,这个RT记录的就是水下物体的深度。整个过程看上去没有特地多的额定工作,感觉能够一试(我没有做过测试,但实践上是可行的)。

感激Xuan@UWA问答社区提供了答复

A2:最近本人试着降级我的项目到URP,发现Game View的湖水深度成果没有了,Scene View的是失常的,做了很多试验发现了两个景象。

我之前的湖水Shader的Queue是Geometry + 150,保障本人在其余不通明物体之后渲染。别的物体有Shadow Caster的Pass,湖水没有,这样在别的不通明物体渲染实现后本人能间接用到正确的_CameraDepthTexture。然而在URP下我必须将Render Queue设置到Transparent层才有正确成果。

发现勾选了MSAA抗锯齿后,就有跟Scene View一样正确的深度成果了。

URP默认优先应用Copy的形式在所有不通明Queue的物体渲染完后把深度Copy到_CameraDepthTexture上,我的湖水Queue设置在不通明层了,即便是最初渲染的,他的深度也进入了深度图中,因而成果没了。勾选MSAA会失常是因为MSAA会影响管线无奈用Copy的形式把深度图拷进去(须要Resolve解析),所以URP默认在这种状况下应用老形式通过渲染Depth Only (原Shadow Caster)的Pass失去深度图,因而回到了老形式,我的Shader就又起成果了。

其实本人如果过后看到Framedebugger的时候,是用心读的而不是只是草草看一眼就花心思在自认为的问题起因上会更容易失去答案。

感激题主安日天@UWA问答社区提供了答复


Q6:渲染大面积草地时,如何降低消耗

请问下大家,渲染大面积草地时,如何降低消耗呢?

A1:答复如下:

  1. 应用DrawMeshInstance;
  2. 下面这个API是不会进行视距剔除、视锥体剔除和遮挡剔除的。

上面有两种计划:
a. 将草地按区域分组,用每组的中心点计算视距,根据间隔切换网格LOD或剔除;还能用向量点乘简略剔除在相机前方的草地(留神临界问题)。
b. 借助CullingGroup。
CullingGroup.onStateChanged事件绑定,通过事件触发调整传入;DrawMeshInstanced的Matrix程序和渲染数量(然而DrawMeshInstanced只能指定渲染前几个Matrix);
通过cullingGroup.SetBoundingSpheres实现视锥体剔除和遮挡剔除;
通过cullingGroup.SetBoundingDistances实现视距剔除和LOD。
这个计划最好也进行区域分组,不然CullingGroup的事件监听占用会比拟高,在中端机上4000个监听会占约2ms的大小。

当前如果有比照两种计划的性能,我再进行补充。

附:

  1. 《CullingGroup API的应用阐明》
  2. 《Unity 3D研究院之Lightmap反对GPU Instancing》
  3. 《如何高效应用GPU Instancing技术来进行草丛渲染》
  4. 降级Unity 2018,DrawMeshInstanced不失效的问题

感激题主李先生@UWA问答社区提供了答复

A2:应用Indirect模式的Instancing,配合Compute Shader实现视锥剔除和遮挡剔除。

感激邹春毅@UWA问答社区提供了答复

A3:举荐一个应用URP制作的草海成果,亲测可在Mobile端应用。
Unity URP Mobile Draw Mesh Instanced Indirect Example性能测试:

  • can handle 10 million instances on Samsung Galaxy A70 (GPU =
    adreno612, not a strong GPU), 50~60fps, performance mainly affected
    by visible grass count on screen(draw distance = 125)
  • can handle 10 million instances on Lenovo S5 (GPU = adreno506, a weak GPU), 30fps, performance mainly affected by visible grass count on screen(draw distance = 75)

感激Vest@UWA问答社区提供了答复


Q7:Packages目录下的Shader打包AssetBundle

Unity引入了Package Manager来进行治理插件治理,例如URP引入Packages之后会有目录Packages/com.unity.render-pipelines.universal@7.3.1。求教一下各位,如何对Packages目录下的资源进行AssetBundle打包?

例如,工程目录中有材质球援用到URP的Shader,那么材质球打成AssetBundle之后会将Shader蕴含进去,会有Shader解析耗时。

A1:我这边是只应用SBP而不必Addressable,这样通过应用AssetBundleBuild是能够将Packages中的资源也打包成AssetBundle的。

将所有依赖到的Shader(包含Packages中的)都应用AssetBundleBuild设置到同一个shader.bundle的,打包后也解包确认了,Packages中的Shader也打包在shader.bundle而不会被蕴含在材质AssetBundle中。

感激黄晓文@UWA问答社区提供了答复

A2:我在尝试将现有我的项目转成URP的时候,遇到和Addressable零碎有些不兼容问题。
在打包援用了URP的Shader的Material时会产生Shader被反复打包景象。
如果想把URP的Shader独自打包,又会发现因为不在Assets目录内,Addressable管不到的问题。

我的解决方案是将用到的URP的Shader拷进去,放到Assets目录下通用Shader目录。
当然须要将该Shader改名,并且要留神将外部援用的Shader也一并拷出治理。

不过个别我的项目中应用的Shader往往还是会本人编写,间接应用官网提供总会遇到这种那种问题。因而我也会思考尽量不必官网默认Shader,这时对于URP而言天然更加须要将Shader拷进去进行革新了。

感激黄程@UWA问答社区提供了答复

A3:通过 黄晓文 的思路,曾经解决。
打包AssetBundle最重要的,就是指定资源Path的源门路,以及去往的目标AssetBundle地址,这个问题要害是须要晓得资源在Packages中的源门路。

例如一个Packages下的Shader资源,Lit.shader,通过AssetDatabase.GetAssetPath能够发现门路是:Packages/com.unity.render-pipelines.universal/Shaders/Lit.shader,这个是正确的门路,用它即可。

而谬误的门路别离是:

  1. Unity中看到的:Packages/Universal RP/Shaders/Lit.shader 谬误。
  2. 在文件目录中看到的:Packages/com.unity.render-pipelines.universal@7.3.1/Shaders/Lit.shader谬误

所以得出结论:Packages 下的资源打包,去除一下 @x.y.z 即可。

感激题主一刀@UWA问答社区提供了答复

A4:能够试试应用Scriptableobject或Material援用到Shader文件,而后把ScriptableObject或Material打到AssetBundle里。

感激上午八点@UWA问答社区提供了答复


Q8:Shared UI Mesh内存占用过高

缓存池中的UI如果不暗藏,Shared UI Mesh会比拟高;如果暗藏,Shared UI Mesh会比拟低,然而UI SetActive又有性能耗费,该如何衡量呢?

暗藏缓存池中的UI时,Shared UI Mesh内存占用:

不暗藏缓存池UI时Shared UI Mesh内存占用:

A1:Shared UI Mesh是源自UGUI框架中的一个动态全局变量Graphic.workerMesh:

而workerMesh次要在以下代码中应用:

该函数是在Rebuild单个UI元素的顶点信息,红框里的FillMesh就是将更新后的顶点属性数组设置到workerMesh上,且每次调用都会先进行Clear操作。

看逻辑,这个workerMesh的内存大小应该只和单个UI元素的顶点量无关,但理论测试下来,是和以后所有激活UI元素的顶点总量相干的。

所以,Shared UI Mesh很大,示意以后所有激活UI元素的顶点总量很高。须要对局部简单元素进行简化。

常见的简单元素有:

  1. Tiled模式的Image:该模式下会依据UI元素的区域和纹理分辨率的大小,主动生成适当数量的四边形,一旦纹理分辨率很小,而区域很大时,就会产生大量的顶点。
  2. Outline成果的Text:Outline成果会将Text文本原来的顶点数放大为5倍。
  3. RichText,且蕴含了较多样式的Text:款式标签局部也会产生顶点数。
    注:2和3同时应用时,款式标签局部的顶点数也会放大。

定位的办法:

  1. 初步定位:间接在SceneView下换到线框模式,肉眼找一下简单元素;
  2. 通过Profiler的UI面板,查看各个Canvas下各个Batch产生的顶点数,并查看对应的GameObject即可。

须要留神的是,Canvas组件被禁用的状况下,Profiler里是看不到的,但其下的激活UI元素仍然会影响Shared UI Mesh的大小。

该答复由UWA提供

A2:如果只有SetActive能力升高Shared UI Mesh,如同就没有其余抉择了;然而如果切换layer能够升高,能够抉择该方法。

感激青麈@UWA问答社区提供了答复

A3:也能够试试把Canvas的Enable设置为False。

感激Crazy_Liu@UWA问答社区提供了答复


Q9:Addressable如何删除旧资源

目前打算应用Addressable来实现资源热更新,理论真机测试发现当资源更新后,旧的资源Addressable并不会把它删除,同时能够看到App占用的数据文件会越来越大。请问有什么方法能够把指定的Group或Label的资源删除吗?

试了Addressable.ClearDependencyCacheAsync也不行。理论测试这个接口只能删除最新版本的资源。当本地曾经是最新版本资源时这个接口的确无效;然而如果本地须要更新资源时,这个接口应该也是尝试去删除最新资源,然而本地并没有最新版的资源,所以大略就有效了。

A:调用Addressable.ClearDependencyCacheAsync本质是调用了 “Caching.ClearAllCachedVersions();”。事实上是应用了Unity的Caching零碎。

在Windows编辑器环境测试了一下。
Caching的目录为“C:UsersUserNameAppDataLocalLowUnityProjectFolder”,当失常下载AssetBundle当前,该目录内就呈现 “stage01_298bd883434eedb69ea7316cb23e0b0d662ab7a0d2aa99bc7a2dbb7baec63872” 之类的目录,并保留着以后的AssetBundle版本,当更新AssetBundle并执行下载当前,该目录也会呈现其余AssetBundle的Caching目录。

在执行下载之前,先执行了一下“Caching.ClearCache();”,这时会发现Caching目录内曾经被清空,所有版本的AssetBundle都没有了。下载实现后,该目录只保留了最新的AssetBundle资源。由此可推,即便不通过Addressable零碎,依然能够通过Caching把所有的资源都清理掉。

于是持续进行第二个试验,间断更新几次AssetBundle当前,Caching目录内曾经有多个版本的AssetBundle目录了,当有新的更新后执行 “Addressables.ClearDependencyCacheAsync(key);”,发现确实并没有将旧版本的AssetBundle都删除。因为“Caching.ClearAllCachedVersions”的参数是对应的AssetBundle名字,而Addressables的治理AssetBundle包名是带Hash的,因为每个版本的AssetBundle文件名都不一样的,Caching零碎也就无奈分辨了。

持续做试验,将打包名字去掉Hash,Caching目录内的AssetBundle目录名也不带Hash了,而后间断更新几个版本后发现,该AssetBundle目录内多了几个不同Hash版本的目录,外部才是真正的AssetBundle。于是走“Addressables.ClearDependencyCacheAsync(key)”,这时就能正确地删除旧版本,而后再更新新版本了。

的确不勾选Hash打包能够胜利删除了,这种形式貌似就是笼罩式的打包,不晓得会不会有其余隐患,目前来看够用。

A:隐患就是如果依照Label来做更新查看,原本能够只下载差别局部,然而因为同样应用Label做革除Caching的工作就会造成反复下载本来不必要更新的局部。于是就须要遍历所有的Location而后去查看更新,并将有更新的AssetBundle放入列表,而后再顺次革除旧缓存,从新下载。这样就和传统计划没太大区别了。

请问下不勾选Hash其实就不必革除了吧?名字一样不是会间接笼罩吗?

A:不勾选Hash,只是在Cache的目录内第一级资源同名子目录是统一的,然而外面保留具体数据的子目录是递增的,因为有不同版本。每个版本都会有一个子目录。这个是Caching系统管理的。

如果不勾选Hash,CDN有可能不会更新文件,所以要联合本人的我的项目应用的CDN状况来确定如何治理这块。

我是用Addressables.ClearDependencyCacheAsync(key)并没有革除Cache屡次更新后越来越多。下面所说的“将打包名字去掉Hash”,是指配置中Bundle Naming抉择Fliename选项嘛?所应用的Key是指本次更新的列表吗?

A:就是“配置中Bundle Naming抉择Fliename”,这个Key其实是应该是这个Group名字,对应到打包后的Bundle名字,让Caching零碎搜寻。貌似1.15.X前面这块有一些更新,还没确认过。

感激黄程@UWA问答社区提供了答复

A:在论坛里看到一个计划(11楼),批改了Addressables.ClearDependencyCacheAsync(key)的实现:

首先在Addressables.ClearDependencyCacheForKey中对以后应用的资源进行Cache标记,而后在Addressables.ClearDependencyCacheAsync里革除游戏运行之后未应用的资源。

批改AddressablesImpl.cs文件中的以下四个办法:

  • ClearDependencyCacheForKey(object key)
  • ClearDependencyCacheAsync(object key)
  • ClearDependencyCacheAsync(IList locations)
  • ClearDependencyCacheAsync(IList keys)

须要留神调用Addressables.ClearDependencyCacheAsync的机会。

 internal void ClearDependencyCacheForKey(object key)        {#if ENABLE_CACHING            IList<IResourceLocation> locations;            if (key is IResourceLocation && (key as IResourceLocation).HasDependencies)            {                foreach (var dep in (key as IResourceLocation).Dependencies)                    Caching.ClearAllCachedVersions(Path.GetFileName(dep.InternalId));            }            else if (GetResourceLocations(key, typeof(object), out locations))            {                foreach (var loc in locations)                {                    if (loc.HasDependencies)                    {              foreach (var dep in loc.Dependencies){              // added by Lukas                            AssetBundleRequestOptions options;                            if ((options = dep.Data as AssetBundleRequestOptions) != null)                            {                   //对以后依赖资源进行标记                                Caching.MarkAsUsed(dep.InternalId, Hash128.Parse(options.Hash));                            }                            //原办法无奈删除旧版本的ab              //Caching.ClearAllCachedVersions(Path.GetFileName(dep.InternalId));                        }                       }                }            }#endif        } 
 public AsyncOperationHandle<bool> ClearDependencyCacheAsync(object key)        {            if (ShouldChainRequest)                return ResourceManager.CreateChainOperation(ChainOperation, op => ClearDependencyCacheAsync(key));            ClearDependencyCacheForKey(key);            // added to ClearCache         Caching.ClearCache((int) Time.realtimeSinceStartup + 10);            var completedOp = ResourceManager.CreateCompletedOperation(true, string.Empty);            Release(completedOp);            return completedOp;        } 

感激小魔女纱代酱@UWA问答社区提供了答复

A:咱们用的是笼罩式更新的流程(不是增量更新)。Addressables版本是1.15.1。

在把Bundle Naming设置为Filename后发现,在Caching中的AssetBundle目录还是带有Hash值的,这个和楼上的解释不统一,不晓得是不是版本的起因。

图中可看到AssetBundle包名曾经是Group的名字了,然而下载到Caching中还是有 Hash。

而后咱们是这么解决的。还是开启文件名的Hash,将Caching中的AssetBundle文件夹名保留到PlayerPrefs中,当检测到有下载的时候,读出PlayerPrefs中的值,把旧的对应AssetBundle包删除,并更新PlayerPrefs。

获取以后Catalog中所有AssetBundle文件夹名的办法,是从Addressables中复制进去的。

 // 取得以后catalog中所有 assetbundle 保留的文件夹名    // 这个函数中援用到的办法没有列出,能够去 addressables 中源码中找     // 示例:CollectBundleNames(new string[]{ "SkllIcons", "ItemIcons", "AvatarIcons" })    private static List<string> CollectBundleNames(object[] keys)    {        List<string> result = new List<string>();#if ENABLE_CACHING        foreach(var key in keys)        {            IList<IResourceLocation> locations;            if (key is IResourceLocation resourceLocation && resourceLocation.HasDependencies)            {                foreach (var dep in resourceLocation.Dependencies)                {                    if (dep.Data is AssetBundleRequestOptions options)                    {                        result.Add(options.BundleName);                    }                }            }            else if (GetResourceLocations(key, typeof(object), out locations))            {                var deps = GatherDependenciesFromLocations(locations);                foreach (var dep in deps)                {                    if (dep.Data is AssetBundleRequestOptions options)                    {                        result.Add(options.BundleName);                    }                }            }        }#endif        return result;    } 

删除AssetBundle包文件夹的办法:

// 这里的 bundleName 就是 CollectBundleNames 的返回值private static void ClearCacheForBundle(string bundleName)    {        List<Hash128> hashList = new List<Hash128>();        Caching.GetCachedVersions(bundleName, hashList);        foreach (Hash128 hash in hashList)        {            Caching.ClearCachedVersion(bundleName, hash);        }    } 

须要留神的是调用CollectBundleNames的机会,如果曾经更新了Catalog,那么返回的是行将要写入Caching中的AssetBundle文件夹名。如果要失去以后AssetBundle文件夹名,要在更新Catalog之前调用。

感激jim@UWA问答社区提供了答复


Q10:LuaJIT性能热点函数优化

我的项目中的这个函数耗时十分重大,有什么优化的办法吗?

A1:这个是table.get,对应获取字段或者拜访数组时调用的函数:

  1. 优先应用间断数组而不是英文名字段,能够显著晋升拜访效率并升高内存耗费,很多团队喜爱应用Class的写法,能够这样革新:
    local obj = ClassA.New()
    obj.abc = 1
    obj.cde = "test"
    变成:
    local obj = ClassA.New()
    obj[1] = 1
    obj[2] = "test"
    这个办法能够针对应用频率较高的代码进行革新。
  2. 本人开发工具,在编译Lua之前,将Lua代码中的常量从英文名变量转换为数值,这个能够联合1应用,就能够在开发期写英文名字段名,而后编译时转换为数组。

感激招文勇@UWA问答社区提供了答复

A2:字符串应该是哈希值计算的耗费,这样的开销应该是很频繁地调用了。

感激王欢@UWA问答社区提供了答复

明天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题兴许都只是冰山一角,咱们早已在UWA问答网站上筹备了更多的技术话题等你一起来摸索和分享。欢送酷爱提高的你退出,兴许你的办法恰能解他人的当务之急;而他山之“石”,也能攻你之“玉”。

官网:www.uwa4d.com
官网技术博客:blog.uwa4d.com
官网问答社区:answer.uwa4d.com
UWA学堂:edu.uwa4d.com
官网技术QQ群:793972859(原群已满员)