Comment le modèle de retour StartCoroutine / yield fonctionne-t-il vraiment dans Unity?

134

Je comprends le principe des coroutines. Je sais comment faire fonctionner le standard StartCoroutine/ yield returnpattern en C # dans Unity, par exemple invoquer une méthode retournant IEnumeratorvia StartCoroutineet dans cette méthode faire quelque chose, yield return new WaitForSeconds(1);attendre une seconde, puis faire autre chose.

Ma question est: que se passe-t-il vraiment dans les coulisses? Que fait StartCoroutinevraiment? Qu'est IEnumerator- ce que le WaitForSecondsretour? Comment StartCoroutineretourne le contrôle à la partie «autre chose» de la méthode appelée? Comment tout cela interagit-il avec le modèle de concurrence d'Unity (où beaucoup de choses se passent en même temps sans utiliser de coroutines)?

Ghopper21
la source
3
Le compilateur C # transforme les méthodes qui renvoient IEnumerator/ IEnumerable(ou les équivalents génériques) et qui contiennent le yieldmot - clé. Recherchez les itérateurs.
Damien_The_Unbeliever
4
Un itérateur est une abstraction très pratique pour une "machine à états". Comprenez-le d'abord et vous obtiendrez également des coroutines Unity. en.wikipedia.org/wiki/State_machine
Hans Passant
2
La balise unity est réservée par Microsoft Unity. Veuillez ne pas en abuser.
Lex Li
11
J'ai trouvé cet article assez éclairant: les coroutines Unity3D en détail
Kay
5
@Kay - J'aimerais pouvoir vous acheter une bière. Cet article est exactement ce dont j'avais besoin. Je commençais à remettre en question ma santé mentale car il semblait que ma question n'avait même pas de sens, mais l'article répond directement à ma question mieux que je n'aurais pu l'imaginer. Peut-être pouvez-vous ajouter une réponse avec ce lien que je peux accepter, au profit des futurs utilisateurs SO?
Ghopper21

Réponses:

109

Le lien de détail des coroutines Unity3D souvent référencé est mort. Puisqu'il est mentionné dans les commentaires et les réponses, je vais publier le contenu de l'article ici. Ce contenu provient de ce miroir .


Les coroutines Unity3D en détail

De nombreux processus dans les jeux se déroulent au cours de plusieurs images. Vous avez des processus `` denses '', comme la recherche de chemin, qui travaillent dur à chaque image mais sont répartis sur plusieurs images afin de ne pas avoir un impact trop important sur la fréquence d'images. Vous avez des processus `` clairsemés '', comme les déclencheurs de gameplay, qui ne font rien pour la plupart des cadres, mais sont parfois appelés à effectuer un travail critique. Et vous avez des processus variés entre les deux.

Chaque fois que vous créez un processus qui se déroulera sur plusieurs images - sans multithreading - vous devez trouver un moyen de diviser le travail en morceaux qui peuvent être exécutés une par image. Pour tout algorithme avec une boucle centrale, c'est assez évident: un pathfinder A *, par exemple, peut être structuré de telle sorte qu'il maintient ses listes de nœuds de manière semi-permanente, ne traitant qu'une poignée de nœuds de la liste ouverte chaque image, au lieu d'essayer pour faire tout le travail en une seule fois. Il y a un certain équilibre à faire pour gérer la latence - après tout, si vous verrouillez votre fréquence d'images à 60 ou 30 images par seconde, votre processus ne prendra que 60 ou 30 étapes par seconde, et cela pourrait entraîner le processus à prendre trop long dans l'ensemble. Une conception soignée pourrait offrir la plus petite unité de travail possible à un niveau - par exemple traiter un seul nœud A * - et superposer par-dessus un moyen de regrouper le travail en morceaux plus grands - par exemple, continuer à traiter les nœuds A * pendant X millisecondes. (Certaines personnes appellent cela le «timelicing», mais je ne le fais pas).

Néanmoins, permettre au travail d'être divisé de cette manière signifie que vous devez transférer l'état d'une image à l'autre. Si vous interrompez un algorithme itératif, vous devez conserver tout l'état partagé entre les itérations, ainsi qu'un moyen de suivre l'itération à effectuer ensuite. Ce n'est généralement pas trop mal - la conception d'une 'classe A * Pathfinder' est assez évidente - mais il y a aussi d'autres cas qui sont moins agréables. Parfois, vous serez confronté à de longs calculs qui effectuent différents types de travail d'une image à l'autre; l'objet capturant leur état peut se retrouver avec un gros désordre de «locaux» semi-utiles, conservés pour transmettre des données d'une image à l'autre. Et si vous avez affaire à un processus clairsemé, vous finissez souvent par devoir implémenter une petite machine à états juste pour savoir quand le travail doit être fait.

Ne serait-il pas intéressant si, au lieu d'avoir à suivre explicitement tout cet état sur plusieurs trames, et au lieu d'avoir à lire et gérer la synchronisation et le verrouillage, etc., vous pouviez simplement écrire votre fonction comme un seul morceau de code, et marquer des endroits particuliers où la fonction doit «faire une pause» et continuer plus tard?

Unity - avec un certain nombre d'autres environnements et langages - fournit cela sous la forme de Coroutines.

A quoi ressemblent-ils? Dans «Unityscript» (Javascript):

function LongComputation()
{
    while(someCondition)
    {
        /* Do a chunk of work */

        // Pause here and carry on next frame
        yield;
    }
}

En C #:

IEnumerator LongComputation()
{
    while(someCondition)
    {
        /* Do a chunk of work */

        // Pause here and carry on next frame
        yield return null;
    }
}

Comment travaillent-ils? Permettez-moi de dire rapidement que je ne travaille pas pour Unity Technologies. Je n'ai pas vu le code source Unity. Je n'ai jamais vu les tripes du moteur de coroutine d'Unity. Cependant, s'ils l'ont implémenté d'une manière radicalement différente de ce que je suis sur le point de décrire, alors je serai assez surpris. Si quelqu'un d'UT veut intervenir et parler de son fonctionnement, ce serait génial.

Les gros indices sont dans la version C #. Tout d'abord, notez que le type de retour de la fonction est IEnumerator. Et deuxièmement, notez que l'une des instructions est yield return. Cela signifie que yield doit être un mot-clé, et comme le support C # de Unity est vanilla C # 3.5, ce doit être un mot-clé vanilla C # 3.5. En effet, le voici dans MSDN - parlant de quelque chose appelé «blocs d'itérateur». Alors que se passe-t-il?

Tout d'abord, il y a ce type IEnumerator. Le type IEnumerator agit comme un curseur sur une séquence, fournissant deux membres significatifs: Current, qui est une propriété vous donnant l'élément sur lequel se trouve actuellement le curseur, et MoveNext (), une fonction qui passe à l'élément suivant de la séquence. Comme IEnumerator est une interface, il ne spécifie pas exactement comment ces membres sont implémentés; MoveNext () pourrait simplement en ajouter un à Current, ou il pourrait charger la nouvelle valeur à partir d'un fichier, ou il pourrait télécharger une image à partir d'Internet et la hacher et stocker le nouveau hachage dans Current… ou il pourrait même faire une chose pour la première élément de la séquence, et quelque chose de complètement différent pour le second. Vous pouvez même l'utiliser pour générer une séquence infinie si vous le souhaitez. MoveNext () calcule la valeur suivante dans la séquence (retournant false s'il n'y a plus de valeurs),

Normalement, si vous souhaitez implémenter une interface, vous devez écrire une classe, implémenter les membres, etc. Les blocs Iterator sont un moyen pratique d'implémenter IEnumerator sans tous ces tracas - il vous suffit de suivre quelques règles, et l'implémentation IEnumerator est générée automatiquement par le compilateur.

Un bloc itérateur est une fonction régulière qui (a) renvoie IEnumerator et (b) utilise le mot-clé yield. Alors, que fait réellement le mot-clé yield? Il déclare quelle est la valeur suivante dans la séquence - ou qu'il n'y a plus de valeurs. Le point auquel le code rencontre un rendement de retour X ou une rupture de rendement est le point auquel IEnumerator.MoveNext () doit s'arrêter; un rendement de retour X fait que MoveNext () renvoie true et la valeur X lui est assignée, tandis qu'une rupture de rendement oblige MoveNext () à renvoyer false.

Maintenant, voici le truc. Les valeurs réelles renvoyées par la séquence n'ont pas d'importance. Vous pouvez appeler MoveNext () à plusieurs reprises et ignorer Current; les calculs seront toujours effectués. Chaque fois que MoveNext () est appelé, votre bloc d'itérateur s'exécute jusqu'à l'instruction 'yield' suivante, quelle que soit l'expression qu'il produit réellement. Vous pouvez donc écrire quelque chose comme:

IEnumerator TellMeASecret()
{
  PlayAnimation("LeanInConspiratorially");
  while(playingAnimation)
    yield return null;

  Say("I stole the cookie from the cookie jar!");
  while(speaking)
    yield return null;

  PlayAnimation("LeanOutRelieved");
  while(playingAnimation)
    yield return null;
}

et ce que vous avez en fait écrit est un bloc itérateur qui génère une longue séquence de valeurs nulles, mais ce qui est significatif, ce sont les effets secondaires du travail qu'il fait pour les calculer. Vous pouvez exécuter cette coroutine en utilisant une simple boucle comme celle-ci:

IEnumerator e = TellMeASecret();
while(e.MoveNext()) { }

Ou, plus utilement, vous pouvez le mélanger avec d'autres travaux:

IEnumerator e = TellMeASecret();
while(e.MoveNext()) 
{ 
  // If they press 'Escape', skip the cutscene
  if(Input.GetKeyDown(KeyCode.Escape)) { break; }
}

Tout est dans le timing Comme vous l'avez vu, chaque instruction yield return doit fournir une expression (comme null) afin que le bloc itérateur ait quelque chose à affecter réellement à IEnumerator.Current. Une longue séquence de valeurs nulles n'est pas vraiment utile, mais nous nous intéressons davantage aux effets secondaires. N'est-ce pas?

Il y a quelque chose de pratique que nous pouvons faire avec cette expression, en fait. Et si, au lieu de simplement donner null et de l'ignorer, nous produisions quelque chose qui indiquait quand nous prévoyons devoir faire plus de travail? Souvent, nous devrons passer directement à l'image suivante, bien sûr, mais pas toujours: il y aura de nombreuses fois où nous voudrons continuer après qu'une animation ou un son ait fini de jouer, ou après un certain laps de temps. Ceux while (playingAnimation) retournent null; les constructions sont un peu fastidieuses, tu ne trouves pas?

Unity déclare le type de base YieldInstruction et fournit quelques types dérivés concrets qui indiquent des types particuliers d'attente. Vous avez WaitForSeconds, qui reprend la coroutine une fois le temps spécifié écoulé. Vous avez WaitForEndOfFrame, qui reprend la coroutine à un moment donné plus tard dans la même image. Vous avez le type Coroutine lui-même, qui, lorsque la coroutine A produit la coroutine B, met la coroutine A en pause jusqu'à la fin de la coroutine B.

À quoi cela ressemble-t-il du point de vue de l'exécution? Comme je l'ai dit, je ne travaille pas pour Unity, donc je n'ai jamais vu leur code; mais j'imagine que cela pourrait ressembler un peu à ceci:

List<IEnumerator> unblockedCoroutines;
List<IEnumerator> shouldRunNextFrame;
List<IEnumerator> shouldRunAtEndOfFrame;
SortedList<float, IEnumerator> shouldRunAfterTimes;

foreach(IEnumerator coroutine in unblockedCoroutines)
{
    if(!coroutine.MoveNext())
        // This coroutine has finished
        continue;

    if(!coroutine.Current is YieldInstruction)
    {
        // This coroutine yielded null, or some other value we don't understand; run it next frame.
        shouldRunNextFrame.Add(coroutine);
        continue;
    }

    if(coroutine.Current is WaitForSeconds)
    {
        WaitForSeconds wait = (WaitForSeconds)coroutine.Current;
        shouldRunAfterTimes.Add(Time.time + wait.duration, coroutine);
    }
    else if(coroutine.Current is WaitForEndOfFrame)
    {
        shouldRunAtEndOfFrame.Add(coroutine);
    }
    else /* similar stuff for other YieldInstruction subtypes */
}

unblockedCoroutines = shouldRunNextFrame;

Il n'est pas difficile d'imaginer comment plus de sous-types YieldInstruction pourraient être ajoutés pour gérer d'autres cas - le support au niveau du moteur pour les signaux, par exemple, pourrait être ajouté, avec un WaitForSignal ("SignalName") YieldInstruction le supportant. En ajoutant plus de YieldInstructions, les coroutines elles-mêmes peuvent devenir plus expressives - yield return new WaitForSignal ("GameOver") est plus agréable à lire que pendant (! Signals.HasFired ("GameOver")) renvoie null, si vous me demandez, en dehors de le fait de le faire dans le moteur pourrait être plus rapide que de le faire dans un script.

Quelques ramifications non évidentes Il y a quelques choses utiles dans tout cela que les gens manquent parfois et que je pensais devoir souligner.

Premièrement, yield return donne juste une expression - n'importe quelle expression - et YieldInstruction est un type régulier. Cela signifie que vous pouvez faire des choses comme:

YieldInstruction y;

if(something)
 y = null;
else if(somethingElse)
 y = new WaitForEndOfFrame();
else
 y = new WaitForSeconds(1.0f);

yield return y;

Les lignes spécifiques renvoient de nouveaux WaitForSeconds (), renvoient un nouveau WaitForEndOfFrame (), etc., sont courantes, mais ce ne sont pas des formes spéciales à part entière.

Deuxièmement, comme ces coroutines ne sont que des blocs d'itération, vous pouvez les parcourir vous-même si vous le souhaitez - vous n'avez pas besoin de laisser le moteur le faire pour vous. J'ai déjà utilisé ceci pour ajouter des conditions d'interruption à une coroutine:

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

IEnumerator DoSomethingUnlessInterrupted()
{
  IEnumerator e = DoSomething();
  bool interrupted = false;
  while(!interrupted)
  {
    e.MoveNext();
    yield return e.Current;
    interrupted = HasBeenInterrupted();
  }
}

Troisièmement, le fait que vous puissiez céder sur d'autres coroutines peut en quelque sorte vous permettre d'implémenter vos propres YieldInstructions, même si elles ne sont pas aussi performantes que si elles étaient implémentées par le moteur. Par exemple:

IEnumerator UntilTrueCoroutine(Func fn)
{
   while(!fn()) yield return null;
}

Coroutine UntilTrue(Func fn)
{
  return StartCoroutine(UntilTrueCoroutine(fn));
}

IEnumerator SomeTask()
{
  /* ... */
  yield return UntilTrue(() => _lives < 3);
  /* ... */
}

cependant, je ne recommanderais pas vraiment cela - le coût de démarrage d'une Coroutine est un peu lourd à mon goût.

Conclusion J'espère que cela clarifie un peu ce qui se passe réellement lorsque vous utilisez une Coroutine dans Unity. Les blocs d'itérateur de C # sont une petite construction géniale, et même si vous n'utilisez pas Unity, vous trouverez peut-être utile d'en tirer parti de la même manière.

James McMahon
la source
2
Merci de reproduire cela ici. C'est excellent et m'a beaucoup aidé.
Naikrovek
96

Le premier titre ci-dessous est une réponse directe à la question. Les deux titres suivants sont plus utiles pour le programmeur de tous les jours.

Détails de mise en œuvre peut-être ennuyeux de Coroutines

Les coroutines sont expliquées sur Wikipedia et ailleurs. Ici, je vais simplement fournir quelques détails d'un point de vue pratique. IEnumerator, yieldEtc. sont C # caractéristiques linguistiques qui sont utilisés pour un peu d'un but différent dans l' unité.

Pour le dire très simplement, an IEnumeratorprétend avoir une collection de valeurs que vous pouvez demander une par une, un peu comme un List. En C #, une fonction avec une signature pour renvoyer un IEnumeratorn'a pas besoin d'en créer et d'en renvoyer une, mais peut laisser C # fournir un implicite IEnumerator. La fonction peut alors fournir le contenu de celui retourné IEnumeratordans le futur de manière paresseuse, par le biais d' yield returninstructions. Chaque fois que l'appelant demande une autre valeur à partir de cet implicite IEnumerator, la fonction s'exécute jusqu'à l' yield returninstruction suivante , qui fournit la valeur suivante. En tant que sous-produit de cela, la fonction s'arrête jusqu'à ce que la valeur suivante soit demandée.

Dans Unity, nous ne les utilisons pas pour fournir des valeurs futures, nous exploitons le fait que la fonction s'arrête. A cause de cette exploitation, beaucoup de choses sur les coroutines dans Unity n'ont pas de sens (Qu'est-ce qui IEnumeratora à voir avec quoi que ce soit? Qu'est-ce que yield? Pourquoi new WaitForSeconds(3)? Etc.). Ce qui se passe "sous le capot", c'est que les valeurs que vous fournissez via IEnumerator sont utilisées par StartCoroutine()pour décider quand demander la valeur suivante, qui détermine quand votre coroutine se relancera.

Votre jeu Unity est à un seul thread (*)

Les coroutines ne sont pas des threads. Il y a une boucle principale d'Unity et toutes ces fonctions que vous écrivez sont appelées par le même thread principal dans l'ordre. Vous pouvez le vérifier en plaçant un while(true);dans l'une de vos fonctions ou coroutines. Cela gèlera le tout, même l'éditeur Unity. C'est la preuve que tout fonctionne dans un thread principal. Ce lien mentionné par Kay dans son commentaire ci-dessus est également une excellente ressource.

(*) Unity appelle vos fonctions à partir d'un thread. Ainsi, à moins que vous ne créiez vous-même un thread, le code que vous avez écrit est à thread unique. Bien sûr, Unity utilise d'autres threads et vous pouvez créer des threads vous-même si vous le souhaitez.

Une description pratique des coroutines pour les programmeurs de jeux

En fait, lorsque vous appelez StartCoroutine(MyCoroutine()), il est exactement comme un appel de fonction régulière MyCoroutine(), jusqu'à ce que le premier yield return X, où Xest quelque chose comme null, new WaitForSeconds(3), StartCoroutine(AnotherCoroutine()), break, etc. C'est quand il commence à partir d' une fonction différente. Unity "met en pause" cette fonction juste à cette yield return Xligne, continue avec d'autres activités et certaines trames passent, et quand il est à nouveau temps, Unity reprend cette fonction juste après cette ligne. Il mémorise les valeurs de toutes les variables locales de la fonction. De cette façon, vous pouvez avoir une forboucle qui boucle toutes les deux secondes, par exemple.

Le moment où Unity reprendra votre coroutine dépend de ce qu'il y Xavait dans votre fichier yield return X. Par exemple, si vous avez utilisé yield return new WaitForSeconds(3);, il reprend après 3 secondes. Si vous l'avez utilisé yield return StartCoroutine(AnotherCoroutine()), il reprend une fois AnotherCoroutine()terminé, ce qui vous permet d'imbriquer les comportements dans le temps. Si vous venez d'utiliser a yield return null;, il reprend directement à l'image suivante.

Gazihan Alankus
la source
2
C'est dommage, UnityGems semble être en panne depuis un certain temps maintenant. Certaines personnes sur Reddit ont réussi à obtenir la dernière version de l'archive: web.archive.org/web/20140702051454/http
//unitygems.com
3
Ceci est très vague et risque d'être incorrect. Voici comment le code se compile et pourquoi cela fonctionne. De plus, cela ne répond pas non plus à la question. stackoverflow.com/questions/3438670/…
Louis Hong
Ouais, je suppose que j'ai expliqué "comment fonctionnent les coroutines dans Unity" du point de vue d'un programmeur de jeux. Le quastion lui-même demandait ce qui se passait sous le capot. Si vous pouvez signaler des parties incorrectes de ma réponse, je serais heureux de la corriger.
Gazihan Alankus
4
Je suis d'accord avec yield return false, je l'ai ajouté parce que quelqu'un a critiqué ma réponse pour ne pas l'avoir et j'étais pressé de vérifier si c'était même utile, et j'ai simplement ajouté le lien. Je l'ai enlevé maintenant. Cependant, je pense que Unity étant à thread unique et comment les coroutines s'intègrent à cela, ce n'est pas évident pour tout le monde. Beaucoup de programmeurs Unity débutants à qui j'ai parlé ont une compréhension très vague de tout cela et bénéficient d'une telle explication. J'ai modifié ma réponse pour fournir une réponse ancrée à la question. Les suggestions sont les bienvenues.
Gazihan Alankus
2
L'unité n'est pas un fwiw à thread unique. Il a un thread principal dans lequel les méthodes de cycle de vie MonoBehaviour s'exécutent - mais il a également d'autres threads. Vous êtes même libre de créer vos propres fils.
benthehutt
10

Cela ne pourrait pas être plus simple:

Unity (et tous les moteurs de jeu) sont basés sur des cadres .

Le point entier, la raison d'être de l'Unity, c'est qu'elle est basée sur un cadre. Le moteur fait les choses «à chaque image» pour vous. (Anime, rend des objets, fait de la physique, etc.)

Vous pourriez demander… "Oh, c'est génial. Et si je veux que le moteur fasse quelque chose pour moi à chaque image? Comment dire au moteur de faire telle ou telle chose dans un cadre?"

La réponse est ...

C'est exactement à cela que sert une "coroutine".

C'est aussi simple que cela.

Et considérez ceci ...

Vous connaissez la fonction "Mettre à jour". Tout simplement, tout ce que vous y mettez est fait à chaque image . C'est littéralement exactement la même chose, pas de différence du tout, de la syntaxe coroutine-yield.

void Update()
 {
 this happens every frame,
 you want Unity to do something of "yours" in each of the frame,
 put it in here
 }

...in a coroutine...
 while(true)
 {
 this happens every frame.
 you want Unity to do something of "yours" in each of the frame,
 put it in here
 yield return null;
 }

Il n'y a absolument aucune différence.

Note de bas de page: comme tout le monde l'a souligné, Unity n'a tout simplement aucun fil . Les "frames" dans Unity ou dans n'importe quel moteur de jeu n'ont absolument aucun lien avec les threads.

Les coroutines / yield sont simplement la façon dont vous accédez aux cadres dans Unity. C'est tout. (Et en effet, c'est absolument la même chose que la fonction Update () fournie par Unity.) C'est tout ce qu'il y a à faire, c'est aussi simple que cela.

Fattie
la source
Merci! Mais votre réponse explique comment utiliser les coroutines - pas comment elles fonctionnent dans les coulisses.
Ghopper21
1
C'est mon plaisir, merci. Je comprends ce que vous voulez dire - cela peut être une bonne réponse pour les débutants qui se demandent toujours ce que sont les coroutines. À votre santé!
Fattie
1
En fait, aucune des réponses, même légèrement, n'explique ce qui se passe «dans les coulisses». (Ce qui est que c'est un IEnumerator qui est empilé dans un planificateur.)
Fattie
Vous avez dit "Il n'y a absolument aucune différence". Alors pourquoi Unity a créé Coroutines alors qu'ils ont déjà une implémentation fonctionnelle exacte comme Update()? Je veux dire qu'il devrait y avoir au moins une légère différence entre ces deux implémentations et leurs cas d'utilisation, ce qui est assez évident.
Leandro Gecozo
hey @LeandroGecozo - je dirais plus, que "Update" est juste une sorte de simplification ("idiote") qu'ils ont ajouté. (Beaucoup de gens ne l'utilisent jamais, n'utilisent que des coroutines!) Je ne pense pas qu'il y ait de bonne réponse à votre question, c'est juste comment est Unity.
Fattie
5

J'ai creusé cela récemment, j'ai écrit un article ici - http://eppz.eu/blog/understanding-ienumerator-in-unity-3d/ - qui jette un éclairage sur les éléments internes (avec des exemples de code denses), l' IEnumeratorinterface sous-jacente , et comment il est utilisé pour les coroutines.

Utiliser des recenseurs de collections à cette fin me semble encore un peu étrange. C'est l'inverse de ce pour quoi les recenseurs se sentent conçus. Le point des énumérateurs est la valeur retournée à chaque accès, mais le point de Coroutines est le code entre les valeurs renvoyées. La valeur réelle renvoyée est inutile dans ce contexte.

Geri Borbás
la source
0

Les fonctions de base dans Unity que vous obtenez automatiquement sont la fonction Start () et la fonction Update (), donc celles de Coroutine sont essentiellement des fonctions tout comme les fonctions Start () et Update (). Toute ancienne fonction func () peut être appelée de la même manière qu'une Coroutine peut être appelée. Unity a évidemment défini certaines limites pour les Coroutines qui les différencient des fonctions régulières. Une différence est au lieu de

  void func()

vous écrivez

  IEnumerator func()

pour les coroutines. Et de la même manière, vous pouvez contrôler l'heure dans les fonctions normales avec des lignes de code comme

  Time.deltaTime

Une coroutine a une poignée spécifique sur la façon dont le temps peut être contrôlé.

  yield return new WaitForSeconds();

Bien que ce ne soit pas la seule chose possible à faire à l'intérieur d'un IEnumerator / Coroutine, c'est l'une des choses utiles pour lesquelles les Coroutines sont utilisées. Vous devrez rechercher l'API de script d'Unity pour apprendre d'autres utilisations spécifiques de Coroutines.

Alexhawkburr
la source
0

StartCoroutine est une méthode pour appeler une fonction IEnumerator. C'est similaire à simplement appeler une simple fonction void, la différence est que vous l'utilisez sur les fonctions IEnumerator. Ce type de fonction est unique car il peut vous permettre d'utiliser une fonction de rendement spéciale , notez que vous devez retourner quelque chose. C'est pour autant que je sache. Ici, j'ai écrit un jeu de scintillement simple sur la méthode du texte dans l'unité

    public IEnumerator GameOver()
{
    while (true)
    {
        _gameOver.text = "GAME OVER";
        yield return new WaitForSeconds(Random.Range(1.0f, 3.5f));
        _gameOver.text = "";
        yield return new WaitForSeconds(Random.Range(0.1f, 0.8f));
    }
}

Je l'ai ensuite appelé hors du IEnumerator lui-même

    public void UpdateLives(int currentlives)
{
    if (currentlives < 1)
    {
        _gameOver.gameObject.SetActive(true);
        StartCoroutine(GameOver());
    }
}

Comme vous pouvez voir comment j'ai utilisé la méthode StartCoroutine (). J'espère que j'ai aidé d'une manière ou d'une autre. Je suis moi-même un débutant, donc si vous me corrigez ou m'appréciez, n'importe quel type de rétroaction serait formidable.

Deepanshu Mishra
la source