Skip to content

Latest commit

 

History

History
636 lines (492 loc) · 21.2 KB

3-1-资源模块.md

File metadata and controls

636 lines (492 loc) · 21.2 KB

3-1. 资源模块 - ResourceModule

📦 模块概述

TEngine 资源模块是基于 YooAsset 构建的上层资源管理框架,提供了完全自动化的资源引用管理和商业级的资源生命周期控制。相比直接使用 YooAsset,TEngine 的资源模块具有以下核心优势:

✨ 核心优势

  1. 🔗 资源引用自动化管理

    • 通过 AssetsReference 组件自动管理资源生命周期
    • 无需手动 Dispose 资源句柄,框架自动处理资源释放
    • 支持 GameObject 和资源对象的自动引用追踪
  2. 📊 上层资源管理框架

    • 基于 YooAsset 构建,但提供了更高层次的抽象
    • 统一的资源加载接口,屏蔽底层实现细节
    • 智能的资源对象池管理,自动复用已加载资源
  3. 🎯 商业级内存管理

    • 支持 LRU(Least Recently Used)缓存策略
    • 自动释放引用计数为 0 的资源包
    • 可配置的资源自动释放间隔
  4. 🚀 高性能异步加载

    • 基于 UniTask 的零 GC 异步加载
    • 支持同步/异步/回调三种加载方式
    • 智能的资源加载队列管理

🎮 运行模式

资源模块支持多种运行模式,覆盖全平台需求:

基础运行模式

  • EditorSimulateMode - 编辑器模拟模式(仅编辑器可用)
  • OfflinePlayMode - 单机运行模式(从 StreamingAssets 加载)
  • HostPlayMode - 联机运行模式(支持热更新)

WebGL 运行模式

  • WebPlayMode - WebGL 运行模式(支持网页游戏)

WebGL 模式下支持两种资源加载方式(通过 LoadResWayWebGL 配置):

  • Remote - 访问远程资源(默认)
  • StreamingAssets - 跳过远程下载资源,直接访问 StreamingAssets

小游戏平台支持

TEngine 资源模块特别优化了对各种小游戏平台的支持:

  • 微信小游戏 - 自动适配微信小游戏的文件系统
  • 其他小游戏平台 - 可通过配置适配不同的小游戏平台

注意:

  • 编辑器模式下以顶部导航栏的选项卡为优先选项
  • 打包后以 Scene 场景中 ResourceModule 脚本的 Enum 选项卡为优先选项(打包后不会走 EditorSimulateMode)
  • WebGL 平台会根据 LoadResWayWebGL 设置自动选择资源加载方式

⚙️ 配置说明

Scene 窗口配置

在 Scene 窗口的 Resource 对象可以设置资源模块的常用配置:

  • 打包后的资源模式(PlayMode)
  • 资源校验等级
  • 自动卸载资源间隔
  • 资源对象池容量和过期时间

image

TEngineSetting 配置

在菜单窗口 TEngineSetting 可以设置资源模块的热更新配置:

  • 资源服务器地址(HostServerURL)
  • 备用服务器地址(FallbackHostServerURL)
  • WebGL 资源加载方式(LoadResWayWebGL)
    • Remote:访问远程资源
    • StreamingAssets:直接访问 StreamingAssets
  • 在线参数配置

提示:

  • 在 Tools/FileServer 目录下有一个基于 Node 的静态文件服务器,可以把在线参数以及 AB 放入用于做热更新测试
  • WebGL 平台需要根据实际情况选择资源加载方式:
    • 开发阶段可使用 StreamingAssets 快速测试
    • 生产环境建议使用 Remote 方式,支持热更新

image


🔑 核心概念

AssetsReference - 资源引用自动化管理

AssetsReference 是 TEngine 资源模块的核心特性,实现了完全自动化的资源引用管理。

工作原理

  1. 自动绑定:加载 GameObject 时,框架自动添加 AssetsReference 组件
  2. 引用追踪:组件自动追踪资源引用关系
  3. 自动释放:当 GameObject 被销毁时,自动释放关联的资源

使用示例

// 方式1:同步加载 GameObject(自动添加 AssetsReference)
GameObject go = GameModule.Resource.LoadGameObject("UI/BattleMainUI");
// 无需手动管理,Destroy 时自动释放资源

// 方式2:异步加载 GameObject(自动添加 AssetsReference)
GameObject go = await GameModule.Resource.LoadGameObjectAsync("UI/BattleMainUI");
// 同样无需手动管理

// 方式3:手动绑定资源引用(用于动态加载的资源)
GameObject instance = Instantiate(prefab);
AssetsReference.Ref(prefab, instance, GameModule.Resource);
// 或者绑定其他资源对象
AssetsReference.Ref<Material>(material, instance, GameModule.Resource);

优势说明

  • ✅ 零手动管理:开发者无需关心资源释放,框架自动处理
  • ✅ 防止内存泄漏:自动追踪所有资源引用,确保正确释放
  • ✅ 支持嵌套引用:可以绑定多个资源对象到一个 GameObject
  • ✅ 克隆安全:自动处理 GameObject 克隆时的引用关系

AssetObject - 资源对象池

TEngine 在 YooAsset 基础上构建了资源对象池系统,提供:

  • 智能复用:相同资源只加载一次,后续直接复用
  • 自动管理:资源对象池自动管理资源生命周期
  • 可配置策略:支持 LRU 等缓存策略

资源组管理(AssetGroup)

虽然当前版本主要使用 AssetsReference 进行资源管理,但框架仍支持资源组概念:

  • 资源分组绑定管理
  • 资源生命周期托管给资源组的根节点
  • 适合批量资源管理场景

🖼️ SetSprite 接口 - 图片自动管理

TEngine 资源模块提供了 SetSprite 扩展接口,实现了完全自动化的图片设置和引用管理。

核心优势

  1. 🔗 自动引用管理

    • 自动绑定图片资源引用
    • 图片更换时自动释放旧资源
    • 组件销毁时自动释放资源
  2. ⚡ 异步加载支持

    • 基于 UniTask 的异步加载
    • 不阻塞主线程
    • 支持取消操作
  3. 🎯 智能资源复用

    • 相同资源自动复用
    • 自动管理资源生命周期
    • 防止资源泄漏

使用示例

基础用法:UI Image

using TEngine;
using UnityEngine.UI;

public class UIIcon : MonoBehaviour
{
    public Image iconImage;
    
    void Start()
    {
        // 方式1:基础用法(异步加载,自动管理引用)
        iconImage.SetSprite("ItemIcon");
        
        // 方式2:设置原始分辨率
        iconImage.SetSprite("ItemIcon", setNativeSize: true);
        
        // 方式3:带回调
        iconImage.SetSprite("ItemIcon", callback: (image) => {
            Debug.Log($"图片加载完成: {image.sprite.name}");
        });
        
        // 方式4:支持取消操作
        var cts = new CancellationTokenSource();
        iconImage.SetSprite("ItemIcon", cancellationToken: cts.Token);
        // 需要取消时:cts.Cancel();
    }
}

2D SpriteRenderer

using TEngine;
using UnityEngine;

public class CharacterSprite : MonoBehaviour
{
    public SpriteRenderer spriteRenderer;
    
    void Start()
    {
        // 设置 SpriteRenderer 的图片(异步加载,自动管理引用)
        spriteRenderer.SetSprite("Player");
        
        // 带回调
        spriteRenderer.SetSprite("Player", callback: (renderer) => {
            Debug.Log($"角色图片加载完成: {renderer.sprite.name}");
        });
    }
}

子图片(图集)支持

using TEngine;
using UnityEngine.UI;

public class UIIcon : MonoBehaviour
{
    public Image iconImage;
    
    void Start()
    {
        // 从图集中加载子图片
        // location: 图集资源地址
        // spriteName: 图集中的子图片名称
        iconImage.SetSubSprite("CommonAtlas", "Icon_Item_001", setNativeSize: true);
        
        // SpriteRenderer 也支持子图片
        // spriteRenderer.SetSubSprite("Atlas", "Player_Idle");
    }
}

API 参考

/// <summary>
/// 设置图片(UI Image)。
/// </summary>
/// <param name="image">UI Image 组件。</param>
/// <param name="location">资源定位地址。</param>
/// <param name="setNativeSize">是否使用原始分辨率。</param>
/// <param name="callback">设置完 Sprite 的回调。</param>
/// <param name="cancellationToken">取消设置资源的 Token。</param>
public static void SetSprite(
    this Image image, 
    string location, 
    bool setNativeSize = false, 
    Action<Image> callback = null, 
    CancellationToken cancellationToken = default
);

/// <summary>
/// 设置图片(2D SpriteRenderer)。
/// </summary>
/// <param name="spriteRenderer">2D SpriteRenderer 组件。</param>
/// <param name="location">资源定位地址。</param>
/// <param name="callback">设置完 Sprite 的回调。</param>
/// <param name="cancellationToken">取消设置资源的 Token。</param>
public static void SetSprite(
    this SpriteRenderer spriteRenderer, 
    string location, 
    Action<SpriteRenderer> callback = null, 
    CancellationToken cancellationToken = default
);

/// <summary>
/// 设置子图片(从图集中加载,UI Image)。
/// </summary>
/// <param name="image">UI Image 组件。</param>
/// <param name="location">图集资源定位地址。</param>
/// <param name="spriteName">图集中的子图片名称。</param>
/// <param name="setNativeSize">是否使用原始分辨率。</param>
/// <param name="cancellationToken">取消设置资源的 Token。</param>
public static void SetSubSprite(
    this Image image, 
    string location, 
    string spriteName, 
    bool setNativeSize = false, 
    CancellationToken cancellationToken = default
);

/// <summary>
/// 设置子图片(从图集中加载,2D SpriteRenderer)。
/// </summary>
/// <param name="spriteRenderer">2D SpriteRenderer 组件。</param>
/// <param name="location">图集资源定位地址。</param>
/// <param name="spriteName">图集中的子图片名称。</param>
/// <param name="cancellationToken">取消设置资源的 Token。</param>
public static void SetSubSprite(
    this SpriteRenderer spriteRenderer, 
    string location, 
    string spriteName, 
    CancellationToken cancellationToken = default
);

工作原理

  1. 自动引用追踪:SetSprite 会自动追踪图片资源引用
  2. 智能资源释放:当图片被更换或组件被销毁时,自动释放旧资源
  3. 资源复用:相同资源自动复用,避免重复加载
  4. 生命周期管理:与 GameObject 生命周期绑定,确保资源正确释放

最佳实践

// ✅ 推荐:使用 SetSprite,自动管理资源
iconImage.SetSprite("UI/Icon/ItemIcon");

// ❌ 不推荐:手动加载和管理资源
var sprite = GameModule.Resource.LoadAsset<Sprite>("UI/Icon/ItemIcon");
iconImage.sprite = sprite;
// 需要手动管理资源释放,容易造成内存泄漏

📖 API 使用指南

基础加载 API

同步加载资源

// 同步加载资源(自动管理引用)
SkillDisplayData data = GameModule.Resource.LoadAsset<SkillDisplayData>("SkillData");

// 同步加载 GameObject(自动添加 AssetsReference)
GameObject go = GameModule.Resource.LoadGameObject("UI/BattleMainUI", parent);

异步加载资源(UniTask)

// 使用 UniTask 异步加载(推荐方式)
SkillDisplayData data = await GameModule.Resource.LoadAssetAsync<SkillDisplayData>(
    "SkillData", 
    cancellationToken: cancellationToken
);

// 异步加载 GameObject(自动添加 AssetsReference)
GameObject go = await GameModule.Resource.LoadGameObjectAsync(
    "UI/BattleMainUI", 
    parent: parent,
    cancellationToken: cancellationToken
);

异步加载资源(回调方式)

// 使用回调方式异步加载
GameModule.Resource.LoadAsset<SkillDisplayData>(
    "SkillData",
    callback: (data) => {
        // 使用资源
        Debug.Log($"Loaded: {data.name}");
    }
);

重要提示:

  • 资源模块默认使用 Addressable 可寻址定位
  • 传入资源名字无需后缀(如 "UI/BattleMainUI" 而不是 "UI/BattleMainUI.prefab")
  • 打包的资源不可以重名

🔧 完整 API 参考

资源加载接口

/// <summary>
/// 同步加载资源。
/// </summary>
/// <param name="location">资源的定位地址。</param>
/// <param name="packageName">指定资源包的名称。不传使用默认资源包</param>
/// <typeparam name="T">要加载资源的类型。</typeparam>
/// <returns>资源实例。</returns>
T LoadAsset<T>(string location, string packageName = "") where T : UnityEngine.Object;

/// <summary>
/// 同步加载游戏物体并实例化(自动添加 AssetsReference)。
/// </summary>
/// <param name="location">资源的定位地址。</param>
/// <param name="parent">资源实例父节点。</param>
/// <param name="packageName">指定资源包的名称。不传使用默认资源包</param>
/// <returns>资源实例。</returns>
/// <remarks>会实例化资源到场景,无需主动UnloadAsset,Destroy时自动UnloadAsset。</remarks>
GameObject LoadGameObject(string location, Transform parent = null, string packageName = "");

/// <summary>
/// 异步加载资源(UniTask)。
/// </summary>
/// <param name="location">资源定位地址。</param>
/// <param name="cancellationToken">取消操作Token。</param>
/// <param name="packageName">指定资源包的名称。不传使用默认资源包</param>
/// <typeparam name="T">要加载资源的类型。</typeparam>
/// <returns>异步资源实例。</returns>
UniTask<T> LoadAssetAsync<T>(string location, CancellationToken cancellationToken = default, string packageName = "") where T : UnityEngine.Object;

/// <summary>
/// 异步加载游戏物体并实例化(自动添加 AssetsReference)。
/// </summary>
/// <param name="location">资源定位地址。</param>
/// <param name="parent">资源实例父节点。</param>
/// <param name="cancellationToken">取消操作Token。</param>
/// <param name="packageName">指定资源包的名称。不传使用默认资源包</param>
/// <returns>异步游戏物体实例。</returns>
/// <remarks>会实例化资源到场景,无需主动UnloadAsset,Destroy时自动UnloadAsset。</remarks>
UniTask<GameObject> LoadGameObjectAsync(string location, Transform parent = null, CancellationToken cancellationToken = default, string packageName = "");

/// <summary>
/// 异步加载资源(回调方式)。
/// </summary>
/// <param name="location">资源的定位地址。</param>
/// <param name="callback">回调函数。</param>
/// <param name="packageName">指定资源包的名称。不传使用默认资源包</param>
/// <typeparam name="T">要加载资源的类型。</typeparam>
UniTaskVoid LoadAsset<T>(string location, Action<T> callback, string packageName = "") where T : UnityEngine.Object;

资源管理接口

/// <summary>
/// 卸载资源。
/// </summary>
/// <param name="asset">要卸载的资源。</param>
void UnloadAsset(object asset);

/// <summary>
/// 资源回收(卸载引用计数为零的资源)。
/// </summary>
void UnloadUnusedAssets();

/// <summary>
/// 强制回收所有资源。
/// </summary>
void ForceUnloadAllAssets();

/// <summary>
/// 强制执行释放未被使用的资源。
/// </summary>
/// <param name="performGCCollect">是否使用垃圾回收。</param>
void ForceUnloadUnusedAssets(bool performGCCollect);

/// <summary>
/// 检查资源是否存在。
/// </summary>
/// <param name="location">资源定位地址。</param>
/// <param name="packageName">资源包名称。</param>
/// <returns>检查资源是否存在的结果。</returns>
HasAssetResult HasAsset(string location, string packageName = "");

/// <summary>
/// 检查资源定位地址是否有效。
/// </summary>
/// <param name="location">资源的定位地址</param>
/// <param name="packageName">指定资源包的名称。不传使用默认资源包</param>
bool CheckLocationValid(string location, string packageName = "");

资源包管理接口

/// <summary>
/// 获取当前资源包版本。
/// </summary>
/// <param name="customPackageName">指定资源包的名称。不传使用默认资源包</param>
/// <returns>资源包版本。</returns>
string GetPackageVersion(string customPackageName = "");

/// <summary>
/// 异步更新最新包的版本。
/// </summary>
/// <param name="appendTimeTicks">请求URL是否需要带时间戳。</param>
/// <param name="timeout">超时时间。</param>
/// <param name="customPackageName">指定资源包的名称。不传使用默认资源包</param>
/// <returns>请求远端包裹的最新版本操作句柄。</returns>
RequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks = false, int timeout = 60, string customPackageName = "");

/// <summary>
/// 向网络端请求并更新清单。
/// </summary>
/// <param name="packageVersion">更新的包裹版本</param>
/// <param name="timeout">超时时间(默认值:60秒)</param>
/// <param name="customPackageName">指定资源包的名称。不传使用默认资源包</param>
UpdatePackageManifestOperation UpdatePackageManifestAsync(string packageVersion, int timeout = 60, string customPackageName = "");

/// <summary>
/// 创建资源下载器,用于下载当前资源版本所有的资源包文件。
/// </summary>
/// <param name="customPackageName">指定资源包的名称。不传使用默认资源包</param>
ResourceDownloaderOperation CreateResourceDownloader(string customPackageName = "");

/// <summary>
/// 清理包裹未使用的缓存文件。
/// </summary>
/// <param name="clearMode">文件清理方式。</param>
/// <param name="customPackageName">指定资源包的名称。不传使用默认资源包</param>
ClearCacheFilesOperation ClearCacheFilesAsync(
    EFileClearMode clearMode = EFileClearMode.ClearUnusedBundleFiles, string customPackageName = "");

/// <summary>
/// 清理沙盒路径。
/// </summary>
/// <param name="customPackageName">指定资源包的名称。不传使用默认资源包</param>
void ClearAllBundleFiles(string customPackageName = "");

资源信息接口

/// <summary>
/// 获取资源信息。
/// </summary>
/// <param name="location">资源的定位地址。</param>
/// <param name="packageName">指定资源包的名称。不传使用默认资源包</param>
/// <returns>资源信息。</returns>
AssetInfo GetAssetInfo(string location, string packageName = "");

/// <summary>
/// 获取资源信息列表。
/// </summary>
/// <param name="resTag">资源标签。</param>
/// <param name="packageName">指定资源包的名称。不传使用默认资源包</param>
/// <returns>资源信息列表。</returns>
AssetInfo[] GetAssetInfos(string resTag, string packageName = "");

/// <summary>
/// 获取资源信息列表。
/// </summary>
/// <param name="tags">资源标签列表。</param>
/// <param name="packageName">指定资源包的名称。不传使用默认资源包</param>
/// <returns>资源信息列表。</returns>
AssetInfo[] GetAssetInfos(string[] tags, string packageName = "");

远端服务地址设置

/// <summary>
/// 设置远端资源服务地址(需同时提供默认和备用地址)。
/// </summary>
/// <param name="defaultHostServer">默认服务器地址。</param>
/// <param name="fallbackHostServer">备用服务器地址。</param>
void SetRemoteServicesUrl(string defaultHostServer, string fallbackHostServer);

💡 最佳实践

1. 优先使用 LoadGameObject

对于 GameObject 资源,优先使用 LoadGameObject 或 LoadGameObjectAsync,框架会自动添加 AssetsReference 组件,无需手动管理。

// ✅ 推荐:自动管理资源引用
GameObject ui = await GameModule.Resource.LoadGameObjectAsync("UI/BattleMainUI", parent);
// 销毁时自动释放资源
Destroy(ui);

// ❌ 不推荐:需要手动管理
AssetHandle handle = GameModule.Resource.LoadAssetAsyncHandle<GameObject>("UI/BattleMainUI");
GameObject ui = Instantiate(handle.AssetObject as GameObject);
// 需要手动 handle.Dispose()

2. 使用 AssetsReference 绑定动态资源

对于动态加载的资源(如 Material、Texture 等),使用 AssetsReference 绑定引用:

GameObject instance = Instantiate(prefab);

// 绑定预制体引用
AssetsReference.Ref(prefab, instance, GameModule.Resource);

// 绑定材质引用
Material mat = GameModule.Resource.LoadAsset<Material>("Materials/MyMaterial");
AssetsReference.Ref(mat, instance, GameModule.Resource);

3. 合理使用资源包

对于大型项目,可以创建多个资源包来管理不同模块的资源:

// 初始化自定义资源包
await GameModule.Resource.InitPackage("BattlePackage", needInitMainFest: true);

// 从指定资源包加载
GameObject go = await GameModule.Resource.LoadGameObjectAsync(
    "BattleUI", 
    packageName: "BattlePackage"
);

📝 注意事项

  1. 资源命名:打包的资源不能重名,使用 Addressable 地址作为唯一标识
  2. 资源后缀:加载资源时无需添加文件后缀(如 .prefab、.png 等)
  3. 生命周期:资源模块的初始化生命周期已在流程模块中实现,一般无需手动调用
  4. 内存管理:框架会自动管理资源引用,但建议定期调用 UnloadUnusedAssets() 释放未使用资源

🔗 相关链接