Wie implementiere ich spezielle Waffen- und Rüstungseffekte (z. B. Vampir, Heilig, Schadensschild, Dornen)?

7

Ich arbeite am Inventarsystem für mein Spiel und bin daran interessiert, wie ich zusätzliche Waffen- und Rüstungseigenschaften implementiere, die nicht nur Schaden oder Rüstung sind.

Wenn ich nur Schaden und Rüstung habe, ist die Gleichung einfach: Schaden = Waffenschaden - Rüstung;

Aber ich suche nach einer interessanteren Benutzererfahrung - nämlich nach Gegenständen mit 2-3 Eigenschaften, die verschiedene Spielmechaniken modifizieren.

Zum Beispiel kann ich eine Reihe von Flags verwenden und eine große if / else-Anweisung in meine Angriffsmethode schreiben und an geeigneten Stellen nach all diesen suchen:

if (Waffe ist vampirisch) {Angreifer für x heilen}

if (Waffe ist heilig) {Verteidiger-Typ prüfen, zusätzlichen Schaden hinzufügen}

if (Rüstung absorbiert den ersten x Schaden) {Reduziert den Schaden des Verteidigers um x}

Ein anderer Weg, den ich für besser hielt, ist das Designmuster des Dekorateurs, bei dem die Ausgabe einer Waffenkreation schrittweise in eine andere eingepackt wird:

public class Main {

    public static final void main(String[] args) {
    Weapon w = new MeleeWeapon();

        w = new Holy(w);
        w = new Vampiric;
        w = new Enchanted(w,1);

        Damage damage = w.damage();

    }

}

Es scheint mir, dass der Dekorateur es mir ermöglichen würde, flexibleren Code zu schreiben und später weitere Typen hinzuzufügen, aber ich bin mir nicht sicher, ob er nur die if / else-Anweisungen in die Methode für besondere Eigenschaften von Waffen verschiebt.

Alex Stone
quelle

Antworten:

7

Die Worte selbst helfen Ihnen: Eine Waffe ist nur ein Träger für Effekte. Einige sind augenblicklich (Schaden), andere sind länger (Gift).

Der Umfang der Effekte ist ebenfalls recht groß: Einige Effekte wirken sich auf den Waffenbesitzer aus, andere auf den Gegner. Und die meiste Zeit müssen Sie alle berücksichtigen (dennoch können Sie die Suche leicht beschleunigen, um zu wissen, welche zutreffend sind).

def Attack( attacker, receiver) :
    status = receiver.status() # original LP, MP, etc
    impacts = [] # the list of impacts (poison, speed, etc) to be applied
                 # impact need to be sortable by category, so we can 
                 # enhance them or cancel them
    for effect in attacker.effects:
        # accumulation the instantaneous impacts
         if effect.instantaneous:
             impacts.append( effects.apply(status, impacts) )
        # some effects last a while, an active instance will be carried by the receiver
        # those instance have a tick method and will stop after a moment and be removed
         elif effect.lasting:
             receiver.effects.append( effect.get_copy() )
         else:
             continue
    # we need to apply receiver effects as some may cancel some impacts of the attacker
    for effect in receiver.effects:        
         if effect.on_impacts: # effects that can modify impacts
             impacts.append( effects.apply(status, impacts) )
         elif effect.on_effects: # effects than can modify lasting effects
             receiver.effects = effect.filter(receiver.effects, status )
         else:
             continue


    # lastly, we apply all the impacts.
    status.change( impacts )
    receiver.status = status

Zum Beispiel würde die Berechnung eines Kriegers, der ein Schwert mit Giftschutz trägt, das von einem riesigen Skorpion gestochen wird, ungefähr so ​​aussehen:

Warrior.effects = [ PoisonProtection(.30), #effect.on_impact,
                    CannotBeStun(), #effect.on_effects,
                    FireDamage(+15%), #effect.instantaneous
                    SensibleToIce(+15%), #effect.instantaneous ]

Scorpion.effects = [ SimpleDamage(min=10, max=30),
                     PoisonDamage( 30),
                     LastingPoisonDamage( dmg_per_sec=3, length=15) ]

Dies muss erweitert werden, um Flächeneffekte zu verwalten, aber Sie erhalten eine allgemeine Vorstellung.

Lionel Barret
quelle
Ihre Antwort gefällt mir sehr gut, insbesondere, wie Effekte entweder sofort auftreten oder andere Effekte beeinflussen können. Ich habe darüber nachgedacht, wie man Dinge wie "kann nicht betäubt werden" implementiert, und hier haben Sie es!
Alex Stone
2

newWenn Sie wiederholt und die Sprünge zwischen den Speicherorten, an denen sich das ursprüngliche Objekt und seine verschiedenen Wrapper befinden, auftreten, ist dies teurer als die von Ihnen erwähnten umfangreichen Bedingungen oder der unten angebotene Ansatz. Außerdem würde ich in diesem Fall zögern, das Decorator-Muster zu verwenden, da es keine klare Reihenfolge gibt, in der die Decorators angewendet werden würden, wodurch der Code beim Debuggen möglicherweise verwirrend wird. Es ist vorzuziehen, alle Optionen linear aufzulisten, aber einzeln gekapselt.

Was ich vorschlagen würde, wären Funktionszeiger, Funktoren oder das Strategiemuster für Sprachen, die keine erstklassigen Funktionen unterstützen oder bei denen ansonsten ein starker Wunsch besteht, an festen Schnittstellen zu arbeiten.

Halten Sie in Ihrer Waffenklasse eine Reihe von Verweisen auf Ihre möglichen Funktionszeiger, Funktoren oder Strategien bereit, die die verschiedenen möglichen Effekte darstellen, und rufen Sie jede (nicht null) zu dem Zeitpunkt auf, zu dem Sie die Waffenverarbeitung durchführen müssen. Sie können diese Referenzen entweder als Mitglieder oder als Einträge in einem Array oder Wörterbuch speichern (dynamischer und mit einer Skript-Engine funktionsfähig, aber auch etwas langsamer). Versuchen Sie, eine Aktualisierungsphase beizubehalten, in der Sie alle Waffeneffekte und nicht nur den Schaden beheben .

PS Dies hängt ziemlich eng damit zusammen, wie oft wir OO-basierte Entity-Component-Systeme erstellen, obwohl hier unterschieden werden sollte, dass wir nicht jedes davon als Bestandteil eines Entity betrachten, sondern als Bestandteil von Eine Waffe, auf die normalerweise die Waffe oder Handkomponente einer Entität verweist. Das heißt, eine Waffe kann selbst eine Art Entität sein.

PPS Achten Sie beim Schreiben von (Echtzeit-) Spielen darauf, dass Sie herkömmliche (sprich: geschäftliche) Designmuster für die Softwareentwicklung anwenden. Nicht alle von ihnen sind hier annähernd so anwendbar wie in der ganzen Welt, da die Spielearchitektur ganz andere Bedenken erfordert.

Ingenieur
quelle