Zoeken…


Syntaxis

  • openbare Coroutine StartCoroutine (IEnumerator routine);
  • openbare Coroutine StartCoroutine (string methodName, objectwaarde = null);
  • openbare nietig StopCoroutine (string methodName);
  • openbare nietig StopCoroutine (IEnumerator routine);
  • openbare nietig StopAllCoroutines ();

Opmerkingen

Prestatieoverwegingen

Het is het beste om coroutines met mate te gebruiken, omdat de flexibiliteit gepaard gaat met prestatiekosten.

  • Coroutines in grote aantallen eisen meer van de CPU dan standaard Update-methoden.
  • Er is een probleem in sommige versies van Unity waarbij coroutines voor elke updatecyclus afval produceren omdat Unity de MoveNext retourwaarde in dozen doet. Dit werd voor het laatst waargenomen in 5.4.0b13. ( Bugrapport )

Verminder afval door caching YieldInstructions

Een veel voorkomende truc om het afval dat in coroutines wordt gegenereerd te verminderen, is het cachen van de YieldInstruction .

IEnumerator TickEverySecond()
{
    var wait = new WaitForSeconds(1f); // Cache
    while(true)
    {
        yield return wait; // Reuse
    }
}

null opbrengen levert geen extra afval op.

coroutines

Allereerst is het essentieel om te begrijpen dat game-engines (zoals Unity) werken op een "op frames gebaseerd" paradigma.

Code wordt tijdens elk frame uitgevoerd.

Dat omvat de eigen code van Unity en uw code.

Wanneer u over frames nadenkt, is het belangrijk om te begrijpen dat er absoluut geen garantie is voor wanneer frames plaatsvinden. Ze gebeuren niet op een regelmatige beat. De openingen tussen frames kunnen bijvoorbeeld 0,02632 dan 0,021167 en vervolgens 0,029778 zijn, enzovoort. In het voorbeeld zijn ze allemaal "ongeveer" 1/50 seconde, maar ze zijn allemaal verschillend. En op elk moment kunt u een frame krijgen dat veel langer of korter duurt; en uw code kan op elk moment binnen het kader worden uitgevoerd.

Met dat in gedachten kun je je afvragen: hoe krijg je toegang tot deze frames in je code, in Unity?

Simpel gezegd, gebruikt u de aanroep Update () of gebruikt u een coroutine. (Inderdaad - ze zijn precies hetzelfde: ze laten code elk frame uitvoeren.)

Het doel van een coroutine is dat:

u kunt een code uitvoeren en vervolgens "stoppen en wachten" tot een toekomstig frame .

U kunt wachten tot het volgende frame, kun je wachten voor een aantal frames, of u kunt wachten voor bepaalde geschatte tijd in seconden in de toekomst.

U kunt bijvoorbeeld wachten op "ongeveer een seconde", wat betekent dat het ongeveer een seconde wacht en vervolgens uw code in ongeveer een seconde van nu in een frame plaatst. (En inderdaad, binnen dat kader kan de code op elk moment worden uitgevoerd, hoe dan ook.) Om te herhalen: het zal niet precies een seconde zijn. Nauwkeurige timing is zinloos in een game-engine.

In een coroutine:

Eén frame wachten:

// do something
yield return null;  // wait until next frame
// do something

Om drie frames te wachten:

// do something
yield return null;  // wait until three frames from now
yield return null;
yield return null;
// do something

Om ongeveer een halve seconde te wachten:

// do something
yield return new WaitForSeconds (0.5f); // wait for a frame in about .5 seconds
// do something

Doe elk frame iets:

while (true)
{
    // do something
    yield return null;  // wait until the next frame
}

Dat voorbeeld is letterlijk identiek aan het simpelweg plaatsen van iets in de "Update" -aanroep van Unity: de code bij "iets doen" wordt elk frame uitgevoerd.

Voorbeeld

Voeg Ticker toe aan een GameObject . Terwijl dat spelobject actief is, wordt het vinkje uitgevoerd. Merk op dat het script de coroutine zorgvuldig stopt wanneer het spelobject inactief wordt; dit is meestal een belangrijk aspect van correct gebruik van coroutine.

using UnityEngine;
using System.Collections;

public class Ticker:MonoBehaviour {

    void OnEnable()
    {
        StartCoroutine(TickEverySecond());
    }

    void OnDisable()
    {
        StopAllCoroutines();
    }

    IEnumerator TickEverySecond()
    {
        var wait = new WaitForSeconds(1f); // REMEMBER: IT IS ONLY APPROXIMATE
        while(true)
        {
            Debug.Log("Tick");
            yield return wait;  // wait for a frame, about 1 second from now
        }
    }
}

Een coroutine beëindigen

Vaak ontwerp je coroutines om op een natuurlijke manier te eindigen wanneer bepaalde doelen worden bereikt.

IEnumerator TickFiveSeconds()
{
    var wait = new WaitForSeconds(1f);
    int counter = 1;
    while(counter < 5)
    {
        Debug.Log("Tick");
        counter++;
        yield return wait;
    }
    Debug.Log("I am done ticking");
}

Om een coroutine van "binnen" de coroutine te stoppen, kunt u niet eenvoudigweg "terugkeren", omdat u eerder uit een gewone functie zou vertrekken. In plaats daarvan gebruikt u yield break .

IEnumerator ShowExplosions()
{
    ... show basic explosions
    if(player.xp < 100) yield break;
    ... show fancy explosions
}

Je kunt ook alle door het script gelanceerde coroutines dwingen te stoppen voordat ze klaar zijn.

void OnDisable()
{
    // Stops all running coroutines
    StopAllCoroutines();
}

De methode om een specifieke coroutine van de beller te stoppen, is afhankelijk van hoe u deze bent gestart.

Als u een coroutine bent gestart op stringnaam:

StartCoroutine("YourAnimation");

dan kun je het stoppen door StopCoroutine aan te roepen met dezelfde stringnaam:

StopCoroutine("YourAnimation");

Als alternatief kunt u een verwijzing naar ofwel het houden IEnumerator geretourneerd door de coroutine methode of de Coroutine object geretourneerd door StartCoroutine , en call StopCoroutine op een van deze:

public class SomeComponent : MonoBehaviour 
{
    Coroutine routine;

    void Start () {
        routine = StartCoroutine(YourAnimation());
    }

    void Update () {
        // later, in response to some input...
        StopCoroutine(routine);
    }

    IEnumerator YourAnimation () { /* ... */ }
}

MonoBehaviour-methoden die Coroutines kunnen zijn

Er zijn drie MonoBehaviour-methoden die tot coroutines kunnen worden gemaakt.

  1. Begin()
  2. OnBecameVisible ()
  3. OnLevelWasLoaded ()

Dit kan worden gebruikt om bijvoorbeeld scripts te maken die alleen worden uitgevoerd als het object zichtbaar is voor een camera.

using UnityEngine;
using System.Collections;

public class RotateObject : MonoBehaviour
{
    IEnumerator OnBecameVisible()
    {
        var tr = GetComponent<Transform>();
        while (true)
        {
            tr.Rotate(new Vector3(0, 180f * Time.deltaTime));
            yield return null;
        }
    }
    
    void OnBecameInvisible()
    {
        StopAllCoroutines();
    }
}

Coroutines ketenen

Coroutines kunnen in zichzelf meegeven en wachten op andere coroutines .

U kunt dus reeksen koppelen - "de een na de ander".

Dit is heel eenvoudig en is een basale, kerntechniek in Unity.

Het is absoluut normaal in games dat bepaalde dingen "in volgorde" moeten gebeuren. Bijna elke "ronde" van een spel begint met een bepaalde reeks gebeurtenissen die zich gedurende een bepaalde tijd in een bepaalde volgorde voordoen. Dit is hoe je een autorace-game zou kunnen starten:

IEnumerator BeginRace()
{
  yield return StartCoroutine(PrepareRace());
  yield return StartCoroutine(Countdown());
  yield return StartCoroutine(StartRace());
}

Dus wanneer u BeginRace belt ...

 StartCoroutine(BeginRace());

Het zal je "voorbereiding race" routine uitvoeren. (Misschien knipperen sommige lichten en laten we wat publieksruis horen, scores resetten enzovoort.) Wanneer dat is voltooid, wordt uw "countdown" -reeks uitgevoerd, waarbij u misschien een countdown op de UI zou animeren. Als dat klaar is, voert het je race-startcode uit, waar je misschien geluidseffecten uitvoert, enkele AI-stuurprogramma's start, de camera op een bepaalde manier beweegt, enzovoort.

Voor de duidelijkheid, begrijp dat de drie oproepen

  yield return StartCoroutine(PrepareRace());
  yield return StartCoroutine(Countdown());
  yield return StartCoroutine(StartRace());

moeten zelf in een coroutine zijn. Dat wil zeggen dat ze een functie van het type IEnumerator . Dus in ons voorbeeld is dat IEnumerator BeginRace . Dus vanuit "normale" code start u die coroutine met de StartCoroutine aanroep.

 StartCoroutine(BeginRace());

Om kettingvorming verder te begrijpen, is hier een functie die coroutines ketent. Je komt een reeks coroutines tegen. De functie voert zoveel coroutines uit als u passeert, de een na de ander.

// run various routines, one after the other
IEnumerator OneAfterTheOther( params IEnumerator[] routines ) 
{
    foreach ( var item in routines ) 
    {
        while ( item.MoveNext() ) yield return item.Current;
    }

    yield break;
}

Hier is hoe je dat zou noemen ... laten we zeggen dat je drie functies hebt. Bedenk dat ze allemaal IEnumerator moeten zijn:

IEnumerator PrepareRace() 
{
    // codesay, crowd cheering and camera pan around the stadium
    yield break;
}

IEnumerator Countdown() 
{
    // codesay, animate your countdown on UI
    yield break;
}

IEnumerator StartRace() 
{
    // codesay, camera moves and light changes and launch the AIs
    yield break;
}

Je zou het zo noemen

StartCoroutine( MultipleRoutines( PrepareRace(), Countdown(), StartRace() ) );

of misschien zo

IEnumerator[] routines = new IEnumerator[] {
     PrepareRace(),
     Countdown(),
     StartRace() };
StartCoroutine( MultipleRoutines( routines ) );

Nogmaals, een van de meest elementaire vereisten in games is dat bepaalde dingen na elkaar "na elkaar" in de loop van de tijd gebeuren. Dat bereik je heel eenvoudig in Unity, met

  yield return StartCoroutine(PrepareRace());
  yield return StartCoroutine(Countdown());
  yield return StartCoroutine(StartRace());

Manieren om op te geven

U kunt wachten tot het volgende frame.

yield return null; // wait until sometime in the next frame

U kunt meerdere van deze oproepen achter elkaar hebben, gewoon wachten op zoveel frames als gewenst.

//wait for a few frames
yield return null;
yield return null;

Wacht ongeveer n seconden. Het is uiterst belangrijk om te begrijpen dat dit slechts een bij benadering tijd is .

yield return new WaitForSeconds(n);

Het is absoluut niet mogelijk om de "WaitForSeconds" -oproep te gebruiken voor enige vorm van nauwkeurige timing.

Vaak wil je acties koppelen. Dus, doe iets, en wanneer dat klaar is, doe dan iets anders, en wanneer dat klaar is, doe dan iets anders. Om dat te bereiken, wacht je op een andere coroutine:

yield return StartCoroutine(coroutine);

Begrijp dat je dat alleen vanuit een coroutine kunt bellen. Zo:

StartCoroutine(Test());

Dat is hoe je een coroutine start vanuit een "normaal" stukje code.

Dan, binnen die lopende coroutine:

Debug.Log("A");
StartCoroutine(LongProcess());
Debug.Log("B");

Dat zal A afdrukken, het lange proces starten en onmiddellijk B afdrukken . Het zal niet wachten tot het lange proces is voltooid. Aan de andere kant:

Debug.Log("A");
yield return StartCoroutine(LongProcess());
Debug.Log("B");

Dat zal A afdrukken, het lange proces starten, wachten tot het klaar is en dan B afdrukken.

Het is altijd de moeite waard om te onthouden dat coroutines absoluut geen verband houden met threading. Met deze code:

Debug.Log("A");
StartCoroutine(LongProcess());
Debug.Log("B");

het is gemakkelijk om het te zien als "als" het starten van de LongProcess op een andere thread op de achtergrond. Maar dat is absoluut onjuist. Het is maar een coroutine. Game-engines zijn gebaseerd op frames en met "coroutines" in Unity heb je eenvoudig toegang tot de frames.

Het is heel eenvoudig om te wachten tot een webverzoek is voltooid.

void Start() {
    string url = "http://google.com";
    WWW www = new WWW(url);
    StartCoroutine(WaitForRequest(www));
}

IEnumerator WaitForRequest(WWW www) {
    yield return www;
    
    if (www.error == null) {
        //use www.data);
    }
    else {
        //use www.error);
    }
}

Voor de volledigheid: in zeer zeldzame gevallen gebruikt u een vaste update in Unity; er is een WaitForFixedUpdate() aanroep die normaal nooit zou worden gebruikt. Er is een specifieke aanroep ( WaitForEndOfFrame() in de huidige versie van Unity) die in bepaalde situaties wordt gebruikt voor het genereren van schermafbeeldingen tijdens de ontwikkeling. (Het exacte mechanisme verandert enigszins naarmate Unity evolueert, dus zoek naar de laatste informatie indien relevant.)



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow