Szukaj…


Składnia

  • public Coroutine StartCoroutine (procedura IEnumerator);
  • public Coroutine StartCoroutine (string methodName, wartość obiektu = null);
  • public void StopCoroutine (string methodName);
  • public void StopCoroutine (procedura IEnumerator);
  • public void StopAllCoroutines ();

Uwagi

Uwagi dotyczące wydajności

Najlepiej jest używać coroutines z umiarem, ponieważ elastyczność wiąże się z kosztem wydajności.

  • Coroutines w dużych ilościach wymaga od procesora więcej niż standardowych metod aktualizacji.
  • W niektórych wersjach Unity występuje problem polegający na tym, że coroutines produkują śmieci w każdym cyklu aktualizacji ze względu na MoveNext przez MoveNext wartości zwracanej MoveNext . Ostatni raz zaobserwowano to w 5.4.0b13. ( Raport o błędzie )

Zmniejsz ilość śmieci, buforując YieldInstructions

Częstą sztuczką w celu zmniejszenia ilości śmieci generowanych w coroutines jest buforowanie instrukcji YieldInstruction .

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

Podanie null nie powoduje dodatkowego śmieci.

Coroutines

Po pierwsze, należy zrozumieć, że silniki gier (takie jak Unity) działają w oparciu o paradygmat oparty na „ramce”.

Kod jest wykonywany podczas każdej ramki.

Obejmuje to własny kod Unity i Twój kod.

Myśląc o ramkach, ważne jest, aby zrozumieć, że absolutnie nie ma gwarancji, kiedy ramki się pojawią. Oni nie stało na regularny rytm. Odstępy między ramkami mogą wynosić na przykład 0,02632, a następnie 0,021167, a następnie 0,029778 i tak dalej. W tym przykładzie wszystkie są „około” 1/50 sekundy, ale wszystkie są różne. I w dowolnym momencie możesz uzyskać ramkę, która trwa znacznie dłużej lub krócej; a Twój kod może być wykonany w dowolnym momencie w ramce.

Mając to na uwadze, możesz zapytać: jak uzyskać dostęp do tych ramek w kodzie w Unity?

Mówiąc najprościej, używasz wywołania Update () lub używasz coroutine. (Rzeczywiście - są dokładnie takie same: umożliwiają uruchamianie kodu w każdej ramce).

Celem coroutine jest to, że:

możesz uruchomić jakiś kod, a następnie „zatrzymać i czekać”, aż pojawi się jakaś ramka w przyszłości .

Możesz poczekać do następnej klatki , poczekać na kilka klatek lub poczekać w przybliżeniu na kilka sekund w przyszłości.

Na przykład możesz poczekać „około jednej sekundy”, co oznacza, że będzie czekać około jednej sekundy, a następnie umieści kod w jakiejś ramce mniej więcej sekundę od teraz. (I rzeczywiście, w tej ramce kod można uruchomić w dowolnym momencie.) Powtarzając: nie będzie to dokładnie jedna sekunda. Dokładny czas nie ma znaczenia w silniku gry.

Wewnątrz rogówki:

Aby poczekać jedną ramkę:

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

Aby czekać trzy ramki:

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

Aby poczekać około pół sekundy:

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

Zrób coś dla każdej klatki:

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

Ten przykład jest dosłownie identyczny z po prostu umieszczeniem czegoś w wywołaniu Unity „Update”: kod „zrób coś” jest uruchamiany w każdej ramce.

Przykład

Dołącz Ticker do GameObject . Gdy ten obiekt gry jest aktywny, tyknięcie będzie działać. Zauważ, że skrypt ostrożnie zatrzymuje koronę, gdy obiekt gry staje się nieaktywny; jest to zwykle ważny aspekt prawidłowego wykorzystania inżynierii korupcji.

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

Kończąc coroutine

Często projektujesz coroutines, aby naturalnie kończyły się po osiągnięciu określonych celów.

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

Aby zatrzymać coroutine od „wewnątrz” coroutine, nie możesz po prostu „powrócić”, tak jak wcześniej wychodziłbyś ze zwykłej funkcji. Zamiast tego używasz yield break .

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

Możesz także wymusić zatrzymanie wszystkich skryptów uruchomionych przez skrypt przed zakończeniem.

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

Metoda zatrzymania określonego hasła od dzwoniącego różni się w zależności od tego, jak go uruchomiłeś.

Jeśli zacząłeś coroutine według nazwy ciągu:

StartCoroutine("YourAnimation");

następnie możesz go zatrzymać, wywołując StopCoroutine z tym samym ciągiem znaków:

StopCoroutine("YourAnimation");

Alternatywnie, można zachować odniesienie do albo IEnumerator zwrócony przez metodę współprogram, albo Coroutine obiektu zwróconego przez StartCoroutine i wezwać StopCoroutine na jeden z tych:

public class SomeComponent : MonoBehaviour 
{
    Coroutine routine;

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

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

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

Metody MonoBehaviour, które mogą być Coroutines

Istnieją trzy metody MonoBehaviour, które można zrobić z rogów.

  1. Początek()
  2. OnBecameVisible ()
  3. OnLevelWasLoaded ()

Można to wykorzystać np. Do tworzenia skryptów wykonujących się tylko wtedy, gdy obiekt jest widoczny dla kamery.

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

Łańcuchy do kortyny

Korpusy mogą ulec w sobie i czekać na inne korutyny .

Możesz więc łączyć sekwencje - „jedna po drugiej”.

Jest to bardzo łatwa i stanowi podstawową, podstawową technikę w Jedności.

W grach jest całkowicie naturalne, że pewne rzeczy muszą się dziać „po kolei”. Prawie każda „runda” gry rozpoczyna się od pewnej serii wydarzeń, które mają miejsce w określonym czasie w określonej kolejności. Oto jak możesz rozpocząć grę wyścigową:

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

Kiedy zadzwonisz do BeginRace ...

 StartCoroutine(BeginRace());

Poprowadzi twoją procedurę „przygotuj wyścig”. (Być może, flashowanie świateł i uruchamianie hałasu tłumu, resetowanie wyników itd.) Po zakończeniu uruchomi się sekwencja „odliczania”, w której można animować odliczanie w interfejsie użytkownika. Kiedy to się skończy, uruchomi kod startowy, w którym być może uruchomisz efekty dźwiękowe, uruchomisz sterowniki AI, poruszysz kamerą w określony sposób i tak dalej.

Dla jasności zrozum, że trzy połączenia

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

sami muszą być w korupcji. Oznacza to, że muszą być w funkcji typu IEnumerator . W naszym przykładzie jest to IEnumerator BeginRace . Zatem z „normalnego” kodu uruchamiasz tę coroutine za pomocą wywołania StartCoroutine .

 StartCoroutine(BeginRace());

Aby lepiej zrozumieć tworzenie łańcuchów, oto funkcja, która łączy łańcuchy z koroutynami. Przechodzisz przez szereg coroutines. Funkcja uruchamia tyle coroutines, ile mijasz, w kolejności, jedna po drugiej.

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

Oto jak nazwałbyś to ... powiedzmy, że masz trzy funkcje. Pamiętaj, że wszystkie muszą być 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;
}

Nazwałbyś to tak

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

a może tak

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

Powtarzając, jednym z najbardziej podstawowych wymagań w grach jest to, że pewne rzeczy dzieją się jedna po drugiej „w sekwencji” w czasie. Osiągasz to w Unity bardzo prosto, dzięki

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

Sposoby ustępowania

Możesz poczekać do następnej klatki.

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

Możesz mieć wiele takich połączeń z rzędu, aby po prostu czekać na tyle ramek, ile chcesz.

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

Poczekaj około n sekund. Jest niezwykle ważne, aby zrozumieć, że jest to tylko bardzo przybliżony czas .

yield return new WaitForSeconds(n);

Absolutnie niemożliwe jest użycie wywołania „WaitForSeconds” dla dowolnej formy dokładnego pomiaru czasu.

Często chcesz połączyć działania. Więc zrób coś, a kiedy to się skończy, zrób coś innego, a kiedy to się skończy, zrób coś innego. Aby to osiągnąć, poczekaj na kolejną koroutynę:

yield return StartCoroutine(coroutine);

Zrozum, że możesz zadzwonić do tego tylko z koroutine. Więc:

StartCoroutine(Test());

W ten sposób zaczynasz coroutine od „normalnego” fragmentu kodu.

Następnie w tej działającej korupcji:

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

Spowoduje to wydrukowanie A, rozpoczęcie długiego procesu i natychmiastowe wydrukowanie B. To nie będzie czekać na długi proces do końca. Z drugiej strony:

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

Spowoduje to wydrukowanie A, rozpoczęcie długiego procesu, poczekanie na zakończenie , a następnie wydrukowanie B.

Zawsze warto pamiętać, że coroutines absolutnie nie mają żadnego związku z wątkami. Z tym kodem:

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

łatwo myśleć o tym jako o „podobnym” uruchomieniu LongProcess w innym wątku w tle. Ale to absolutnie niepoprawne. To tylko korupcja. Silniki gier oparte są na ramkach, a „coroutines” w Unity po prostu umożliwiają dostęp do ramek.

Bardzo łatwo jest czekać na zakończenie żądania internetowego.

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

Dla kompletności: w bardzo rzadkich przypadkach używasz stałej aktualizacji w Unity; istnieje WaitForFixedUpdate() które normalnie nigdy nie byłoby używane. Istnieje specyficzne wywołanie ( WaitForEndOfFrame() w aktualnej wersji Unity), które jest używane w niektórych sytuacjach w związku z generowaniem zrzutów ekranu podczas programowania. (Dokładny mechanizm zmienia się nieznacznie w miarę ewolucji Unity, więc w razie potrzeby wyszukaj najnowsze informacje).



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow