Séparation de l'état du monde et de l'animation dans un jeu au tour par tour

9

Comment gérez-vous la séparation de l'animation de l'état mondial dans un jeu au tour par tour? Je travaille actuellement sur un jeu basé sur une grille 2D. Le code ci-dessous est simplifié pour mieux expliquer.

Lorsqu'un acteur se déplace, je veux interrompre le flux de tours pendant que la créature s'anime et se déplace vers sa nouvelle position. Sinon, l'écran pourrait être considérablement en retard sur l'état du monde, ce qui provoquerait une apparence visuelle étrange. Je veux également avoir des animations qui ne bloquent pas le déroulement du jeu - un effet de particules pourrait se dérouler sur plusieurs tours sans affecter le gameplay.

Ce que j'ai fait, c'est introduire deux types d'animations, que j'appelle des animations bloquantes et des animations non bloquantes. Lorsque le joueur veut se déplacer, le code qui est exécuté est

class Actor {
    void move(PositionInfo oldPosition, PositionInfo newPosition) {
        if(isValidMove(oldPosition, newPosition) {
             getGame().pushBlockingAnimation(new MoveAnimation(this, oldPosition, newPosition, ....));
             player.setPosition(newPosition);
        }
    }
}

Ensuite, la boucle de mise à jour principale fait:

class Game {
    void update(float dt) {
        updateNonBlockingAnimations(dt); //Effects like particle explosions, damage numbers, etc. Things that don't block the flow of turns.
        if(!mBlockingAnimations.empty()) {
            mBlockingAnimations.get(0).update(dt);
        } else {
             //.. handle the next turn. This is where new animations will be enqueued..//
        }
        cleanUpAnimations(); // remove finished animations
    }
}

... où l'animation met à jour la position à l'écran de l'acteur.

Une autre idée que j'implémente est d'avoir une animation de blocage simultanée, où plusieurs animations de blocage seraient mises à jour simultanément, mais le prochain tour ne se produirait pas avant qu'elles ne soient toutes terminées.

Cela vous semble-t-il une manière saine de faire les choses? Quelqu'un a-t-il des suggestions ou même des références à la façon dont d'autres jeux similaires font une telle chose?

mdkess
la source

Réponses:

2

Si votre système fonctionne pour vous, je ne vois aucune raison de ne pas le faire.

Eh bien, une raison: cela peut devenir compliqué lorsque vous ne pouvez pas facilement juger les conséquences de "player.setPosition (newPosition);" plus.

Exemple (juste pour inventer les choses): Imaginez que vous déplacez le joueur avec votre setPosition au-dessus d'un piège. L'appel à "player.setPosition" lui-même déclenchera un autre appel à .. disons un code d'écoute de trap qui à son tour poussera une animation de blocage par elle-même qui affiche un "splash blessant" sur lui-même.

Vous voyez le problème: le splash s'affiche simultanément avec le déplacement du joueur vers le piège. (Supposons ici que vous ne voulez pas cela, mais que l'animation de piège soit jouée juste après le déplacement;)).

Donc, tant que vous êtes capable de garder à l'esprit toutes les conséquences de vos actions après les appels "pushBlockingAnimation", tout va bien.

Vous devrez peut-être commencer à envelopper la logique du jeu dans des classes de "blocage de quelque chose" afin qu'elles puissent être mises en file d'attente pour s'exécuter une fois l'animation terminée. Ou vous passez un rappel "callThisAfterAnimationFinishes" à pushBlockingAnimation et déplacez setPosition dans la fonction de rappel.

Une autre approche serait les langages de script. Par exemple, Lua a "coroutine.yield" qui renvoie la fonction avec un état spécial afin qu'elle puisse être appelée plus tard pour continuer à l'instruction suivante. De cette façon, vous pouvez facilement attendre la fin de l'animation pour exécuter la logique du jeu sans encombrer l'aspect "normal" du flux de programme de votre code. (signifie que votre code ressemblerait toujours un peu à "playAnimation (); setPosition ();" au lieu de passer des rappels et d'encombrer la logique du jeu sur plusieurs fonctions).

Unity3D (et au moins un autre système de jeu que je connais) dispose de l'instruction C # "yield return" pour simuler cela directement dans le code C #.

// instead of simple call to move(), you have to "iterate" it in a foreach-like loop
IEnumerable<Updatable> move(...) {
    // playAnimation returns an object that contain an update() and finished() method.
    // the caller will not iterate over move() again until the object is "finished"
    yield return getGame().playAnimation(...)
    // the next statement will be executed *after* the animation finished
    player.setPosition(newPosition);
}

Bien sûr, dans ce schéma, l'appel à move () obtient tout le sale boulot maintenant. Vous appliqueriez probablement cette technique uniquement pour des fonctions spécifiques où vous savez exactement qui les appelle d'où. Donc, ne prenez cela que comme une idée de base de la façon dont les autres moteurs sont organisés - c'est probablement beaucoup trop compliqué pour votre problème spécifique actuel.

Je vous recommande de vous en tenir à votre idée pushBlockingAnimation jusqu'à ce que vous rencontriez des problèmes dans le monde réel. Modifiez-le ensuite. ;)

Imi
la source
Merci beaucoup d'avoir pris le temps d'écrire cette réponse, je l'apprécie vraiment.
mdkess