1)Addressable 资源管理
2)Addressable 热更新问题
3)不合理旧图集拆分成新的小图集
4)XLua 中在 Lua 和 C# 传递自定义值类型
5)Toggle 的 onValueChanged 如何正确移除某个匿名的监听
这是第 216 篇 UWA 技术常识分享的推送。明天咱们持续为大家精选了若干和开发、优化相干的问题,倡议浏览工夫 10 分钟,认真读完必有播种。
UWA 问答社区:answer.uwa4d.com
UWA QQ 群 2:793972859(原群已满员)
Addressable
Q1:大家用 Addressable 治理资源,对于 AssetsGroups 的 Bundle Mode:选用 Pack Together 好,还是选用 Pack Separately 好?大家有什么好的倡议吗?
A:Pack Together 是最简略的治理形式,然而适宜打包后比拟小的资源集,比方大量通用的 Shader 能够打包在一起。然而往往还是 Pack Separately 用的比拟多,集体感觉 Separately 更容易治理。
其实具体如何抉择要看咱们须要打包的资源如何分类。假如一个游戏的资源分出以下类型:玩家、怪物、NPC、地图、特效、音乐和通用资源等,那么从性能和治理的角度思考,来对这些资源进行目录治理。
RemoteAssets |__Players (Group) | |__Player01 (拖入 Group) | | |__Animations (作为 SubAsset,不可批改,会被打包进 Player01) | | |__Materials | | |__Textures | | |__Models | | |__Prefabs | |__Player02 |__Monsters | |__Common | |__Monster001 | |__Monster002 |__Maps |__Map001
不同 Group 内则以更新为单位来思考,比方版本 2 须要新加 Monster003,版本 4 须要添 Map002。天然应该按这些小分类做 Bundle 打包。
于是能够以大分类做 Group,抉择 Pack Separately,以小分类的目录拖进 Group 作为 AssetEntry,这样根本就能治理好了。并且个别每个 Bundle 的大小也能管制在正当范畴内。
然而离开打包可能因为各个包内资源援用到其余共用资源,造成反复打包。因而整体尺寸会大于 Together。于是咱们能够在某个 Group 内建设一个 Common 的子目录,将这个 Group 内可能被共用的局部提出来打包。
另外一部分资源比方 UI,能够思考纳入一个 Group,而后抉择按 Label 打包。按 UI 应用的界面和更新批次建设 Label,咱们这个步骤是联合 AssetGraph 利用文件名主动生成 Label 来设置 Group 的。
一些通用资源能够独自建设 Group,而后抉择 Pack Together。然而咱们仍然是建设一个叫 Common 的 Group,而后还是抉择 Separately。
这个组外面增加各个资源目录:
Common |__ Fonts |__ Shaders |__ Materials
大抵思路是这样,然而还会要联合各个我的项目的理论需要,性能宰割,更新和经营要求来做设计。
Q2:目前比拟奇怪的景象是用 Pack Separately 打出的包要比 Pack Together 的大很多。
另外对于 Addressable 咱们会有针对性地设置它的 Group 分组,比方依照性能、依照资源类型进行分组,如果所有资源都是 Pack Together 模式,那么 Addressable 会对应生成 Group 个数的 Bundle 文件,而后咱们利用 Addressable 的 Analyze 工具,进行冗余资源剖析,主动剖析出的 Group 资源,咱们还是用 Separately 模式在剖析一次?测验下是否还有冗余吗?
另外还有个想法:把所有资源 Group 进行 Separately 打包,而后再用 Analyze 剖析工具进行冗余查看,和先用 Together 模式进行打包再剖析冗余,这两种操作形式,剖析出的后果会是一样的吗?
A:其实咱们根本不必 Analyze 工具,咱们尽量作到所有资源的具体去向(打到那个具体包)都能清晰地治理和把握,最多联合 UWA 的 AssetBundle 剖析工具来找出冗余,而后手动来做挪动和治理。只有每增加一个资源都能严格依照既定的标准来治理,目录宰割清晰。导出和生成预制体的流程应用工具自动化,那么还是很好治理的。
感激黄程 @UWA 问答社区提供了答复
Addressable
Q1:Addressable 打热更新资源的时候会生成一个新的资源在原来的目录下,和旧的资源在一个目录,那我是每次都要把这整个文件夹都上传到 CDN 上吗?有没有什么方法能把每次须要热更新的资源独自提取进去放在一个额定版本文件夹呢?
A:次要有以下几点:
- 只有传新的 AssetBundle 和 Catalog。
- 如果不蕴含 Hash,那么 AssetBundle 文件名一样的状况下会笼罩旧资源(可能存在 CDN 缓存问题)。
- 能够每次出更新包批改 RemoteBuildPath,指向不同目录。
- 继承一个 BuildScriptPackedMode 本人去实现一个 Build 脚本。
Addressables 自身不对更新资源做版本号治理的,这个须要本人做。
Q2:麻烦问下,如果本人增加了版本控制,Addressable 能够做到回退版本吗?例如曾经应用版本 2 的 Catalogs 实现了更新,此时须要回退回版本 1,用客户端版本 2 的 Catalogs 和服务端版本 1 的比照,能够再更回去吗?
A:没有试过,实践上能够,因为 Addressable 没有版本号概念,只比照 Hash,不同就认为有更新,至于新旧,它不论。
Q3:组设置里,有个 Use Asset Bundle Crc.,如果勾选这个,那客户端的文件 CRC 校验失败(例如被修改器之类的改了),Addressable 会怎么解决呢?看形容说,这个是本地文件和近程文件都起作用的,这里对于 Load Path 别离是本地和近程的组来说,有什么不同吗?
A:CRC 检测失败,天然这个资源的载入就失败了,Addressable 会提醒谬误。这个过程一般来说相当于用记录在 Catalog 内的 CRC 对下载的 Bundle 做校验。这个和本地近程应该没多大关系,就算放在本地的包,其实也能够通过更新来做替换应用的。这个时候也是能够做 CRC 测验的。更细节的局部我也没有具体关注过,你也能够读一下源代码,看看具体怎么实现的。
感激黄程 @UWA 问答社区提供了答复
Texture
Q:咱们这边有这样的一个问题:用的 TexturePacker 打出的图集有大量的有效区域(很久的老图集了)有没有什么方法能够将这些图集拆分一下(须要思考材质的宰割和从新援用)。
A1:如果还有 TexturePacker 的工程,那么将 ForceSquared 的勾去掉,这样就不会强制正方形贴图,会依据理论应用的尺寸进行缩减了。
如果没有原工程,那么能够解析导出的.tpsheet 文件,都是文本模式的。比方:
不同版本可能格局不同,然而都是能够解析的。而后做个小工具就能够合成并且重组了。粗犷点间接批改也行。个别只有 Sprite 名字不变,那么应用 TexturePacker 的 SDK 应该不须要思考材质的宰割和从新援用。
感激黄程 @UWA 问答社区提供了答复
A2:TP 打图集前期想拆分很难了,包含 Sprite 改名字都麻烦。如果你们能保障每个 Sprite 的名字都是惟一的,能够写脚本批量替换,否则就放弃。
感激 Walker@UWA 问答社区提供了答复
A3:感觉是有不太有自动化的办法,举荐是手写脚本批量替换,我倒感觉 Sprite 名不定要惟一。
假如图集名字为 a,新图集为 b。
- 先遍历工程,找出对图集 a 援用的资源 A 等等。
- 将 A 援用的 Sprite 替换为 b 中对应的 Sprite。
- 都替换实现后,再反复 1,确定 a 没有被援用。
还有 1 种状况是 Atlas – Sprite 对在代码中被应用,而非间接援用。那可能要定向查问配表信息 / 字符串常量了。
感激 cloud@UWA 问答社区提供了答复
Script
Q:XLua 在传递值类型的时候,可通过 GCOptimizeAttribute 来优化 GC 体现,通过 AdditionalPropertiesAttribute 来申明在 C# 和 Lua 之间做值传递时,当作字段来传递的属性(Property)。此外,对例如 UnityEngine.Vector3 的类型,Lua 一侧在一些状况下可间接用 Table 传递,如:
local v = {x=1, y=2, z=3} + Vector3(4, 5, 6)
失去的 V 是个重量为 5、7、9 的 Vector3。这个行为在生成 Wrapper 代码,或者以反射模式运行的时候,都是成立的。
然而我自定义的 Struct 类型,申明了上述 Attribute,也生成了代码(包含 CopyByValues 等),相似行为只有生成 Wrapper 能力正确执行,在反射模式下传递的值都是 0。请问为什么?
Struct 代码:
public struct SB
{
private int intField;
private float floatField;
private long longField;
public static SB operator +(SB a, SB b)
{
return new SB
{
IntField = a.IntField + b.IntField,
FloatField = a.FloatField + b.FloatField,
LongField = a.LongField + b.LongField,
};
}
public int IntField
{
get => intField;
set => intField = value;
}
public float FloatField
{
get => floatField;
set => floatField = value;
}
public long LongField
{
get => longField;
set => longField = value;
}
public override string ToString()
{return $"int: {intField}, float: {floatField}, long: {longField}";
}
public static SB Create(int intField, float floatField, long longField)
{
return new SB
{
IntField = intField,
FloatField = floatField,
LongField = longField,
};
}
}
Lua 侧:
local SB = CS.SB
local sb1 = SB.Create(1, 2.0, 3)
local sb2 = SB.Create(2, 3.0, 4)
Logger.LogWarningSafe(sb1 + sb2)
Logger.LogWarningSafe(sb1 + {IntField=1, FloatField=2, LongField=3})
Logger.LogWarningSafe({IntField=1, FloatField=2, LongField=3} + sb1)
用反射模式运行的后果:
生成 Wrapper 代码后的运行后果:
后者运行后果正确。所以,应用 AdditionalProperties,是否还须要做什么额定工作能力使得上述代码在反射模式和 Wrapper 模式下都能正确运行呢?
环境:Unity 2018.4.18f1,以及大概是 2019 年较早时候的 xlua-framework。
A:你看一下 AdditionalPropertiesAttribute 的被援用状况就晓得了,只在 Generator 里用到,反射模式下基本不判断这个的。
Vector3 能够,是因为 X、Y、Z 都是 Field。在反射形式下,只会用 Field 形式去尝试匹配(见 ObjectCaster.cs 672 行)。
感激 snuc@UWA 问答社区提供了答复
Script
Q:如下述测试代码,OnDisable 内的无奈正确移除 Test(),其后果是屡次 OnEnable 后 onValueChanged 时调用了很屡次 Test();
public void OnEnable()
{toggle.onValueChanged.AddListener(_ => Test());
}
public void OnDisable()
{toggle.onValueChanged.RemoveListener(_ => Test());
}
public void Test()
{}
A:RemoveListener 的时候_ => Test() 作为匿名函数是独自的实例,和 Add 的时候不是同一个,天然无奈移除了。
public void OnEnable() {toggle.onValueChanged.AddListener(this.Test); } public void OnDisable() {toggle.onValueChanged.RemoveListener(this.Test); } public void Test() {}
试试:
private Data _data; private delegate void OnValueChangedDelegate; private OnValueChangedDelegate onValueChanged; public void OnEnable() {this.onValueChanged = ()=>{OnValueChange_FileDelete(this.toggle, this._data); }; toggle.onValueChanged.AddListener(this.onValueChanged); } public void OnDisable() {toggle.onValueChanged.RemoveListener(this.onValueChanged); } public void OnValueChange_FileDelete(Toggle toggle, Data _data) { }
感激黄程 @UWA 问答社区提供了答复
封面图来源于网络
明天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题兴许都只是冰山一角,咱们早已在 UWA 问答网站上筹备了更多的技术话题等你一起来摸索和分享。欢送酷爱提高的你退出,兴许你的办法恰能解他人的当务之急;而他山之“石”,也能攻你之“玉”。
官网:www.uwa4d.com
官网技术博客:blog.uwa4d.com
官网问答社区:answer.uwa4d.com
UWA 学堂:edu.uwa4d.com
官网技术 QQ 群:793972859(原群已满员)