踩坑合集

按照我们的语言习惯,在表示小数时,用点号“.”表示小数点,用逗号“,”表示分位符,如1,111.1
但在越南和一些欧洲国家如法国德国等则是反过来的,用逗号“,”表示小数点,用点号“.”表示分位符, 如1.111,1
另外更特别的是波斯语中是用“/”表示小数点!
因此,你在保存数字时或解析字符串时则需要注意该语言差导,否则会有意相不到的bug等着你。
CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");或者.ToString(CultureInfo.InvariantCulture)简单处理可以这样统一语言环境。


 2023.02.27 00:00:00这种时间格式字符串在印尼等国家语言环境下DateTime.Parse(str)解析失败,类似上一条需要固定区域:

DateTime.Parse(timeStr, System.Globalization.CultureInfo.InvariantCulture)

TextMeshPro中文显示会遇到只能显示一部分的情况,报错如下:

UnassignedReferenceException: The variable m_AtlasTextures of TMP_FontAsset has not been assigned.
You probably need to assign the m_AtlasTextures variable of the TMP_FontAsset script  in the inspector.

中文字符量大,一般使用的是动态字体,需要运行时动态生成多张文字图集:

 文字消失就是当默认的第一张图集被塞满了后生成第二张图时m_AtlasTextures为空出错而生成失败导致的。

 m_AtlasTexture首次运行时本身就为空,但如果ui上有用到该字体就会走到以上代码就会被初始化,所以多数人遇不到。
而我们项目主要使用英文,中文是通过设置fallback字体来实现的,所以没有直接用到该字体也就不会被初始化。
也好解决,在你第一个ui上增加引用就行,或者自己手动调用该属性来解决,或源代码中用m_AtlasTexture改为用属性atlasTexture也解决了,但改代码得从upm中移出来。
期待Unity官方修复这个bug(我在官方论坛回复了问题原因,但并没有得到回应)。


C#调用Process StartInfo执行命令行时在Windows下需要在Arguments参数上命令前增加/c才能正确执行。
而Mac上面则分情况,bash命令如ls、svn等则需要在命令前增加-c指令且需要把命令用引号包起来:-c "ls -l",如果仍然执行失败则需要再添加-l指令(脚本调起的可能为非交互式非登录Shell下的环境变量只有最基础的几个)来指定为当前用户的shell:-l -c "svn info";
若是代码调用外部shell脚本失败,可考虑将脚本路径改为全路径,相对路径可能不行,仍有问题则可能也需要加上-l -c,如果仍然有问题,则.command脚本首行也需要通过i l指定shell:#!/bin/bash -ilex,对于e参数表示一旦出错,就退出当前的shell,x参数表示可以显示所执行的每一条命(x视情况是否需要)。


 C#调用Process StartInfo执行命令行时获取的结果输出会乱码:

 指定输出编码格式可解决:

p.StartInfo.StandardOutputEncoding = Encoding.UTF8;
p.StartInfo.StandardErrorEncoding = Encoding.GetEncoding(CultureInfo.CurrentCulture.TextInfo.OEMCodePage);
网上大部分都说设置UTF8即可,但自测发现UTF8只有在MAC苹果电脑上可以解决乱码问题,在windows电脑上寻常编码格式全都试过了都会出错,最后通过以上下面那行加粗代码动态获取编码格式才得以解决:

故需要用宏将不同平台设置为不同的编码,StandardOutputEncoding和StandardErrorEncoding最好都同时设置为同样的编码格式


 float Epsilon = 1e-10;该行shader代码在ipad 5和ipad mini 4机型上发现显示异常,有一些黑色块出现,而其他设备没有问题。应该是ipad部分机型硬件问题,不支持e这做写法

解决办法即将1e-10改为0.0001,另外需要确保该变量定义需要在函数内部而不能在外部,如果需要在外部定义常量给多个函数共用则需要用#define宏定义。

 粒子在Prewarm勾上时,编辑器运行与非运行状态下在同一样的时间时效果不一样,即预热的那段时间会带来显示上的差异,ParticleSystem.Simulate()函数指定时间时可能跟你在编辑器制作时停留时间预览不一样,如果需要运行时跟编辑时一致,建议取消Prewarm。


 当有很多的2D UI图片要显示时可以考虑用UGUI的CavasRenderer手动传入SubMesh的方式来优化(必须同时挂上Canvas组件,否则会巨卡)。

但底层限制了数量最多只能有8个子Mesh,当有大量子Mesh要显示时就只能转为普通3D的MeshRenderer来显示了,性能会好很多。
当3D物体也需要被UGUI的Mask或者RectMask2D遮罩时会有问题,当MeshRenderer用UI相同的shader时:
* 配合Mask组件使用时,即便Mesh上的材质指定为同被遮罩的UI元素一样的stencil值遮罩不正常,整个Mesh都会不可见,原因为UGUI在Mask功能渲染完毕后又会把stencil的值清空,你的Mesh读到的值就是错误的。需要自己手动指定遮罩区域的sencil值,等于需要自己实现Mask组件的相应功能,包括嵌套Mask的情况。
* 配合RectMask2D组件使用时,需要手动传入Mesh上材质的_ClipRect裁剪区域坐标,如果在ScrollRect滑动列表中则还需要实时修改裁剪区域坐标,而UGUI传入的UI自身的相对坐标,将UI上能被正常裁剪的_ClipRect复制到Mesh上材质是有问题的,Mesh上需要传入的坐标为另一参考系(手动改值慢慢尝试发现类似世界坐标,明显比UI上的值小个数量级),未深入研究不清楚规律。


 xLua调用Unity的动画类Animation时如果不生成代码则只能反射调用,结果部分函数如animation.GetClip()、animation.Stop()会找不到函数而出错,必须配置LuaGenConfig生成代码后调用才能成功。


 Unity内播放视频文件最简单的方法就是使用VideoPlayer组件。

坑1:同为mp4文件查看属性时也有h.264的说明,但其编码格式可能android机上播放不了(ios上可以)(不同视频编辑软件可能功能不一样也许默认导出的mp4也能播放,此处测试为Mac机上某软件有MPEG4/H.264/H.265三种选择)。
解决办法:在导出视频时需要显式指定为h.264(推荐)或h.265(不推荐)或者在Unity中将视频文件勾上Transcode进行转码(转码如果选择为高配置则文件大小可能会倍增)。
坑2:在Android 9(不含)以下的设备不支持直接播放AssetBundle中的视频文件,报错:

W/Unity: AndroidVideoMedia::OpenExtractor could not translate archive:/CAB-de617f9f2b6b7a4679af2e89406b6bab/CAB-de617f9f2b6b7a4679af2e89406b6bab.resource to local file. Make sure file exists, is on disk (not in memory) and not compressed.
W/Unity: AndroidVideoMedia: Error opening extractor: -10004

尝试过Unity中打AB包时选择Uncompress不压缩,且Android build.gradle文件中noCompress配置上所有.ab文件都不压缩,仍然播放不了。
解决办法:视频文件直接放到Resources目录中(会增加app启动时间),或者将视频文件放到StreamingAssets目录通过VideoPlayer.url使用(推荐),或者将视频文件从AB包中提取出来复制到其它缓存目录中(将视频文件后缀修改为.bytes直接加载AB包获得数据另存为mp4文件)即可通过url指定本地路径播放。
坑3:高分辨率(宽高为1080*2400)在Android 9(不含)以下的设备上播放不了。
解决办法:修改原始视频文件宽高(不清楚具体限制是多少),或者在Unity中将视频文件勾上Transcode进行转码,把Dimensions选择为3/4或更低(画质会有影响)。
另,高码率(数据速率kbps)会显著增加视频文件的大小,适度降低码率对画质的影响不大,但可以极大降低文件大小。


 lua侧加载及读取Excel配置表数据有多种实现,最简单的方式为数据档直接保存为.bytes二进制文件,使用lua protobuf库直接加载;另外则是生成对应的lua文件直接require之。

当表数量多且部分表有几十M甚至更大时则需要考虑时间与空间的取舍:
生成的Lua文件有几十M时,使用lua语言去加载解析lua文件则效率相当低,此时只能牺牲空间换取时间,使用bytes二进制是更优的方案,pb库是C语言实现,加载和解析都更快,但数据量大则必定lua的table巨多最后lua侧占用很多内存。
其他大量的小配置文件则可以用时间换空间,当数据量少时用二进制文件读取的速度提升可能并不明显,而用lua文件保存数据则可以采用更优的生成算法优化lua table的生成:

 1 local defaults = {
 2   activityId = 2,
 3   aniType = 2,
 4   num = 1,
 5 }
 6 
 7 local duplicates0 = {
 8   t0 = {["type"] = 2,["id"] = 90053,["count"] = 1,},
 9   t1 = {["type"] = 2,["id"] = 90054,["count"] = 1,},
10   t2 = {["type"] = 2,["id"] = 90095,["count"] = 1,},
11   t3 = {["type"] = 2,["id"] = 90112,["count"] = 1,},
12 }
13 
14 local duplicates1 = {
15   t4 = {["eff"] = [=[]=],["width"] = 0.0,["height"] = 0.0,},
16   t5 = {--[[1]]0.8,--[[2]]1.0,},
17   t6 = {--[[1]]1.0,--[[2]]1.2,},
18   t7 = {--[[1]]duplicates0.t0,},
19   t8 = {--[[1]]0.8,--[[2]]1.2,},
20   t9 = {--[[1]]duplicates0.t1,},
21   t10 = {["eff"] = [=[Angel/eff_angel]=],["width"] = 456.922,["height"] = 553.171,},
22   t11 = {--[[1]]0.5,--[[2]]0.8,},
23   t12 = {--[[1]]1.0,--[[2]]1.0,},
24   t13 = {--[[1]]duplicates0.t2,},
25   t14 = {["eff"] = [=[Joker/eff_joker]=],["width"] = 456.922,["height"] = 553.171,},
26   t15 = {--[[1]]duplicates0.t3,},
27 }
28 
29 local duplicates2 = {
30   t16 = {["refreshWeight"] = 1,["pic"] = duplicates1.t10,["rect"] = duplicates1.t4,["catchProb"] = 100.0,["num"] = 2,["size"] = duplicates1.t11,["transparent"] = duplicates1.t12,["reward"] = duplicates1.t13,},
31   t17 = {["refreshWeight"] = 1,["pic"] = duplicates1.t10,["rect"] = duplicates1.t4,["catchProb"] = 80.0,["num"] = 3,["size"] = duplicates1.t11,["transparent"] = duplicates1.t12,["reward"] = duplicates1.t13,},
32   t18 = {["refreshWeight"] = 1,["pic"] = duplicates1.t14,["rect"] = duplicates1.t4,["catchProb"] = 100.0,["num"] = 2,["size"] = duplicates1.t5,["transparent"] = duplicates1.t12,["reward"] = duplicates1.t15,},
33   t19 = {["refreshWeight"] = 1,["pic"] = duplicates1.t14,["rect"] = duplicates1.t4,["catchProb"] = 80.0,["num"] = 3,["size"] = duplicates1.t5,["transparent"] = duplicates1.t12,["reward"] = duplicates1.t15,},
34 }
35 
36 local duplicates3 = {
37   t20 = {--[[1]]duplicates2.t16,--[[2]]duplicates2.t17,},
38   t21 = {--[[1]]duplicates2.t18,--[[2]]duplicates2.t19,},
39 }
40 
41 
42 local raw =
43 {
44   PbMT(defaults, {
45     ["id"] = 105,
46     ["activityId"] = 1,
47     ["type"] = {
48       {
49         ["refreshWeight"] = 1,
50         ["pic"] = {
51           ["eff"] = [=[ghost/eff_ghostH_5_ani]=],
52           ["width"] = 292.637,
53           ["height"] = 259.894,
54         },
55         ["rect"] = duplicates1.t4,
56         ["catchProb"] = 70.0,
57         ["num"] = 3,
58         ["size"] = duplicates1.t8,
59         ["transparent"] = duplicates1.t5,
60         ["reward"] = duplicates1.t9,
61       },
62     },
63     ["aniType"] = 1,
64   }),
65   PbMT(defaults, {
66     ["id"] = 201,
67     ["type"] = duplicates3.t20,
68   }),
69   PbMT(defaults, {
70     ["id"] = 202,
71     ["type"] = duplicates3.t20,
72   }),
73 ...
View Code

通过将出现最多的值作为默认值、复用同样数据的table可以积少成多节省不少内存,也可加快解析的速度。
另外分享一下另一种优化,因为配置表中所有的字段是确定且不变的,可以改为不用key做键,直接使用proto里定义的数字,这样就少了大量的文本字段,数字连续时也变成了数组,大大降低的文本大小及运行时的内存大小,但是!每次读取都需要转换一次找到实际的key的id,当数据表量大时导致运行时的速度降低很多,只能放弃……示例如下:


 Unity的图片选项中Alpha Source有些误导人,给人感觉是只要我选了None它就应该自动给我去掉alpha通道节省至少1/4的内存:

然而实际该选项仅在ASTC格式下起作用:

这可能就是为什么ASTC的命名中A写在括号中,它是可选的,但也只是对表现有效,图片不再有半透效果,实际内存占用并不能减少!
其它格式则由格式本身带不带A通道决定了,与Alpha Source完全没有关系。
综上,Alpha Source大多数情况下都是可以不用管的,仅当需要使用ASTC格式且图片本身就有透明像素在显示时需要去掉透明像素的情况,或者使用平台自动决定格式时。


 UGUI上双指(三指及以上不触发点击)同时点击或者快速多次点击同一个按钮(或不同按钮)都会多次触发onClick事件!即便你在点击事件处理中立即打开了一个全屏UI阻挡点击也没有用(可能因为这多个点击事件是在同一帧触发,而UI表现层在当前帧设置后并不能立即生效),

要解决事件并发需要在事件回调处理中增加多点触控的判断如:if ((Input.touchCount <= 1) clickCallback()
不能使用IPointerClickHandler点击回调参数给的PointerEventData中的clickCount来判断点击次数,其只在电脑上即鼠标操作时有效,触摸屏下永远是1,而Input.touchCount在鼠标下一直是0在手机屏幕下为手指个数或连点次数。


 图片压缩格式经验总结:

ASTC压缩比在不同图片下不一样,时高时低,在多数情况下画质显示效果好,无图尺寸要求,在极少数android低端机型上不支持,会自动使用RGBA32原画质显示,此时内存占用会很大;

ETC2压缩比一般,显示效果较好,画质在少数情况优于ASTC,要求图的尺寸必须是4的倍数;

CrunchedETC2压缩比高,画质在小图时较好,大图有些比ASTC更好有时更差,要求图的尺寸必须是4的倍数;

内存占用多数情况下ASTC8会优于CrunchedETC2,ASTC4则会更高,不同情况不一样;

总的来说画质和内存表现上不会有特别明显的差距,跟图片上本身像素的情况有关,但综合看来ASTC是更优的压缩格式。

如果项目不需要支持少数低端机型,图片又不是超级多小图,建议无脑选择ASTC。

有大量小图时则优先选择CrunchedETC2,其他大图使用ASTC,使用工具批量将图全部转为4倍大小。

普通ETC2较少使用,除非嫌CrunchedETC2效果不好又需要支持全部低端机型。


 在打完AssetBundle包,之后的第一次运行unity(重加载域时)或者触发代码编译的时候会在Reload Script Assemblies卡特别久:

 第二次及以后就正常了不会再卡进度条。打AB包时使用的是upm里的库Scriptable Build Pipline,用原旧api打包则不会有这问题。
解决办法:删掉Library/BuildCache整个文件夹。看起来是SBP库有bug,当缓存过多时会导致这个问题。
更新:Unity2021可以通过删掉缓存解决,2022版本又不行了……


 imageimage

AssetBundle.LoadFromFileAsync异步加载AssetBundle包, 和AssetBundle.LoadAssetAsync从AssetBundle包中异步加载资源Asset,在加载未完成时立即访问数据会卡主进程直到加载完成,即是说虽然是异步接口,完全可以当成同步接口使用!

经在Unity2022.3版本中测试了9千多个ab包(单个不到1M,未测试大包会不会表现不同),加载ab,异步接口无论是异步等待(yield return或判断isDone)还是同步等待(直接访问.assetBundle或.asset,速度最快,耗时比异步等待少一半)都明显好于同步接口(不带"Async"版本接口,比异步接口在耗时多了将近一倍);

加载全部assets,异步接口的异步等待都是最慢的,从大量的ab包中加载资源时更是慢得出奇;同步接口在量大时则是最快的,量小时(几十或百来个ab包中加载)异步接口的同步等待则是比同步接口稍快一点点。

虽然同步接口加载assets是最优的,但纯异步场景下,ab包和asset的加载可能是同时发生的,加载asset不能单独使用同步实现。

总结:想避免卡顿则只能使用纯异步版本,异步接口异步等待,无论是加载ab还是加载asset;只能使用同步逻辑或者不会卡的地方建议加载ab包按异步的方式批量加载后不等加载完毕直接以同步的方获取资源,用异步的接口实现同步逻辑是最优的方案,至于加载asset则直接使用同步接口。

 另外,测试中发现,异步等待使用yield return会卡很久,如果要实现纯异步加载,建议在Update中判断isDone的方式等待(或者实现await)。

// foreach (var request in requests)
// {
//     //使用yield return在量大(1000个)时会卡很久(17s)
//     yield return request; 或者yield return new WaitUntil(()=>request.isDone)
//     //使用await就正常(至少2022版本需要自行增加代码实现支持await)
//     //await request;
// }

//以下方式判断等待则正常(633ms)
while (requests.Any(r: AssetBundleCreateRequest => !r.isDone)) yield return null;
posted @ 2022-08-05 15:23  桫椤  阅读(2069)  评论(0)    收藏  举报