Hintergrund
Ich mache Spieleentwicklung als Hobby und suche nach einer besseren Möglichkeit, sie zu entwerfen. Derzeit verwende ich einen Standard-OOP-Ansatz (ich mache seit 8 Jahren Unternehmensentwicklung, daher erfolgt dies nicht mehr). Nehmen Sie zum Beispiel einen "Bösewicht"
public class Baddie:AnimatedSprite //(or StaticSprite if needed, which inherit Sprite)
{
//sprite base will have things like what texture to use,
//what the current position is, the base Update/Draw/GetInput methods, etc..
//an AnimatedSprite contains helpers to animated the player while
//a StaticSprite is just one that will draw whatever texture is there
}
Das Problem
Nehmen wir an, ich mache einen 2D-Plattformer und brauche den Bösewicht, um springen zu können. Normalerweise füge ich den entsprechenden Code in die Update / GetInput-Methoden ein. Wenn ich dann den Spieler kriechen, ducken, klettern lassen muss, wird der Code dorthin gehen.
Wenn ich nicht aufpasse, werden diese Methoden unübersichtlich, sodass ich am Ende Methodenpaare wie dieses erstelle
CheckForJumpAction(Input input)
und DoJump()
CheckforDuckAction(Input input)
und DoDuck()
GetInput sieht also so aus
public void DoInput(Input input)
{
CheckForJumpAction(input);
CheckForDuckAction(input);
}
und Update sieht aus wie
public void Update()
{
DoJump();
DoDuck();
}
Wenn ich ein anderes Spiel erstelle , in dem der Spieler springen und sich ducken muss, gehe ich normalerweise in ein Spiel mit der Funktionalität und kopiere es. Chaotisch, ich weiß. Deshalb suche ich etwas Besseres.
Lösung?
Ich mag es wirklich, wie Blend Verhaltensweisen hat, die ich an ein Element anhängen kann. Ich habe darüber nachgedacht, dasselbe Konzept in meinen Spielen zu verwenden. Schauen wir uns also dieselben Beispiele an.
Ich würde ein Basis-Verhaltensobjekt erstellen
public class Behavior
{
public void Update()
Public void GetInput()
}
Und damit kann ich Verhaltensweisen erzeugen. JumpBehavior:Behavior
undDuckBehavior:Behavior
Ich kann dann der Sprite-Basis eine Sammlung von Verhaltensweisen hinzufügen und jeder Entität hinzufügen, was ich brauche.
public class Baddie:AnimatedSprite
{
public Baddie()
{
this.behaviors = new Behavior[2];
this.behaviors[0] = new JumpBehavior();
//etc...
}
public void Update()
{
//behaviors.update
}
public GetInput()
{
//behaviors.getinput
}
}
Wenn ich jetzt Jump and Duck in vielen Spielen verwenden wollte, kann ich das Verhalten einfach überdenken. Ich könnte sogar eine Bibliothek für die üblichen machen.
Funktioniert es?
Was ich nicht herausfinden kann, ist, wie man den Zustand zwischen ihnen teilt. Bei Jump und Duck wirken sich beide nicht nur auf den aktuellen Teil der gezeichneten Textur aus, sondern auch auf den Status des Spielers. (Der Sprung wird mit der Zeit eine abnehmende Menge an Aufwärtskraft aufbringen, während die Ente nur die Bewegung stoppt, die Textur und die Kollisionsgröße des Bösewichts ändert.
Wie kann ich das zusammenbinden, damit es funktioniert? Soll ich Abhängigkeitseigenschaften zwischen den Verhaltensweisen erstellen? Sollte ich jedes Verhalten über das Elternteil wissen lassen und es direkt ändern? Eine Sache, die ich dachte, war, einen Delegaten in jedes Verhalten zu übergeben, um ausgeführt zu werden, wenn es ausgelöst wird.
Ich bin mir sicher, dass es weitere Probleme gibt, über die ich nachdenke, aber der gesamte Zweck besteht darin, dass ich diese Verhaltensweisen zwischen Spielen und Entitäten im selben Spiel problemlos wiederverwenden kann.
Also übergebe ich es dir. Möchten Sie erklären, wie / ob dies möglich ist? Hast du eine bessere Idee? Ich bin ganz Ohr.
Antworten:
Schauen Sie sich diese Präsentation an. Klingt ziemlich nah an dem Muster, nach dem Sie suchen. Dieses Muster unterstützt Verhaltensweisen und anhängbare Eigenschaften. Ich glaube nicht, dass die Präsentation dies erwähnt, aber Sie können auch anhängbare Ereignisse erstellen. Diese Idee ähnelt den in WPF verwendeten Abhängigkeitseigenschaften.
quelle
Für mich klingt dies wie ein fast lehrbuchartiger Fall für die Verwendung des Strategiemusters . In diesem Muster können Ihre generischen Verhaltensweisen in Schnittstellen definiert werden, mit denen Sie verschiedene Implementierungen von Verhaltensweisen zur Laufzeit austauschen können (denken Sie darüber nach, wie sich ein Power-Up auf die Sprung- oder Lauffähigkeit Ihres Charakters auswirken kann).
Für (ein erfundenes) Beispiel:
Jetzt können Sie verschiedene Arten von Sprüngen implementieren, die Ihr Charakter verwenden kann, ohne unbedingt die Details zu den einzelnen Sprüngen zu kennen:
Alle Charaktere, die Sie springen möchten, können gerade einen Verweis auf ein IJumpable enthalten und Ihre verschiedenen Implementierungen verbrauchen
Dann kann Ihr Code ungefähr so aussehen:
Jetzt können Sie diese Verhaltensweisen problemlos wiederverwenden oder später sogar erweitern, wenn Sie weitere Arten von Sprüngen hinzufügen oder mehr Spiele erstellen möchten, die auf einer einzigen Side-Scrolling-Plattform basieren.
quelle
Werfen Sie einen Blick auf die DCI-Architektur und sehen Sie sich die OO-Programmierung an, die möglicherweise hilfreich ist.
Das Implementieren einer Architektur im DCI-Stil in C # erfordert die Verwendung einfacher Domänenklassen und die Verwendung von Kontextobjekten (Verhalten) mit Erweiterungsmethoden und -schnittstellen, damit die Domänenklassen unter verschiedenen Rollen zusammenarbeiten können. Die Schnittstellen werden verwendet, um die für ein Szenario erforderlichen Rollen zu markieren. Domänenklassen implementieren die Schnittstellen (Rollen), die für das beabsichtigte Verhalten gelten. Die Zusammenarbeit zwischen den Rollen erfolgt in den Kontext- (Verhaltens-) Objekten.
Sie können eine Bibliothek mit allgemeinen Verhaltensweisen und Rollen erstellen, die von Domänenobjekten aus separaten Projekten gemeinsam genutzt werden können.
Siehe DCI in C # für Codebeispiele in C #.
quelle
Was ist mit der Übergabe eines Kontextobjekts an ein Verhalten, das Methoden zum Ändern des Status der von einem Verhalten betroffenen Sprites / Objekte bereitstellt? Wenn ein Objekt mehrere Verhaltensweisen aufweist, werden sie jeweils in einer Schleife aufgerufen, sodass sie den Kontext ändern können. Wenn eine bestimmte Änderung einer Eigenschaft die Änderung anderer Eigenschaften ausschließt / einschränkt, kann das Kontextobjekt Methoden zum Setzen von Flags bereitstellen, um diese Tatsache anzuzeigen, oder es kann einfach unerwünschte Änderungen ablehnen.
quelle