Unity Xlua热更新框架(三):资源管理

10 篇文章 5 订阅

4. 资源加载

逻辑:解析版本文件,保存文件信息——>获取文件信息——>加载Bundle(——>检查依赖——>获取依赖文件信息再次递归加载bundle)——>加载资源——>完成后回调
可能一个bundle需要另一个bundle(而这个有需要别的bundle),需要递归加载。

必须加载完依赖bundle和自身bundle都,才能加载资源,,,最后在通知应用层加载完成。
这种异步加载方式,在商业中比较常用。

C# internal解析_愤怒的YYZ的博客-CSDN博客_c# internal

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System;
//改名
using UObject = UnityEngine.Object;

/// <summary>
/// 资源管理器类:
/// 定义了所有使用bundle信息可用的bundleinfo类,该类用于加载bundle时使用,,,另外字典m_BundleInfos存放资源名与所保存该资源的bundle信息
/// ParseVersionFile解析版本文件:获得版本文件中所有要加载的资源名和其所在的bundle信息以及该资源又依赖哪些bundle,,并把lua文件的信息传给luaManager保存
/// 加载资源:加载前需要解析版本文件,LoadBundleAsync通过m_BundleInfos查找资源递归(因为有的bundle依赖别的bundle)的异步加载bundle资源,或在编辑器模式下使用EditorLoadAsset加载资源
/// 封装了loadxx的方法,用来加载指定类型资源,例如loadUI,从UI目录加载UI类型资源
/// </summary>
public class ResourceManager : MonoBehaviour
{
    //定义一个类,用来解析版本信息
    internal class BundleInfo
    {
        public string AssetName;
        public string BundleName;
        public List<string> Dependeces;
    }

    //存放bundle信息的集合
    private Dictionary<string, BundleInfo> m_BundleInfos = new Dictionary<string, BundleInfo>();

    /// <summary>
    /// 解析版本文件
    /// </summary>
    private void ParseVersionFile()
    {
        //拿到版本文件路径
        string url = Path.Combine(PathUtil.BundleResourcePath, AppConst.FileListName);
        //对文件进行读取
        string[] data = File.ReadAllLines(url);

        //解析文件信息
        for (int i = 0; i < data.Length; i++)
        {
            BundleInfo bundleInfo = new BundleInfo();
            string[] info = data[i].Split('|');
            bundleInfo.AssetName = info[0];
            bundleInfo.BundleName = info[1];
            //list特性:本质是数组,可动态扩容
            bundleInfo.Dependeces = new List<string>(info.Length - 2);
            for (int j = 2; j < info.Length; j++)
            {
                bundleInfo.Dependeces.Add(info[j]);
            }
            m_BundleInfos.Add(bundleInfo.AssetName, bundleInfo);
        }
    }

    //异步加载bundle,使用回调通知应用层加载完毕,,,需要通过Action回调把加载好的Object返回回去
    //Object在UnityEngine和System会重名,使用using改名UObject
    /// <summary>
    /// 异步加载资源
    /// </summary>
    /// <param name="assetName">资源名</param>
    /// <param name="action">完成回调,默认null,不传值默认为空</param>
    /// <returns></returns>
    IEnumerator LoadBundleAsync(string assetName,Action<UObject> action = null)
    {
        string bundleName = m_BundleInfos[assetName].BundleName;
        //这个是小写的bundle.ab的路径名
        string bundlePath = Path.Combine(PathUtil.BundleResourcePath, bundleName);
        List<string> dependences = m_BundleInfos[assetName].Dependeces;
        if(dependences != null && dependences.Count > 0)
        {
            //递归加载依赖bundle,因为依赖的资源目录名就是bundle资源名
            for (int i = 0; i < dependences.Count; i++)
            {
                yield return LoadBundleAsync(dependences[i]);
            }
        }
        //创建异步加载bundle申请
        AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(bundlePath);
        yield return request;

        //从bundle申请加载指定路径名的文件,例如prefab
        AssetBundleRequest bundleRequest = request.assetBundle.LoadAssetAsync(assetName);
        yield return bundleRequest;

        //如果回调和request都不为空,语法糖
        action?.Invoke(bundleRequest?.asset);
    }

    //直接上面异步的方法向外面提供接口使用StartCoroutine不方便,提供一个接口
    public void LoadAsset(string assetName, Action<UObject> action)
    {
        StartCoroutine(LoadBundleAsync(assetName, action));
    }

    void Start()
    {
        ParseVersionFile();
        LoadAsset("Assets/BuildResources/UI/Prefabs/TestUI.prefab", OnComplete);
    }

    private void OnComplete(UObject obj)
    {
        GameObject go = Instantiate(obj) as GameObject;
        go.transform.SetParent(this.transform);
        go.SetActive(true);
        go.transform.localPosition = Vector3.zero;
    }
}

5. 资源路径规划

需要把每一个资源按照这个文件夹的类型写一个接口
image.png

public static string GetLuaPath(string name)
{
    return string.Format("Assets/BuildResources/LuaScripts/{0}.bytes", name);
}

public static string GetUIPath(string name)
{
    return string.Format("Assets/BuildResources/UI/Prefabs/{0}.prefab", name);
}

public static string GetMusicPath(string name)
{
    return string.Format("Assets/BuildResources/Audio/Music/{0}", name);
}

public static string GetSoundPath(string name)
{
    return string.Format("Assets/BuildResources/Audio/Sound/{0}", name);
}

public static string GetEffectPath(string name)
{
    return string.Format("Assets/BuildResources/Effect/Prefabs/{0}.prefab", name);
}

public static string GetModelPath(string name)
{
    return string.Format("Assets/BuildResources/Model/Prefabs/{0}.prefab", name);
}

//例如图片音乐等,后缀名也要传进来
public static string GetSpritePath(string name)
{
    return string.Format("Assets/BuildResources/Sprites/{0}", name);
}

public static string GetScenePath(string name)
{
    return string.Format("Assets/BuildResources/Scenes/{0}.unity", name);
}

6. 编辑器模式加载资源

需要让编辑器在不需要打Bundle的时候,直接使用Unity内部的资源加载来测试逻辑。

#if UNITY_EDITOR
/// <summary>
/// 在编辑器环境下使用,虽然是同步加载,但是模拟成前面的异步加载函数
/// </summary>
/// <param name="assetName"></param>
/// <param name="action"></param>
void EditorLoadAsset(string assetName, Action<UObject> action = null)
{
    UObject obj = UnityEditor.AssetDatabase.LoadAssetAtPath(assetName, typeof(UObject));
    if(obj = null)
    {
        Debug.LogError("asset name is not exist:" + assetName);
        action?.Invoke(obj);
    }
}
#endif

//直接上面异步的方法向外面提供接口使用StartCoroutine不方便,提供一个接口
private void LoadAsset(string assetName, Action<UObject> action)
{
    //如果是编辑器模式,就加载Assets/BuildResources/UI/Prefabs/TestUI.prefab
#if UNITY_EDITOR
    if (AppConst.GameMode == GameMode.EditorMode)
        EditorLoadAsset(assetName, action);
    else    //否则加载StreamingAssets/ui/prefabs/testui.prefab.ab
#endif
        StartCoroutine(LoadBundleAsync(assetName, action));
}

:::info
GameMode主要是ResourceManager和LuaManager运行时用来加载文件进行判断当前的运行模式,但是只判断EditorMode和else(PackageMode和UpdateMode),因为两者的加载方法不一样,前者从文件路径直接加载,后者需要从ab包加载。。第三处用到GameMode的是PathUtil的BundleResourcePath属性,需要根据到底是PackageMode还是UpdateMode返回到底是只读目录还是可读写目录。
:::

public enum GameMode
{
    //编辑器模式,包模式,更新模式
    EditorMode,
    PackageBundle,
    UpdateMode,
}

public class AppConst
{
    public const string BundleExtension = ".ab";
    public const string FileListName = "filelist.txt";
	//为什么不写成const?因为要在inspector中手动修改状态,而不能每次进来修改代码
    public static GameMode GameMode = GameMode.EditorMode;
}

在Hierarchy中添加框架的入口(空物体)root,添加新脚本GameStart

public class GameStart : MonoBehaviour
{
    public GameMode GameMode;
    // Start is called before the first frame update
    void Awake()
    {
        AppConst.GameMode = this.GameMode;
    }
}

Unity 出现error CS0103: The name ‘AssetDatabase‘ does not exist in the current context_旭God的舔狗的博客-CSDN博客_unity报错cs0103
问题描述
在Unity场景中,在进行build操作时出现这种报错,导致资源bundle无法正常生成,出现以下问题:
error CS0103: The name ‘AssetDatabase’ does not exist in the current context
error CS0234: The type or namespace name ‘AssetDatabase’ does not exist in the namespace ‘UnityEditor’ (are you missing an assembly reference?)
ps:上面两种错误都是同一种问题造成的,报错不一样的原因是由于UnityEditor在代码中的位置不同造成的:
前者在开头声明了using UnityEditor,在方法中使用AssetDatabase.LoadAssetAtPath;
后者未声明using UnityEditor,在方法中使用了UnityEditor.AssetDatabase.LoadAssetAtPath
原因分析
在非Editor文件夹下的脚本中,存在着有关UnityEditor方法的使用
方法中第一行使用了UnityEditor中的AssetDatabase.LoadAssetAtPath方法,并且该方法所在的文件并非是在Editor文件夹下,导致build操作时出现报错
image.png
6nld413u.bmp
解决方案
添加
#if UNITY_EDITOR
#endif
image.png
注意
注:需要把调用该方法的地方也要用#if #endif包括起来
因为该方法时需要被调用的,然后测试的时候出现了以下问题
error CS0103: The name ‘EditorLoadAsset’ does not exist in the current context
出现问题的原因是调用此方法的地方未用#if #endif包含进去,在正式运行状态下,他会认为该方法不存在,找不到该方法导出出现报错。所以要将调用该方法的地方也要用#if #endif包括进来,让正式运行状态下也不用执行调用该方法的语句

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值