Script et cinématique sans filetage

12

J'ai du mal à implémenter les scripts dans mon moteur de jeu. Je n'ai que quelques exigences: cela devrait être intuitif, je ne veux pas écrire un langage personnalisé, un analyseur et un interprète, et je ne veux pas utiliser le filetage. (Je suis certain qu'il existe une solution plus simple; je n'ai pas besoin des tracas de plusieurs threads de logique de jeu.) Voici un exemple de script, en Python (alias pseudocode):

def dramatic_scene(actors):
    alice = actors["alice"]
    bob = actors["bob"]

    alice.walk_to(bob)
    if bob.can_see(alice):
        bob.say("Hello again!")
    else:
        alice.say("Excuse me, Bob?")

Ce morceau épique de narration pose des problèmes de mise en œuvre. Je ne peux pas simplement évaluer toute la méthode à la fois, car cela walk_toprend du temps de jeu. Si cela revient tout de suite, Alice commencerait à marcher vers Bob et (dans le même cadre) dire bonjour (ou être salué). Mais s'il walk_toy a un appel de blocage qui revient quand elle atteint Bob, alors mon jeu est bloqué, car il bloque le même fil d'exécution qui ferait marcher Alice.

J'ai envisagé de faire de chaque fonction la mise en file d'attente d'une action - alice.walk_to(bob)pousserait un objet dans une file d'attente, qui serait sauté après qu'Alice ait atteint Bob, où qu'il soit. C'est plus subtilement cassé: la ifbranche est évaluée immédiatement, donc Bob pourrait saluer Alice même si son dos lui est tourné.

Comment les autres moteurs / personnes gèrent-ils les scripts sans créer de threads? Je commence à chercher des idées dans des domaines autres que les développeurs de jeux, comme les chaînes d'animation jQuery. Il semble qu'il devrait y avoir de bons modèles pour ce genre de problème.

ojrac
la source
1
+1 pour la question mais surtout pour "Python (aka pseudocode)" :)
Ricket
Python, c'est comme avoir une superpuissance.
ojrac

Réponses:

3

La façon dont quelque chose comme Panda fait cela est avec les rappels. Au lieu de bloquer, ce serait quelque chose comme

def dramatic_scene(actors):
    alice = actors["alice"]
    bob = actors["bob"]

    def cb():
        if bob.can_see(alice):
            bob.say("Hello again!")
        else:
            alice.say("Excuse me, Bob?")
    alice.walk_to(bob, cb)

Un rappel complet vous permet d'enchaîner ce type d'événements aussi profondément que vous le souhaitez.

EDIT: exemple JavaScript car qui a une meilleure syntaxe pour ce style:

function dramatic_scene(actors) {
    var alice = actors.alice;
    var bob = actors.bob;
    alice.walk_to(bob, function() {
        if(bob.can_see(alice)) {
            bob.say('Hello again!');
        } else {
            alice.say('Excuse me, Bob?');
        }
     });
}
coderanger
la source
Cela fonctionne assez pour un +1, je pense juste que c'est mal pour la conception de contenu. Je suis prêt à faire un travail supplémentaire de mon côté pour que les scripts soient simples et clairs.
ojrac
1
@ojrac: En fait, c'est mieux ainsi, car ici, dans le même script, vous pouvez dire à plusieurs acteurs de commencer à marcher en même temps.
Bart van Heukelom
Ugh, bon point.
ojrac
Cependant, pour améliorer la lisibilité, la définition de rappel pourrait être imbriquée dans l'appel walk_to (), ou placée après (pour les deux va: si le langage prend en charge) afin que le code qui sera appelé plus tard soit vu plus tard dans la source.
Bart van Heukelom
Ouais, Python n'est pas vraiment génial pour ce genre de syntaxe malheureusement. Il semble beaucoup plus agréable en JavaScript (voir ci-dessus, ne peut pas utiliser la mise en forme du code ici).
coderanger
13

Le terme que vous souhaitez rechercher ici est " coroutines " (et généralement le mot-clé de la langue ou le nom de la fonction est yield).

Les coroutines sont des composants de programme qui généralisent les sous-programmes pour permettre plusieurs points d'entrée pour suspendre et reprendre l'exécution à certains emplacements.

L'implémentation dépendra tout d'abord de votre langue. Pour un jeu, vous voulez que la mise en œuvre soit aussi légère que possible (plus légère que les fils ou même les fibres). La page Wikipedia (liée) contient des liens vers diverses implémentations spécifiques au langage.

J'ai entendu dire que Lua avait un support intégré pour les coroutines. GameMonkey aussi.

UnrealScript implémente cela avec ce qu'il appelle des "états" et des "fonctions latentes".

Si vous utilisez C #, vous pouvez consulter ce billet de blog de Nick Gravelyn.

De plus, l'idée de "chaînes d'animation", bien que n'étant pas la même chose, est une solution pratique au même problème. Nick Gravelyn a également une implémentation C # de cela .

Andrew Russell
la source
Belle prise, Tetrad;)
Andrew Russell
C'est vraiment bien, mais je ne suis pas sûr que cela m'atteigne à 100%. Il semble que les coroutines vous permettent de céder à la méthode d'appel, mais je veux un moyen de céder à partir d'un script Lua, tout le long de la pile jusqu'au code C # sans écrire while (walk_to ()! = Done) {yield}.
ojrac
@ojrac: Je ne sais pas pour Lua, mais si vous utilisez la méthode C # de Nick Gravelyn, vous pouvez renvoyer un délégué (ou un objet en contenant un) qui contient les conditions que votre gestionnaire de scripts doit vérifier (le code de Nick renvoie juste une heure qui est implicitement un conditionnel). Vous pourriez même avoir les fonctions latentes elles-mêmes renvoyer le délégué, vous pouvez donc écrire: yield return walk_to();dans votre script.
Andrew Russell
Le rendement en C # est génial, mais j'optimise ma solution pour des scripts simples et difficiles à gérer. J'aurai plus de facilité à expliquer les rappels que le rendement, donc j'accepterai l'autre réponse. Je ferais +2 si je pouvais.
ojrac
1
Vous n'avez normalement pas à expliquer l'appel de rendement - vous pouvez envelopper cela dans la fonction "walk_to", par exemple.
Kylotan
3

ne pas fileter est intelligent.

La plupart des moteurs de jeu fonctionnent comme une série d'étapes modulaires avec des éléments en mémoire qui guident chaque étape. Pour votre `` marche vers l'exemple '', vous avez généralement une étape IA où vos personnages marchés sont dans un état où ils ne devraient pas chercher d'ennemis à tuer, une étape d'animation où ils devraient exécuter l'animation X, une étape physique (ou étape de simulation) où leur position réelle est mise à jour, etc.

dans votre exemple ci-dessus, 'alice' est un acteur composé de morceaux qui vivent dans plusieurs de ces étapes, donc un acteur bloquant.walk_to call (ou une coroutine que vous appelez next () une fois par image) n'aurait probablement pas le contexte approprié pour prendre beaucoup de décisions.

Au lieu de cela, une fonction 'start_walk_to' ferait probablement quelque chose comme:

def start_cutscene_walk_to(actor,target):
    actor.ai.setbrain(cutscene_brain)
    actor.physics.nocoll = 1
    actor.anims.force_anim('walk')
    # etc.

Ensuite, votre boucle principale exécute son tick ai, son tick physique, son tick d'animation et son cutscene, et la cutscene met à jour l'état de chacun des sous-systèmes de votre moteur. Le système de cinématiques doit définitivement suivre ce que fait chacune de ses cinématiques, et un système basé sur la coroutine pour quelque chose de linéaire et déterministe comme un scéne pourrait avoir du sens.

La raison de cette modularité est qu'elle garde les choses simples et agréables, et pour certains systèmes (comme la physique et l'IA), vous devez connaître l'état de tout en même temps pour résoudre les choses correctement et garder le jeu dans un état cohérent .

J'espère que cela t'aides!

Aaron Brady
la source
J'aime ce que vous cherchez, mais en fait, je ressens fortement la valeur de acteur.walk_to ([un autre acteur, une position fixe ou même une fonction qui renvoie une position]). Mon objectif est de fournir des outils simples et compréhensibles et de gérer toute la complexité loin de la partie création de contenu du jeu. Vous m'avez également aidé à réaliser que tout ce que je veux vraiment, c'est un moyen de traiter chaque script comme une machine à états finis.
ojrac
heureux d'avoir pu aider! J'avais l'impression que ma réponse était un peu hors sujet :) certainement d'accord avec la valeur d'une fonction acteur.walk_to pour atteindre vos objectifs, j'ai hâte d'entendre parler de votre mise en œuvre.
Aaron Brady
Il semblerait que j'irai avec un mélange de rappels et de fonctions chaînées de style jQuery. Voir réponse acceptée;)
ojrac