unity3d
coroutine
Ricerca…
Sintassi
- Coroutine pubblica StartCoroutine (routine IEnumerator);
- public Coroutine StartCoroutine (string methodName, object value = null);
- public void StopCoroutine (string methodName);
- public void StopCoroutine (routine IEnumerator);
- pubblico vuoto StopAllCoroutines ();
Osservazioni
Considerazioni sulle prestazioni
È preferibile utilizzare le coroutine con moderazione in quanto la flessibilità si accompagna a un costo in termini di prestazioni.
- Le coroutine in gran numero richiedono più dalla CPU rispetto ai metodi di aggiornamento standard.
- Esiste un problema in alcune versioni di Unity in cui le coroutine producono immondizia a ogni ciclo di aggiornamento a causa della boxing Unity del valore restituito
MoveNext
. Questo è stato osservato per l'ultima volta in 5.4.0b13. ( Bug report )
Riduci la spazzatura memorizzando nella cache YieldInstructions
Un trucco comune per ridurre la spazzatura generata nelle coroutine è memorizzare nella cache YieldInstruction
.
IEnumerator TickEverySecond()
{
var wait = new WaitForSeconds(1f); // Cache
while(true)
{
yield return wait; // Reuse
}
}
La resa null
produce rifiuti inutili.
coroutine
Innanzitutto è essenziale capire che i motori di gioco (come Unity) lavorano su un paradigma "frame based".
Il codice viene eseguito durante ogni frame.
Ciò include il codice di Unity e il tuo codice.
Quando si pensa ai frame, è importante capire che non c'è assolutamente alcuna garanzia di quando i frame avvengono. Non succedono su un ritmo regolare. Gli spazi tra i frame potrebbero essere, ad esempio, 0,02632 quindi 0,021167 quindi 0,029778 e così via. Nell'esempio sono tutti "circa" 1/50 di secondo, ma sono tutti diversi. E in qualsiasi momento, puoi ottenere una cornice che richiede molto più tempo o più breve; e il tuo codice può essere eseguito in qualsiasi momento all'interno del frame.
Tenendolo a mente, potresti chiedere: come accedi a questi frame nel tuo codice, in Unity?
Molto semplicemente, si usa la chiamata Update () o si usa una coroutine. (In effetti - sono esattamente la stessa cosa: permettono al codice di essere eseguito su ogni fotogramma.)
Lo scopo di una coroutine è che:
è possibile eseguire un po 'di codice e quindi "fermarsi e attendere" fino ad alcuni frame futuri .
Puoi aspettare fino al fotogramma successivo , puoi aspettare un numero di fotogrammi , oppure puoi aspettare un po 'di tempo approssimativo in secondi in futuro.
Ad esempio, puoi aspettare "circa un secondo", il che significa che aspetterà per circa un secondo, e quindi metterà il tuo codice in qualche fotogramma all'incirca un secondo da ora. (E in effetti, all'interno di quel frame, il codice potrebbe essere eseguito in qualsiasi momento, in qualsiasi momento.) Per ripetere: non sarà esattamente un secondo. Il tempismo preciso non ha senso in un motore di gioco.
All'interno di una coroutine:
Per aspettare un frame:
// do something
yield return null; // wait until next frame
// do something
Per aspettare tre frame:
// do something
yield return null; // wait until three frames from now
yield return null;
yield return null;
// do something
Attendere circa mezzo secondo:
// do something
yield return new WaitForSeconds (0.5f); // wait for a frame in about .5 seconds
// do something
Fai qualcosa ogni singolo fotogramma:
while (true)
{
// do something
yield return null; // wait until the next frame
}
Questo esempio è letteralmente identico al semplice inserimento di qualcosa all'interno della chiamata "Aggiornamento" di Unity: il codice di "fare qualcosa" viene eseguito su ogni frame.
Esempio
Allegare Ticker a un oggetto GameObject
. Mentre quell'oggetto di gioco è attivo, verrà eseguito il segno di spunta. Si noti che lo script blocca con attenzione la coroutine, quando l'oggetto del gioco diventa inattivo; questo di solito è un aspetto importante dell'utilizzo corretto della 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
}
}
}
Termina una coroutine
Spesso progetti le coroutine per terminare naturalmente quando vengono raggiunti determinati obiettivi.
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");
}
Per fermare una coroutine da "dentro" la coroutine, non puoi semplicemente "tornare" come se dovessi partire presto da una funzione ordinaria. Invece, usi la yield break
.
IEnumerator ShowExplosions()
{
... show basic explosions
if(player.xp < 100) yield break;
... show fancy explosions
}
Puoi anche forzare tutte le coroutine lanciate dallo script a fermarsi prima di finire.
void OnDisable()
{
// Stops all running coroutines
StopAllCoroutines();
}
Il metodo per interrompere una specifica coroutine dal chiamante varia a seconda di come è stato avviato.
Se hai avviato una coroutine per nome stringa:
StartCoroutine("YourAnimation");
quindi puoi fermarlo chiamando StopCoroutine con lo stesso nome stringa:
StopCoroutine("YourAnimation");
In alternativa, è possibile mantenere un riferimento sia alla IEnumerator
restituito dal metodo coroutine, o l' Coroutine
oggetto restituito da StartCoroutine
, e chiamare StopCoroutine
su uno di questi:
public class SomeComponent : MonoBehaviour
{
Coroutine routine;
void Start () {
routine = StartCoroutine(YourAnimation());
}
void Update () {
// later, in response to some input...
StopCoroutine(routine);
}
IEnumerator YourAnimation () { /* ... */ }
}
Metodi MonoBehaviour che possono essere Coroutine
Ci sono tre metodi MonoBehaviour che possono essere fatti in coroutine.
- Inizio()
- OnBecameVisible ()
- OnLevelWasLoaded ()
Questo può essere usato per creare, per esempio, script che vengono eseguiti solo quando l'oggetto è visibile a una telecamera.
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();
}
}
Coroutine a catena
Le coroutine possono cedere dentro se stesse e attendere altre coroutine .
Quindi, puoi sequenze a catena - "una dopo l'altra".
Questo è molto semplice, ed è una tecnica di base, fondamentale in Unity.
È assolutamente naturale nei giochi che certe cose debbano accadere "in ordine". Quasi ogni "round" di un gioco inizia con una serie di eventi che avvengono, in un certo lasso di tempo, in un certo ordine. Ecco come potresti iniziare un gioco di corse automobilistiche:
IEnumerator BeginRace()
{
yield return StartCoroutine(PrepareRace());
yield return StartCoroutine(Countdown());
yield return StartCoroutine(StartRace());
}
Quindi, quando chiami BeginRace ...
StartCoroutine(BeginRace());
Gestirà la tua routine di "preparazione alla gara". (Forse, facendo lampeggiare un po 'di luci e facendo rumori di folla, azzerando i punteggi e così via.) Quando avrà finito, eseguirà la sequenza "conto alla rovescia", dove animeresti forse un conto alla rovescia sull'interfaccia utente. Al termine, verrà eseguito il codice di partenza della gara, in cui potresti eseguire effetti sonori, avviare alcuni driver AI, spostare la videocamera in un determinato modo e così via.
Per chiarezza, capire che le tre chiamate
yield return StartCoroutine(PrepareRace());
yield return StartCoroutine(Countdown());
yield return StartCoroutine(StartRace());
devono essere in una coroutine. Vale a dire, devono essere in una funzione del tipo IEnumerator
. Quindi nel nostro esempio è IEnumerator BeginRace
. Quindi, dal codice "normale", si avvia quella coroutine con la chiamata StartCoroutine
.
StartCoroutine(BeginRace());
Per comprendere meglio il concatenamento, ecco una funzione che incatena le coroutine. Passi in una serie di coroutine. La funzione esegue tutte le coroutine che si passano, in ordine, una dopo l'altra.
// 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;
}
Ecco come lo chiameresti ... diciamo che hai tre funzioni. Ricordiamo che devono essere tutti 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;
}
Lo chiameresti in questo modo
StartCoroutine( MultipleRoutines( PrepareRace(), Countdown(), StartRace() ) );
o forse così
IEnumerator[] routines = new IEnumerator[] {
PrepareRace(),
Countdown(),
StartRace() };
StartCoroutine( MultipleRoutines( routines ) );
Per ripetere, uno dei requisiti di base dei giochi è che certe cose accadono una dopo l'altra "in una sequenza" nel tempo. Lo ottieni in Unity molto semplicemente, con
yield return StartCoroutine(PrepareRace());
yield return StartCoroutine(Countdown());
yield return StartCoroutine(StartRace());
Modi per cedere
Puoi aspettare fino al fotogramma successivo.
yield return null; // wait until sometime in the next frame
È possibile avere più di queste chiamate in fila, per attendere semplicemente il numero di frame desiderato.
//wait for a few frames
yield return null;
yield return null;
Attendere circa n secondi. È estremamente importante capire che questo è solo un tempo molto approssimativo .
yield return new WaitForSeconds(n);
Non è assolutamente possibile utilizzare la chiamata "WaitForSeconds" per qualsiasi forma di sincronizzazione accurata.
Spesso vuoi concatenare azioni. Quindi, fai qualcosa, e quando questo è finito fai qualcos'altro, e quando questo è finito fai qualcos'altro. Per riuscirci, aspetta un'altra coroutine:
yield return StartCoroutine(coroutine);
Comprendi che puoi chiamarlo solo da una coroutine. Così:
StartCoroutine(Test());
È così che si avvia una coroutine da un pezzo di codice "normale".
Quindi, all'interno di quella coroutine in esecuzione:
Debug.Log("A");
StartCoroutine(LongProcess());
Debug.Log("B");
Ciò stamperà A, avvia il processo lungo e stampa immediatamente B. Non sarà attendere che il processo lungo per terminare. D'altro canto:
Debug.Log("A");
yield return StartCoroutine(LongProcess());
Debug.Log("B");
Ciò stamperà A, avvierà il processo lungo, attenderà fino a quando non sarà finito , e quindi stamperà B.
Vale sempre la pena ricordare che le coroutine non hanno assolutamente alcuna connessione, in alcun modo, alla discussione. Con questo codice:
Debug.Log("A");
StartCoroutine(LongProcess());
Debug.Log("B");
è facile pensarlo come "simile" all'avvio di LongProcess su un altro thread in background. Ma questo è assolutamente scorretto. È solo una coroutine. I motori di gioco sono basati su frame e "coroutines" in Unity ti permettono semplicemente di accedere ai frame.
È molto semplice aspettare che una richiesta web venga completata.
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);
}
}
Per completezza: in casi molto rari si utilizza l'aggiornamento fisso in Unity; c'è una chiamata WaitForFixedUpdate()
che normalmente non verrebbe mai utilizzata. Esiste una chiamata specifica ( WaitForEndOfFrame()
nella versione corrente di Unity) che viene utilizzata in determinate situazioni in relazione alla generazione di acquisizioni dello schermo durante lo sviluppo. (Il meccanismo esatto cambia leggermente con l'evoluzione di Unity, quindi google per le ultime informazioni se pertinenti).