1. 概述
在日常项目开发过程中,对于资源的加载操作,通常要适配不同的平台与模式。为了防止在载入大量资源时,因为I/O操作而造成游戏卡顿,大部分开发者都会选择异步加载的方式。
基于迭代器设计的加载接口,可以很好的与Unity原生的协程(Coroutine)配合使用,在接口调用时,即方便又工整。
2. 功能核心接口说明
- 异步加载指定名称或路径的资源
- 获得当前的加载进度
- 获得当前加载完成的资源对象
- 加载完成时的回调处理
3. 主要代码实现
- BaseAsync 异步加载基类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
using System; using System.Collections; namespace GEngine { public abstract class BaseAsync : IEnumerator { public enum LoadStatus { None , Loading , Loaded } //加载进度值 public float Progress { get; protected set; } //加载完成的资源 public UnityEngine.Object MainAsset { get; protected set; } //完成加载后的处理 public Action<BaseAsync> OnFinished; protected string nameOrPath; protected LoadStatus state; public BaseAsync() { } public BaseAsync(string nameOrPath) { this.nameOrPath = nameOrPath; } #region IEnumerator public virtual bool MoveNext() { if (state == LoadStatus.None) { state = LoadStatus.Loading; OnLoading(); } return IsDone() != true; } public void Reset() { state = LoadStatus.None; } public object Current { get; private set; } #endregion /// <summary> /// 执行加载 /// </summary> public abstract void OnLoading(); /// <summary> /// 是否已完成 /// </summary> public abstract bool IsDone(); public virtual void OnFinish() { Progress = 1.0f; state = LoadStatus.Loaded; if(OnFinished != null) OnFinished.Invoke(this); } } } |
- AssetAsync Unity编辑器模式下的加载(非Resouces目录)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
using UnityEngine; using System.Collections; namespace GEngine { /// <summary> /// 编辑器下无资源目录(Resources)加载 /// </summary> public class AssetAsync : BaseAsync { public AssetAsync(string nameOrPath) : base(nameOrPath) { } public override void OnLoading() { #if UNITY_EDITOR MainAsset = UnityEditor.AssetDatabase.LoadAssetAtPath<Object>(this.nameOrPath); #endif } public override bool IsDone() { this.OnFinish(); return true; } } } |
- BundleAsync 用于支持AssetBundle文件模式载入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
using UnityEngine; using System.Collections; namespace GEngine { public class BundleAsync : BaseAsync { public BundleAsync(string nameOrPath) : base(nameOrPath) { } public override void OnLoading() { throw new System.NotImplementedException(); } public override bool IsDone() { throw new System.NotImplementedException(); } } } |
- SceneAsync 用于支持Unity场景文件载入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
using UnityEngine; using System.Collections; using UnityEngine.SceneManagement; namespace GEngine { /// <summary> /// 场景加载 /// </summary> public class SceneAsync : BaseAsync { private AsyncOperation asyncLoading; private LoadSceneMode loadSceneMode; public SceneAsync(string sceneName, LoadSceneMode mode = LoadSceneMode.Single) : base(sceneName) { loadSceneMode = mode; } public override bool MoveNext() { if (state == LoadStatus.None) { state = LoadStatus.Loading; OnLoading(); } if (asyncLoading != null) { Progress = asyncLoading.progress; if (Progress >= 0.9f) asyncLoading.allowSceneActivation = true; } bool isDone = IsDone(); if(isDone) this.OnFinish(); return isDone != true; } public override void OnLoading() { asyncLoading = SceneManager.LoadSceneAsync(this.nameOrPath , loadSceneMode); asyncLoading.allowSceneActivation = false; } public override bool IsDone() { if(asyncLoading != null ) return asyncLoading.isDone; return true; } } } |
- GResources 通用资源加载接口封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
using System; using UnityEngine; using System.Collections; namespace GEngine { public sealed class GResources { //是否为Bundle模式 public static bool AssetBundleMode = false; //资源的根目录 public static string ResourceRoot = "Assets/RuntimeAssets"; #region 异步接口 public static BaseAsync LoadAssetAsync(string nameOrPath) { if (AssetBundleMode) return new BundleAsync(nameOrPath); string assetPath = string.Concat(ResourceRoot, "/", nameOrPath); return new AssetAsync(assetPath); } public static BaseAsync LoadAssetAsync(string nameOrPath , Action<BaseAsync> onFinish) { BaseAsync @async; if (AssetBundleMode) async = new BundleAsync(nameOrPath); else { string assetPath = string.Concat(ResourceRoot, "/", nameOrPath); async = new AssetAsync(assetPath); } async.OnFinished = onFinish; return async; } #endregion #region 同步接口 public static GameObject LoadPrefab(string assetName) { if (AssetBundleMode) return null; #if UNITY_EDITOR string assetPath = string.Concat(ResourceRoot, "/", assetName); return UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(assetPath); #endif return null; } #endregion } } |
4. 测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
using UnityEngine; using System.Collections; using GEngine; public class TestResources : MonoBehaviour { // Use this for initialization void Start () { } // Update is called once per frame void Update () { } void OnGUI() { if (GUILayout.Button("Load GameObject Async")) { this.StartCoroutine(testLoadGameObjectAsync()); } if (GUILayout.Button("Load GameObject Callback Async")) { testLoadGameObjectCallBackAsync(); } } private IEnumerator testLoadGameObjectAsync() { var loader = GResources.LoadAssetAsync("Prefabs/Cube.prefab"); yield return loader; GameObject.Instantiate(loader.MainAsset); } private void testLoadGameObjectCallBackAsync() { StartCoroutine(GResources.LoadAssetAsync("Prefabs/Cube.prefab", operation => { GameObject ins = GameObject.Instantiate(operation.MainAsset) as GameObject; ins.transform.localPosition = Vector3.one; })); } } |