unity3d
Coroutines
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
przezMoveNext
wartości zwracanejMoveNext
. 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.
- Początek()
- OnBecameVisible ()
- 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).