Muster zum Ausführen von Spielaktionen

11

Gibt es ein allgemein akzeptiertes Muster für die Ausführung verschiedener Aktionen innerhalb eines Spiels? Eine Möglichkeit, wie ein Spieler Aktionen ausführen kann und wie eine KI Aktionen ausführen kann, wie z. B. Bewegung, Angriff, Selbstzerstörung usw.

Ich habe derzeit eine abstrakte BaseAction, die .NET-Generika verwendet, um die verschiedenen Objekte anzugeben, die von den verschiedenen Aktionen zurückgegeben werden. Dies alles wird in einem Muster implementiert, das dem Befehl ähnlich ist, wobei jede Aktion für sich selbst verantwortlich ist und alles tut, was sie benötigt.

Mein Grund dafür, abstrakt zu sein, ist, dass ich möglicherweise einen einzelnen ActionHandler habe und AI einfach verschiedene Aktionen in die Warteschlange stellen kann, die die baseAction implementieren. Der Grund dafür ist, dass die verschiedenen Aktionen Ergebnisinformationen zurückgeben können, die für die Aktion relevant sind (da verschiedene Aktionen völlig unterschiedliche Ergebnisse im Spiel haben können), zusammen mit einigen gemeinsamen Implementierungen von beforeAction und afterAction.

Also ... gibt es einen akzeptierten Weg, dies zu tun, oder klingt das in Ordnung?

Arkiliknam
quelle
Es hört sich gut an, die Frage ist, was meinst du mit Warteschlange? Die meisten Spiele haben eine sehr schnelle Antwort? "KI kann verschiedene Aktionen in die Warteschlange stellen"
AturSams
Guter Punkt. Es gibt keine Warteschlange. Es muss nur wissen, ob beschäftigt ist, und wenn nicht, Aktion ausführen.
Arkiliknam

Antworten:

18

Ich glaube nicht, dass es einen akzeptierten Weg gibt, dieses Konzept umzusetzen, aber ich möchte wirklich gerne mitteilen, wie ich in meinen Spielen normalerweise damit umgehe. Es ist eine Art Kombination aus dem Command- Entwurfsmuster und dem zusammengesetzten Entwurfsmuster.

Ich habe eine abstrakte Basisklasse für Aktionen, die nichts anderes als ein Wrapper um eine UpdateMethode ist, die für jeden Frame aufgerufen wird, und ein FinishedFlag, das angibt, wann die Aktion beendet wurde.

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

Ich verwende auch das zusammengesetzte Entwurfsmuster, um eine Art von Aktionen zu erstellen, die andere Aktionen hosten und ausführen können. Auch dies ist eine abstrakte Klasse. Läuft darauf hinaus:

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

Dann habe ich zwei Implementierungen von zusammengesetzten Aktionen, eine für die parallele Ausführung und eine für die sequentielle Ausführung . Das Schöne ist jedoch, dass Parallelität und Sequenz selbst Aktionen sind und kombiniert werden können, um komplexere Ausführungsabläufe zu erstellen.

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

Und derjenige, der sequentielle Aktionen regelt.

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;
    }
 }

Wenn dies vorhanden ist, müssen lediglich konkrete Aktionsimplementierungen erstellt und die Aktionen Parallelund verwendet werden Sequence, um den Ausführungsfluss zu steuern. Ich werde mit einem Beispiel enden:

// 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);

Ich habe dieses System bereits erfolgreich verwendet, um das gesamte Gameplay in einem grafischen Abenteuer zu steuern, aber es sollte wahrscheinlich für so ziemlich alles funktionieren. Es war auch einfach genug, andere Arten von zusammengesetzten Aktionen hinzuzufügen, die zum Erstellen von Ausführungsschleifen und Bedingungen verwendet wurden.

David Gouveia
quelle
Das sieht nach einer sehr schönen Lösung aus. Wie lassen Sie dann aus Neugier die Benutzeroberfläche wissen, was zu zeichnen ist? Enthalten Ihre Spielobjekte (wie Charaktere) einen Status, mit dem identifiziert wird, was zu Renderingzwecken passiert ist, oder ist es die Aktion selbst, die dies tut?
Arkiliknam
1
Normalerweise ändern meine Aktionen nur den Status der Entitäten, und alle Änderungen an der gerenderten Ausgabe treten als Folge dieser Statusänderung auf, nicht durch die Aktionen selbst. Bei einem Renderer im Sofortmodus ist beispielsweise kein zusätzlicher Schritt erforderlich, da die DrawMethode bereits auf dem Status der Entität basiert und die Änderungen automatisch erfolgen. In einem Renderer im beibehaltenen Modus wie Flash können Sie das beobachtbare Muster verwenden, um Änderungen an Ihren Entitäten vorzunehmen, die auf die Anzeigeobjekte übertragen werden, oder die Verbindung manuell innerhalb der Entität selbst herstellen.
David Gouveia
1
Nehmen wir in der ersten Situation an, Ihre CharacterKlasse verfügt über eine PositionEigenschaft und eine DrawMethode, die den aktuellen Wert von liest Positionund dort das richtige Bild zeichnet. In dieser Situation müssen Sie nur den Wert aktualisieren Position, damit das Ergebnis automatisch auf dem Bildschirm angezeigt wird.
David Gouveia
1
Die zweite Situation ist, wenn Sie Charactereine PositionEigenschaft haben, diese aber das Rendern an eine Art SpriteObjekt delegiert, das automatisch von einem Szenendiagramm oder etwas anderem gerendert wird. In dieser Situation müssen Sie sicherstellen, dass sowohl die Position des Charakters als auch die Position des Sprites immer synchron sind, was etwas mehr Arbeit erfordert. In beiden Fällen verstehe ich jedoch nicht, warum der Aktionsmanager etwas damit zu tun haben sollte. :)
David Gouveia
1
Beide Methoden haben Vor- und Nachteile. Ich habe mich für die zweite Methode für mein 2D-Spiel entschieden und es manchmal bereut, weil es wesentlich komplizierter ist, alles synchron zu halten. Aber es gibt auch Vorteile, zum Beispiel beim Versuch zu erkennen, auf welche Entität geklickt wurde oder was gezeichnet werden sollte oder nicht, da alles, was gerendert wird, in derselben Datenstruktur enthalten ist, anstatt auf N Entitätstypen verteilt zu sein.
David Gouveia