Spielobjekte gruppieren

7

Ich versuche, ein einfaches rundenbasiertes Konsolenspiel (Befehlszeilenspiel) zu erstellen, bin jedoch verwirrt darüber, welchen Ansatz ich beim Erstellen von Klassen für Objekte verwenden soll. Mein Anliegen ist Wartbarkeit und Flexibilität.

Erste Ansatz:

class Weapon{
    ...
}

class Sword:Weapon{
    ...
}

class ShortSword:Sword{
    ...
}

class TwoHandedSword:Sword{
    ...
}    

class Axe:Weapon{
    ...
}

class HeavyAxe:Axe{
    ...
}

Zweiter Ansatz:

class Sword{
    SwordType type;
    ...
}

class Axe{
    AxeType type;
    ...
}

Was ist also besser, erstens oder zweitens? Warum?

Wichtiger Hinweis: Ich bin ein Anfänger in der Spieleentwicklung.

dpp
quelle

Antworten:

11

Im Wesentlichen, was Aron_dc sagte, obwohl ich noch weiter gehen würde. Im Allgemeinen sollten Sie versuchen, Ihre Klassenstruktur so flach wie möglich zu halten und Code (Algorithmen) von Daten (Attributen) zu trennen.

Sie benötigen nur eine Waffenklasse für Nahkampf- / Sofortschlagwaffen. Diese Klasse kann entweder sofort überprüfen, was sie getroffen hat, oder sie erhält ein Objekt, dem sie Schaden zufügen kann. Dies schließt Fernkampfwaffen ein, die wie ein Laser sofort getroffen werden (eine Waffe könnte es auch sein, wenn Sie sie nicht zu realistisch modellieren möchten).

class InstantWeapon{
  Sprite sprite; //should be inherited from EquippableObject, or DrawableGameObject, or something
  WeaponType weaponType;
  DamageType dmgType;
  Double dmgOnHit;
  ...

  void Hit(ObjectWithHitPoints o){...}
  void Hit(Ray hitRay)
}

Eine zweite Klasse wird für Partikellaichwaffen verwendet, beispielsweise für einen Feuerballstab. Diese Waffen beschädigen nicht direkt, sondern erzeugen ein neues Objekt, das sich nach seiner eigenen Logik bewegt und Schaden verursacht, wenn es mit einem Objekt kollidiert. In diesem Fall enthält das Partikel alle erforderlichen Informationen (Schadensmenge und -typ, Radius usw.) und es ist einfach, ihnen eine benutzerdefinierte Logik zu geben (Zielsuchrakete oder Verlust von Schaden, je länger es sich bewegt, ...).

class ParticleSpawner{
  InteractiveParticle sampleParticle;

  void Fire(Vector3 position, Vector3 direction){
    ParticleEngine.Add(sampleParticle.Clone());
  }
}

Je nachdem, wie komplex Ihre Waffe und Ihr Schadenssystem sein sollen, können Sie sich das Komponentenmuster ansehen . Das Grundprinzip ist, dass Sie eine WeaponKlasse und eine Liste mit mehreren Instanzen der DamageComponentKlasse haben würden. Die DamageComponentKlasse enthält den Algorithmus und die Daten für alle Waffeneffekte, die für diese eine Waffe gelten.

class Weapon{
  List<DamageComponent> damageComponents;

  void Fire(Ray hitRay){
    foreach(component : DamageComponent)
      component.Apply(hitRay);
  }
}

Jede Komponente kann jetzt einen Feuerball erzeugen, feindliche Rüstungen reduzieren, Standardschaden verursachen, einen "Feuer" -Effekt auf Ziele anwenden usw. Zusätzlich kann jede Komponente den Waffenschwung so handhaben, wie sie will. Holen Sie sich nur das Ziel direkt vor oder spalten Sie in einem weiten Bogen, etc ...

Beachten Sie auch, dass das Komponentensystem an anderen Stellen nützlich sein kann.

  • Verschiedene Arten von Rüstungskomponenten schützen unterschiedlich vor verschiedenen Schadensarten
  • Partikel auf Update-Komponenten können ein einzelnes Partikel auf unterschiedliche Weise beeinflussen
  • Fähigkeiten, die durch Fertigkeiten oder Objekte gewährt werden, müssen nur einmal implementiert werden, da sie dem Spieler einfach die Can-Fly- oder die Can-See-Underground-Komponente hinzufügen.

Im Allgemeinen können Sie viele kleine Komponenten erstellen, die für sich genommen nicht viel bewirken, aber äußerst komplexe Systeme bilden können, da ihre Kombinationen mit jeder neuen Implementierung exponentiell zunehmen.

Die daraus resultierenden Effekte des Systems können sehr komplex werden, was sehr schön ist, wenn Sie ein aufstrebendes Verhalten wünschen und Dinge auf komplexe Weise modellieren möchten. Dies macht es jedoch schwieriger, Dinge wie Waffenbalance oder feindliche Schwierigkeiten zu tun. Ein beliebtes Beispiel ist Diablo3 mit seinen zufälligen Fähigkeitskomponenten. Bestimmte Kombinationen sind viel stärker als die anderen.

Darcara
quelle
Ich habe gerade zwei wundervolle Artikel gefunden, die etwas detaillierter sind. Refactoring Game Entities mit Komponenten und dem Komponentenmuster
Darcara
8

Nachteile der ersten (Unterklasse):

  1. Sie überladen Ihren Code mit vielen Unterklassen, die Sie offensichtlich nicht benötigen
  2. Sie müssen Ihre Vererbungstiefe (?) im Auge behalten, wenn Sie etwas in Ihrer Waffenklassifizierungshierarchie ändern
  3. Ich denke nicht, dass dieser Ansatz viele Vorteile hat

Nachteile des zweiten (Strategiemuster):

  1. Sie haben noch viele Klassen (aber sie halten nicht so hart zusammen wie in der 1. Variante)

Vorteile des zweiten (Strategiemuster):

  1. Es gibt viele Möglichkeiten, Ihre Klassen auf diese Weise wiederzuverwenden. Willst du eine Kanone, die auch ein Schwert ist? Füge einfach die beiden Typen in einer Klasse zusammen und implementiere einen Supertyp SwordWithCannon;) (das kannst du mit dem ersten Ansatz nicht so einfach machen)
  2. Weniger Code
  3. Sie können sogar das Verhalten der Waffe während des Spiels ändern. Damit dein Schwert stumpf wird, ändere einfach den Schwerttyp in "bluntSword" (oder ähnliches).

Wenn sich Ihre Waffen nur in einigen Statistiken unterscheiden, müssen Sie nichts davon verwenden. Fügen Sie die Statistiken einfach in die Basisklasse ein und füllen Sie sie entsprechend Ihren Wünschen aus (boolesche Zweihandigkeit, int Schaden, Stringname, ....).

Aron_dc
quelle
Ich das ist weit von der Frage entfernt, aber es ist tatsächlich verwandt. Wie soll ich Daten speichern? Angenommen, ich habe ein Inventar. Soll ich Waffen und Rüstungen in einem Tisch aufbewahren, obwohl es sich um verschiedene Arten handelt? Rüstung hat Verteidigung, Waffe nicht, wenn ich Spaltenverteidigung und Schaden habe, soll man keinen Wert (null) haben, das ist ein schlechtes Datenbankdesign.
dpp
Was Sie beschreiben, ist ein breiter Tabellenansatz. Sie haben alle Spalten, die Sie für alle Elemente in einer Tabelle benötigen, und füllen nur diejenigen aus, die für dieses bestimmte Element wichtig sind. Persönlich würde ich eine Tabelle "Inventar" nehmen, die nur IDs für andere Tabellen enthält (die Waffen, Tränke, Zauber usw. enthalten). Ich bin mir nicht ganz sicher, ob Sie dort eine Forgein-Schlüsselbeziehung verwenden könnten, da der Schlüssel mit mehr als einer Tabelle verknüpft wäre, aber wenn er richtig programmiert ist, würden Sie ihn nicht benötigen. Ist es ein Einzelspieler- oder ein Mehrspieler-Spiel? Vielleicht benutzt die Verwendung einer Datenbank einen Vorschlaghammer, um eine Nuss zu knacken;)
Aron_dc
Oder sollte ich sie als binär speichern? Ich denke darüber nach, die Klassen serialisierbar zu machen, damit ich sie als XML speichern kann.
dpp
Entschuldigung, ich arbeite mit Datenbanken: D Welchen alternativen Speicher kann ich neben der Datenbank verwenden?
dpp
Wie Sie sagten, Sie könnten XML oder einfach jedes Format verwenden, das Sie sich vorstellen können. Sie sind nicht auf vorhandene Formate beschränkt, solange Sie Ihr eigenes Format analysieren können, können Sie es verwenden. Wenn Sie kein eigenes Format erfinden möchten, verwenden Sie zunächst XML- oder Nur-Text-Dateien. Diese sind leicht zu analysieren. Und Sie müssen nicht mit großen Datenbanken herumspielen (für eine kleine Datenmenge).
Aron_dc
2

Wenn Sie nur Parameter ändern, ist keine Vererbung erforderlich. Wenn Sie jedoch andere Funktionen hinzufügen möchten, empfehlen wir Ihnen, Unterklassen zu verwenden. Ich bezweifle, dass Sie in einem Konsolenspiel zusätzliche Funktionen haben, nur Parameter wie damageund addsAttributes.

Dies ist jedoch sehr spezifisch für die Implementierung.

jcora
quelle
2

Ich neige dazu, die Vererbung so weit wie möglich zu vermeiden, wenn es darum geht, Objekte anhand von Daten zu klassifizieren , die in Spielen verwendet werden , anstatt Systeme zu klassifizieren , die in Spielen verwendet werden. Was ich meine ist, es kann sinnvoll sein, eine AnimatorKlasse und dann eine zu haben SpriteAnimatorund die zu TileAnimatorerben, Animatorwenn die Verhaltensunterschiede in ihren Funktionen diskret fest codiert werden müssen. Wenn die Unterschiede im Verhalten von a Swordund an Axeeinfach durch die Werte in einer Gruppe von Statistiken bestimmt werden sollen, die am besten als Datencontainer für Spiele behandelt werden, sollten sie nur durch Parameter definiert werden, die alle Objekte gemeinsam haben.

Wenn Sie mit dem MVC-Muster vertraut sind, entspricht die Verwendung der Mehrfachvererbung zum Erstellen ähnlicher Datencontainer dem Erstellen einer Reihe von Controllern, die sich nur in den Werten in den Datenelementen unterscheiden. Offensichtlich ist es besser, nur einen Controller zu haben und sich auf das Modell zu verlassen, um die benötigten Werte abzurufen.

Sie haben nicht eingehend untersucht, welche Eigenschaften Ihre Klassen haben, aber das Waffeninventar scheint viel mehr von einem datengesteuerten Design zu profitieren . Haben Sie also nur eine Waffenklasse und weisen Sie ihr verschiedene Eigenschaften zu. Um sie zu benennen, ist der Name einfach ein schreibgeschütztes Zeichenfolgenmitglied.

Wenn Sie hybride Gegenstände haben möchten (wie oben SwordWithCannonerwähnt), definieren Sie möglicherweise eine Waffenklasse, die Eigenschaften mehrerer Waffenobjekte annehmen kann, d. H. in einem Vektor oder einer Liste, die einen oder mehrere Sätze von Eigenschaften enthält.

ChrisC
quelle