Sök…


Syntax

  • offentliga Coroutine StartCoroutine (IEnumerator routine);
  • offentlig Coroutine StartCoroutine (strängmetodnamn, objektvärde = null);
  • public void StopCoroutine (strängmetodnamn);
  • public void StopCoroutine (IEnumerator routine);
  • public void StopAllCoroutines ();

Anmärkningar

Resultathänsyn

Det är bäst att använda koroutiner i moderation eftersom flexibiliteten kommer med en prestandakostnad.

  • Coroutines i stort antal kräver mer av CPU än vanliga uppdateringsmetoder.
  • Det finns ett problem i vissa versioner av Unity där koroutiner producerar skräp varje uppdateringscykel på grund av att Unity MoveNext returvärdet. Detta observerades senast i 5.4.0b13. ( Bugrapport )

Minska skräp genom att bibehålla YieldInstructions

Ett vanligt trick för att minska skräpet som alstras i koroutiner är att cache YieldInstruction .

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

Utbyte av null ger inget extra skräp.

korutin

Först är det viktigt att förstå att spelmotorer (som Unity) fungerar på ett "rambaserat" paradigm.

Koden körs under varje ram.

Det inkluderar Unitys egen kod och din kod.

När man tänker på ramar är det viktigt att förstå att det absolut inte finns någon garanti för när ramar inträffar. De inte hända på en vanlig takt. Mellanrummen mellan ramar kan till exempel vara 0,02632, sedan 0,021167, sedan 0,029778 och så vidare. I exemplet är de alla "ungefär" 1/50 av en sekund, men de är alla olika. Och när som helst kan du få en ram som tar mycket längre eller kortare tid; och din kod kan köras när som helst inom ramen.

Med tanke på det kan du fråga: hur får du åt dessa ramar i din kod, i Enhet?

Helt enkelt använder du antingen samtalet Update () eller använder du en coroutine. (Faktum är att de är exakt samma sak: de tillåter att koden körs varje ram.)

Syftet med en koroutin är att:

Du kan köra lite kod och sedan "stoppa och vänta" tills någon framtida ram .

Du kan vänta till nästa ram , du kan vänta på ett antal ramar , eller du kan vänta på ungefärlig tid i sekunder i framtiden.

Till exempel kan du vänta på "ungefär en sekund", vilket betyder att den kommer att vänta i ungefär en sekund och sedan lägga din kod i någon ram ungefär en sekund från och med nu. (Och faktiskt inom den ramen kan koden köras när som helst, vilket som helst.) För att upprepa: det kommer inte att vara exakt en sekund. Exakt tidtagning är meningslös i en spelmotor.

Inuti en koroutin:

För att vänta en ram:

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

Vänta tre ramar:

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

Att vänta ungefär en halv sekund:

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

Gör något varje ram:

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

Det exemplet är bokstavligen identiskt med att helt enkelt sätta något i Unitys "Uppdatera" -samtal: koden på "gör något" körs varje ram.

Exempel

Bifoga Ticker till ett GameObject . Medan det spelobjektet är aktivt, kommer fästingen att köras. Observera att skriptet stoppar försiktigt koroutinen när spelobjektet blir inaktivt; detta är vanligtvis en viktig aspekt av korrekt konstruktion av koroutinanvändning.

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
        }
    }
}

Avsluta en koroutin

Ofta utformar du koroutiner för att naturligt ta slut när vissa mål uppnås.

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");
}

För att stoppa en koroutin från "inuti" koroutinen, kan du inte bara "återvända" som du skulle lämna tidigt från en vanlig funktion. Istället använder du yield break .

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

Du kan också tvinga alla koroutiner som har lanserats av skriptet att stanna innan du är klar.

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

Metoden för att stoppa en specifik koroutin från den som ringer varierar beroende på hur du startade den.

Om du startade en koroutin efter strängnamn:

StartCoroutine("YourAnimation");

då kan du stoppa det genom att ringa StopCoroutine med samma strängnamn:

StopCoroutine("YourAnimation");

Alternativt kan du behålla en referens till antingen IEnumerator returneras med coroutine-metoden eller till Coroutine objektet som returneras av StartCoroutine och ringa StopCoroutine på någon av dessa:

public class SomeComponent : MonoBehaviour 
{
    Coroutine routine;

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

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

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

MonoBehaviour-metoder som kan vara Coroutines

Det finns tre MonoBehaviour-metoder som kan göras till koroutiner.

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

Detta kan användas för att till exempel skapa skript som bara körs när objektet är synligt för en kamera.

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();
    }
}

Kedja koroutiner

Coroutines kan ge sig inom sig själva och vänta på andra coroutines .

Så du kan kedja sekvenser - "den ena efter den andra".

Detta är väldigt enkelt och är en grundläggande, kärnteknik i Unity.

Det är helt naturligt i spel att vissa saker måste hända "i ordning". Nästan varje "runda" i ett spel börjar med att en viss serie händelser händer, över en viss tid, i någon ordning. Så här kan du starta ett bilrace-spel:

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

Så när du ringer BeginRace ...

 StartCoroutine(BeginRace());

Det kommer att köra din "förbered ras" -rutin. (Kanske, blinkar några ljus och kör lite publikbuller, återställer poäng och så vidare.) När det är klart kommer det att köra din "nedräkning" -sekvens, där du kanske animerar en nedräkning på UI. När det är klart kommer det att köra din race-startkod, där du kanske skulle köra ljudeffekter, starta några AI-drivrutiner, flytta kameran på ett visst sätt, och så vidare.

För tydlighet, förstå att de tre samtal

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

måste själva vara i en koroutin. Det vill säga, de måste vara i en funktion av typen IEnumerator . Så i vårt exempel är det IEnumerator BeginRace . Så från "normal" kod startar du den koroutinen med StartCoroutine samtalet.

 StartCoroutine(BeginRace());

För att ytterligare förstå kedjan är här en funktion som kedjer koroutiner. Du passerar in en rad koroutiner. Funktionen kör lika många koroutiner som du passerar, i ordning, en efter den andra.

// 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;
}

Så här kan du kalla det ... låt oss säga att du har tre funktioner. Kom ihåg att de alla måste vara IEnumerator :

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;
}

Du skulle kalla det så här

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

eller kanske så här

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

För att upprepa är ett av de mest grundläggande kraven i spel att vissa saker händer en efter en "i en sekvens" över tid. Du uppnår det i Enhet mycket enkelt, med

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

Sätt att ge

Du kan vänta till nästa bild.

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

Du kan ha flera av dessa samtal i rad för att helt enkelt vänta på så många ramar som önskas.

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

Vänta i ungefär n sekunder. Det är oerhört viktigt att förstå att detta bara är en mycket ungefärlig tid .

yield return new WaitForSeconds(n);

Det är absolut inte möjligt att använda "WaitForSeconds" -samtalet för någon form av korrekt timing.

Ofta vill du kedja handlingar. Så gör något, och när det är klart gör något annat, och när det är klart gör något annat. För att uppnå det, vänta på en annan koroutin:

yield return StartCoroutine(coroutine);

Förstå att du bara kan ringa det inifrån en coroutine. Så:

StartCoroutine(Test());

Det är så du startar en koroutin från en "normal" kod.

Sedan inuti den löpande koroutinen:

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

Det kommer att skriva ut A, starta den långa processen och skriva ut B omedelbart . Det kommer inte att vänta tills den långa processen är klar. Å andra sidan:

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

Det kommer att skriva ut A, starta den långa processen, vänta tills den är klar och skriv sedan ut B.

Det är alltid värt att komma ihåg att koroutiner inte har någon koppling, på något sätt, till gängning. Med denna kod:

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

det är lätt att tänka på att det är "som" att starta LongProcess på en annan tråd i bakgrunden. Men det är helt felaktigt. Det är bara en koroutin. Spelmotorer är rambaserade, och "coroutines" i Unity gör det enkelt att komma åt ramarna.

Det är väldigt lätt att vänta på att en webbegäran ska slutföras.

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);
    }
}

För fullständighet: I mycket sällsynta fall använder du fast uppdatering i Unity; det finns ett WaitForFixedUpdate() som normalt aldrig skulle användas. Det finns ett specifikt samtal ( WaitForEndOfFrame() i den aktuella versionen av Unity) som används i vissa situationer i relation till att generera skärmdumpar under utveckling. (Den exakta mekanismen ändras något när Unity utvecklas, så google för den senaste informationen om relevant.)



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow