前言
游戏开发过程中资源管理一直都是很重要的一个模块,涉及到的东西也比较复杂一些:资源加载/卸载(处理依赖) 远程拉取(网络下载) 分包(DLC) 热更
Unity3d的资源解决方案
加载的方法:文件(AssetDatabase或System.IO等)/Resources/AssetBundle/Addressable asset System(官方给的AssetBundle的轮子)
与之对应卸载。
加载方式分为:同步加载和异步加载。 现在一般采用异步加载。
a. 资源目录(分包) 存放资源的目录,包括一般美术资源和代码资源
b. 资源加载 涉及到解压、解密, 普通美术资源,就几种常用方法, 代码一般就是加载程序集或者像Lua这种解释执行的脚本(其实也是普通资源)
c. 打包工具(自动化工具)
这里会涉及到打包的平台,压缩方式,加密方式等 。然后还有一些分包策略。
d. 热更
热更资源很容易,热更代码一般两种方案ILRumtime 第二就是成熟的Lua方案(Xlua等) ,如果不需要热更新功能,只是修复Bug,甚至都用不到以上方案。有一个InjectFix,很轻量级修复Bug。
热更步骤:需要有个版本文件,还会为每个AB生成md5,对比后把新生成的Ab和修改的AB复制到新目录,此为需要热更的包。 配置服务器文件,每次开始游戏从服务器拉取文件对比serverInfo,如果需要热更,则下载相应的资源,然后加载方式配合一下,就可以重定位到新的资源。
e. 旧包跨版本更新资源问题
学习了解文档:如下
官方文档
常用的两种管理方案:
1.(旧)使用AssetBundle 要自己写一套管理依赖的流程,以及下载等等。
2.(新)Addressable asset System
整个资源管理的难题:就在于资源的依赖/下载/卸载等之间
两者的比较:
1.AB是Unity官方最早提出的资源管理方式,但是没有一套成熟的体系,都是开发者自己造轮子,然后就是百花齐放的样子。
2.Addressable Asset System是官方提供的一套轮子,避免了重复造轮子,但是可能会存在一定的Bug。
处理依赖的问题:
- 要么一开始生成所有依赖的字典,加载时候去读取
- 要么就临时判断依赖
使用方法一,直观,可以用开发工具生成相应的依赖管理文件
使用方法二,不是那么直观,可能会造成后期难以管理
AB管理思路
待更新
Adressable 管理思路
官方文档:
https://docs.unity3d.com/Packages/com.unity.addressables@1.10/manual/AddressableAssetsGettingStarted.html
参考连接
https://www.jianshu.com/p/e79b2eef97bf
https://www.jianshu.com/p/8009c16fcab3
The Addressable Asset System 正式版应用(一)
The Addressable Asset System 正式版应用(二)
UWA学堂官网文档翻译
代码
using FairyGUI;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.SceneManagement;
using UnityEngine.U2D;
public class AddressableTools
{
private struct SceneStruct
{
public Action<UnityEngine.ResourceManagement.ResourceProviders.SceneInstance> callback;
public UnityEngine.SceneManagement.LoadSceneMode mode;
public bool autoReleaseHandle;
}
private struct ObjectStruct
{
public Action<UnityEngine.Object> callback;
public bool autoReleaseHandle;
}
private static Dictionary<object, AsyncOperationHandle<UnityEngine.Object>> dicObjectHandles = new Dictionary<object, AsyncOperationHandle<UnityEngine.Object>>();
private static Dictionary<object, Queue<ObjectStruct>> dicObjectCallbacks = new Dictionary<object, Queue<ObjectStruct>>();
public static void InitializeAsync(Action<IResourceLocator> callback)
{
#if UNITY_EDITOR
callback?.Invoke(null);
return;
#endif
var handle = Addressables.InitializeAsync();
Action<AsyncOperationHandle<IResourceLocator>> complete = null;
complete = data =>
{
handle.Completed -= complete;
if (data.Status == AsyncOperationStatus.Succeeded && data.IsValid())
{
callback?.Invoke(null);
}
else
{
}
};
handle.Completed += complete;
}
private static string FindAssetFile(string path)
{
var fileNames = path.Split('/');
var fileName = fileNames[fileNames.Length - 1];
var directoryPath = Application.dataPath + "/" + path;
directoryPath = directoryPath.Substring(0, directoryPath.LastIndexOf('/'));
if (!Directory.Exists(directoryPath))
return null;
var files = Directory.GetFiles(directoryPath, "*.*", SearchOption.TopDirectoryOnly);
string result = null;
foreach (var f in files)
{
if (f.IndexOf(fileName + ".") > -1)
{
result = f.Replace(Application.dataPath, "Assets");
break;
}
}
return result;
}
public static AsyncOperationHandle<UnityEngine.Object> Load(object key, Action<UnityEngine.Object> callback = null, bool autoReleaseHandle = false)
{
if (string.IsNullOrEmpty(key.ToString()))
{
Debug.LogError("Load address 为Null 或 空");
return default(AsyncOperationHandle<UnityEngine.Object>);
}
#if UNITY_EDITOR
string skey = key.ToString();
string path = key.ToString();
//if(skey.IndexOf("Zero/")==0)
//{
// path=
//}
if (path == null)
{
if (skey != "shader" && skey != "pb")
Debug.LogWarning("找不到路径:" + skey);
if (callback != null) callback(null);
return default(AsyncOperationHandle<UnityEngine.Object>);
}
var obj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path);
callback?.Invoke(obj);
return default(AsyncOperationHandle<UnityEngine.Object>);
#endif
AsyncOperationHandle<UnityEngine.Object> handle;
if (dicObjectHandles.TryGetValue(key, out handle))
{
callback?.Invoke(handle.Result);
return handle;
}
Queue<ObjectStruct> callbacks;
if (dicObjectCallbacks.TryGetValue(key, out callbacks))
{
dicObjectCallbacks[key].Enqueue(new ObjectStruct() { callback = callback, autoReleaseHandle = autoReleaseHandle });
return default(AsyncOperationHandle<UnityEngine.Object>);
}
else
{
dicObjectCallbacks[key] = new Queue<ObjectStruct>();
dicObjectCallbacks[key].Enqueue(new ObjectStruct() { callback = callback, autoReleaseHandle = autoReleaseHandle });
}
var loadAsset = Addressables.LoadAssetAsync<UnityEngine.Object>(key);
Action<AsyncOperationHandle<UnityEngine.Object>> func = null;
func = data =>
{
loadAsset.Completed -= func;
if (data.IsValid() && data.Status == AsyncOperationStatus.Succeeded && data.Result != null)
{
if (!autoReleaseHandle)
dicObjectHandles[key] = data;
while (dicObjectCallbacks[key].Count > 0)
{
var v = dicObjectCallbacks[key].Dequeue();
v.callback?.Invoke(data.Result);
}
dicObjectCallbacks[key].Clear();
dicObjectCallbacks.Remove(key);
}
else
{
Debug.LogError("资源地址加载失败:" + key);
callback?.Invoke(null);
}
if (autoReleaseHandle)
Addressables.Release(data);
};
loadAsset.Completed += func;
return loadAsset;
}
public static AsyncOperationHandle<UnityEngine.Object> LoadBinary(object key, Action<byte[]> callback = null)
{
return Load(key, data => {
if (data != null && data is TextAsset)
{
callback((data as TextAsset).bytes);
}
else
{
Debug.LogError("{0} can't get Bytes(obj({1})) type:{2}" + key + data.GetType());
}
}, true);
}
public static AsyncOperationHandle<UnityEngine.Object> Load(object key, object subKey, Action<UnityEngine.Object> callback = null, bool autoReleaseHandle = false)
{
return Load(key + "[" + subKey + "]", callback, autoReleaseHandle);
}
public static AsyncOperationHandle<UnityEngine.Object> LoadSprites(object key, Action<Sprite[]> callback = null, bool autoReleaseHandle=false)
{
return Load(key, data => {
var sa = data as SpriteAtlas;
var sprites = new Sprite[sa.spriteCount];
sa.GetSprites(sprites);
callback?.Invoke(sprites);
}, autoReleaseHandle);
}
public static AsyncOperationHandle<IList<UnityEngine.Object>> LoadList(object key, Action<IList<UnityEngine.Object>> callback = null, bool autoReleaseHandle = false)
{
var loadAsset = Addressables.LoadAssetAsync<IList<UnityEngine.Object>>(key);
Action<AsyncOperationHandle<IList<UnityEngine.Object>>> func = null;
func = data => {
loadAsset.Completed -= func;
if (data.Status == AsyncOperationStatus.Succeeded && data.IsValid() && data.Result != null)
{
callback?.Invoke(data.Result);
}
else
{
Debug.LogError("资源加载失败:" + key);
callback?.Invoke(null);
}
if (autoReleaseHandle)
Addressables.Release(data);
};
loadAsset.Completed += func;
return loadAsset;
}
public static AsyncOperationHandle<IList<IList<UnityEngine.Object>>> Loads(IList<object> lists, Action<IList<UnityEngine.Object>> callback = null)
{
return Addressables.LoadAssetsAsync(lists, callback);
}
public static AsyncOperationHandle GetDownLoadSizeAsync(IList<object> lists, Action<long> callback = null)
{
Action<AsyncOperationHandle<long>> func = null;
func = data =>
{
callback?.Invoke(data.Result);
Addressables.Release(data);
};
var handle = Addressables.GetDownloadSizeAsync(lists);
handle.Completed += func;
return handle;
}
public static AsyncOperationHandle DownLoadDependenciesAsync(IList<object> lists)
{
return Addressables.DownloadDependenciesAsync(lists, Addressables.MergeMode.None, true);
}
public static AsyncOperationHandle GetObjectHandle(string key)
{
AsyncOperationHandle<UnityEngine.Object> handle;
dicObjectHandles.TryGetValue(key, out handle);
return handle;
}
public static void Release(AsyncOperationHandle handle)
{
if(handle.IsValid())
{
Addressables.Release(handle);
}
}
public static void Release(string key)
{
#if !LOAD_ASSETBUNDLE
return;
#endif
AsyncOperationHandle<UnityEngine.Object> handle;
if(dicObjectHandles.TryGetValue(key,out handle))
{
Release(handle);
dicObjectHandles.Remove(key);
}
else
{
Debug.LogError("Release:"+key+" 为null或空");
}
}
private static AsyncOperationHandle<GameObject> Instantiate(string key,Action<GameObject> callback=null,Transform parent=null,bool isWorldSpace=false,bool trackHandle=true)
{
var instantiate = Addressables.InstantiateAsync(key, parent, isWorldSpace, trackHandle);
Action<AsyncOperationHandle<GameObject>> func = null;
func = data =>
{
instantiate.Completed -= func;
if (data.Status == AsyncOperationStatus.Succeeded && data.IsValid() && data.Result != null)
{
callback?.Invoke(data.Result);
}
else
{
Debug.LogError("资源Instaniate失败:"+ key);
callback(null);
}
};
instantiate.Completed += func;
return instantiate;
}
private static bool ReleaseInstantiate(GameObject go)
{
if(go!=null)
{
return Addressables.ReleaseInstance(go);
}
return false;
}
public static IEnumerator DownLoadUpdate(List<object> list)
{
var init = Addressables.InitializeAsync();
yield return init;
var downLoadSize = Addressables.GetDownloadSizeAsync(list);
yield return downLoadSize;
Debug.Log("Start ready DownLoad:" + downLoadSize.Result);
var downLoad = Addressables.DownloadDependenciesAsync(list, Addressables.MergeMode.None);
yield return downLoad;
Debug.Log("DownLoad Finish");
Addressables.Release(downLoad);
}
public static byte[] GetBytes(UnityEngine.Object obj)
{
if(obj is TextAsset)
{
return (obj as TextAsset).bytes;
}
return null;
}
}