导入Addressables包
创建资源管理配置。这里引入参考链接Unity简单使用 Addressables远端打包流程 - 剑起苍穹 - 博客园
此时会生成如下图文件夹里面很多配置文件 (如下图所示)
配置远程加载资源服务器的路径,这里也可以用Unity自带的快速本地服务测试。
参考 Addressable(8)Addressable Hosting可寻址托管窗口配置_addressables hosting services-优快云博客
这里涉及服务器的地址。我用nodejs 临时搭了一个。
过程也记录一下。引入参考链接 http-server:Node.js下的轻量级静态服务器工具
npm install -g http-server
安装报错,大概是版本问题。
然后我根据提示修复了一下,感觉不用修复也行。
到这个地方http sever 服务就启动起来了。
后面遇到跨域问题。加--cors启动就行。
http-server -p 8000 --cors
这里的服务器路径填进去。
下面就是将预制体打包成ab了
简单做几个预制体。放进去。
我们需要将Addressables配置改成从远程加载
打包一份。
本地选择选择加载方式。
默认打包就行。
在项目Assets同级目录下会生成对应包体。
将里面的文件放到,上面做好的服务器磁盘路径里。
加载ab资源代码示例。
// 定义一个类来存储资源信息和优先级
using System.Collections.Generic;
[System.Serializable]
public class AssetInfo
{
public string assetBundleName;
public string assetName;
public int priority;
// 重写 Equals 方法用于对比
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
return false;
AssetInfo other = (AssetInfo)obj;
return assetBundleName == other.assetBundleName &&
assetName == other.assetName &&
priority == other.priority;
}
// 重写 GetHashCode 方法
public override int GetHashCode()
{
return (assetBundleName != null ? assetBundleName.GetHashCode() : 0) ^
(assetName != null ? assetName.GetHashCode() : 0) ^
priority.GetHashCode();
}
}
// 用于存储 JSON 配置的类
[System.Serializable]
public class AssetConfig
{
public List<AssetInfo> assets;
}
// 定义一个 ScriptableObject 类来存储配置
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "AssetConfigSO", menuName = "Asset Bundle/Asset Config ScriptableObject")]
public class AssetConfigSO : ScriptableObject
{
public List<AssetInfo> assets;
}
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;
using System.IO;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
#if UNITY_EDITOR_WIN
using UnityEditor;
#endif
// 异步加载事件委托定义
public delegate void LoadingStartedHandler();
public delegate void LoadingUpdatedHandler(float progress);
public delegate void LoadingCompletedHandler();
// 新增配置加载完成事件委托
public delegate void ConfigLoadedHandler();
// 新增枚举类型表示数据源
public enum DataSource
{
JSON,
ScriptableObject
}
public class AssetBundleManager : MonoBehaviour
{
// 用于存储待加载的资源信息的队列
private List<AssetInfo> assetQueue = new List<AssetInfo>();
// 事件声明
public event LoadingStartedHandler OnLoadingStarted;
public event LoadingUpdatedHandler OnLoadingUpdated;
public event LoadingCompletedHandler OnLoadingCompleted;
// 新增配置加载完成事件
public event ConfigLoadedHandler OnConfigLoaded;
// 根据平台和数据源选择加载方式,并对比更新
public IEnumerator LoadConfig(string urlOrPath, DataSource source)
{
AssetConfig jsonConfig = null;
AssetConfigSO soConfig = null;
if (source == DataSource.JSON)
{
if (Application.platform == RuntimePlatform.WebGLPlayer)
{
// WebGL 平台从网页加载
using (UnityWebRequest uwr = UnityWebRequest.Get(new Uri(urlOrPath)))
{
yield return uwr.SendWebRequest();
if (uwr.result == UnityWebRequest.Result.Success)
{
string json = uwr.downloadHandler.text;
jsonConfig = JsonUtility.FromJson<AssetConfig>(json);
}
else
{
Debug.LogError($"Error loading config from {urlOrPath}: {uwr.error}");
}
}
}
else
{
// 其他平台从文件加载
if (File.Exists(urlOrPath))
{
string json = File.ReadAllText(urlOrPath);
jsonConfig = JsonUtility.FromJson<AssetConfig>(json);
}
else
{
Debug.LogError($"Config file {urlOrPath} not found.");
}
}
soConfig = Resources.Load<AssetConfigSO>("AssetConfigSO");
if (soConfig != null)
{
#if UNITY_EDITOR_WIN
if (!File.Exists(urlOrPath))
{
// Convert ScriptableObject config to JSON and save it
jsonConfig = new AssetConfig { assets = soConfig.assets };
string jsonContent = JsonUtility.ToJson(jsonConfig, true);
File.WriteAllText(urlOrPath, jsonContent);
}
#endif
if (!IsConfigsEqual(jsonConfig.assets, soConfig.assets))
{
UpdateScriptableObjectConfig(soConfig, jsonConfig.assets);
}
}
else
{
#if UNITY_EDITOR_WIN
soConfig = ScriptableObject.CreateInstance<AssetConfigSO>();
soConfig.assets = jsonConfig.assets;
AssetDatabase.CreateAsset(soConfig, "Assets/Resources/AssetConfigSO.asset");
AssetDatabase.SaveAssets();
#endif
}
foreach (var info in jsonConfig.assets)
{
AddToQueue(info);
}
}
else if (source == DataSource.ScriptableObject)
{
string soConfigname = Path.GetFileNameWithoutExtension(urlOrPath);
Debug.Log(soConfigname);
soConfig = Resources.Load<AssetConfigSO>("AssetConfigSO");
if (soConfig != null)
{
foreach (var info in soConfig.assets)
{
AddToQueue(info);
}
}
else
{
Debug.LogError($"ScriptableObject {urlOrPath} not found.");
}
}
yield return null;
// 触发配置加载完成事件
OnConfigLoaded?.Invoke();
}
// 对比两个配置列表是否一致
private bool IsConfigsEqual(List<AssetInfo> list1, List<AssetInfo> list2)
{
if (list1.Count != list2.Count)
return false;
for (int i = 0; i < list1.Count; i++)
{
if (!list1[i].Equals(list2[i]))
return false;
}
return true;
}
// 更新 ScriptableObject 配置
private void UpdateScriptableObjectConfig(AssetConfigSO soConfig, List<AssetInfo> newAssets)
{
#if UNITY_EDITOR_WIN
soConfig.assets = newAssets;
EditorUtility.SetDirty(soConfig);
AssetDatabase.SaveAssets();
#endif
}
// 向队列中添加资源信息
public void AddToQueue(AssetInfo info)
{
assetQueue.Add(info);
// 每次添加后按优先级排序
assetQueue = assetQueue.OrderBy(item => item.priority).ToList();
}
// 开始加载队列中的资源
public IEnumerator StartLoading()
{
if (assetQueue.Count == 0)
{
Debug.LogError("No configuration data loaded. Please load the configuration file first.");
yield break;
}
// 触发开始事件
OnLoadingStarted?.Invoke();
int totalAssets = assetQueue.Count;
int loadedAssets = 0;
foreach (var info in assetQueue)
{
AsyncOperationHandle<GameObject> assetRequest = Addressables.LoadAssetAsync<GameObject>(info.assetBundleName);
while (!assetRequest.IsDone)
{
// 触发更新事件
float progress = (loadedAssets + assetRequest.PercentComplete) / totalAssets;
OnLoadingUpdated?.Invoke(progress);
yield return null;
}
if (assetRequest.Status == AsyncOperationStatus.Succeeded)
{
GameObject asset = assetRequest.Result as GameObject;
if (asset != null)
{
Instantiate(asset);
}
}
else
Addressables.Release(assetRequest);
loadedAssets++;
}
// 触发完成事件
OnLoadingCompleted?.Invoke();
// 加载完成后清空队列
assetQueue.Clear();
}
string GetAssetBundleURL(string bundleName)
{
switch (Application.platform)
{
case RuntimePlatform.WebGLPlayer:
return Application.streamingAssetsPath + "/AssetBundles/" + bundleName;
case RuntimePlatform.WindowsPlayer:
case RuntimePlatform.OSXPlayer:
case RuntimePlatform.LinuxPlayer:
return Application.dataPath + "/StreamingAssets/AssetBundles/" + bundleName;
default:
Debug.LogError("Unsupported platform for AssetBundle loading.");
return "";
}
}
}
在Resources里右键建一个加载顺序和优先级的表。
这里我用一个测试脚本,尝试加载配置表。
using System;
using System.IO;
using UnityEngine;
public class ExampleUsage : MonoBehaviour
{
public AssetBundleManager manager;
void Start()
{
manager.OnConfigLoaded += OnConfigLoaded;
string configPath = Application.streamingAssetsPath + "/asset_config.json";
StartCoroutine(manager.LoadConfig(configPath, DataSource.ScriptableObject));
}
void OnConfigLoaded()
{
StartCoroutine(manager.StartLoading());
}
}
在编辑器里播放是红的,不影响,打包出来的效果。
为了后面方便对接后端数据,我们可以把配置表导出json。改写加载方式。
打包编译出来。
这里为了方便暂时不压缩。
这里我直接用rider 测试一下。
如果不用rider,就按照刚刚搭建http server的方式,重新给Output搭一个服务,是一样的。
复制链接在浏览器里面访问
这里再引入跨项目资源更新使用方式。
Unity Addressables跨工程加载资源_remoteproviderexception : invalid path in assetbun-优快云博客
在新项目中同样引入addressable包
这里addressable报错。引入Addressable报错缺少内置的GUI_missing built-in guistyle toolbarseachtextfieldpop-优快云博客
可以升级版本,报错只是编辑器内的ui错误。
com.unity.addressables
1.21.17
新建的项目想要使用A项目的包。
同样的代码,改写ExampleUsage 使用类。就是需要先加载A项目的资源目录,然后才能使用目录里的ab包。
using System;
using System.IO;
using UnityEngine;
using UnityEngine.AddressableAssets;
public class ExampleUsage : MonoBehaviour
{
public AssetBundleManager manager;
void Start()
{
manager.OnConfigLoaded += OnConfigLoaded;
var catalogPath = "http://192.168.1.2:8000/catalog_2025.02.08.03.44.08.json";
// 加载catalog,并在加载完成事件回调中进行资源加载
Addressables.LoadContentCatalogAsync(catalogPath).Completed += (resLocatorAopHandler) =>
{
string configPath = Application.streamingAssetsPath + "/asset_config.json";
StartCoroutine(manager.LoadConfig(configPath, DataSource.JSON));
};
}
void OnConfigLoaded()
{
StartCoroutine(manager.StartLoading());
}
}
这里测试加载进B项目。