有什么不明白的地方,扫描右方二维码加我微信交流。
       

github中有Demo点击跳转

 

UGUI使用网络图片有以下几种方式:

  1. WWW(下载必须协程)
  2. UnityWebRequest(下载必须协程)
  3. UnityWebRequestTexture(下载必须协程)
  4. HttpClient(下载可同步线程,也可多线程)

前3种加载图片的方式没有什么好说的,中规中矩的UnityAPI,Unity的官方文档里介绍的很详细。如果只是简单的下载一个头像,一个icon,使用这些API就够了。

HttpClient是C#的API,可在异步线程中使用,适用于更复杂的场景。例如排行榜,下载100个人甚至1000个人的头像数据,使用异步线程,效率将会大大提升。异步线程可提升效率,但注意线程安全,注意在异步线程中不要使用UnityAPI。

 

思路如下:

  1. 使用网络请求下载图片数据;
  2. 加载图片数据为Texture2D对象;
  3. 创建Sprite对象;
  4. 替换Image组件中的Sprite。

 

WWW加载方式如下:

IEnumerator GetTexFromWWW()
{
    Debug.Log("1");
    //WWW在后续的版本中已被弃用,不建议再继续使用
    //可以查看WWW的内部实际上是UnityWebRequest
    var imgUrlWWW = new WWW(_imgUrl);
    yield return imgUrlWWW;

    Debug.Log("2");
    if (imgUrlWWW.error != null)
    {
        Debug.Log("error\t" + imgUrlWWW.error);
        yield return null;
    }
    
    var sp = Sprite.Create(imgUrlWWW.texture, new Rect(0, 0, imgUrlWWW.texture.width, imgUrlWWW.texture.height), Vector2.zero);
    imageWWW.sprite = sp;
    imageWWW.SetNativeSize();
    Debug.Log("3");
}

UnityWebRequest加载方式如下:

IEnumerator GetTexFromUnityWebRequest()
{
    Debug.Log("1");
    using var request = UnityWebRequest.Get(_imgUrl);
    yield return request.SendWebRequest();
    
    Debug.Log("2");
    if (request.result != UnityWebRequest.Result.Success)
    {
        Debug.Log(request.result);
        Debug.Log(request.error);
    }
    else
    {
        Debug.Log("3");
        var texture = new Texture2D(200, 200);
        texture.LoadImage(request.downloadHandler.data);
        var sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height),
            new Vector2(0.5f, 0.5f));
        imageUnityWebRequest.sprite = sprite;
        imageUnityWebRequest.SetNativeSize();

        Resources.UnloadUnusedAssets();
        
        Debug.Log("4");
    }
}

UnityWebRequestTexture加载方式如下:

IEnumerator GetTexFromUnityWebRequestTexture()
{
    Debug.Log("1");
    using var request = UnityWebRequestTexture.GetTexture(_imgUrl);
    yield return request.SendWebRequest();
    
    Debug.Log("2");
    if (request.result != UnityWebRequest.Result.Success)
    {
        Debug.Log(request.result);
        Debug.Log(request.error);
    }
    else
    {
        Debug.Log("3");
        var texture = DownloadHandlerTexture.GetContent(request);
        var sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height),
            new Vector2(0.5f, 0.5f));
        imageUnityWebRequestTexture.sprite = sprite;
        imageUnityWebRequestTexture.SetNativeSize();
        
        Debug.Log("4");
    }
}

HttpClient加载方式如下:

private async void GetTexFromHttpClient()
{
    var clientTex = new HttpClient {Timeout = TimeSpan.FromSeconds(5)};
    HttpResponseMessage responseTex = null;
    try
    {
        await Task.Run(async () =>
        {
            Debug.Log("1");
            
            responseTex = await clientTex.GetAsync(_imgUrl);

            Debug.Log("2");
            responseTex.EnsureSuccessStatusCode(); //用来抛异常的
            var responseBody = await responseTex.Content.ReadAsByteArrayAsync();
            Debug.Log(responseBody);

            Debug.Log("3");
            //此步骤为在异步线程中调用Unity主线程,因为UnityEngine的大部份API只能在Unity主线程中调用
            //可下载gitDemo,在Demo中查看MainThread源码
            MainThread.RunTask(() =>
            {
                if (responseTex.IsSuccessStatusCode)
                {
                    Debug.Log("4");
                    var tex = new Texture2D(100, 100);
                    tex.LoadImage(responseBody);

                    var sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height),
                        new Vector2(0.5f, 0.5f));
                    imageHttpClient.sprite = sprite;
                    imageHttpClient.SetNativeSize();
                }

                
            });
        });
    }
    catch (Exception e)
    {
        Debug.LogError(e.Message);
    }
    finally
    {
        clientTex.Dispose();
        responseTex?.Dispose();
    }
}

 

下载速度对比

经过测试,主线程中使用协程的下载速度远高于异步线程。

但这是在理想状况下,主线程当前压力较小,基本没有任务在运行。但当有大量的网络请求、I/O操作或者复杂数据计算时,是否依然是这样的结果呢?

我在Update方法里加了以下代码:

var a = 1;
for (var i = 0; i < 1000; i++)
{
    ++a;
    PlayerPrefs.SetInt("test", a);
    PlayerPrefs.Save();
}

进行1000次的数据写入,结果如下:

主线程压力较大时,结果是:

协程的速度明显慢了5431ms,91.4%。

异步线程的运行速度慢了2312ms,24.1%。

结论:理想情况下,主线程使用协程下载速度比异步线程快;复杂情况下,异步线程要好一点。

 

什么时候使用异步线程下载

在Update里执行1000次的Save方法,这种操作几乎没有。但游戏越做越大,功能越来越复杂,游戏运行时的实际情况比我们想像的要复杂的多。当主线程有压力、或者有明显卡顿时就可以考虑使用异步线程来分担主线程压力。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注