Home > Back-end >  Unity game stops while coroutine running
Unity game stops while coroutine running

Time:06-04

Unity(character movement and everything in current game scene) stops while downloading textures from web url. I'm using Socket.IO for online multiplayer.

  1. request 'map info' with socket.emit in void Start()
  2. get 'map info' and create map entities with Instantiate()
  3. get profile images(textures) from url and change texture of map entities
IEnumerator DownloadImage(string MediaUrl, SpriteRenderer spr) {
    // check if url is malformed
    if (MediaUrl == null || MediaUrl.Contains("Null")) {
        yield break;
    }

    UnityWebRequest request = UnityWebRequestTexture.GetTexture(MediaUrl);
    yield return request.SendWebRequest();

    if (request.result == UnityWebRequest.Result.ConnectionError || request.result == UnityWebRequest.Result.DataProcessingError || request.result == UnityWebRequest.Result.ProtocolError)
        Debug.Log(request.error);
    else {
        Texture2D tex = ((DownloadHandlerTexture)request.downloadHandler).texture;

        spr.sprite = Sprite.Create(tex, new Rect(0.0f, 0.0f, tex.width, tex.height), new Vector2(0.5f, 0.5f), tex.width / 2f);
    }
}

Is there a way to keep game running with user interactions(character movement or button actions) while downloading textures?

CodePudding user response:

Unity co-routines (CR) are a entry-level and sometimes hazardous form of performing work over a series of frames on the Main Unity thread. They are useful for what is essentially a logical lerp across a series of frames such as fading, explode-my-spaceship-after-5-seconds, or deferring cockpit display updates.

CRs are sliced up and multiplexed over the Main Thread so the slowest part of your game is going to be the CR with the largest demand for time in a single frame. Performing anything lengthy during a single yield per frame is going to slow down your game, particularly I/O operations.

Alternative - Unity Jobs

Instead, consider using Unity 'IJob' which allows for operations to be executed on a worker thread thus freeing up the Unity thread to do what it does best and not lengthy I/O operations.

Unity:

Use IJob to schedule a single job that runs in parallel to other jobs and the main thread. When a job is scheduled, the job's Execute method is invoked on a worker thread. More...

Now wasting a whole thread that may spend it's life waiting for I/O to complete is arguably a waste of a perfectly good thread but it is certainly better than blocking the Main Thread.

Coroutines...thar be dragons

So earlier I mentioned "hazardous" with regards to CRs and the reason is three-fold:

  1. When firing off a CR in a method that is called many times per second such as Update() make sure you adequately guard the StartCoroutine() else you could end up with zillions of them running only to run out of memory. This one is quite common on Stack Overflow
  2. Don't make the mistake of thinking they are somehow a worker thread or other piece of magic that won't slow down the Main Thread
  3. In many ways CRs are like DoEvents, Application.DoEvents in Visual Basic and .NET respectively and both leads to a nasty case of re-entrancy (where state is clobbered and unpredictable just like in a multi-threaded app but without the threads)

There's also the whole is Unity promoting a strange use of IEnumerator and yield? debate.

My other tip is, if you need something lerped or delayed and isn't doing I/O, consider just making a class and use Time.

e.g. in my flight sim, I only want to update cockpit displays every 100ms or so. For that I define a Delay class:

public class Delay
{
    private float _lastInterval;

    /// <summary>
    ///     The timeout in seconds
    /// </summary>
    /// <param name="timeout"></param>
    private Delay(float timeout)
    {
        Timeout = timeout;
        _lastInterval = Time.time;
    }

    public float Timeout { get; }

    public bool IsTimedOut => Time.time> _lastInterval   Timeout;

    public void Reset()
    {
        _lastInterval = Time.time;
    }

    public static Delay StartNew(float delayInSeconds)
    {
        return new Delay(delayInSeconds);
    }
}
.
.
.
private void Update()
{
    if (!_delay.IsTimedOut)
    {
        return;
    }

    // Do something time consuming
 
    _delay.Reset();
}

  • Related