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:次要有以下几点:

  1. 只有传新的AssetBundle和Catalog。
  2. 如果不蕴含Hash,那么AssetBundle文件名一样的状况下会笼罩旧资源(可能存在CDN缓存问题)。
  3. 能够每次出更新包批改RemoteBuildPath,指向不同目录。
  4. 继承一个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。

  1. 先遍历工程,找出对图集a援用的资源A等等。
  2. 将A援用的Sprite替换为b中对应的Sprite。
  3. 都替换实现后,再反复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(原群已满员)