# 一、UGUI
# 超链接
已解决,超链接点击返回剔除富文本信息
详细可见另一篇文档:处理 Unity2019 的 UGUI 顶点数据更新后的图文混排问题
# 图文混排
已解决,计算表情绘制下标时剔除表情标签、剔除富文本信息
详细可见另一篇文档:处理 Unity2019 的 UGUI 顶点数据更新后的图文混排问题
# 竖行排版
由于 Unity 本身提供的 UILineInfo 包含了换行符信息、而顶点数据并不包括导致。
已解决,自行通过『\n』 分割文本,并剔除富文本信息再作计算。
详细可见另一篇文档:处理 Unity2019 的 UGUI 顶点数据更新后的图文混排问题
# AorTextIncSprites 缺少最后一位内容
查看代码,并经过调试,最后发现是因为绘制时手动减掉了最后 4 位顶点,关键代码如下:
//Last 4 verts are always a new line...
int vertCount = verts.Count-4;
这里根据注释,就可以明白了,最后一位以前是当换行符去掉了,新版顶点并无这些信息,不再减去 4 个顶点即可。
# 行军线绘制
使用 UGUI + 自定义 Shader 实现
由于自定义 UGUI 顶点绘制,新版 Unity 无法正常工作,通过禁用 raycastTarget 可以避免报错,走上正常逻辑,但不可见。
- 2022.7.22 打出来真机包可见,此时暂时可排除顶点绘制函数本身逻辑不兼容嫌疑
- 后进行调试,发现新新军线绘制代码 OnPopulateMesh 无调用
- 后查询 UGUI Graphic 源码,得知更新条件需要 CanvasRender
- 并经调试查询,发现内部有缺失 CanvasRender 报错
- 由此可知 CanvasRender 是一个关键点
- 尝试运行时手动添加,无效
- 尝试按照源码规则调用,发现获取失败
- 且修改调用方式
gameObject.GetComponent<CanvasRenderer>()
可以获取 - 该类直接放置于另一个类脚本中,按理说挂载的脚本都是单独一个类,且类名需与代码文件名一致,怀疑是否因此问题导致 (尝试后表明于此无关)
- 试图添加此绘制组件前,先添加 CanvasRender,cull 置为 false
obj.AddComponent<CanvasRenderer>().cull = false
已解决
注:其实 UGUI 源代码中,对于 CanvasRenderer 的获取,也是做了额外判断的,若获取空则会添加,不清楚是什么条件造成了添加代码无效的。
# AorUIStage# 自适应子节点长宽为 NaN
可确定由 AorUiRuntimeUtility 动态创建 AorUIStage# 后,再动态创建可自适应大小的 MainLayer 子容器导致
//AorUIStage
GameObject stage = CreatePrefab_UIBase(canv.transform, 0, 0, 512f, 512f, 0.5f, 0.5f, 0.5f, 0.5f);
stage.name = "AorUIStage#";
//AorUIStage.mainLayer
GameObject ml = CreatePrefab_UIBase(stage.transform);
ml.name = "MainLayer";
GameObject tl = CreatePrefab_UIBase(stage.transform);
tl.name = "TopLayer";
修改 AorUIManager updateStageSize 方法,增加重置计算
private void updateStageSize()
{
if (Application.isEditor && !Application.isPlaying)
{
if (!isAwakeInit || !isInit)
{
return;
}
}
switch (_scaleMode)
{
case ScaleModeType.widthBase:
set2WidthBaseMode();
break;
case ScaleModeType.heightBase:
set2HeightBaseMode();
break;
case ScaleModeType.fitScreen:
set2FitScreenMode();
break;
case ScaleModeType.envelopeScreen:
set2EnvelopeScreenMode();
break;
default:
break;
}
// 升级 2019 后,自适应子节点会出现问题,这里强制重设一次
for (int i = 0; i < Stage.childCount; i++)
{
RectTransform rt = Stage.GetChild(i) as RectTransform;
rt.anchoredPosition = Vector2.zero;
rt.sizeDelta = Vector2.zero;
}
}
已解决
注:解决方式应当不止这种。
# 二、Socket 连接
在编辑器 / 真机包 返回登录界面时触发。
查询是由于调用 socket.Disconnect(true);
导致,估计是以前想复用 Socket 实例,不过查了下代码,每次 open 实际上都是重新创建的一个,将 Disconnect 的调用替换为如下代码解决:
try | |
{ | |
socket.Shutdown(SocketShutdown.Both); | |
} | |
catch (Exception) | |
{ | |
//Do Nothing | |
} | |
socket.Close(); | |
socket = null; |
# 三、自定义 DLL 的库文件引用
已解决
DLL 框架版本设置为 4.0 以上 (我是 4.5)
并重新分散引用 Unity 内部 DLL,路径位于对应 Unity 安装目录
Unity 2021.3.0f1\Editor\Data\Managed\UnityEngine
注:重新引用后注意修改 DLL 复制本地 为 false
# 四、打 Assetbundle 包出现资源反复 Import 问题
表现为初始化环境时,还没轮到正式打 Assetbundle,每个 prefab 皆出现 Import 提示
经过调试,发现新版 Unity 在每次打包之前重设标签时,都会导致重新导入一次资源,增加大量额外时间消耗。
例如:
已解决,重设标签时,判断旧标签是否与新标签一致。
代码位于 AssetBundleTool AddLabel
public static void AddLabel(Object obj, string label) | |
{ | |
//ClearLabel(obj); | |
UnityEngine.Object _object = obj; | |
if (null != _object) | |
{ | |
List<string> _labelList = AssetDatabase.GetLabels(obj).ToList(); | |
// 对象目前已有标签与设置标签不一致,或者对象拥有多个标签 (不合法),则重设标签 | |
if (!_labelList.Contains(label) || _labelList.Count > 1) | |
{ | |
ClearLabel(obj); | |
AssetDatabase.SetLabels(obj, new string[] { label }); | |
} | |
// 如果这个物体本来就有指定的 label,就不需要重复处理了 | |
//AssetDatabase.SetLabels(obj, _labelList.ToArray()); | |
} | |
} |
注:方法开口有调用 ClearLabel,可知一个对象只会存在一个标签,因此此处直接判断设置标签是否相等即可。
# 五、打 Assetbundle 包崩溃
查询日志得出为栈溢出:
Couldn't extract exception string from exception of type StackOverflowException (another exception of class 'StackOverflowException' was thrown while processing the stack trace)
StackOverflowException: The requested operation caused a stack overflow.
at (wrapper managed-to-native) System.String.FastAllocateString(int)
at System.String.CreateStringFromEncoding (System.Byte* bytes, System.Int32 byteLength, System.Text.Encoding encoding) [0x00013] in <6073cf49ed704e958b8a66d540dea948>:0
at System.Text.Encoding.GetString (System.Byte* bytes, System.Int32 byteCount) [0x00033] in <6073cf49ed704e958b8a66d540dea948>:0
at System.Text.Encoding.GetString (System.ReadOnlySpan`1[T] bytes) [0x00013] in <6073cf49ed704e958b8a66d540dea948>:0
at System.String.Ctor (System.SByte* value, System.Int32 startIndex, System.Int32 length, System.Text.Encoding enc) [0x0006d] in <6073cf49ed704e958b8a66d540dea948>:0
at System.String.CreateString (System.SByte* value, System.Int32 startIndex, System.Int32 length, System.Text.Encoding enc) [0x00000] in <6073cf49ed704e958b8a66d540dea948>:0
at (wrapper managed-to-managed) System.String..ctor(sbyte*,int,int,System.Text.Encoding)
at UnityEngine.StackTraceUtility.ExtractStackTrace () [0x0002c] in <1332c534a8a44623b2e5cc943a0b790e>:0
at (wrapper managed-to-native) UnityEngine.DebugLogHandler.Internal_Log(UnityEngine.LogType,UnityEngine.LogOption,string,UnityEngine.Object)
at UnityEngine.DebugLogHandler.LogFormat (UnityEngine.LogType logType, UnityEngine.Object context, System.String format, System.Object[] args) [0x0000b] in <1332c534a8a44623b2e5cc943a0b790e>:0
at UnityEngine.Logger.Log (UnityEngine.LogType logType, System.Object message) [0x00027] in <1332c534a8a44623b2e5cc943a0b790e>:0
at UnityEngine.Debug.Log (System.Object message) [0x00006] in <1332c534a8a44623b2e5cc943a0b790e>:0
at EditorAssetData.caculateTop () [0x00123] in D:\[Works]\sanguo_release\Unity\Assets\Scripts\Editor\AssetBundleTool\EditorAssetData.cs:184
at EditorAssetData.caculateTop () [0x0013a] in D:\[Works]\sanguo_release\Unity\Assets\Scripts\Editor\AssetBundleTool\EditorAssetData.cs:185
at EditorAssetData.caculateTop () [0x0013a] in D:\[Works]\sanguo_release\Unity\Assets\Scripts\Editor\AssetBundleTool\EditorAssetData.cs:185
//余下省略========================================================
//余下省略========================================================
//余下省略========================================================
查询代码可知为递归获取上级引用问题。
经过调试可发现出现问题者,皆为名称与本身不一致的对象。
如图所示:
出现问题的原因,多为美术 (特效),原因想来很简单,美术直接单纯复制对象,未做正确操作。
由此可以得出结论,老版本 Unity 可能直接有一套兼容机制处理,新版 Unity 直接崩掉了。
已解决,通过检测递归层数,人为限制,数量超出上限则打印错误信息,以便修复:
private int _stackOverflowCheck = 0; | |
/// <summary> | |
/// 一直向上递归,直到上级资源的上级资源数为 0 或上级资源为 BuildSingle | |
/// </summary> | |
public List<EditorAssetData> caculateTop() | |
{ | |
_stackOverflowCheck++; | |
if (_stackOverflowCheck > 50) | |
{ | |
Debug.LogError(LoadPath+ " 超出递归限制上限,可能已经出现了问题!"); | |
return TopUpDependenceList; | |
} |
效果:
# 六、报 load IL2CPP 失败
通过 ADB 拉取的日志查看,一堆 Could not load symbol XXX 的错误
经过对 Unity 导出资源的目录的对比,发现新版资源路径都已经不一样了,多了为两个目录 『Il2CppOutputProject』、『jniStaticLibs』, 且 jniLibs 目录中少了 libil2cpp.so,
经过查询信息表示,新版 Unity 导出的 AndroidStudio 工程,不再编译 libil2cpp.so ,而是直接提供源码,得自己处理编译的功能。
打开导出的资源 unityLibrary 目录下的 build.gradle,其中就有编译的代码:
// 省略
将其复制到我们的 SDK 下 build.gradle 中对应位置,并依照我们项目作出部分修改:
android {
//已有代码
//省略
//省略
aaptOptions {
noCompress = ['.unity3d', '.ress', '.resource', '.obb', '.bundle', '.unityexp','.assetbundle']
}
packagingOptions {
doNotStrip '*/armeabi-v7a/*.so'
doNotStrip '*/arm64-v8a/*.so'
}
task BuildIl2CppTask {
doLast {
BuildIl2Cpp(project(':UnitySdk').projectDir.toString().replaceAll('\\\\', '/'), 'Release', 'armv7', 'armeabi-v7a', [ ] as String[]);
BuildIl2Cpp(project(':UnitySdk').projectDir.toString().replaceAll('\\\\', '/'), 'Release', 'arm64', 'arm64-v8a', [ ] as String[]);
}
}
afterEvaluate {
if (project(':UnitySdk').tasks.findByName('mergeDebugJniLibFolders'))
project(':UnitySdk').mergeDebugJniLibFolders.dependsOn BuildIl2CppTask
if (project(':UnitySdk').tasks.findByName('mergeReleaseJniLibFolders'))
project(':UnitySdk').mergeReleaseJniLibFolders.dependsOn BuildIl2CppTask
}
sourceSets {
main {
jni.srcDirs = ["src/main/Il2CppOutputProject"]
}
}
}
def getSdkDir() {
Properties local = new Properties()
local.load(new FileInputStream("${rootDir}/local.properties"))
return local.getProperty('sdk.dir')
}
def BuildIl2Cpp(String workingDir, String configuration, String architecture, String abi, String[] staticLibraries) {
def commandLineArgs = []
commandLineArgs.add("--compile-cpp")
commandLineArgs.add("--platform=Android")
commandLineArgs.add("--architecture=" + architecture)
commandLineArgs.add("--outputpath=" + workingDir + "/src/main/jniLibs/" + abi + "/libil2cpp.so")
commandLineArgs.add("--libil2cpp-static")
commandLineArgs.add("--baselib-directory=" + workingDir + "/src/main/jniStaticLibs/" + abi)
commandLineArgs.add("--incremental-g-c-time-slice=3")
commandLineArgs.add("--configuration=" + configuration)
commandLineArgs.add("--dotnetprofile=unityaot-linux")
commandLineArgs.add("--profiler-report")
commandLineArgs.add("--profiler-output-file=" + workingDir + "/build/il2cpp_"+ abi + "_" + configuration + "/il2cpp_conv.traceevents")
commandLineArgs.add("--print-command-line")
commandLineArgs.add("--generatedcppdir=" + workingDir + "/src/main/Il2CppOutputProject/Source/il2cppOutput")
commandLineArgs.add("--cachedirectory=" + workingDir + "/build/il2cpp_"+ abi + "_" + configuration + "/il2cpp_cache")
commandLineArgs.add("--tool-chain-path=" + android.ndkDirectory)
staticLibraries.eachWithIndex {fileName, i->
commandLineArgs.add("--additional-libraries=" + workingDir + "/src/main/jniStaticLibs/" + abi + "/" + fileName)
}
def executableExtension = ""
if (org.gradle.internal.os.OperatingSystem.current().isWindows())
executableExtension = ".exe"
exec {
executable workingDir + "/src/main/Il2CppOutputProject/IL2CPP/build/deploy/il2cpp" + executableExtension
args commandLineArgs
environment "ANDROID_SDK_ROOT", getSdkDir()
}
delete workingDir + "/src/main/jniLibs/" + abi + "/libil2cpp.sym.so"
ant.move(file: workingDir + "/src/main/jniLibs/" + abi + "/libil2cpp.dbg.so", tofile: workingDir + "/symbols/" + abi + "/libil2cpp.so")
}
重新执行 AS 打包操作 —— 发现 jniLibs 根本没有生成对应的 libil2cpp.so 文件,导致最终出来的包还是有问题。
经过与 SDK 同学一块检查,发现我这边是出包的时候根本没执行 BuildIl2CppTask 代码:
也就是说,就算如果增加了上述代码,要是按照正常流程打包,BuildIl2CppTask 会不执行,后边经过研究,只能手动执行 BuildIl2CppTask,手动执行输出也是正常的 (经过与 SDK 同学讨论,暂时也没发现为何没有自动执行的原因)。
这一步尝试成功后,就出了新包测试,确认无误后 —— 如此第一个能跑的包终于出来了!
# 无法正常调用 BuildIl2Cpp 编译 libil2cpp.so
上文提到了直接出包,AS 根本没有执行附加的 BuildIl2CppTask 这条任务
这一步操作肯定不可能手动执行的,而且有时候涉及到旧资源出包,就算能够成功执行,反复编译也是费时间的麻烦事
于是向 SDK 同学要了命令『./gradlew BuildIl2CppTask』,修改了打包工具,直接在向 AS 复制资源之前,执行一次 il2cpp 的检查及编译的操作,并将生成的 libil2cpp.so 复制回来作资源缓存:
//===== 编译 il2cpp====== | |
string packil2CppSources = Path.Combine(packResRootPath, "Il2CppOutputProject"); | |
string packjniStaticLibsPath = Path.Combine(packResRootPath, "jniStaticLibs"); | |
string sdkil2CppSources = Path.Combine(sdkMainPath, "Il2CppOutputProject"); | |
string sdkjniStaticLibsPath = Path.Combine(sdkMainPath, "jniStaticLibs"); | |
//libil2cpp.so 文件 | |
string packIl2cppv8Files = Path.Combine(packJniPath, "arm64-v8a/libil2cpp.so"); | |
string packIl2cppv7Files = Path.Combine(packJniPath, "armeabi-v7a/libil2cpp.so"); | |
string sdkIl2cppv8Files = Path.Combine(sdkJniPath, "arm64-v8a/libil2cpp.so"); | |
string sdkIl2cppv7Files = Path.Combine(sdkJniPath, "armeabi-v7a/libil2cpp.so"); | |
if (File.Exists(sdkIl2cppv8Files)) File.Delete(sdkIl2cppv8Files); | |
if (File.Exists(sdkIl2cppv7Files)) File.Delete(sdkIl2cppv7Files); | |
// 不存在,编译 | |
if (!File.Exists(packIl2cppv8Files) || !File.Exists(packIl2cppv7Files)) | |
{ | |
// 清理可能存在的旧文件 | |
if (Directory.Exists(sdkil2CppSources)) Directory.Delete(sdkil2CppSources, true); | |
if (Directory.Exists(sdkjniStaticLibsPath)) Directory.Delete(sdkjniStaticLibsPath, true); | |
// 复制 il2cpp 源码过去 | |
FileCompare.Copy(packil2CppSources, sdkil2CppSources); | |
FileCompare.Copy(packjniStaticLibsPath, sdkjniStaticLibsPath); | |
// 正式编译 | |
string rootSdkDir = Path.GetDirectoryName(Info.AndroidSDKPath); | |
// 刷新 | |
DoCmd(rootSdkDir, "gradlew", ""); | |
Thread.Sleep(1000); | |
// 编译 | |
DoCmd(rootSdkDir, "gradlew", "BuildIl2CppTask"); | |
Thread.Sleep(1000); | |
// 复制回来,避免第二次复用时打包重复编译 | |
if (File.Exists(sdkIl2cppv8Files)) FileCompare.Copy(sdkIl2cppv8Files, packIl2cppv8Files); | |
if (File.Exists(sdkIl2cppv7Files)) FileCompare.Copy(sdkIl2cppv7Files, packIl2cppv7Files); | |
} | |
//================ |
经测试新流程可以跑通。
注:此处调用的 AS 命令其实也是调用外部进程编译,所以不走 AS ,直接新开一个控制台命令也是可以的,这样还可以省下复制源码至 AS 的消耗,后边可以考虑优化。
# 七、安卓加载 StreamingAsset 目录资源失败
查询官方文档看到如下说明:
Unity 会将放置在 Unity 项目中名为 StreamingAssets__(区分大小写)的文件夹中的所有文件逐字复制到目标计算机上的特定文件夹。要获取此文件夹,请使用 Application.streamingAssetsPath 属性。在任何情况下,最好使用 Application.streamingAssetsPath 来获取 StreamingAssets__ 文件夹的位置,因为它总是指向运行应用程序的平台上的正确位置。
Application.streamingAssetsPath 返回的位置因平台而异:
- 大多数平台(Unity Editor、Windows、Linux 播放器、PS4、Xbox One、Switch)使用 Application.dataPath + "/StreamingAssets"。
- macOS 播放器使用 Application.dataPath + "/Resources/Data/StreamingAssets"。
- iOS 使用 Application.dataPath + "/Raw"。
- Android 使用经过压缩的 APK/JAR 文件中的文件:"jar:file://" + Application.dataPath + "!/assets"。
其中安卓的路径,与日志输出并不一致,怀疑是否此处引起了差异,于是查询我们项目内相关代码,发现对获取 StreamingAssets 有如下组合:
- FResourceCommon GetStreamingAssetsPath 方法
- LauncherLoading GetStreamingAssetsPath 方法
原初始化 streamingAssetsPath 路径的代码,判断为安卓没有直接取系统 Application.streamingAssetsPath,而且自己组合的路径,新版 Unity 可能不再对此作兼容性判断,导致无法再使用。
if (Application.platform == RuntimePlatform.Android) | |
{ | |
STREAMING_ASSET_PATH = Application.dataPath + "!assets"; // 安卓平台 | |
} | |
else | |
{ | |
STREAMING_ASSET_PATH = Application.streamingAssetsPath; // 其他平台 | |
} |
将其修改为直接取 Application.streamingAssetsPath 后,加载正常。
STREAMING_ASSET_PATH = Application.streamingAssetsPath; // 新版本所有平台均使用 streamingAssetsPath |
# 八、无法调试问题
将 DLL 设置中,高级生成设置 -> 调试信息 设置为可移植,并删除项目中 DLL 已有的 MDB 文件,重新编译。
# 九、Google 包黑屏问题
表现为闪屏后黑屏,且有 Debug 且存在部分 Unity 输出
增加打印并检查日志:
从日志中及对应代码推测,可以推测主要有两个问题:
# 打 Google 包 UseOBB 选项未生效
其中涉及代码如下:
Debug.Log("Launcher Awake + "+ UseOBB); | |
#if UNITY_ANDROID && !UNITY_EDITOR | |
SDKManager.Inst.SendMsg(SDKManager.SEND_HAVE_CUTOUT); | |
if(UseOBB) | |
{ | |
Debug.Log("Launcher Awake + UseOBB"); | |
InitGoogleOBBResources(ShowUI); | |
return; | |
} | |
#elif UNITY_IPHONE && !UNITY_EDITOR | |
SDKManager.Inst.AddEvent(eSDKMngEvent.GetDynamicUpdateStateSuccess, OnGetDynamicUpdateStateSuccess); | |
SDKManager.Inst.SendMsg(SDKManager.SEND_GET_DYNAMIC_UPDATE_NO); | |
return; | |
#endif |
在场景的 Launcher 上,就有 UseOBB 这个选项,用于控制我们游戏内是否能够正确识别为 Google 包
增加打印后,此处开始为 false(注:上述日志截图为解决后的表现,因此显示为 true)
这个 UseOBB 选项,之前是在出 Google 包时,通过代码直接设置的。
但是经过个人反复测试,发现新版 Unity 在代码设置场景物体属性后,该项不会正常生效!
最简单的重现方式为:代码修改场景物体属性后,重启 Unity,物体属性被还原 (即使修改后调用保存场景及资源的接口,依然无效)。
解决方式为:将出包时自动设置 UseOBB 属性处修改为一个判断,若出包类型为 Google OBB 模式,且 UseOBB 未勾选,弹出提示需要手动勾选保存。
# OBB 无法被主应用正确识别
还是根据上述日志分析,看着像是没有正确识别 OBB,导致资源未能成功加载而报空。
在之前我们 Unity2017 出包的时候,导出的 AS 项目资源中,其中 AndroidManifest.xml 文件存在一个 build-id 的字段。
不过新版 Unity 导出资源中,AndroidManifest.xml 已经没有了,所以开始是直接打的包。
2021 升级文档: Upgrading to Unity 2021 LTS
其中有这么一个描述:
Changed how Unity checks to see if an obb is compatible with an apk
. Both the apk and obb now have unity_obb_guid file inside them and if the contents match between them, Unity treats them as being compatible.
意思是现在 Unity 通过 APK 及 OBB 包中的一个名为 unity_obb_guid 文件来确认 APK 与 OBB 是否一致。
也就是说,从 Unity2021 开始,OBB 识别机制不走 build-id ,而是走另外文件对比了。
解包 OBB 后,果然发现了这个东西:
同时检查打包出来的 APK,发现缺失,通过搜索 Unity 导出资源目录,可发现 APK 中该文件位于 unityLibrary\src\main\assets\unity_obb_guid
在之前我们的打包工具中,对 assets 目录的内容只复制其子目录,导致缺了文件,修改打包工具代码:
assetPaths = Directory.GetDirectories(packAssetsPath); | |
for (int i = 0; i < assetPaths.Length; i++) | |
{ | |
FileCompare.Copy(assetPaths[i], (sdkAssetsPath + assetPaths[i].Replace(packAssetsPath, ""))); | |
} | |
// 复制 unity_obb_guid 文件 | |
string packObbGuidFile = Path.Combine(packAssetsPath, "unity_obb_guid"); | |
string sdkObbGuidFile = Path.Combine(sdkAssetsPath, "unity_obb_guid"); | |
FileCompare.Copy(packObbGuidFile, sdkObbGuidFile); |
这时候果然就可以了:
# 十、进出战斗崩溃
最初表现为打开引导时,概率发生于进入战斗时崩溃。
第一次查询的崩溃日志如下:
Received signal SIGSEGV
Obtained 50 stack frames
0x00007ff7b5c0aceb (Unity) GameObject::SendMessageAny
0x00007ff7b607e835 (Unity) Transform::BroadcastMessageAny
0x00007ff7b607e899 (Unity) Transform::BroadcastMessageAny
0x00007ff7b607e899 (Unity) Transform::BroadcastMessageAny
0x00007ff7b688d7f6 (Unity) UI::Canvas::WillDestroyComponent
0x00007ff7b5c0d3a1 (Unity) GameObject::WillDestroyGameObject
0x00007ff7b5f18901 (Unity) PreDestroyRecursive
0x00007ff7b5f16927 (Unity) DestroyObjectHighLevel_Internal
0x00007ff7b5f16564 (Unity) DestroyObjectHighLevel
0x00007ff7b626638f (Unity) Scripting::DestroyObjectFromScriptingImmediate
0x00007ff7b54dd663 (Unity) Object_CUSTOM_DestroyImmediate
0x0000018b373dcb35 (Mono JIT Code) (wrapper managed-to-native) UnityEngine.Object:DestroyImmediate (UnityEngine.Object,bool)
0x0000018b373dca53 (Mono JIT Code) UnityEngine.Object:DestroyImmediate (UnityEngine.Object)
0x0000018c1010bae3 (Mono JIT Code) YoukiaUnity.Resource.PoolManager:CleanPool (string)
0x0000018c1219f07b (Mono JIT Code) [BattleStage.cs:85] Demo.Stage.BattleStage:CleanPool ()
0x0000018c100fabab (Mono JIT Code) [BattleStage.cs:79] Demo.Stage.BattleStage:OnAsyncHandle ()
0x0000018c1219efbb (Mono JIT Code) [BattleStage.cs:116] Demo.Stage.BattleStage:<OnSceneLoaded>b__10_0 ()
0x0000018c1219ef76 (Mono JIT Code) [BattleManager.cs:1798] BattleManager:_onCreateFinish ()
0x0000018b3af83c05 (Mono JIT Code) YoukiaCore.AsyncCombiner:RefreshAsyncHandles ()
0x0000018b3af8d2c3 (Mono JIT Code) YoukiaCore.AsyncCombiner/AsyncHandle:Finish ()
0x0000018c1011fbdb (Mono JIT Code) [BattleManager.cs:2004] BattleManager/<>c__DisplayClass139_0:<_onRoleAdd>b__0 ()
0x0000018c1219dd26 (Mono JIT Code) [ModelChiefView.cs:147] Demo.ModelChiefView/<>c__DisplayClass10_0:<Create>b__2 ()
0x0000018c1219db21 (Mono JIT Code) [ModelChiefView.cs:193] Demo.ModelChiefView/<>c__DisplayClass13_0:<AddMountSkillClip>b__0 (object)
0x0000018b3af41b1c (Mono JIT Code) FrameWork.FProcess:allDone ()
0x0000018b3af41573 (Mono JIT Code) FrameWork.FProcess:AssetLoaded (object[])
0x0000018b3af40fff (Mono JIT Code) FrameWork.FGameEvent:DispatchEvent (System.Enum,object[])
0x0000018b3af40e43 (Mono JIT Code) FrameWork.FEventManager:DispatchEvent (System.Enum,object[])
0x0000018b3af40d33 (Mono JIT Code) FrameWork.FProcess:ProcessComplete ()
0x0000018b3af3a9ab (Mono JIT Code) FrameWork.FProcess/<_load>d__18:MoveNext ()
0x0000018b3862cff0 (Mono JIT Code) UnityEngine.SetupCoroutine:InvokeMoveNext (System.Collections.IEnumerator,intptr)
0x0000018b3862d11f (Mono JIT Code) (wrapper runtime-invoke) <Module>:runtime_invoke_void_object_intptr (object,intptr,intptr,intptr)
0x00007ffdf5e6e4b4 (mono-2.0-bdwgc) [mini-runtime.c:3445] mono_jit_runtime_invoke
0x00007ffdf5dae764 (mono-2.0-bdwgc) [object.c:3066] do_runtime_invoke
0x00007ffdf5dae8fc (mono-2.0-bdwgc) [object.c:3113] mono_runtime_invoke
0x00007ff7b626dac4 (Unity) scripting_method_invoke
0x00007ff7b6268644 (Unity) ScriptingInvocation::Invoke
0x00007ff7b621a1df (Unity) Coroutine::Run
0x00007ff7b6217c1c (Unity) Coroutine::ContinueCoroutine
0x00007ff7b5d35344 (Unity) DelayedCallManager::Update
0x00007ff7b5f45d29 (Unity) `InitPlayerLoopCallbacks'::`2'::UpdateScriptRunDelayedDynamicFrameRateRegistrator::Forward
0x00007ff7b5f2c89c (Unity) ExecutePlayerLoop
0x00007ff7b5f2c973 (Unity) ExecutePlayerLoop
0x00007ff7b5f325d9 (Unity) PlayerLoop
0x00007ff7b6e7993f (Unity) PlayerLoopController::UpdateScene
0x00007ff7b6e77bdf (Unity) Application::TickTimer
0x00007ff7b72c37aa (Unity) MainMessageLoop
0x00007ff7b72c805b (Unity) WinMain
0x00007ff7b864b42e (Unity) __scrt_common_main_seh
0x00007ffe3a797034 (KERNEL32) BaseThreadInitThunk
0x00007ffe3b5c2651 (ntdll) RtlUserThreadStart
对比代码发现,我们游戏在进入战斗时,会强制清理一次对象池,而清理方式则是直接调用 GameObject.DestroyImmediate
GameObject.DestroyImmediate 和 GameObject.Destroy 一直是有区别的,记得很早以前官方都推荐正式环境用 Destroy 而非 DestroyImmediate。
为了确认看了下文档:
立即销毁对象 /obj/。强烈建议您改用 Destroy。
该函数应只在编写 Editor 代码时使用,因为在编辑模式下, 永远不会调用延迟销毁。 在游戏代码中,您应该改用 Object.Destroy。Destroy 始终延迟进行 (但在同一帧内执行)。 使用该函数时要务必小心,因为它可以永久销毁资源! 另请注意,切勿循环访问数组并销毁正在迭代的元素。这会导致严重的问题(这是一条通用的编程实践,而不仅仅是在 Unity 中)。
说明 DestroyImmediate 肯定是有一定风险的,特别是 CleanPool 中的代码,还正好是一个循环调用的 DestroyImmediate。
于是怀疑是否这里的影响,尝试将其修改为 Destroy。
如此之后 ———— 还是崩溃了,日志变成如下:
Asset Pipeline Refresh: Total: 0.027 seconds - Initiated by RefreshV2(AllowForceSynchronousImport)
Received signal SIGSEGV
Obtained 22 stack frames
0x00007ff7b5c0aceb (Unity) GameObject::SendMessageAny
0x00007ff7b607e835 (Unity) Transform::BroadcastMessageAny
0x00007ff7b607e899 (Unity) Transform::BroadcastMessageAny
0x00007ff7b607e899 (Unity) Transform::BroadcastMessageAny
0x00007ff7b688d7f6 (Unity) UI::Canvas::WillDestroyComponent
0x00007ff7b5c0d3a1 (Unity) GameObject::WillDestroyGameObject
0x00007ff7b5f18901 (Unity) PreDestroyRecursive
0x00007ff7b5f16927 (Unity) DestroyObjectHighLevel_Internal
0x00007ff7b5f16564 (Unity) DestroyObjectHighLevel
0x00007ff7b5d3345b (Unity) DelayedDestroyCallback
0x00007ff7b5d35344 (Unity) DelayedCallManager::Update
0x00007ff7b5f457b9 (Unity) `InitPlayerLoopCallbacks'::`2'::PostLateUpdateScriptRunDelayedDynamicFrameRateRegistrator::Forward
0x00007ff7b5f2c89c (Unity) ExecutePlayerLoop
0x00007ff7b5f2c973 (Unity) ExecutePlayerLoop
0x00007ff7b5f325d9 (Unity) PlayerLoop
0x00007ff7b6e7993f (Unity) PlayerLoopController::UpdateScene
0x00007ff7b6e77bdf (Unity) Application::TickTimer
0x00007ff7b72c37aa (Unity) MainMessageLoop
0x00007ff7b72c805b (Unity) WinMain
0x00007ff7b864b42e (Unity) __scrt_common_main_seh
0x00007ffe3a797034 (KERNEL32) BaseThreadInitThunk
0x00007ffe3b5c2651 (ntdll) RtlUserThreadStart
由于这次属于延时销毁,所以不再有实际调用销毁的堆栈打印,不过最终走到的崩溃位置还是一样: GameObject::SendMessageAny
在此处增加对应销毁物体打印,只看到一个 TS_MainButtomView
<color=lightblue>1660365391</color> Debug:销毁:TS_MainButtomView
造成崩溃对象可能就是这个 Prefab,在我们项目是一个 UI 组件。
并且后续经过尝试,若不销毁该对象,同样有可能崩溃,且会发生于退出战斗时也可能崩溃。
这时候基本上已经相当怀疑这个对象了,只是由于每次碰到的崩溃最终堆栈信息都还不尽相同,所以无法定性,常见有:
- 销毁
- 创建
- 刷新精灵 Sprite
tlsf_free
DynamicHeapAllocator::Deallocate
正准备试试官方文档介绍的 Debug 方式 的时候,后续测试同学竟还找到了一个稳定复现之处,再经个人反复尝试和查询,最终得到如下指向比较清晰一点日志:
Received signal SIGSEGV
Obtained 51 stack frames
0x00007ff7e8a5f2a4 (Unity) UI::CanvasManager::AddCanvas
0x00007ff7e8a73633 (Unity) UI::Canvas::AddToManager
0x00007ff7e8a73c65 (Unity) UI::Canvas::AwakeFromLoad
0x00007ff7e8512fe7 (Unity) AwakeFromLoadQueue::InvokeAwakeFromLoad
0x00007ff7e850e003 (Unity) AwakeFromLoadQueue::AwakeFromLoadAllQueues
0x00007ff7e7df417a (Unity) GameObject::ActivateAwakeRecursively
0x00007ff7e7dfb719 (Unity) GameObject::SetSelfActive
0x00007ff7e767fd9b (Unity) GameObject_CUSTOM_SetActive
0x00000126e405c104 (Mono JIT Code) (wrapper managed-to-native) UnityEngine.GameObject:SetActive (UnityEngine.GameObject,bool)
0x00000126f84444fb (Mono JIT Code) [MainButtomView.cs:1442] MainButtomView/<>c__DisplayClass115_0:<OpenView>b__0 (object)
崩溃结果指向,界面的子节点挂载的 Canvas,该对象 (TS_MainButtomView) 属于主界面 Aorpage 的一个 子节点 Prefab,且自身挂载了 Canvas。
这里经过尝试,表明该组件并非是必须的。
于是移除之后就真的好了..
# 十一、重打图集混乱
例如,更改图集大小,最明显的一个错误表现如下:
如果现在点击提交,发现 meta 文件都未被修改:
而且编辑器中也可以明显看出 Position 依然是旧的 —— 经过分析调试,在编辑器工具生成新的图集数据时,确实也是赋值了新的数据的:
于怀疑是不是 Unity 内部又出啥事了。
经过查询关键字 textureImporter.spritesheet,有找到一篇可能有所关联的博客 unity 2D Sprite 网格 Slice 工具
这篇文章描述了 Unity 的 textureImporter.spritesheet 大约存在一个 BUG,当图集中图片未被修改,仅发生位置表动时,将导致其数据无法正常更新。虽然这篇博文已经早是 2015 年的东西了,不过本着尝试一下的感觉,没成想真是这个问题!
末尾给加上
textureImporter.mipmapEnabled = !textureImporter.mipmapEnabled; | |
textureImporter.SaveAndReimport(); | |
textureImporter.mipmapEnabled = false; | |
textureImporter.SaveAndReimport(); |
就正常了,关键是这个在之前 Unity2017 还能正常工作的。
# 十二、正式包
其它比较简单一点、或者一些功能上的的升级问题,这里就不作记录了,最终正式包如下: