在Unity的UGUI中使用网络图片的4种方式及性能对比
有什么不明白的地方,扫描右方二维码加我微信交流。
github中有Demo,点击跳转。
UGUI使用网络图片有以下几种方式:
- WWW(下载必须协程)
- UnityWebRequest(下载必须协程)
- UnityWebRequestTexture(下载必须协程)
- HttpClient(下载可同步线程,也可多线程)
前3种加载图片的方式没有什么好说的,中规中矩的UnityAPI,Unity的官方文档里介绍的很详细。如果只是简单的下载一个头像,一个icon,使用这些API就够了。
HttpClient是C#的API,可在异步线程中使用,适用于更复杂的场景。例如排行榜,下载100个人甚至1000个人的头像数据,使用异步线程,效率将会大大提升。异步线程可提升效率,但注意线程安全,注意在异步线程中不要使用UnityAPI。
思路如下:
- 使用网络请求下载图片数据;
- 加载图片数据为Texture2D对象;
- 创建Sprite对象;
- 替换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方法,这种操作几乎没有。但游戏越做越大,功能越来越复杂,游戏运行时的实际情况比我们想像的要复杂的多。当主线程有压力、或者有明显卡顿时就可以考虑使用异步线程来分担主线程压力。