加载方式 | 加载接口 | 加载后缀 | 异步 | 热更 | 卸载某个 |
Resource | Load | 无 | √ | × | |
AssetDatabase | LoadAssetAtPath | 文件后缀 | × | × | × |
AssetBundle | GetDependent→LoadDpAB→LoadAB→LoadAsset | 无 | √ | √ | √ |
加载资源,指从磁盘加载到内存中,反序列化然后实例化Instance才会真正到Scene中。
Resource.Load是Unity加载Resources文件夹的加载方式,Resources文件夹会随着打包一起被打到游戏包内。
正式项目切勿使用此接口,无法热更,没有找到分包的方法,每次发包都要重新打。
一般来说最先接触到的资源加载接口是Res.Load,对于项目前期或者小项目会比较方便,因为不需要对打包资源做任何操作,放在resources下的资源就能随着build一起打了,但对于大项目需要热更项目来说,基本不会使用了,因为不能分包也无法热更(也可能是我没找到办法),上线就肯定用AssetBundle
Editor下的加载方式,加载路径是项目下的路径,除了Resources文件夹都不会随着打包打到游戏本体内。
注意路径是带Assets开头,并且需要后缀
但AssetDataBase不支持异步,所以editor下的效果和打包效果不一样,而且这样的加载还需要后缀,或者用ab名去索引,否则导致会多些一些代码,不过问题不大,习惯就好。
如果DestroyImmediately(true)后除非重启unity,再也加载不出来
加载Asset Bundle可以用于热更资源。
public class SampleBehaviour : MonoBehaviour
{
IEnumerator Start()
{
var uwr = UnityWebRequestAssetBundle.GetAssetBundle("http://myserver/myBundle.unity3d");
yield return uwr.SendWebRequest();
// Get an asset from the bundle and instantiate it.
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(uwr);
var loadAsset = bundle.LoadAssetAsync("Assets/Players/MainPlayer.prefab");
yield return loadAsset;
Instantiate(loadAsset.asset);
}
}
AssetBundle(简称AB)这是一个分包和热更的方案,调用打包接口之后就能产生出一堆的AB包,这样做使得增量打包更快(不需要每次把所有资源重新打),热更时更新的内容也相对的小。
Asset才是真正的资源例如贴图、网格、动画那些都是Asset,AB可以理解成Asset的包,AB只会记录里面有那些Asset,依赖了其他的哪些AB,是一个非常小的东西
一般来说同一类的资源会打成一个AB,例如角色Prefab会依赖贴图网格动画等..
AB可能又会依赖多个其他AB,所以加载经常会需要先加载依赖,否则加载出来的东西也还是空的,依赖使用Manifest获取
Manifest是会随着打包产生出来,最好能在初始化的时候把Manifest加载了,然后存起来使用。
但AB缺点是editor下使用比较麻烦,总不能每次改了资源都重新打AB,所以editor可以用AssetDataBase
meta
guid就唯一标识这个资源
同时保存了一些设置 和 ABName..
Library
Unity会把Asset下支持的资源导入成自身识别的格式,以及编译代码成为DLL文件,都放在Library文件夹中。
如果打包编译的shader也会存在这
如果打图集也会存在这
大项目如果删除这个文件夹会导致导入很久很久...
在AssetDataBaseV2之前,如果切平台也会重导很久...
Asset
用到的资源,比如,模型文件,贴图文件,声音文件等等
注:这才是真正占用内存的大头,一般Prefab的目标Asset也很小,大的是依赖,依赖被目标Asset引用,只有卸载了Asset再调用UnloadUnuse才真正的释放了内存
hashCode为正
GameObject
hashCode为负数
AssetBundle
AssetBundle是一个存档文件,其中包含平台在运行时加载的特定资产(模型,纹理,预制,音频剪辑,甚至整个场景)。
AssetBundles可以表示彼此之间的依赖关系;例如AssetBundle A中的一个材质可以引用AssetBundle B中的一个纹理。
为了通过网络进行有效的传递,可以根据用例要求,选择内置算法(LZMA和LZ4)来对AssetBundles进行压缩。
AssetBundles可用于可下载内容(DLC),减少初始安装大小,加载为最终用户平台优化的资产,并降低运行时内存压力。
由上图可见AssetBundle是包含了多个或者单个Asset的包,里面的Asset可能是贴图可能是模型,
所以加载过程上来说,一个prefab可能会包含多个AssetBundle,
加载必须先加载AssetBundle以此来寻找Asset,可以通过AssetBundleManifest.GetAllDependencies获取依赖的AssetBundle。
细节:AssetBundle是一个小文本,记录了依赖和aaset的一些预制信息,一个没有依赖的AB镜像文件大概会有7kb左右的内存,一般文件会在10多k,一般几个依赖的ab加载会有0-2ms左右,很小如果把很多文件都打成一个ab会引起依赖特别多的情况,所以分包是一个需要好好控制的事情
Manifest
随着调用打包的API会自动产生Manifest,是一个保存所有AssetBundle关系的清单。
一般在加载过程中都需要通过Manifest获取依赖然后再加载真正的Asset.
加载与Instance
为什么要Instance呢,因为可能会有多个重复加载,用一个加载好的,通过部分复制,大部分引用的方式来使用。
图示
1.设计AssetBundle
//todo 如果多人看就搞个全程截图教程
应用:
按照一定策略打包
一个关卡/UI面板多张贴图打成一张贴图,以此减少drawcall但也增加了内存。
如果不使用loadassetall,单独的资源打成一个Asset,减少加载量和加载的包量,但也使需要打的包数量增加。
2.打包
接口:BuildPipeline.BuildAssetBundles
使用:会打包所有标记了assetbundle名的资源,并且会产生一个AssetBundleManifest 其会listing all AssetBundles included in this build.
BuildPipeline.BuildAssetBundles("Assets/ABs", BuildAssetBundleOptions, BuildTarget);
public class TestEditor
{
[MenuItem("Test/build Test")]
public static void BuildTest()
{
BuildAssetBundleTest(EditorUserBuildSettings.activeBuildTarget);
}
const string AssetBundlesOutputPath = "Assets/StreamingAssets/";
static void BuildAssetBundleTest(BuildTarget buildTarget)
{
string outputPath;
outputPath = AssetBundlesOutputPath;
Debug.Log("outputPath:" + outputPath);
if (!Directory.Exists(outputPath))
{
Directory.CreateDirectory(outputPath);
}
BuildAssetBundleOptions buildOptions = BuildAssetBundleOptions.ChunkBasedCompression |
BuildAssetBundleOptions.DeterministicAssetBundle |
BuildAssetBundleOptions.DisableWriteTypeTree;
BuildPipeline.BuildAssetBundles(outputPath, buildOptions, buildTarget);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log("打包完成");
}
}
3.Build Asset上传到场外
4.加载依赖
获取 : Manifest.GetAllDependencies(abPath);
4.同步加载
4.AB异步加载
目的:缓解同步加载卡顿
注意:异步的时候同步加载会造成冲突,导致加载失败等问题
5.Cache
把加载的AB Cache住,因为ab不能重复加载。
把加载的Asset Cache住,不用每次都去判断ab是否加载、寻找依赖、加载依赖。load的时候直接返回cache。
应用:
方案①做池管理加载和卸载,底层不cache只负责unload
方案②cache了加载好的Obj,用于下次调用直接返回obj ,每次调用进行了计数+1,调用Unload可以-1
6.实例化
注:实例化如果是异步,可以在底层列队
7.卸载Assets
目的:
①游戏内容多,导致内存过大,
②没有内存问题的游戏可以只在切换场景的时候才清空Cache、GC释放内存
使用api:
①asset.unload(false) 只会卸载包头文件
②asset.unload(true) 包头与asset都会删除(如果场景还有GameObject会丢失 或者 如果不卸载会内存过高/泄漏)
说明:
加载系统一般会保存目标asset,而其引用着依赖的asset
目标asset的unload只会unload目标 但一般引用的asset才是内存占用最高的 所以存在2种卸载方式①调用卸载时 把依赖的也unloadtrue(目前只卸载了目标ab和asset) ②调用unloadunuse才卸载依赖的asset
如何卸载:
①当asset引用为0 unload(true) 此时只是把目标的AB卸载掉 依赖依然在内存中 但引用为0 调用UnloadUnuseAssets 下次GC便会清除
②切场景全部删除(除了列表里的)
UnloadUnuseAsset这个API是Unity写的 是个异步的 一般会有几帧的小卡 做个回调 等UnloadUnuse做完才调用C#的GC C#的GC是会同步的卡顿的。需要在适合的时机调用
assetbundle:Not Saved→AssetBundle 序列化Other→SerializedFile
-------------------------------------------------------------------------------------
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class AssetBundleMgr
{
protected AssetBundleManifest manifest;
protected string manifestPath = "StreamingAssets";
protected Dictionary cacheAB = new Dictionary();
private static AssetBundleMgr mInstance = null;
public static AssetBundleMgr GetInstance()
{
if (mInstance == null)
{
mInstance = new AssetBundleMgr();
}
return mInstance;
}
private AssetBundleMgr()
{
AssetBundle ab = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, manifestPath));
if (ab != null)
{
var manifest = ab.LoadAsset("AssetBundleManifest");
if (manifest == null)
Debug.LogError("Load Manifest Asset Fail");
else
this.manifest = manifest as AssetBundleManifest;
}
else
{
Debug.LogError("Load Manifest AssetBundle Fail");
}
}
public Object Load(string abPath, string prefabName)
{
LoadDependencies(abPath);
if (!cacheAB.TryGetValue(abPath, out var ab))
ab = LoadAssetBundle(abPath);
var asset = LoadAsset(ab, prefabName);
return asset;
}
protected void LoadDependencies(string abPath)
{
if (manifest == null)
return;
string[] dependences = manifest.GetAllDependencies(abPath);
for (int i = 0; i < dependences.Length; i++)
{
string dependABPath = dependences[i];
if (!cacheAB.ContainsKey(dependABPath))
{
cacheAB[dependABPath] = LoadAssetBundle(dependABPath);
}
}
}
protected AssetBundle LoadAssetBundle(string abPath)
{
var fullAbPath = Path.Combine(Application.streamingAssetsPath, abPath);
var ab = AssetBundle.LoadFromFile(fullAbPath);
cacheAB[abPath] = ab;
if (ab == null)
Debug.LogError("Failed to load AssetBundle" + abPath);
return ab;
}
protected Object LoadAsset(AssetBundle assetBundle, string assetName)
{
Object asset = assetBundle.LoadAsset(assetName);
if (asset == null)
Debug.Log("Failed to load asset:" + assetName);
return asset;
}
}
using System.IO;
using UnityEditor;
using UnityEngine;
public class BuildTest
{
static string mResPath = "Assets/Res";
static string outputPath = Application.streamingAssetsPath;
[MenuItem("Tools/Build PC")]
public static void BuildPCAssetBundle()
{
BuildAssetBundle(BuildTarget.StandaloneWindows);
}
public static void BuildAssetBundle(BuildTarget buildTarget)
{
SetFolderBundleName(mResPath);
if (!Directory.Exists(outputPath))
{
Directory.CreateDirectory(outputPath);
}
BuildAssetBundleOptions buildOptions =
BuildAssetBundleOptions.ChunkBasedCompression |
BuildAssetBundleOptions.DeterministicAssetBundle |
BuildAssetBundleOptions.DisableWriteTypeTree;
BuildPipeline.BuildAssetBundles(outputPath, buildOptions, buildTarget);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log("打包完成");
}
[MenuItem("Tools/Set All BundleName")]
public static void SetResFolderAllBundleName()
{
SetFolderBundleName(mResPath);
Debug.Log("设置AB Name完成");
}
public static void SetFolderBundleName(string rootPath)
{
DirectoryInfo folder = new DirectoryInfo(rootPath);
FileSystemInfo[] files = folder.GetFileSystemInfos();
int length = files.Length;
for (int i = 0; i < length; i++)
{
if (files[i] is DirectoryInfo)
{
SetFolderBundleName(files[i].FullName);
}
else
{
if (!files[i].Name.EndsWith(".meta") &&
!files[i].Name.EndsWith(".cs"))
{
file(files[i].FullName);
}
}
}
}
static void file(string source)
{
string assetPath = "Assets" + source.Substring(Application.dataPath.Length);
string assetName = source.Substring(Application.dataPath.Length + 1);
//在代码中给资源设置AssetBundleName
AssetImporter assetImporter = AssetImporter.GetAtPath(assetPath);
if (Path.GetExtension(assetName) != null && Path.GetExtension(assetName) != "") { assetName = assetName.Replace(Path.GetExtension(assetName), ""); }
assetImporter.assetBundleName = assetName;
}
}
----------------------------------------------------------------------------------
end