Unity C# - 有没有比 UnityWebRequest、WWW 或 File.ReadAllBytes 更快的方法从磁盘加载大量图像文件?

问题描述 投票:0回答:1

它是一款 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);
        }
    }
}

}

c# image unity-game-engine image-loading
1个回答
0
投票

我使用并喜欢 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);
                }
            }
        }
    }
}
最新问题
© www.soinside.com 2019 - 2024. All rights reserved.