Unity的Addressables使用体验

之前大致的了解了Addressalbe这个插件的大致功能和原理,并没有深入的使用。刚好这段时间有时间,就按照具体的项目场景搭建了一个简单的框架来体验。我是用它跟XLua来搭建整个游戏的框架的。Addressables的版本是1.22.3。我的体验也是围绕这个版本来讲的。使用的过程中发现它的下面几个问题,让我觉得目前还是不要在项目中使用它。 1.性能问题 通过标签一次性加载大量粒度很细的文件,会非常的慢。 Adressables可以通过lable标签来加载同一类型的资源。比如在项目中,把所有类型的lua文件都打上 <Lua> 标签。你可以选择 Addressables.LoadResourceLocationsAsync 先获取所有打了该标签的文件地址,然后再用一个循环去加载每一个地址的lua文件,然后把文件按照Key-Value的方式存在一个字典中。也可使用 Addressables.LoadAssetsAsync<Type>("key"), (asset)=>{} 方式按照标签加载所有的资源,然后在它里面的回调中去处理每一个lua文件,并保存到字典。这个回调每加载一个该标签下的文件,都会回调一次。 很显然,这个加载循环无法避免。由于lua文件的数量巨大,导致这个循环完成非常的耗时,比如在这次测试的项目中,总共有接近400个lua文件,总耗时在webgl平台达到惊人的 5.89 秒,并且随着功能的增多,这个数量只会往上加。这是很难接受的。android和IOS我没有测试,不过由于多线程的加持,估计是会比较快的。我们的主要平台是webgl. 2.工具黑盒 这个插件并不是开源的,对于用户来说,资源的下载,打包过程都是不受用户控制的。也就是说,它对用户来说是一个黑盒。他在官网有用户手册和API说明,但也就仅仅是那些而已。我甚至觉得这个东西不是为游戏开发者准备的,更像是一个功能说明。它的加载API少的非常的可怜,估计也就3个左右吧,不知道是不是我看的不够仔细,我真的没有再发现别的加载API了。而且API设计的很模糊,太多的功能细节集中到一个API上了,然后例子又少。虽然GitHub上有官方的示例,但我真的还是第一次见到如此模糊的API设计,我真的无法通过参数去了解该API到底能做什么事情。 用户没有办法了解其中具体的细节,也没有办法去定制项目需要的功能,万一有一些东西满足不了项目的需求需要修改,就只能干着急,你做不了任何事情。它的封装级别很高,但是对于游戏开发者来说,这过头了。这个插件更像是面向产品用来设计快速原型的产物。就拿上面的第一个问题来说,所有的lua其实已经打在一个AB包里面了,那么AB包下载之后,再从AB包中去加载lua文件,应该是非常的快的(毫秒级别),但不知道为什么,400多个文件加载居然要5.89秒,它里面是不是每一个文件加载都去走了什么特殊流程导致非常的慢。然后勾选了缓存,第二次,第三次,第四次的时间也是一样的,居然没有任何的时间减少。用户知道的太少了。 3.各种的小问题 我这里例举一个下载进度的问题。 public IEnumerator LoadAssetAsyn(string key, Action<Object> completeCB, Action<float> pgrFunc = null) { AsyncOperationHandle<Object> opHandle; opHandle = Addressables.LoadAssetAsync<Object>(key); while (opHandle.Status == AsyncOperationStatus.None) { Log.Error("Percent=" + opHandle.GetDownloadStatus().Percent + " pp=" + opHandle.PercentComplete); if (pgrFunc != null) { pgrFunc(opHandle.GetDownloadStatus().Percent); } yield return null; } yield return opHandle; if (opHandle.Status == AsyncOperationStatus.Succeeded) { Object instObj = Object.Instantiate(opHandle.Result); completeCB(instObj); if (pgrFunc != null) { pgrFunc(1); } } yield return null; } 上面的这个方法去加载一个资源,然后通过回调告诉目前的下载进度。opHandle.GetDownloadStatus().Percent 和 opHandle.PercentComplete 都不准确。前者是通过大小计算百分比,后者是通过数量计算百分比。 下面是文档的原话: AsyncOperationHandle.PercentComplete: Reports the percentage of sub-operations that have finished. For example, if an operation uses six sub-operations to perform its task, the PercentComplete indicates the entire operation is 50% complete when three of those operations have finished (it doesn’t matter how much data each operation loads). AsyncOperationHandle.GetDownloadStatus: Returns a DownloadStatus struct that reports the percentage in terms of total download size. For example, if an operation has six sub-operations, but the first operation represented 50% of the total download size, then GetDownloadStatus indicates the operation is 50% complete when the first operation finishes. ...

November 7, 2024 · 1 min · 188 words · LINK

Unity的Addressables学习日志

1.构建Player的时候,项目的资产数据是如何放进Player中的 由下图可以看出来Unity中的资产分成了4个主要类别: 1.被场景引用的资源 2.放在Resources文件夹中的资源 3.标记为了Addressables Group的资源 4.放在了StreamingAssets文件夹中的资源 2.如果同一个资产被划分到了不止一个类别上,那么该资产在构建的时候就会产生重复,划分了多少个就会复制多少个 3.Addressables Groups中的共享资产 对于一个标记为非Adressables的资产,如果有不止一个Adressablees的资产引用了它,那么它会在每个引用了它的Addressables的bundle中都复制一份。如下图所示: 为了消除这种复制,那么可以将这个共有的资产也标记为Addressables。然后把它引入到一个已存在的bundle中就可以了。如果此时一个bundle要引用它,那么必须在该bundle实例化前,将引入了共有资产的bundle先加载才可以。 4.Sprite的依赖 图集的引用跟其他资产不一样。 情形一 三张纹理,分别在三个不同的group中,它们会分别进入三个不同的ab包,彼此之间没有依赖,每个ab中图片的大小是500KB 情形二 还是这三张图片,它们被放进了一个图集,这个图集没有被标记为Addressables。那么包含这个图集的ab包将有图集的1500KB的大小。而另外两个ab包则只包含Sprite的元数据(只有很小的几KB),并且将包含图集的ab包列为自己的依赖。无论是哪个ab包包含这个图集,在重新构建之后都是跟前面一样的结果,这个过程由Unity控制,用户决定不了。这是跟标准的处理依赖重复资源过程的最大不同。Sprites的加载依赖它的图集,而图集的加载只依赖包含它的ab包的加载。 情形三 在上面的情形二的基础上,图集被标记成了Addressables并且单独放在它自己的图集中。这时候就会创建4个ab包。如果使用的是Unity2020.x或者更新的版本,那么在构建之后,4个ab包之间的依赖关系会和期待的一样,即图集资源在图集的这个ab包,有1500KB。另外三个ab包把图集的ab包作为依赖,只有几KB的引用数据而已。如果使用的是Unity2019.x或者更旧的版本,那么纹理资源就可能存在于这4个ab包中的某一个。另外三个ab包依然把图集所在的ab包作为依赖。然而此时图集所在的ab包可能仅仅只包含了图集的一些元数据,而真正的纹理资源可能在其他的另外3个ab包中。 5.Addressable预制体跟Sprite的依赖 情形一 跟三个标记为Addressables的纹理不同的是,这里是三个标记为Addressables的Sprite的预制体。每个预制体包含它独自的Sprite。在构建之后,三个ab包的大小如期望的那样,都是500KB。 情形二 跟情形一类似,3个预制体分别引用不同的Sprite,只不过所有的3个Sprite添加到一个图集中,但是这个图集没有标记为Addressables。在这种情形下,图集的纹理机会产生重复。在构建之后,三个ab包都大概有1500KB。这个重复资源的规则类似于一般的Addressables重复资源规则,但是跟前面介绍的 Sprite的依赖 中的情形二又不一样。 情形三 基于上面的情形二的预制体,纹理和图集,不过这里把图集标记为Addressables。此时图集纹理就仅仅只存在在包含图集的ab包中。另外的三个预制体的ab包就把这个图集的ab包作为依赖。 6.ab包的分包规则 1.可以将一个group中所有Addressables的资源都打进一个ab包中 2.可以将一个group中的每个Addressables资源分别打进它独自的ab包中 3.可以将使用同一个label的所有的Addressables资源打包进一个ab包中 7.同一个group中的场景资源会跟其他的Addressables资源分开打包 也就是说一个group中如果含有场景资源和其他非场景资源,那么构建之后,至少会得到两个bundle,一个包含场景资源,一个是除了场景外的其他资源。

August 29, 2023 · 1 min · 35 words · LINK

Ubuntu22.04运行unity2020.3.xx报错unity_csc.sh_Aborted

在Ubuntu22.04中出现,而且出现这个错误的版本是低于Unity2022.x的版本。自己测试了Unity2020.x和Unity2021.x都会出现这个错误。 重现的步骤也很简单,创建一个空项目并进入就会报错,然后提示进入safety mode。 这个错误会在Unity的Editor.log中留下线索,不过在打开项目前,记得先把Editor.log先删除,这样在报错的时候会生成一个新的Editor.log文件。也可以不用这样做,这样做的主要目的是日志少检查起来比较简单。 打开Unity编辑器的日志:Editor.log。发现以下关键信息 -----CompilerOutput:-stderr---------- No usable version of the libssl was found /home/eyap/Unity/Hub/Editor/2020.3.33f1/Editor/Data/Tools/RoslynScripts/unity_csc.sh: line 3: 25959 Aborted (core dumped) "/home/eyap/Unity/Hub/Editor/2020.3.33f1/Editor/Data/Tools/RoslynScripts/../../Tools/Roslyn/csc" /shared /noconfig @temp/UnityTempFi -----EndCompilerOutput--------------- 从 No usable version of the libssl was found 可以看出是缺少了 libssl库导致的。用下面的命令安装一个就行了: echo "deb http://security.ubuntu.com/ubuntu focal-security main" | sudo tee /etc/apt/sources.list.d/focal-security.list sudo apt-get update sudo apt-get install libssl1.1 相关的讨论连接: https://forum.unity.com/threads/unity2020-3-doesnt-works-on-linux.1338869/

August 29, 2023 · 1 min · 55 words · LINK

Xlua集成第三方库

XLua集成第三方库(Rapidjson举例) 参考教程:https://zhuanlan.zhihu.com/p/389424029 1.环境准备 (1)cmake的安装 https://cmake.org/download/ (2)安装Visual Studio最新版(2017,2019,2022)都行,去微软官网下载 (3)安卓的话,下载Android Studio,然后安装好Android SDK,NDK,并且配置好ANDROID_SDK和ANDROID_NDK 2.将XLua克隆到本地 xlua的地址 https://github.com/Tencent/xLua 进入到拉取下来的xlua文件夹, 在build文件夹中新建lua-rapidjson文件夹,并在该文件夹下新建include和source文件夹 4.拉取lua-rapidjson的项目到本地 这里需要说明一下,lua-rapidjson两个版本: 一个是原作者的最新版 https://github.com/xpol/lua-rapidjson 另一个是XLua的做过兼容的版本: https://github.com/chexiongsheng/build_xlua_with_libs 新版肯定是更新,但是再编译的过程中会遇到很多错误,非常的麻烦浪费时间,所以我们还是选择XLua做过兼容的版本,节省时间,并且也能满足要求。 将build_xlua_with_libs克隆到本地。 5,直接将build_xlua_with_libs中build文件夹下的lua-rapidjson文件夹复制到自己的xlua的build文件夹下面。 6.修改xlua/build/CMakeList.txt文件,在头部加入如下代码 #begin lua-rapidjson set (RAPIDJSON_SRC lua-rapidjson/source/rapidjson.cpp ) set_property( SOURCE ${RAPIDJSON_SRC} APPEND PROPERTY COMPILE_DEFINITIONS LUA_LIB ) list(APPEND THIRDPART_INC lua-rapidjson/include) set (THIRDPART_SRC ${THIRDPART_SRC} ${RAPIDJSON_SRC}) #end lua-rapidjson 并且把头部的VERSION 改成自己的cmake版本(在控制台输入cmake –version),我这里是cmake_minimum_required(VERSION 3.26.4) 6.编译windows的xlua.dll 这里我们选择lua54版本 所有编译脚本都是按这个方式命名:make_平台_lua版本.后缀。 比如windows 64位lua53版本是make_win64_lua53.bat,android的luajit版本是make_android_luajit.sh,要编译哪个版本就执行相应的脚本即可。 执行完编译脚本会自动拷贝到plugin_lua53或者plugin_luajit目录,前者是lua53版本放置路径,后者是luajit。 用代码编辑器打开这个bat文件,将Visual Studio版本改成自己安装的对应版本, 如果不知道就打开 Developer Command Prompt这个控制台,里面会有如图所示 ...

July 13, 2023 · 1 min · 211 words · Link

Unity编辑器为切换平台,工程加载完成事件添加自动处理

有时候我们在Unity项目中,需要在切换平台后,或者项目加载完后,做一些自动化的处理,比如项目加载完后检测一些配置,或者检测项目版本。或者在切换项目平台的时候,需要跟据不同的平台设置不同的字体移除不属于该平台的一些资源等等。下面记录一下如何添加对这两个事件的处理。 1.Unity切换平台事件的处理 要处理这个事件,我们需要将类实现 IActiveBuildTargetChanged 接口,并重写该接口的 OnActiveBuildTargetChanged方法。那么当切换平台的时候,就会执行这个类的 OnActiveBuildTargetChanged 方法。在这个方法中,我们就可以添加自定义的一些处理逻辑了。代码如下: /// <summary> /// 切换平台后,对一些资源的处理 /// 有些资源只有再特定的平台才会使用,再切换平添后,不要的资源要移除或者移动到别的地方 /// </summary> public class BuildTargetChangedTool : IActiveBuildTargetChanged { public int callbackOrder { get; } public void OnActiveBuildTargetChanged(BuildTarget previousTarget, BuildTarget newTarget) { //切换平台后的一些处理 PlatformResTool.ProcessFonts(newTarget); PlatformResTool.ProcessThirdSDK(newTarget); LinkXmlBuildProcessor.LinkXmlOp(newTarget); AssetDatabase.Refresh(); } } 2.Unity项目加载完事件的处理 项目加载完成的事件跟切换平台的事件的处理方式不一样,这里需要使用 InitializeOnLoad 这个特性来实现。我们需要给类添加这个特性,同时它需要一个静态构造方法,当项目加载完成后,Unity编辑器就会调用这个静态构造方法,然后在这个静态构造方法中我们就可以添加一些自己的处理逻辑了。代码如下: /// <summary> /// 工程加载完,要处理这些跟平台相关的资源 /// </summary> [InitializeOnLoad] public class StartUpTools { static StartUpTools() { if (!EditorApplication.isPlayingOrWillChangePlaymode) { BuildTarget buildTarget = Packager.GetBuildTarget(); PlatformResTool.ProcessFonts(buildTarget); PlatformResTool.ProcessThirdSDK(buildTarget); LinkXmlBuildProcessor.LinkXmlOp(buildTarget); AssetDatabase.Refresh(); } } }

June 26, 2023 · 1 min · 74 words · Link

XLua框架,Unity3D,WEBGL,报错ArgumentException-Destination-array-was-not-long-enough

Unity使用XLua框架,打WEBGL包,运行时报错:ArgumentException: Destination array was not long enough 具体的错误如下: dangerArgumentException: Destination array was not long enough. Check destIndex and length, and the array's lower bounds {: .prompt-danger } 由于我们UI使用的时FGUI,所以这个错误的具体表现是: 错误表现 在编辑器中运行良好,没有任何问题 打包成WEBGL运行就报这个错,但不是每次都报错, 如果报错,那么UI逻辑是正常的,如果不报错,那么UI逻辑异常,比如按钮的绑定事件错乱 解决办法 我在XLua的Issue中,找到了大佬的解决办法。是XLua在释放资源时的Lock操作引起的。原issue地址。但是大佬的贴的代码,不能直接运行,需要稍加修改才行【大佬可能给的时伪代码】。我在这里把解决步骤归纳一下: 1.添加一个非锁互斥队列【也可以不用添加,就用系统的也行】: using System.Threading; namespace XLua { public class LockFreeQueue<T> { internal class SingleLinkNode<U> where U : T { public SingleLinkNode<U> Next; public U Item; } static private bool CAS<T>(ref T location, T comparand, T newValue) where T : class { return comparand == Interlocked.CompareExchange(ref location, newValue, comparand); } SingleLinkNode<T> head; SingleLinkNode<T> tail; int count; public int Count { get { return count; } } public bool IsEmpty { get { return count <= 0; } } public LockFreeQueue() { head = new SingleLinkNode<T>(); tail = head; count = 0; } public void Enqueue(T item) { SingleLinkNode<T> oldTail = null; SingleLinkNode<T> oldTailNext; SingleLinkNode<T> newNode = new SingleLinkNode<T>(); newNode.Item = item; bool newNodeAdded = false; while (!newNodeAdded) { oldTail = tail; oldTailNext = oldTail.Next; if (tail == oldTail) { if (oldTailNext == null) newNodeAdded = CAS<SingleLinkNode<T>>(ref tail.Next, null, newNode); else CAS<SingleLinkNode<T>>(ref tail, oldTail, oldTailNext); } } CAS<SingleLinkNode<T>>(ref tail, oldTail, newNode); Interlocked.Increment(ref count); } public bool TryDequeue(out T item) { item = default(T); SingleLinkNode<T> oldHead = null; bool haveAdvancedHead = false; while (!haveAdvancedHead) { oldHead = head; SingleLinkNode<T> oldTail = tail; SingleLinkNode<T> oldHeadNext = oldHead.Next; if (oldHead == head) { if (oldHead == oldTail) { if (oldHeadNext == null) { return false; } CAS<SingleLinkNode<T>>(ref tail, oldTail, oldHeadNext); } else { item = oldHeadNext.Item; haveAdvancedHead = CAS<SingleLinkNode<T>>(ref head, oldHead, oldHeadNext); } } } Interlocked.Decrement(ref count); return true; } public T Dequeue() { T result; if (TryDequeue(out result)) return result; return default(T); } public void Clear() { while (Count > 0) { Dequeue(); } } } } 2.修改XLua源码,添加条件编译,让WEBGL在释放引用的时候不加锁 改下面几个地方: ...

October 31, 2022 · 2 min · 376 words · Link