浅谈Unity资源异步加载和Coroutine的使用

为了节省内存,游戏的一些资源往往需要在运行时(runtime)动态加载。如果资源本身加载比较耗时,采用同步方法会产生卡顿现象,对此的解决方法通常采用多线程或者使用引擎本身自带的异步加载方法。在Unity开发中,由于一些方法(如Resources.Load)本身不支持在其它线程调用,因此多线程的使用会受到限制;而Unity脚本API对许多加载方式都有相应的异步方法,因此我们需要对Unity异步加载方法的机制有一定的理解。此外,协同进程(Coroutine)常常和异步加载方法联合使用,这里顺便也研究一下。

1. Unity异步加载资源

Unity在runtime加载资源有Resources和AssetBundle两类方法,由于AssetBundle需要先进行打包,因此在Editor中进行开发测试阶段使用不太方便。这里介绍Resources.LoadAsync异步加载资源方法,先上代码和运行结果:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class tryLoadAsync : MonoBehaviour
{

    public static TerrainData td;
    public static GameObject terrain00;
    private ResourceRequest request;

    void Start()
    {
#if UNITY_EDITOR
        request = Resources.LoadAsync("tt/myTD00"); //调用异步加载方法
        Debug.Log("异步方法!");
#endif
    }

    void Update()
    {
        if (request != null)
        {
            if (request.isDone)
            {
                Debug.Log("异步加载完成了!");
                td = request.asset as TerrainData;
                if (!td)
                {
                    Debug.Log("加载terraindata失败!");
                }
                //创建地形
                terrain00 = Terrain.CreateTerrainGameObject(td);
                terrain00.transform.position = new Vector3(0, 0, 0);
                terrain00.GetComponent<Terrain>().Flush();

                request = null;
            }
            else
            {
                Debug.Log("已经过了一帧了!"); //异步方法至少需要一帧的时间才能生效!
            }
        }
    }
}

异步加载和同步加载的区别用另外一种概念描述就是“阻塞”。同步方法会阻塞在当前代码的执行,而其它部分(如UI)都在等着它结束调用,因此如果资源加载很耗时,那么就会出现“卡住了”的现象。异步加载则是非阻塞的,调用完异步方法后,代码继续执行,而加载工作由Unity在后台另开辟一个异步线程来进行。比如上面的代码中,在初始化Start方法中调用了Resources.LoadAsync异步方法加载一个TerrainData地形资源,该方法立即返回一个ResourceRequest类型的消息,之后继续执行下一条Debug.Log语句,而并不会等待加载的完成。

加载状态的查询有两种方法:一种是后面要介绍的yield return方式,另一种是这里采用的手动查询方式。我们在每帧运行的Update方法中通过ResourceRequestisDone成员查询异步加载是否完成了。但是从控制台窗口的输出来看,第一帧执行的是else分支,也就是在第一帧时异步加载还没有完成。这并不是因为我们的资源过大(事实上这个TerrainData资源也很小),而是异步方法的生效至少要一帧的时间。异步加载的这个特性是十分重要的,不仅对于Resources类的方法,对于AssetBundle也一样。比如,你要在Update中查询一些游戏状态以决定是否要加载新的资源,你编写了判断条件、加载代码,这都很很棒。然后你继续编写代码想让这些资源立即生效,然而抱歉,在这一帧你的这些代码就没有任何意义了,因为无论你怎么样查询加载状态,它都会告诉你异步加载还没有成功,因为它至少有一帧的延迟微笑这种情况是值得注意的。


2. Couroutine的使用

协同进程Coroutine并不是线程,它使用yield return语句强行将代码分为两部分执行,每一帧Unity都会查询一下yield返回之前的位置是否满足条件。如果满足那么继续运行协程剩余的代码,并且至少会推迟一帧。比如下面的代码和运行结果:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using System.Threading;

public class tryCoroutine : MonoBehaviour
{

    public static TerrainData td;
    public static GameObject terrain00;

    private ResourceRequest request;
    // Use this for initialization
    void Start()
    {
#if UNITY_EDITOR
        StartCoroutine(LoadTerrain());
        Debug.Log("开始协同进程!");
#endif
    }

    IEnumerator LoadTerrain()
    {
        request = Resources.LoadAsync("tt/myTD00");
        yield return request;

        Debug.Log("异步加载完成了!");
        td = request.asset as TerrainData;
        if (!td)
        {
            Debug.Log("加载terraindata失败!");
        }
        terrain00 = Terrain.CreateTerrainGameObject(td);
        terrain00.transform.position = new Vector3(0, 0, 0);
        terrain00.GetComponent<Terrain>().Flush();
    }

    // Update is called once per frame
    void Update()
    {
        if (td == null)
            Debug.Log("等一等");
    }
}

程序实现的功能和上面一样,都是加载一个TerrainData资源并创建一个地形。该脚本创建了一个名为LoadTerrainCoroutine,注意返回值一定是IEnumerator类型。在第一次运行至yield return语句时,程序返回,执行该帧的Update方法;在下一帧,由于异步加载完成,程序回到yield return的位置,继续执行这个Coroutine剩余的代码。可见,Coroutine并不是一个线程,它仍然运行在主线程中。


参考文献:

1. AssetBundle官方教程:https://unity3d.com/cn/learn/tutorials/topics/best-practices/guide-assetbundles-and-resources

2. Unity Script API:https://docs.unity3d.com/ScriptReference/index.html

  • 12
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: 在Unity中,可以使用异步加载资源的方法来提高游戏的性能和流畅度。具体的实现方法可以使用Unity提供的协程或者异步操作来完成。其中,协程可以通过使用yield return语句来实现异步加载资源的功能,而异步操作则可以使用Unity提供的AsyncOperation类来实现。需要注意的是,在使用异步加载资源的过程中,需要注意资源的释放和管理,以避免内存泄漏和性能问题的出现。 ### 回答2: 在Unity中,可以使用异步加载资源的方法来提高游戏的性能和流畅度。异步加载资源是指在后台线程中加载资源,使得游戏的主线程不会被阻塞,从而保持游戏的响应能力。 Unity提供了一些相关的API来实现异步加载资源。其中最常用的方法是使用协程(Coroutine)和AssetBundle。 首先,可以使用协程来异步加载资源。我们可以在一个协程函数中通过使用yield return关键字来暂停执行协程,等待资源加载完成。例如,可以使用WWW类来异步加载网络或本地的资源文件。具体的代码如下: ``` IEnumerator LoadAssetAsync(string url) { UnityWebRequest request = UnityWebRequest.GetAssetBundle(url); yield return request.SendWebRequest(); AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request); // 使用加载资源做其他操作 } ``` 另一种常用的方法是使用AssetBundle来异步加载资源。AssetBundle是一种资源打包格式,可以将多个资源文件打包成一个文件,然后在游戏运行时进行加载。我们可以使用AssetBundle类的异步加载方法来加载资源。具体的代码如下: ``` IEnumerator LoadAssetBundleAsync(string url) { using (UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(url)) { yield return request.SendWebRequest(); AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request); // 使用加载资源做其他操作 } } ``` 通过使用协程和AssetBundle,我们可以实现在后台线程中异步加载资源,避免了主线程的阻塞,提高了游戏的性能和流畅度。当资源加载完成后,我们可以使用加载资源做其他操作,比如进行实例化、显示和播放等。 需要注意的是,异步加载资源需要一定的时间,因此在使用加载资源之前,需要确保资源已经加载完成。可以使用回调函数或事件来通知资源加载完成后再进行后续操作。 ### 回答3: 在Unity中,异步加载资源可以通过以下步骤进行: 1. 使用Unity异步加载函数(如:AssetBundle.LoadAssetAsync)来加载需要的资源异步加载函数与同步加载函数的主要区别是,异步加载函数会在后台线程中进行资源加载,而不会阻塞主线程。 2. 通过Unity的协程(Coroutine)来处理异步加载过程。协程可以在代码执行过程中暂停和恢复执行。可以使用Unity提供的协程函数(如:StartCoroutine)来启动异步加载过程,并使用yield关键字来暂停和恢复协程的执行。 3. 在协程的过程中使用异步加载函数加载资源。可以通过实例化一个异步加载函数,并使用yield关键字将其包装成一个等待函数,以在协程中等待资源加载完成。例如: ``` IEnumerator LoadAssetAsync() { AssetBundle bundle = AssetBundle.LoadFromFile("bundlePath"); AssetBundleRequest request = bundle.LoadAssetAsync<GameObject>("assetName"); yield return request; GameObject asset = request.asset as GameObject; // 使用加载好的资源 } ``` 在这个例子中,首先通过异步加载函数LoadFromFile加载AssetBundle资源包,然后使用LoadAssetAsync异步加载GameObject资源。通过yield return将异步加载过程包装成一个等待函数,以在协程中等待资源加载完成。最后,可以将加载好的资源用于后续的逻辑。 通过以上步骤,可以在Unity中实现资源异步加载,避免资源加载过程对主线程的阻塞,提高游戏的性能和用户体验。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值