Search code examples
c#imageunity-game-engineasynchronousscene

Unity load www images in scene async


In Unity, I have a home screen. When I click on it, I want to open a scene with a list of games with miniatures (900x900).

My problem is Unity does not load the images in the "loadasync" part. Here's my code :

Home's code to load the next scene async (called in a OnPointerClick)

IEnumerator LoadSceneAsyncIEnumerator(string sceneName)
{
    yield return null;
    AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
    while (!asyncLoad.isDone)
    {
        yield return null;
    }
}

And here's the Awake of my ListGames' scene :

void Awake(){
    List<JeuIO> jeux = GetGames ();
    foreach (JeuIO j in jeux){
        StartCoroutine(LoadImageAsync(j.image1));
    }
}

GetGames does an http call to get the list of games (which is done before the scene opens), but LoadImageAsync are not resolved before asyncLoad.isDone == true.

IEnumerator LoadImageAsync(string url) {
    string u = url;
    bool found = false;
    if (File.Exists(Application.persistentDataPath + "/"+url.GetHashCode()+".png")){
        u = Application.persistentDataPath + "/"+url.GetHashCode()+".png";
        found = true;
    }

    WWW www = new WWW(u);
    yield return www;

    Texture2D t = null;
    if(!found && www.bytes != null) {
        File.WriteAllBytes(Application.persistentDataPath + "/"+url.GetHashCode()+".png", www.bytes);
        t = www.textureNonReadable;
    } else {
        t = www.texture;
    }
    if (t != null){
        Sprite.Create(t, new Rect(0, 0, t.width, t.height), new Vector2(0, 0));
    }
    Debug.Log("IMAGE "+url+" OVERRRRRRRR");
}

Of course this is not the final code ^^ I'll create the sprites directly in the gameobjects of my grid.

So, how can I force unity to create all my sprites before saying the awaiting is done ?


Solution

  • It looks to me that your loadscene script and ListGames script are not connected in any way. So there is currently no way to force this behaviour.

    There are various ways to tackle your problem. A solution could be:

    Move your List<JeuIO> jeux to a ScriptableObject, which you can optionally pass as a parameter to your LoadScene script. That way you keep some form of code flexibility and extensibility.

    Second, you might want to implement a callback function in your LoadImageAsync method:

    IEnumerator LoadImageAsync(string url, Action<Sprite> callback) {
        // your code
        var sprite Sprite.Create(t, new Rect(0, 0, t.width, t.height), new Vector2(0, 0));
    
    
        callback?.Invoke(sprite);
    }
    

    Implement it like this:

    IEnumerator LoadSceneAsyncIEnumerator(string sceneName, SomeScriptableObject scriptable)
    {
        yield return null;
        var targetCount = scriptable.jeux.Count();
        var loadedCount = 0;
    
        AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
        foreach (var j in scriptable.jeux)
        {
            // for each image that is processed, increment the counter
            StartCoroutine(LoadImageAsync(j.image1,(sprite) =>
            {
                someList.Add(sprite);
                loadedCount++;
            }));
        }
    
        while (!asyncLoad.isDone || targetCount != loadedCount)
        {
            yield return null;
        }
    }
    

    EDIT: You can retrieve your sprite by passing a parameter to the Action. It can be used in the callback.