它是一款 VN 风格的游戏,具有用户生成的内容,我需要立即加载图像。
由于它是用户生成的内容,图像将位于游戏的文件夹中。 由于同样的原因,我无法预加载图像,因为我不知道下一个要加载的图像是什么。
我尝试过 UnityWebRequest、WWW 或 File.ReadAllBytes,但它们的延迟都比我预期的要长,即使你在 SSD 上运行它也是如此。
有没有更快的方法?
我用于测试图像加载时间的代码
using UnityEngine;
using System.IO;
using UnityEngine.Networking;
using System.Collections;
using UnityEngine.UI;
using System.Threading.Tasks;
/// <summary>
/// 2020/19/05 -- Unity 2019.3.3f1 -- C#
/// </summary>
public class itemCreatorImageLoad : MonoBehaviour
{
public Image image; // this is referencing to a UI Panel
private Texture2D texture2D;
private UnityWebRequest uwr;
public RawImage rawImage; // this is referencing to a UI rawImage
// path = @"C:\UnityTests\Referencing\Referencing\Assets\StreamingAssets\Items\image.png"
// the @ handles the / and \ conventions that seem to come from the program using paths for the web
// C:/.../.../... web
// C:\...\...\... pc
public void LoadImageWWW(string path)
{
if (texture2D)
{
Destroy(texture2D); // this follows the reference and destroys the texture. Else it would just get a new one and the old textures start piling up in your memory, without you being able to remove them.
}
texture2D = new Texture2D(1, 1);
texture2D = new WWW(path).textureNonReadable as Texture2D;
image.sprite = Sprite.Create(texture2D, new Rect(0.0f, 0.0f, texture2D.width, texture2D.height), new Vector2(0.5f, 0.5f), 100.0f);
image.preserveAspect = true;
}
public void LoadImageWWWv2(string path)
{
// "http://url/image.jpg"
StartCoroutine(setImage(path));
}
IEnumerator setImage(string url) // this comes from https://stackoverflow.com/questions/31765518/how-to-load-an-image-from-url-with-unity
{
Texture2D texture = image.canvasRenderer.GetMaterial().mainTexture as Texture2D;
WWW www = new WWW(url);
yield return www;
// calling this function with StartCoroutine solves the problem
Debug.Log("Why on earh is this never called?");
www.LoadImageIntoTexture(texture);
www.Dispose();
www = null;
}
public void LoadImageReadAllBytes(string path)
{
byte[] pngBytes = File.ReadAllBytes(path);
if (texture2D)
{
Destroy(texture2D); // this follows the reference and destroys the texture. Else it would just get a new one and the old textures start piling up in your memory, without you being able to remove them.
}
texture2D = new Texture2D(1, 1);
texture2D.LoadImage(pngBytes);
image.sprite = Sprite.Create(texture2D as Texture2D, new Rect(0.0f, 0.0f, texture2D.width, texture2D.height), new Vector2(0.5f, 0.5f), 100.0f);
image.preserveAspect = true;
}
public void LoadImageUnityWebRequest(string path)
{
StartCoroutine(LoadImageCorroutine());
IEnumerator LoadImageCorroutine()
{
using (uwr = UnityWebRequestTexture.GetTexture(@path))
{
yield return uwr.SendWebRequest();
// I would always check for errors first
if (uwr.isHttpError || uwr.isNetworkError)
{
Debug.LogError($"Could not load texture do to {uwr.responseCode} - \"{uwr.error}\"", this);
yield break;
}
// Destroy the current texture instance
if (rawImage.texture)
{
Destroy(texture2D); // this follows the reference and destroys the texture. Else it would just get a new one and the old textures start piling up in your memory, without you being able to remove them.
}
rawImage.texture = DownloadHandlerTexture.GetContent(uwr);
image.sprite = Sprite.Create(rawImage.texture as Texture2D, new Rect(0.0f, 0.0f, rawImage.texture.width, rawImage.texture.height), new Vector2(0.5f, 0.5f), 100.0f);
image.preserveAspect = true;
}
StopCoroutine(LoadImageCorroutine());
}
}
public void LoadImageUnityWebRequestv2(string path)
{
StartCoroutine(LoadImageUnityWebRequestv2Coroutine(path));
}
IEnumerator LoadImageUnityWebRequestv2Coroutine(string MediaUrl) // this comes from https://stackoverflow.com/questions/31765518/how-to-load-an-image-from-url-with-unity
{
UnityWebRequest request = UnityWebRequestTexture.GetTexture(MediaUrl);
yield return request.SendWebRequest();
if (request.isNetworkError || request.isHttpError)
{
Debug.Log(request.error);
}
else
{
rawImage.texture = ((DownloadHandlerTexture)request.downloadHandler).texture;
}
}
// a async version that I grabbed from somewhere, but I dont remember where anymore
[SerializeField] string _imageUrl;
[SerializeField] Material _material;
public async void MyFunction()
{
Texture2D texture = await GetRemoteTexture(_imageUrl);
_material.mainTexture = texture;
}
public static async Task<Texture2D> GetRemoteTexture(string url)
{
using (UnityWebRequest www = UnityWebRequestTexture.GetTexture(url))
{
//begin requenst:
var asyncOp = www.SendWebRequest();
//await until it's done:
while (asyncOp.isDone == false)
{
await Task.Delay(1000 / 30);//30 hertz
}
//read results:
if (www.isNetworkError || www.isHttpError)
{
//log error:
#if DEBUG
Debug.Log($"{ www.error }, URL:{ www.url }");
#endif
//nothing to return on error:
return null;
}
else
{
//return valid results:
return DownloadHandlerTexture.GetContent(www);
}
}
}
}
我使用并喜欢 Unity 友好的等待/异步支持套件,称为 ProtoPromise。这是对普通 Unity C# async/await 的一个很好的答案,因为它有很多可以减少 GC 负载的优化,例如池化(免分配异步调用)。蒂姆也是一个友好而细心的人,他关心他的代码,使其稳定,使其有能力,并使其快速。
也就是说,这是我的图像加载代码。它在跨平台上表现良好,并且包装了基于 Web 和基于文件的纹理加载。
using Proto.Promises;
using Proto.Promises.Threading;
public interface IAssetResponder {
void OnAssetResolved<T>(AssetReq<T> request) where T : UnityEngine.Object;
}
public struct AssetReq<T> where T : UnityEngine.Object {
public AssetRef AssetRef; // what to load
public IAssetResponder Responder; // receives OnAssetRequestComplete
public UnityEngine.Object Asset; // Init to null;
public string ErrMsg; // null denotes success
public bool IsComplete => ErrMsg != null || Asset != null;
public AssetReq(IAssetResponder responder, AssetRef assetRef, LoadAssetOpts opts = LoadAssetOpts.None) {
AssetRef = assetRef;
Responder = responder;
Opts = opts;
Asset = null;
ErrMsg = null;
}
public void Resolve(UnityEngine.Object obj) {
if (!TryResolve(obj)) {
if (obj == null) {
Fail("Asset is null");
} else {
Fail($"Asset of type {obj.GetType().Name} could not be resolved to type {typeof(T).Name}");
}
}
}
public bool TryResolve(UnityEngine.Object obj) {
if (obj == null)
return false;
if (obj is T assetAsT) {
Asset = assetAsT;
return true;
} else if (obj is Texture2D tex2D) {
if (typeof(T) == typeof(ArcGlyph))
Asset = tex2D;
} else if (obj is Sprite sprite) {
if (typeof(T) == typeof(Sprite))
Asset = sprite;
}
if (this.Asset != null) {
Responder.OnAssetResolved(this);
return true;
}
return false;
}
public void Fail(string err) {
ErrMsg = err;
Responder.OnAssetResolved(this);
}
}
public class AssetService : MonoBehaviour {
public bool TryResolveAsset<T>(AssetReq<T> req) where T : UnityEngine.Object {
...
}
...
AsyncSemaphore _maxFileLoads = new AsyncSemaphore(8);
AsyncSemaphore _maxWebReqs = new AsyncSemaphore(3);
async Promise loadTextureFromFile<T>(string pathname, AssetReq<T> texReq) where T : UnityEngine.Object {
await Promise.SwitchToBackground();
byte[] texData = null;
using (await _maxFileLoads.EnterScopeAsync()) {
if (!texReq.AssetRef.GetURIForScheme(URIScheme.File, out pathname)) {
texReq.Fail($"AssetRepo: failed to get pathname for AssetRef '{texReq.AssetRef}'");
return;
}
try {
texData = File.ReadAllBytes(pathname);
} catch (Exception e) {
texReq.Fail($"AssetRepo: failed to read '{pathname}': {e.Message}");
return;
}
}
await Promise.SwitchToForeground();
Texture2D tex2D = new Texture2D(1,1);
if (tex2D.LoadImage(texData)) {
texReq.Resolve(tex2D);
} else {
texReq.Fail($"AssetRepo: failed to load texture from file '{pathname}'");
}
}
async Promise loadTextureUsingWebRequest<T>(AssetReq<T> req) where T : UnityEngine.Object {
using (await _maxWebReqs.EnterScopeAsync()) {
var url = req.AssetRef.URL;
using (var webReq = UnityWebRequestTexture.GetTexture(url, false)) {
await PromiseYielder.WaitForAsyncOperation(webReq.SendWebRequest());
if (webReq.result == UnityWebRequest.Result.Success) {
req.Resolve(((DownloadHandlerTexture) webReq.downloadHandler).texture);
} else {
req.Fail(webReq.error);
}
}
}
}
}