Pensez au fonctionnement d'un foreach
:
foreach (var number in Enumerable.Range(1, 1000000))
{
if (number > 10) break;
}
Le contrôle de l'itération est sur l'appelant - si vous arrêtez l'itération (ici avec break
), c'est tout.
Le yield
mot-clé est un moyen simple de rendre un énumérable en C #. Le nom l'indique - yield return
redonne le contrôle à l'appelant (dans ce cas, le nôtre foreach
); c'est l'appelant qui décide quand passer à l'élément suivant. Vous pouvez donc créer une méthode comme celle-ci:
IEnumerable<int> ToInfinity()
{
var i = 0;
while (true) yield return i++;
}
Cela a l'air naïf de fonctionner pour toujours; mais en réalité, cela dépend entièrement de l'appelant. Vous pouvez faire quelque chose comme ça:
var range = ToInfinity().Take(10).ToArray();
Cela peut être un peu déroutant si vous n'êtes pas habitué à ce concept, mais j'espère qu'il est également évident que c'est une propriété très utile. C'était le moyen le plus simple de donner le contrôle à votre appelant, et lorsque l'appelant décide de faire un suivi, il peut simplement faire l'étape suivante (si Unity a été créé aujourd'hui, il utiliserait probablement à la await
place de yield
; mais await
n'existait pas en arrière puis).
Tout ce dont vous avez besoin pour implémenter vos propres coroutines (il va sans dire que les coroutines les plus stupides les plus simples) sont les suivantes:
List<IEnumerable> continuations = new List<IEnumerable>();
void StartCoroutine(IEnumerable coroutine) => continuations.Add(coroutine);
void MainLoop()
{
while (GameIsRunning)
{
foreach (var continuation in continuations.ToArray())
{
if (!continuation.MoveNext()) continuations.Remove(continuation);
}
foreach (var gameObject in updateableGameObjects)
{
gameObject.Update();
}
}
}
Pour ajouter une WaitForSeconds
implémentation très simple , vous avez juste besoin de quelque chose comme ceci:
interface IDelayedCoroutine
{
bool ShouldMove();
}
class Waiter: IDelayedCoroutine
{
private readonly TimeSpan time;
private readonly DateTime start;
public Waiter(TimeSpan time)
{
this.start = DateTime.Now;
this.time = time;
}
public bool ShouldMove() => start + time > DateTime.Now;
}
Et le code correspondant dans notre boucle principale:
foreach (var continuation in continuations.ToArray())
{
if (continuation.Current is IDelayedCoroutine dc)
{
if (!dc.ShouldMove()) continue;
}
if (!continuation.MoveNext()) continuations.Remove(continuation);
}
Ta-da - c'est tout ce dont un système coroutine a besoin. Et en cédant le contrôle à l'appelant, l'appelant peut décider de n'importe quel nombre de choses; ils peuvent avoir une table d'événements triée plutôt que d'itérer dans toutes les coroutines sur chaque trame; ils peuvent avoir des priorités ou des dépendances. Il permet une mise en œuvre très simple du multitâche coopératif. Et regardez à quel point c'est simple, grâce à yield
:)
"Fire1"
, est-ce quelque chose que vous pouvez configurer dans le moteur pour permettre les remappages de touches plutôt que de taperKeycode.Foo
?