Actions de jeu nécessitant plusieurs images pour terminer

20

Je n'ai jamais vraiment fait beaucoup de programmation de jeu auparavant, une question assez simple.

Imaginez que je construis un jeu Tetris, avec la boucle principale ressemblant à ceci.

for every frame
    handle input
    if it's time to make the current block move down a row
        if we can move the block
            move the block
        else
            remove all complete rows
            move rows down so there are no gaps
            if we can spawn a new block
                spawn a new current block
            else
                game over

Tout dans le jeu arrive si loin instantanément - les choses sont instantanément donné naissance, les lignes sont supprimées instantanément , etc. Mais si je ne veux que les choses se passent instantanément ( par exemple) les choses animées?

for every frame
    handle input
    if it's time to make the current block move down a row
        if we can move the block
            move the block
        else
            ?? animate complete rows disappearing (somehow, wait over multiple frames until the animation is done)
            ?? animate rows moving downwards (and again, wait over multiple frames)
            if we can spawn a new block
                spawn a new current block
            else
                game over

Dans mon clone Pong, ce n'était pas un problème, car à chaque image, je déplaçais simplement la balle et vérifiais les collisions.

Comment puis-je comprendre ce problème? La plupart des jeux impliquent sûrement une action qui prend plus qu'un cadre, et d'autres choses s'arrêtent jusqu'à ce que l'action soit terminée.


la source

Réponses:

11

La solution traditionnelle à cela est une machine à états finis, qui est suggérée dans plusieurs commentaires.

Je déteste les machines à états finis.

Bien sûr, ils sont simples, ils sont pris en charge dans toutes les langues, mais ils sont si difficiles à travailler. Chaque manipulation nécessite une tonne de code copier-coller anti-bogues, et peaufiner l'effet de petites manières peut être un énorme changement de code.

Si vous pouvez utiliser une langue qui les prend en charge, je recommande les coroutines. Ils vous permettent d'écrire du code qui ressemble à:

function TetrisPieceExplosion()
  for brightness = 0, 1, 0.2 do
    SetExplosionBrightness(brightness)
    coroutine.yield()
  end

  AllowNewBlockToFall()

  SpawnABunchOfParticles()

  RemoveBlockPhysics()

  for transparency = 0, 1, 0.5 do
    SetBlockTransparency(transparency)
    coroutine.yield()
  end

  RemoveBlockGraphics()
end

Évidemment, c'est plutôt un pseudocode, mais il doit être clair que non seulement il s'agit d'une simple description linéaire de l'effet spécial, mais qu'il nous permet facilement de déposer un nouveau bloc pendant que l'animation est encore en cours . Accomplir cela avec une machine d'état sera généralement horrible.

À ma connaissance, cette fonctionnalité n'est pas facilement disponible en C, C ++, C #, Objective C ou Java. C'est l'une des principales raisons pour lesquelles j'utilise Lua pour toute ma logique de jeu :)

ZorbaTHut
la source
Vous pouvez également implémenter quelque chose dans ce sens dans d'autres langages OOP. Imaginez une sorte de Actionclasse et une file d'actions à effectuer. Lorsqu'une action est terminée, supprimez-la de la file d'attente et effectuez l'action suivante, etc. Bien plus flexible qu'une machine à états.
bummzack
3
Cela fonctionne, mais alors vous cherchez à dériver d'Action pour chaque action unique. Cela suppose également que votre processus s'intègre parfaitement dans une file d'attente - si vous souhaitez créer des branches ou des boucles avec des conditions de fin non définies, la solution de file d'attente se décompose rapidement. C'est certainement plus propre que l'approche de la machine d'état, mais je pense que les coroutines l'emportent toujours sur la lisibilité :)
ZorbaTHut
C'est vrai, mais pour l'exemple de Tetris, cela devrait suffire :)
bummzack
Les co-routines rock- mais Lua en tant que langue aspire de bien d'autres façons, je ne peux pas le recommander.
DeadMG
Tant que vous avez seulement besoin de céder au niveau supérieur (et non à partir d'un appel de fonction imbriqué), vous pouvez accomplir la même chose en C #, mais oui, les coroutines Lua rock.
munificent
8

Je prends cela de Game Coding Complete par Mike McShaffry.

Il parle d'un «gestionnaire de processus», qui se résume à une liste de tâches à effectuer. Par exemple, un processus contrôlerait l'animation pour dessiner une épée (AnimProcess), ou ouvrir une porte, ou dans votre cas, faire disparaître la ligne.

Le processus serait ajouté à la liste du gestionnaire de processus, qui serait itéré à chaque trame et à Update () appelé pour chacun. Donc des entités très similaires, mais pour des actions. Il y aurait un indicateur de suppression à supprimer de la liste une fois terminé.

L'autre chose intéressante à leur sujet est de savoir comment ils peuvent se lier, en ayant un pointeur vers le processus suivant. De cette façon, votre processus de ligne animée peut en fait consister en:

  • Un processus d'animation pour la ligne qui disparaît
  • Un Processus de Mouvement pour retirer les pièces
  • Un processus de score pour ajouter des points au score

(Étant donné que les processus peuvent être des objets à usage unique, conditionnellement là, ou là pour X quantité de temps)

Si vous voulez plus de détails, demandez loin.

Le canard communiste
la source
3

Vous pouvez utiliser une file d'attente prioritaire d'actions. Vous poussez dans une action et un temps. À chaque image, vous obtenez l'heure, et vous supprimez toutes les actions qui ont une heure spécifiée comme avant cette heure et vous les exécutez. Bonus: l'approche est parfaitement parallèle et vous pouvez réellement implémenter presque toute la logique du jeu de cette façon.

DeadMG
la source
1

Vous devez toujours connaître la différence de temps entre l'image précédente et l'image actuelle, puis vous devez faire deux choses.

-Décidez quand mettre à jour votre modèle: par exemple. dans tetris lorsqu'une suppression de ligne commence, vous ne voulez plus que des éléments entrent en collision avec la ligne, vous supprimez donc la ligne du «modèle» de votre application.

-Vous devez ensuite gérer l'objet qui est dans un état de transition vers une classe distincte qui résout l'animation / l'événement sur une période de temps. Dans l'exemple tetris, la ligne s'estompe lentement (modifiez un peu l'opacité de chaque image). Une fois l'opacité égale à 0, vous transférez tous les blocs en haut de la ligne un vers le bas.

Cela peut sembler un peu compliqué au premier abord, mais vous y arriverez, assurez-vous simplement d'abstraire beaucoup de choses dans différentes classes, cela vous facilitera la tâche. Assurez-vous également que les événements qui prennent du temps, comme la suppression d'une ligne dans tetris, sont du type "Fire and Forget", créez simplement un nouvel objet qui gère tout ce qui doit être fait automatiquement et que, lorsque tout est fait, se supprime de votre scénario.

Roy T.
la source
De plus, dans certains cas, des calculs lourds peuvent dépasser le temps autorisé pour un pas de temps physique (par exemple, détection de collision et planification de trajectoire). Dans ces cas, vous pouvez sauter des calculs lorsque le temps imparti a été utilisé et continuer le calcul à la trame suivante.
Nailer
0

Vous devez considérer le jeu comme une "machine à états finis". Le jeu peut être dans l'un des états suivants: dans votre cas, "attente d'entrée", "pièce descendant", "ligne explosant".

Vous faites des choses différentes selon l'état. Par exemple, pendant le "déplacement de la pièce", vous ignorez les entrées du joueur et animez la pièce de sa ligne actuelle à la ligne suivante. Quelque chose comme ça:

if state == ACCEPTING_INPUT:
    if player presses any key:
        handle input
    row_timer = row_timer - time_since_last_frame
    if row_timer < 0:
        state = MOVING_PIECE_DOWN
elif state == MOVING_PIECE_DOWN:
    piece.y = piece.y + piece.speed*time_since_last_frame
    if piece.y >= target_piece_y:
        piece.y = target_piece_y
        state = ACCEPTING_INPUT
ggambett
la source