Modèle pour effectuer des actions de jeu

11

Existe-t-il un modèle généralement accepté pour effectuer diverses actions dans un jeu? Une façon dont un joueur peut effectuer des actions et aussi qu'une IA puisse effectuer des actions, telles que déplacer, attaquer, s'autodétruire, etc.

J'ai actuellement une BaseAction abstraite qui utilise des génériques .NET pour spécifier les différents objets qui sont retournés par les différentes actions. Tout cela est implémenté dans un modèle similaire à la commande, où chaque action est responsable d'elle-même et fait tout ce dont elle a besoin.

Mon raisonnement pour être abstrait est pour que je puisse avoir un seul ActionHandler, et l'IA peut simplement mettre en file d'attente différentes actions implémentant la baseAction. Et la raison pour laquelle il est générique est que les différentes actions peuvent renvoyer des informations de résultat pertinentes pour l'action (car différentes actions peuvent avoir des résultats totalement différents dans le jeu), ainsi que des implémentations communes avant et après action.

Alors ... y a-t-il une façon plus acceptée de le faire, ou est-ce que ça vous va?

Arkiliknam
la source
Cela sonne bien, la question est de savoir ce que vous entendez par file d'attente? La plupart des jeux ont une réponse très rapide? "L'IA peut mettre en file d'attente différentes actions"
AturSams
Bon point. Il n'y a pas de file d'attente. Il a juste besoin de savoir s'il est occupé et sinon, d'exécuter une action.
Arkiliknam

Réponses:

18

Je ne pense pas qu'il existe une façon acceptée de mettre en œuvre ce concept, mais j'aimerais vraiment partager la façon dont je gère généralement cela dans mes jeux. C'est un peu une combinaison du modèle de conception Command et du modèle de conception Composite .

J'ai une classe de base abstraite pour les actions qui n'est rien de plus qu'un wrapper autour d'une Updateméthode qui est appelée chaque trame, et un Finishedindicateur pour indiquer quand l'action a fini de s'exécuter.

abstract class Action
{
    abstract void Update(float elapsed);
    bool Finished;
}

J'utilise également le modèle de conception composite pour créer un type d'actions capable d'héberger et d'exécuter d'autres actions. C'est aussi une classe abstraite. Se résume à:

abstract class CompositeAction : Action
{
    void Add(Action action) { Actions.Add(action); }
    List<Action> Actions;
}

Ensuite, j'ai deux implémentations d'actions composites, une pour l' exécution parallèle et une pour l'exécution séquentielle . Mais la beauté est que le parallèle et la séquence étant eux-mêmes des actions, ils peuvent être combinés afin de créer des flux d'exécution plus complexes.

class Parallel : CompositeAction
{
    override void Update(float elapsed) 
    {
        Actions.ForEach(a=> a.Update(elapsed));
        Actions.RemoveAll(a => a.Finished);
        Finished = Actions.Count == 0;
    }
}

Et celui qui régit les actions séquentielles.

class Sequence : CompositeAction
{
    override void Update(float elapsed) 
    {
        if (Actions.Count > 0) 
        {
            Actions[0].Update(elapsed);
            if (Actions[0].Finished)
                Actions.RemoveAt(0);
        }
        Finished = Actions.Count == 0;
    }
 }

Avec cela en place, il s'agit simplement de créer des implémentations d'actions concrètes et d'utiliser les actions Parallelet Sequencepour contrôler le flux d'exécution. Je termine avec un exemple:

// Create a parallel action to work as an action manager
Parallel actionManager = new Parallel();

// Send character1 to destination
Sequence actionGroup1 = new Sequence();
actionGroup1.Add(new MoveAction(character1, destination));
actionGroup1.Add(new TalkAction(character1, "Arrived at destination!"));
actionManager.Add(actionGroup1);

// Make character2 use a potion on himself
Sequence actionGroup2 = new Sequence();
actionGroup2.Add(new RemoveItemAction(character2, ItemType.Potion));
actionGroup2.Add(new SetHealthAction(character2, character2.MaxHealth));
actionGroup2.Add(new TalkAction(character2, "I feel better now!"));
actionManager.Add(actionGroup2);

// Every frame update the action manager
actionManager.Update(elapsed);

J'ai utilisé avec succès ce système pour piloter tout le gameplay dans une aventure graphique auparavant, mais il devrait probablement fonctionner à peu près n'importe quoi. Il était également assez simple d'ajouter d'autres types d'actions composites, qui étaient utilisées pour créer des boucles d'exécution et des conditions.

David Gouveia
la source
Cela ressemble à une très bonne solution. Par curiosité, comment alors faire savoir à l'interface utilisateur quoi dessiner? Vos objets de jeu (comme les personnages) contiennent-ils un état qui est utilisé pour identifier ce qui s'est passé à des fins de rendu, ou est-ce l'action elle-même qui fait cela?
Arkiliknam du
1
Habituellement, mes actions ne modifient que l' état des entités et toute modification de la sortie rendue se produit en conséquence de ce changement d'état, et non au moyen des actions elles-mêmes. Par exemple, avec un rendu en mode immédiat, aucune étape supplémentaire n'est requise, car la Drawméthode est déjà construite au-dessus de l'état de l'entité et les modifications sont automatiques. Dans un rendu en mode conservé tel que Flash, vous pouvez utiliser le modèle observable pour apporter des modifications à vos entités propager aux objets d'affichage, ou établir la connexion manuellement à l'intérieur de l'entité elle-même.
David Gouveia
1
Dans la première situation, disons que votre Characterclasse a une Positionpropriété et une Drawméthode qui lit la valeur actuelle de Positionet y dessine l'image correcte. Dans cette situation, il vous suffit de mettre à jour la valeur pour Positionque le résultat soit automatiquement affiché à l'écran.
David Gouveia
1
La deuxième situation est, lorsque votre propriété Characterpossède une Positionpropriété, mais qu'elle délègue le rendu à une sorte d' Spriteobjet qui est automatiquement rendu par un graphique de scène ou quelque chose. Dans cette situation, vous devez vous assurer que la position du personnage et celle du sprite sont toujours synchronisées, ce qui implique un peu plus de travail. Pourtant, dans les deux cas, je ne vois pas pourquoi le gestionnaire d'actions devrait avoir quelque chose à voir avec cela. :)
David Gouveia
1
Les deux méthodes ont des avantages et des inconvénients .. J'ai opté pour la deuxième méthode pour mon jeu 2D, et je l'ai parfois regretté car il est beaucoup plus compliqué de tout synchroniser. Mais il y a aussi des avantages, par exemple lorsque vous essayez de détecter quelle entité a été cliquée, ou ce qui doit ou non être dessiné, car tout ce qui sera rendu est contenu dans la même structure de données au lieu d'être dispersé entre N types d'entités.
David Gouveia