feat: refactor AssetReference

将资源弱引用的公共逻辑抽象到基类中,并新增对 Texture2D 的支持。
This commit is contained in:
何冠峰
2026-06-03 17:23:13 +08:00
parent 89b516942e
commit a6299268d3
11 changed files with 162 additions and 35 deletions

View File

@@ -1,16 +1,22 @@
using System;
using System.Collections;
using UnityEngine;
using UnityEditor;
[CustomPropertyDrawer(typeof(GameObjectReference))]
public class GameObjectReferenceDrawer : PropertyDrawer
[CustomPropertyDrawer(typeof(AssetReference), true)]
public class AssetReferenceDrawer : PropertyDrawer
{
private const float LineSpacing = 2f;
private Type _assetType;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
SerializedProperty packageNameProp = property.FindPropertyRelative("_packageName");
SerializedProperty assetGUIDProp = property.FindPropertyRelative("_assetGUID");
Type assetType = GetAssetType();
EditorGUI.BeginProperty(position, label, property);
{
float lineHeight = EditorGUIUtility.singleLineHeight;
@@ -19,19 +25,19 @@ public class GameObjectReferenceDrawer : PropertyDrawer
// 绘制 PackageName
packageNameProp.stringValue = EditorGUI.TextField(line, "Package Name", packageNameProp.stringValue);
// 加载 GameObject
// 加载当前资源对象
string assetGUID = assetGUIDProp.stringValue;
GameObject current = null;
UnityEngine.Object current = null;
if (string.IsNullOrEmpty(assetGUID) == false)
{
string assetPath = AssetDatabase.GUIDToAssetPath(assetGUID);
if (string.IsNullOrEmpty(assetPath) == false)
current = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
current = AssetDatabase.LoadAssetAtPath(assetPath, assetType);
}
// 绘制 GameObject
// 绘制资源对象字段
line.y += lineHeight + LineSpacing;
GameObject newAsset = (GameObject)EditorGUI.ObjectField(line, "Game Object", current, typeof(GameObject), false);
UnityEngine.Object newAsset = EditorGUI.ObjectField(line, assetType.Name, current, assetType, false);
if (newAsset != current)
{
if (newAsset == null)
@@ -46,7 +52,7 @@ public class GameObjectReferenceDrawer : PropertyDrawer
}
}
// 绘制 AssetGUID
// 绘制 AssetGUID(只读)
line.y += lineHeight + LineSpacing;
EditorGUI.BeginDisabledGroup(true);
EditorGUI.TextField(line, "Asset GUID", assetGUIDProp.stringValue);
@@ -54,8 +60,31 @@ public class GameObjectReferenceDrawer : PropertyDrawer
}
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUIUtility.singleLineHeight * 3 + LineSpacing * 2;
}
/// <summary>
/// 通过反射获取字段对应子类的 AssetType
/// </summary>
private Type GetAssetType()
{
if (_assetType != null)
return _assetType;
Type fieldType = fieldInfo.FieldType;
// 兼容数组
if (fieldType.IsArray)
fieldType = fieldType.GetElementType();
// 兼容 List<T>
else if (fieldType.IsGenericType && typeof(IEnumerable).IsAssignableFrom(fieldType))
fieldType = fieldType.GetGenericArguments()[0];
var instance = (AssetReference)Activator.CreateInstance(fieldType);
_assetType = instance.AssetType;
return _assetType;
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 53eb285fc3a7b614089a000f8c9c738c
guid: ee2a8d68b06fc434c8d70a9e1d94caf6
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -3,19 +3,19 @@ using UnityEngine;
using YooAsset;
/// <summary>
/// 游戏对象弱引用,序列化时只保存资源 GUID
/// 资源弱引用基类
/// </summary>
[Serializable]
public class GameObjectReference
public abstract class AssetReference
{
[SerializeField]
private string _packageName = "DefaultPackage";
protected string _packageName = "DefaultPackage";
[SerializeField]
private string _assetGUID = "";
protected string _assetGUID = "";
[NonSerialized]
private AssetHandle _handle;
protected AssetHandle _handle;
/// <summary>
/// 资源所属的包裹名称
@@ -27,6 +27,16 @@ public class GameObjectReference
/// </summary>
public string AssetGUID => _assetGUID;
/// <summary>
/// 当前加载句柄(未加载时为 null
/// </summary>
public AssetHandle Handle => _handle;
/// <summary>
/// 该引用负责加载的资源类型,由子类指定
/// </summary>
public abstract Type AssetType { get; }
/// <summary>
/// 检查运行时引用键是否有效
@@ -37,18 +47,18 @@ public class GameObjectReference
return false;
var package = YooAssets.GetPackage(_packageName);
var assetInfo = package.GetAssetInfoByGuid(_assetGUID, typeof(GameObject));
var assetInfo = package.GetAssetInfoByGuid(_assetGUID, AssetType);
return assetInfo.IsValid;
}
/// <summary>
/// 异步加载引用的游戏对象
/// 异步加载引用的资源
/// </summary>
/// <returns>加载操作句柄</returns>
public AssetHandle LoadAssetAsync()
{
if (_handle != null)
throw new InvalidOperationException("GameObject reference has already been loaded. Release it first.");
throw new InvalidOperationException($"{GetType().Name} has already been loaded. Release it first.");
if (string.IsNullOrEmpty(_packageName))
throw new ArgumentException("Package name is not set.", nameof(_packageName));
@@ -56,7 +66,7 @@ public class GameObjectReference
throw new ArgumentException("Asset GUID is not set.", nameof(_assetGUID));
var package = YooAssets.GetPackage(_packageName);
var assetInfo = package.GetAssetInfoByGuid(_assetGUID, typeof(GameObject));
var assetInfo = package.GetAssetInfoByGuid(_assetGUID, AssetType);
_handle = package.LoadAssetAsync(assetInfo);
return _handle;
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 15552077c0d6ff441a4cd62af62b7d5a
guid: 7de4fe4f4d6834c47977a97c7ad20bd3
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -0,0 +1,11 @@
using System;
using UnityEngine;
/// <summary>
/// GameObject 资源弱引用
/// </summary>
[Serializable]
public class AssetReferenceGameObject : AssetReference
{
public override Type AssetType => typeof(GameObject);
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 764d480d7edd8cd48b8105dc6b24bd2a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -3,36 +3,58 @@ using UnityEngine;
using YooAsset;
/// <summary>
/// 演示如何使用 GameObjectReference 弱引用加载并实例化游戏对象
/// 演示如何使用 AssetReference 弱引用加载不同类型的资源
/// </summary>
public class AssetReferenceSample : MonoBehaviour
{
[SerializeField]
private GameObjectReference _reference;
private AssetReferenceGameObject _prefabReference;
[SerializeField]
private AssetReferenceTexture2D _textureReference;
private IEnumerator Start()
{
if (_reference.RuntimeKeyIsValid() == false)
// 加载并实例化预制体
if (_prefabReference.RuntimeKeyIsValid())
{
yield break;
AssetHandle handle = _prefabReference.LoadAssetAsync();
yield return handle;
if (handle.Status == EOperationStatus.Succeeded)
{
GameObject instance = handle.InstantiateSync(new InstantiateOptions(true, transform, false));
if (instance == null)
Debug.LogError($"Failed to instantiate GameObject reference '{_prefabReference.AssetGUID}'.");
}
else
{
Debug.LogError($"Failed to load GameObject reference '{_prefabReference.AssetGUID}': {handle.Error}.");
}
}
AssetHandle handle = _reference.LoadAssetAsync();
yield return handle;
// 加载纹理并赋值给渲染器材质
if (_textureReference.RuntimeKeyIsValid())
{
AssetHandle handle = _textureReference.LoadAssetAsync();
yield return handle;
if (handle.Status == EOperationStatus.Succeeded)
{
GameObject instance = handle.InstantiateSync(new InstantiateOptions(true, transform, false));
if (instance == null)
Debug.LogError($"Failed to instantiate GameObject reference '{_reference.AssetGUID}'.");
}
else
{
Debug.LogError($"Failed to load GameObject reference '{_reference.AssetGUID}': {handle.Error}.");
if (handle.Status == EOperationStatus.Succeeded)
{
var renderer = GetComponent<Renderer>();
if (renderer != null)
renderer.material.mainTexture = handle.AssetObject as Texture2D;
}
else
{
Debug.LogError($"Failed to load Texture2D reference '{_textureReference.AssetGUID}': {handle.Error}.");
}
}
}
private void OnDestroy()
{
_reference.ReleaseAsset();
_prefabReference?.ReleaseAsset();
_textureReference?.ReleaseAsset();
}
}

View File

@@ -0,0 +1,11 @@
using System;
using UnityEngine;
/// <summary>
/// Texture2D 资源弱引用
/// </summary>
[Serializable]
public class AssetReferenceTexture2D : AssetReference
{
public override Type AssetType => typeof(Texture2D);
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0c6c6d8423826d24083eb0608e0fd023
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
using System;
using UnityEngine;
/// <summary>
/// Texture3D 资源弱引用
/// </summary>
[Serializable]
public class AssetReferenceTexture3D : AssetReference
{
public override Type AssetType => typeof(Texture3D);
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: be5c51911e8f45a45b7ed11af7d0b50c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: