Codage de différents états dans les jeux d'aventure

12

Je prévois un jeu d'aventure et je ne peux pas comprendre quelle est la bonne façon de mettre en œuvre le comportement d'un niveau en fonction de l'état de progression de l'histoire.

Mon jeu solo propose un monde immense où le joueur doit interagir avec les gens d'une ville à différents moments du jeu. Cependant, en fonction de la progression de l'histoire, différentes choses seraient présentées au joueur, par exemple, le chef de guilde changera les emplacements de la place de la ville en divers emplacements dans la ville; Les portes ne se déverrouillaient qu'à certains moments de la journée après avoir terminé une routine particulière; Différents événements d'écran de déclenchement / déclencheur se produisent uniquement après qu'un jalon particulier a été atteint.

J'ai pensé naïvement à utiliser une instruction switch {} au départ pour décider ce que le PNJ devrait dire ou à qui il pouvait être trouvé, et rendre les objectifs de quête interactifs uniquement après avoir vérifié l'état d'une variable globale game_state. Mais j'ai réalisé que je rencontrerais rapidement de nombreux états de jeu et boîtiers de commutation différents afin de changer le comportement d'un objet. Cette instruction switch serait également extrêmement difficile à déboguer, et je suppose qu'elle pourrait également être difficile à utiliser dans un éditeur de niveau.

J'ai donc pensé qu'au lieu d'avoir un seul objet avec plusieurs états, je devrais peut-être avoir plusieurs instances du même objet, avec un seul état. De cette façon, si j'utilise quelque chose comme un éditeur de niveau, je peux mettre une instance du PNJ à tous les différents endroits où il pourrait apparaître, ainsi qu'une instance pour chaque état de conversation qu'il a. Mais cela signifie qu'il y aura beaucoup d'objets de jeu inactifs et invisibles flottant autour du niveau, ce qui pourrait être un problème de mémoire, ou tout simplement difficile à voir dans un éditeur de niveau, je ne sais pas.

Ou tout simplement, créez un niveau identique mais séparé pour chaque état du jeu. Cela semble être la manière la plus propre et sans bogue de faire les choses, mais cela ressemble à un travail manuel massif pour s'assurer que chaque version du niveau est vraiment identique les unes aux autres.

Toutes mes méthodes semblent si inefficaces, donc pour récapituler ma question, existe-t-il un moyen meilleur ou standardisé de mettre en œuvre un comportement d'un niveau en fonction de l'état de progression de l'histoire?

PS: Je n'ai pas encore d'éditeur de niveau - je pense utiliser quelque chose comme JME SDK ou créer le mien.

Cardin
la source

Réponses:

9

Je pense que ce dont vous avez besoin dans ce cas est le modèle de conception d'état . Au lieu d'avoir plusieurs instances de chaque objet de jeu, créez une seule instance, mais encapsulez son comportement dans une classe distincte. Créez plusieurs classes, une pour chaque comportement possible, et donnez à toutes les classes la même interface. Associez-en un à votre objet de jeu (l'état initial) et, lorsque les conditions changent (un jalon est atteint, l'heure du jour passe, etc.), vous changez l'état de cet objet (c.-à-d. L'associez à un objet différent en fonction de votre logique de jeu) et mettre à jour ses propriétés le cas échéant.

Un exemple de l'apparence d'une interface d'état (entièrement composée - juste pour illustrer le niveau de contrôle que ce schéma vous donne):

interface NPCState {
    Scene whereAmI(NPC o);
    String saySomething(NPC o);
}

Et deux classes d'implémentation:

class Busy implements NPCState {
    Scene whereAmI(NPC o) {
        return o.getWorkScene();
    }
    String saySomething(NPC o) {
        return "Can't talk now, I'm busy!";
    }
}

class Available implements NPCState {
    Scene whereAmI(NPC o) {
        return TAVERN;
    }
    String saySomething(NPC o) {
        String[] choices = o.getRandomChat();
        return choices[RANDOM.getInt(choices.length)];
    }
}

Et les états de commutation:

// The time of day passed from "afternoon" to "evening"
NPCState available = new Available();
for ( NPC o : list ) {
    Scene oldScene = o.state.whereAmI(o);
    o.state = available;
    Scene newScene = o.state.whereAmI(o);
    moveGameObject(o, oldScene, newScene);
    ...

Les PNJ importants peuvent avoir leurs états personnalisés, la logique de choix de l'état peut être plus personnalisable et vous pouvez avoir différents états pour différentes facettes du jeu (dans cet exemple, j'ai utilisé une seule classe pour indiquer à la fois l'emplacement et le chat, mais vous pouvez séparer et faites de nombreuses combinaisons).

Cela fonctionne aussi bien avec les éditeurs de niveau: vous pouvez avoir une simple zone de liste déroulante pour changer l'état "global" d'un niveau, puis ajouter et repositionner les objets du jeu comme vous voulez qu'ils apparaissent dans cet état. Le moteur de jeu serait uniquement responsable de "l'ajout" de cet objet à la scène lorsqu'il a le bon état - mais ses paramètres seraient toujours modifiables de manière conviviale.

(Avertissement: j'ai peu d'expérience dans le monde réel avec les éditeurs de jeux, donc je peux dire avec confiance comment fonctionnent les éditeurs professionnels; mais mon point sur le modèle d'état est toujours valable, organiser votre code de cette façon devrait être propre, maintenable et ne pas gaspiller le système Ressources.)

mgibsonbr
la source
vous savez, vous pouvez combiner ce modèle de conception d'état avec le tableau associatif que j'ai décrit. Vous pouvez coder les objets d'état comme décrit ici, puis choisir entre différents objets d'état à l'aide d'un tableau associatif comme je l'ai suggéré.
jhocking
Je suis d'accord, il est également bon de séparer le jeu de son moteur, et la logique de jeu en codage dur renforce le couplage entre eux (ce qui réduit les possibilités de réutilisation). Il y a cependant un compromis, car en fonction de la complexité de votre comportement prévu, essayer de "coder en douceur" tout peut entraîner un encombrement inutile . Dans ce cas, une approche mixte peut être souhaitable (c'est-à-dire avoir une logique de transition d'état "générique", mais permettant également d'incorporer du code personnalisé)
mgibsonbr
Donc, si je comprends bien, il existe un mappage un-à-un entre NPCState et GameState. Ensuite, je mettrais les PNJ dans un tableau et j'itérerais à travers celui-ci, en affectant le nouveau NPCState lorsqu'un changement d'état de jeu est observé. Le NPCState doit être capable de savoir comment gérer chaque NPC diff qui lui est envoyé, donc essentiellement le NPCState contient le comportement de tous les NPC pour un état donné? J'aime que tous les comportements soient stockés proprement dans un seul NPCState, qui mappe proprement à l'implémentation de l'éditeur de jeu, mais cela rend le NPCState assez énorme.
Cardin
Oh, je pense que j'ai mal compris ur ans. Je l'ai un peu changé pour inclure des observateurs. Il s'agit donc d'un NPCState diff pour chaque NPC diff, à l'exception des super génériques comme Crowd NPC qui peuvent partager l'état. Pour chaque état de jeu, le PNJ s'enregistrera lui-même et son état NPC auprès d'un observateur. Par conséquent, l'Observateur saura exactement quel PNJ est enregistré pour changer le comportement à quel état de jeu, et simplement le parcourir. Et du côté de l'éditeur de jeu, l'éditeur de jeu n'a qu'à passer un signal à l'observateur pour changer l'état de tout le niveau.
Cardin
1
Oui, c'est l'idée! Les PNJ importants auront de nombreux états, et la transition entre les états dépendra principalement des jalons achevés. Les PNJ génériques peuvent également réagir aux jalons parfois, et même avoir leur état choisi dépendant d'une propriété interne (disons que tous les PNJ ont un état initial par défaut, et lorsque vous parlez à l'un d'eux pour la première fois, il se présente, puis entrez le cycle de commutation d'état normal).
mgibsonbr
2

Les choix que je considérerais sont soit de faire répondre les objets individuels à différents gamestates, soit de servir différents niveaux dans différents gamestates. Le choix entre ces deux dépendra de ce que j'essaye de faire exactement dans le jeu (quels sont les différents états? Comment la transition du jeu entre les états? Etc.)

Quoi qu'il en soit, je ne le ferais pas en codant en dur les états dans le code du jeu. Plutôt qu'une instruction switch massive dans des objets NPC, je préfère remplacer les comportements NPC chargés dans un tableau associatif à partir d'un fichier de données, puis utiliser ce tableau associatif pour exécuter un comportement différent pour leurs états associés, quelque chose comme ceci:

if (state in behaviors) {
  behaviors[state]();
}
jhocking
la source
Ce fichier de données serait-il une sorte de langage de script? Je pense qu'un fichier de données en texte brut pourrait ne pas être suffisant pour décrire le comportement. En tout cas, vous avez raison de dire qu'il doit être chargé dynamiquement. Je ne peux pas vraiment penser à utiliser un éditeur de jeu pour générer du code Java valide, il doit certainement être analysé quelque peu.
Cardin
1
Eh bien, c'était ma pensée initiale, mais après avoir vu la réponse de mgibsonbr, j'ai réalisé que vous pouviez coder les divers changeurs en classes distinctes et ensuite dans le fichier de données dire simplement quelles classes de comportement vont avec quel état. Chargez ces données dans un tableau associatif lors de l'exécution.
jhocking
Oh .. Oui, c'est vraiment plus simple! : D Par rapport au scénario d'incorporation de quelque chose comme Lua haha ​​..
Cardin
2

Qu'en est-il de l'utilisation d'un modèle d'observateur pour rechercher des changements d'étape? Si un changement se produit, certaines classes le reconnaîtront et géreront par exemple un changement qui doit être effectué sur un npc.

Au lieu du modèle de conception d'état mentionné, j'utiliserais un modèle de stratégie.

Si un PNJ a n façons d'interagir avec le personnage et m positions où il pourrait être, il y a un maximum de (m * n) +1 classes que vous devez concevoir. En utilisant le modèle de stratégie, vous vous retrouveriez avec n + m + 1 classes, mais ces stratégies pourraient également être utilisées par d'autres PNJ.

Il pourrait donc y avoir une classe qui gère les jalons, et des classes qui observent cette classe et gèrent soit les PNJ, soit les ennemis ou tout ce qui devrait être changé. Si les observateurs sont mis à jour, ils décideront s'ils doivent changer quelque chose aux instances qu'ils gouvernent. La classe NPC par exemple, dans le constructeur, informerait le NPC-Manager quand il doit être mis à jour et ce qui doit être mis à jour ...

TOAOGG
la source
Le modèle Observer semble intéressant. Je pense que cela pourrait clairement laisser à l'APN toutes les responsabilités pour s'enregistrer auprès de l'observateur de l'État. Le modèle de stratégie ressemble beaucoup au déclencheur d'Unity Engine et aux comportements de l'IA, qui est utilisé pour partager le comportement entre différents objets de jeu (je pense). Cela semble faisable. Je ne sais pas quels sont les avantages / inconvénients en ce moment, mais le fait que Unity utilise également la même méthode est quelque peu rassurant haha ​​..
Cardin
J'ai juste utilisé ces deux modèles à quelques reprises, donc je ne peux pas vous parler des inconvénients: - / Mais je pense que c'est bien en cas de responsabilité unique et de disponibilité pour tester chaque stratégie :) Cela peut devenir déroutant si votre en utilisant une stratégie dans de nombreuses classes différentes et vous voulez trouver chaque classe qui l'utilise.
TOAOGG
0

Toutes les approches données sont valables. Cela dépend de la situation dans laquelle vous vous trouvez à un moment donné. De nombreuses aventures ou MMO utilisent une combinaison de ceux-ci.

Par exemple, si un événement charnière modifie une grande partie du niveau (par exemple, un agent de recouvrement nettoie votre appartement et que tous ceux qui s'y trouvent sont arrêtés), il est généralement plus facile de remplacer toute la pièce par une deuxième pièce qui semble similaire.

OTOH, si les personnages se promènent sur la carte et font différentes choses à différents endroits, vous avez souvent un seul acteur qui tourne à travers différents objets de comportement (par exemple, marcher droit devant / pas de conversations vs rester ici / conversation sur la mort de Mitch), qui pourrait inclure "caché" si leur objectif a été atteint.

Cela dit, avoir généralement des doublons d'un objet que vous créez manuellement ne devrait pas poser de problème. Combien d'objets pouvez-vous créer? Si vous pouvez créer plus d'objets que votre jeu ne peut boucler, regardez leur propriété "cachée" et sautez, votre moteur est trop lent. Je ne m'inquiéterais donc pas trop de cela. De nombreux jeux en ligne le font. Certains personnages ou objets sont toujours là, mais ne sont pas affichés aux personnages qui n'ont pas la mission correspondante.

Vous pouvez même combiner les approches: avoir deux portes dans votre immeuble. L'une mène à l'appartement «avant le collecteur de créances», l'autre à l'appartement après. Lorsque vous entrez dans le couloir, seul celui qui s'applique à votre progression dans l'histoire est réellement affiché. De cette façon, vous pouvez simplement avoir un mécanisme générique pour «l'élément est visible au point actuel de l'histoire» et une porte avec une seule destination. Alternativement, vous pouvez créer des portes plus compliquées qui peuvent avoir des comportements qui peuvent être échangés, et l'un d'eux est "aller à l'appartement complet", l'autre "aller à l'appartement vide". Cela peut sembler absurde si vraiment seulement la destination de la porte change, mais si son apparence change également (par exemple, un gros verrou devant la porte que vous devez d'abord casser),

uliwitness
la source