Wie kann ein flexibles Buff / Debuff-System implementiert werden?

66

Überblick:

Viele Spiele mit RPG-ähnlichen Statistiken ermöglichen Charakter-"Buffs", die von einfachen "Verursachen von 25% mehr Schaden" bis zu komplizierteren Dingen wie "Verursachen von 15 Schadenspunkten bei Treffern an Angreifer" reichen.

Die Besonderheiten der einzelnen Buff-Typen sind nicht wirklich relevant. Ich suche nach einer (vermutlich objektorientierten) Möglichkeit, mit willkürlichen Buffs umzugehen.

Einzelheiten:

In meinem speziellen Fall habe ich mehrere Charaktere in einer rundenbasierten Kampfumgebung, daher stellte ich mir Buffs vor, die an Ereignisse wie "OnTurnStart", "OnReceiveDamage" usw. gebunden sind. Vielleicht ist jeder Buff eine Unterklasse einer abstrakten Buff-Hauptklasse, in der nur die relevanten Ereignisse sind überladen. Dann könnte jedem Charakter ein Vektor von Buffs zugewiesen werden.

Ist diese Lösung sinnvoll? Ich kann mit Sicherheit feststellen, dass Dutzende von Ereignistypen erforderlich sind. Es fühlt sich so an, als ob eine neue Unterklasse für jeden Buff übertrieben ist und es scheint keine Buff- "Interaktionen" zuzulassen. Das heißt, wenn ich eine Begrenzung für Schadenserhöhungen implementieren wollte, sodass Sie, selbst wenn Sie 10 verschiedene Buffs hatten, die alle 25% zusätzlichen Schaden verursachen, nur 100% anstatt 250% zusätzlichen Schaden verursachen.

Und es gibt kompliziertere Situationen, die ich idealerweise kontrollieren könnte. Ich bin sicher, jeder kann sich Beispiele ausdenken, wie anspruchsvollere Fans möglicherweise auf eine Weise miteinander interagieren können, die ich als Spieleentwickler vielleicht nicht möchte.

Als relativ unerfahrener C ++ - Programmierer (ich habe C im Allgemeinen in eingebetteten Systemen verwendet) halte ich meine Lösung für einfach und nutze die objektorientierte Sprache wahrscheinlich nicht in vollem Umfang aus.

Gedanken? Hat jemand hier schon einmal ein ziemlich robustes Buff-System entworfen?

Bearbeiten: Bezüglich der Antwort (en):

Ich habe eine Antwort ausgewählt, die in erster Linie auf guten Details und einer soliden Antwort auf die von mir gestellte Frage beruhte, aber das Lesen der Antworten gab mir weitere Einblicke.

Vielleicht nicht überraschend, scheinen die verschiedenen Systeme oder optimierten Systeme in bestimmten Situationen besser zu funktionieren. Welches System für mein Spiel am besten funktioniert, hängt von der Art, der Varianz und der Anzahl der Buffs ab, die ich anwenden möchte.

In einem Spiel wie Diablo 3 (siehe unten), in dem fast jede Ausrüstung die Stärke eines Buffs verändern kann, sind die Buffs nur Charakterstatistiken. Ein System scheint eine gute Idee zu sein, wann immer es möglich ist.

Für die rundenbasierte Situation, in der ich mich befinde, ist der ereignisbasierte Ansatz möglicherweise geeigneter.

Auf jeden Fall hoffe ich immer noch, dass jemand mit einer ausgefallenen "OO" -Zauberkugel daherkommt, die es mir ermöglicht, einen Buff von +2 Bewegungsentfernung pro Runde anzuwenden , was 50% des Schadens ausmacht, der dem Buff des Angreifers zugefügt wird, und Ein Spieler teleportiert sich automatisch zu einem nahe gelegenen Plättchen, wenn er von einem Buff mit 3 oder mehr Plättchen Abstand in einem einzigen System angegriffen wird, ohne einen Buff mit +5 Stärke in eine eigene Unterklasse zu verwandeln .

Ich denke, das Nächste ist die Antwort, die ich markiert habe, aber der Boden ist noch offen. Vielen Dank an alle für den Input.

gkimsey
quelle
Ich poste dies nicht als Antwort, da ich nur ein Brainstorming mache, aber wie wäre es mit einer Liste von Buffs? Jeder Buff hat eine Konstante und einen Faktor-Modifikator. Konstante wäre +10 Schaden, Faktor wäre 1,10 für einen + 10% Schadensschub. Bei Ihren Schadensberechnungen durchlaufen Sie alle Stärkungszauber, um einen Gesamtmodifikator zu erhalten, und legen dann alle gewünschten Einschränkungen fest. Sie würden dies für jede Art von modifizierbarem Attribut tun. Für komplizierte Dinge benötigen Sie jedoch eine spezielle Fallmethode.
William Mariager
Im Übrigen hatte ich so etwas für mein Stats-Objekt bereits implementiert, als ich ein System für ausrüstbare Waffen und Zubehör baute. Wie du gesagt hast, ist es eine ausreichend gute Lösung für Buffs, die nur vorhandene Attribute modifizieren, aber natürlich möchte ich, dass bestimmte Buffs nach X Runden ablaufen, andere, sobald der Effekt Y-mal auftritt, usw. Hab ich nicht Erwähne dies in der Hauptfrage, da es schon sehr lange dauert.
Gkimsey
1
Wenn Sie über eine "onReceiveDamage" -Methode verfügen, die von einem Nachrichtensystem oder manuell oder auf andere Weise aufgerufen wird, sollte es einfach genug sein, einen Verweis darauf aufzunehmen, von wem / was Sie Schaden nehmen. Dann könnten Sie diese Informationen Ihrem
Richtig, ich hatte erwartet, dass jede Ereignisvorlage für die abstrakte Buff-Klasse solche relevanten Parameter enthält. Es würde sicherlich funktionieren, aber ich zögere, weil ich das Gefühl habe, dass es nicht gut skaliert. Es fällt mir schwer, mir ein MMORPG mit mehreren hundert verschiedenen Buffs vorzustellen. Für jeden Buff ist eine eigene Klasse definiert, die aus hundert verschiedenen Events besteht. Nicht, dass ich so viele Buffs mache (wahrscheinlich näher an 30), aber wenn es ein einfacheres, eleganteres oder flexibleres System gibt, würde ich es gerne verwenden. Flexibleres System = interessantere Buffs / Fähigkeiten.
Gkimsey
4
Dies ist keine gute Antwort auf das Interaktionsproblem, aber es scheint mir, dass das Dekorationsmuster hier gut zutrifft; Wende einfach mehr Buffs (Dekorateure) übereinander an. Vielleicht mit einem System, das die Interaktion durch "Zusammenführen" von Buffs bewältigt (zB 10x 25% verschmelzen zu einem 100% Buff).
ashes999

Antworten:

32

Dies ist ein kompliziertes Problem, da Sie über einige verschiedene Dinge sprechen, die (heutzutage) als "Buffs" zusammengefasst werden:

  • Modifikatoren für die Attribute eines Spielers
  • Spezialeffekte, die bei bestimmten Ereignissen auftreten
  • Kombinationen der oben genannten.

Ich implementiere immer die erste mit einer Liste der aktiven Effekte für einen bestimmten Charakter. Das Entfernen von der Liste, ob basierend auf der Dauer oder ausdrücklich, ist ziemlich trivial, daher werde ich das hier nicht behandeln. Jeder Effekt enthält eine Liste von Attributmodifikatoren und kann durch einfache Multiplikation auf den zugrunde liegenden Wert angewendet werden.

Dann wickle ich es mit Funktionen um, um auf die geänderten Attribute zuzugreifen. z.B.:

def get_current_attribute_value(attribute_id, criteria):
    val = character.raw_attribute_value[attribute_id]
    # Accumulate the modifiers
    for effect in character.all_effects:
        val = effect.apply_attribute_modifier(attribute_id, val, criteria)
    # Make sure it doesn't exceed game design boundaries
    val = apply_capping_to_final_value(val)
    return val

class Effect():
    def apply_attribute_modifier(attribute_id, val, criteria):
        if attribute_id in self.modifier_list:
            modifier = self.modifier_list[attribute_id]
            # Does the modifier apply at this time?
            if modifier.criteria == criteria:
                # Apply multiplicative modifier
                return val * modifier.amount
        else:
            return val

class Modifier():
    amount = 1.0 # default that has no effect
    criteria = None # applies all of the time

Auf diese Weise können Sie mühelos multiplikative Effekte anwenden. Wenn Sie auch additive Effekte benötigen, legen Sie fest, in welcher Reihenfolge Sie sie anwenden (wahrscheinlich zuletzt), und durchlaufen Sie die Liste zweimal. (Ich hätte wahrscheinlich separate Modifikatorlisten in Effect, eine für Multiplikative, eine für Additive).

Mit dem Kriterienwert können Sie "+ 20% vs Undead" implementieren. Legen Sie den UNDEAD-Wert für den Effekt fest und übergeben Sie nur den UNDEAD-Wert an get_current_attribute_value() wenn Sie einen Schadenswurf gegen einen untoten Gegner berechnen.

Übrigens wäre ich nicht versucht, ein System zu schreiben, das Werte direkt auf den zugrunde liegenden Attributwert anwendet und nicht anwendet - das Endergebnis ist, dass Ihre Attribute aufgrund von Fehlern sehr wahrscheinlich vom beabsichtigten Wert abweichen. (Wenn Sie z. B. etwas mit 2 multiplizieren, es dann aber mit einer Kappe abschließen und es erneut durch 2 dividieren, ist es niedriger als es begonnen hat.)

Bei ereignisbasierten Effekten, z. B. "Füge Angreifern bei Treffern 15 Schadenspunkte zu", können Sie Methoden für die Effektklasse hinzufügen. Wenn Sie jedoch ein eindeutiges und willkürliches Verhalten wünschen (z. B. einige Effekte für das oben genannte Ereignis können Schäden widerspiegeln, andere können Sie heilen oder Sie werden zufällig teleportiert, was auch immer), benötigen Sie benutzerdefinierte Funktionen oder Klassen, um damit umzugehen. Sie können Ereignishandlern für den Effekt Funktionen zuweisen und dann die Ereignishandler für alle aktiven Effekte aufrufen.

# This is a method on a Character, called during combat
def on_receive_damage(damage_info):
    for effect in character.all_effects:
        effect.on_receive_damage(character, damage_info)

class Effect():
    self.on_receive_damage_handler = DoNothing # a default function that does nothing
    def on_receive_damage(character, damage_info):
        self.on_receive_damage_handler(character, damage_info)

def reflect_damage(character, damage_info):
    damage_info.attacker.receive_damage(15)

reflect_damage_effect = new Effect()
reflect_damage_effect.on_receive_damage_handler = reflect_damage
my_character.all_effects.add(reflect_damage_effect)

Natürlich verfügt Ihre Effect-Klasse für jeden Ereignistyp über einen Event-Handler, und Sie können so vielen Handlerfunktionen zuweisen, wie Sie jeweils benötigen. Sie müssen Effect nicht in Unterklassen unterteilen, da jede durch die Zusammensetzung der enthaltenen Attributmodifikatoren und Ereignishandler definiert wird. (Es wird wahrscheinlich auch einen Namen, eine Dauer usw. enthalten.)

Kylotan
quelle
2
+1 für exzellentes Detail. Dies ist die Antwort, die der offiziellen Beantwortung meiner Frage am nächsten kommt, wie ich gesehen habe. Das grundlegende Setup hier scheint viel Flexibilität und eine kleine Abstraktion der sonst möglicherweise chaotischen Spielelogik zu ermöglichen. Wie Sie sagten, würden die funkyeren Effekte immer noch ihre eigenen Klassen benötigen, aber das erledigt den Großteil der Anforderungen eines typischen "Buff" -Systems, denke ich.
Gkimsey
+1 für das Hervorheben der hier verborgenen konzeptuellen Unterschiede. Nicht alle funktionieren mit derselben ereignisbasierten Aktualisierungslogik. Eine völlig andere Anwendung finden Sie in der Antwort von @ Ross. Beide müssen nebeneinander existieren.
Dienstag,
22

In einem Spiel, an dem ich mit einem Freund für eine Klasse gearbeitet habe, haben wir ein Buff / Debuff-System für den Fall erstellt, dass der Benutzer in hohem Gras gefangen ist und Geschwindigkeitsplättchen hat und was nicht und einige kleinere Dinge wie Blutungen und Gifte.

Die Idee war einfach, und obwohl wir sie in Python angewendet haben, war sie ziemlich effektiv.

Grundsätzlich ist hier, wie es ging:

  • Benutzer hatte eine Liste der aktuell angewendeten Buffs und Debuffs
  • Buffs verfügen über eine Vielzahl von Attributen wie Dauer, Name und Text für die Anzeige von Informationen und die Lebensdauer. Die wichtigsten sind die Zeit am Leben, die Dauer und ein Verweis auf den Schauspieler, auf den dieser Buff angewendet wird.
  • Wenn der Buff über player.apply (buff / debuff) an den Player angehängt wird, wird eine start () -Methode aufgerufen. Dadurch werden die kritischen Änderungen auf den Player angewendet, z. B. Geschwindigkeit erhöhen oder Verlangsamung.
  • Wir würden dann jeden Buff in einer Update-Schleife durchlaufen und die Buffs aktualisieren, dies würde ihre Lebenszeit verlängern. Unterklassen implementieren Dinge wie das Vergiften des Players, das Erteilen von HP für den Player im Laufe der Zeit usw.
  • Wenn der Buff für "timeAlive> = duration" ausgeführt wurde, entfernte die Aktualisierungslogik den Buff und rief eine finish () -Methode auf, die von der Aufhebung der Geschwindigkeitsbegrenzungen für einen Spieler bis zur Verursachung eines kleinen Radius (man denke an einen Bombeneffekt) reicht nach einem DoT)

Wie man nun Buffs aus der ganzen Welt anwendet, ist eine andere Geschichte. Hier ist allerdings mein Anlass zum Nachdenken.

Ross
quelle
1
Das klingt nach einer besseren Erklärung dessen, was ich oben zu beschreiben versuchte. Es ist relativ einfach, auf jeden Fall leicht zu verstehen. Sie haben dort im Wesentlichen drei "Ereignisse" erwähnt (OnApply, OnTimeTick, OnExpired), um dies mit meinem Denken in Verbindung zu bringen. So wie es ist, würde es Dinge wie das Zurückgeben von Schaden bei Treffern und so weiter nicht unterstützen, aber es skaliert besser für viele Buffs. Ich möchte lieber nicht einschränken, was meine Stärkungszauber tun können (was = die Anzahl der Ereignisse einschränken, die von der Hauptspiellogik aufgerufen werden müssen), aber die Stärkungsskalierbarkeit ist möglicherweise wichtiger. Danke für deinen Beitrag!
Gkimsey
Ja, wir haben so etwas nicht implementiert. Es klingt wirklich ordentlich und ein tolles Konzept (eine Art Dornenfan).
Ross
@gkimsey Für Dinge wie Dornen und andere passive Stärkungszauber würde ich die Logik in deiner Mob-Klasse als passiven Wert implementieren, der Schaden oder Gesundheit ähnelt, und diesen Wert erhöhen, wenn der Stärkungszauber angewendet wird. Dies vereinfacht eine Menge der Fall , wenn Sie mehrere Dornen Buffs sowie halten die Schnittstelle sauber (10 Buffs eher würde zeigen , 1 Retour Schaden als 10) und lässt das Buff - System einfach bleiben.
3Doubloons
Dies ist ein fast intuitiv einfacher Ansatz, aber ich begann über mich selbst nachzudenken, als ich Diablo 3 spielte. Ich bemerkte, dass der Lebensraub, das Leben bei einem Treffer, der Schaden an Nahkampfangreifern usw. ihre eigenen Werte im Charakterfenster waren. Zugegeben, D3 hat nicht das komplizierteste Poliersystem oder die kompliziertesten Interaktionen der Welt, aber es ist kaum trivial. Das macht sehr viel Sinn. Dennoch gibt es möglicherweise 15 verschiedene Buffs mit 12 verschiedenen Effekten, die in diese Kategorie fallen würden. Scheint seltsam,
wenn
11

Ich bin mir nicht sicher, ob Sie das noch lesen, aber ich habe lange mit solchen Problemen zu kämpfen.

Ich habe zahlreiche verschiedene Arten von Affektsystemen entworfen. Ich werde sie jetzt kurz durchgehen. Dies alles basiert auf meinen Erfahrungen. Ich behaupte nicht, alle Antworten zu kennen.


Statische Modifikatoren

Diese Art von System basiert hauptsächlich auf einfachen ganzen Zahlen, um Änderungen zu bestimmen. Zum Beispiel +100 bis max. HP, +10 für Angriffe und so weiter. Dieses System könnte auch Prozente handhaben. Sie müssen nur sicherstellen, dass das Stapeln nicht außer Kontrolle gerät.

Ich habe die generierten Werte für diese Art von System nie wirklich zwischengespeichert. Wenn ich zum Beispiel die maximale Gesundheit von etwas anzeigen möchte, würde ich den Wert sofort generieren. Dies verhinderte, dass die Dinge fehleranfällig und für alle Beteiligten einfacher zu verstehen waren.

(Ich arbeite in Java, daher ist das Folgende Java-basiert, aber es sollte mit einigen Änderungen für andere Sprachen funktionieren.) Dieses System kann leicht mit Aufzählungen für die Änderungstypen und dann mit ganzen Zahlen durchgeführt werden. Das Endergebnis kann in eine Sammlung gestellt werden, die nach Schlüsseln und Werten geordnete Paare enthält. Dies wird eine schnelle Suche und Berechnung sein, so dass die Leistung sehr gut ist.

Insgesamt funktioniert es sehr gut, wenn nur statische Modifikatoren verwendet werden. Code muss jedoch an den richtigen Stellen vorhanden sein, damit die Modifizierer verwendet werden können: getAttack, getMaxHP, getMeleeDamage usw.

Wo diese Methode (für mich) versagt, ist die Interaktion zwischen Buffs sehr komplex. Es gibt keinen wirklich einfachen Weg, um Interaktion zu haben, außer durch ein bisschen Ghetto. Es gibt einige einfache Interaktionsmöglichkeiten. Dazu müssen Sie die Art und Weise ändern, in der Sie die statischen Modifikatoren speichern. Anstatt eine Aufzählung als Schlüssel zu verwenden, verwenden Sie einen String. Diese Zeichenfolge ist der Name der Aufzählung + zusätzliche Variable. 9 Mal von 10 wird die zusätzliche Variable nicht verwendet, daher behalten Sie den Namen der Aufzählung als Schlüssel bei.

Lassen Sie uns ein kurzes Beispiel machen: Wenn Sie in der Lage sein möchten, Schaden an untoten Kreaturen zu ändern, könnten Sie ein geordnetes Paar wie das folgende haben: (DAMAGE_Undead, 10) DAMAGE ist die Aufzählung und Undead ist die zusätzliche Variable. Während Ihres Kampfes können Sie also Folgendes tun:

dam += attacker.getMod(Mod.DAMAGE + npc.getRaceFamily()); //in this case the race family would be undead

Wie auch immer, es funktioniert ziemlich gut und ist schnell. Aber es scheitert an komplexen Interaktionen und hat überall "speziellen" Code. Betrachten Sie zum Beispiel die Situation „25% Chance, sich beim Tod zu teleportieren“. Dies ist eine "ziemlich" komplexe. Das obige System kann damit umgehen, aber es ist nicht einfach, da Sie Folgendes benötigen:

  1. Bestimmen Sie, ob der Spieler diesen Mod hat.
  2. Haben Sie irgendwo Code, um die Teleportation auszuführen, wenn dies erfolgreich ist. Der Speicherort dieses Codes ist eine Diskussion für sich!
  3. Holen Sie sich die richtigen Daten aus der Mod-Map. Was bedeutet der Wert? Ist es der Raum, in den sie sich auch teleportieren? Was ist, wenn ein Spieler zwei Teleport-Mods hat? Werden die Beträge nicht addiert ?????? FEHLER!

Das bringt mich zu meinem nächsten:


Das ultimative komplexe Buff-System

Ich habe einmal versucht, ein 2D-MMORPG selbst zu schreiben. Das war ein schrecklicher Fehler, aber ich habe viel gelernt!

Ich habe das Affektsystem dreimal umgeschrieben. Die erste verwendete eine weniger mächtige Variante der obigen. Der zweite war, worüber ich sprechen werde.

Dieses System hatte eine Reihe von Klassen für jede Modifikation, also Dinge wie: ChangeHP, ChangeMaxHP, ChangeHPByPercent, ChangeMaxByPercent. Ich hatte eine Million dieser Leute - sogar Dinge wie TeleportOnDeath.

Meine Klassen hatten Dinge, die folgendes bewirken würden:

  • applyAffect
  • removeAffect
  • checkForInteraction <--- wichtig

Anwenden und entfernen erklären sich von selbst (obwohl der Affekt bei Dingen wie Prozenten protokollieren würde, um wie viel die HP erhöht wurden, um sicherzustellen, dass der Affekt nachlässt und nur den hinzugefügten Betrag entfernt. Dies war fehlerhaft, lol und Ich habe lange gebraucht, um sicherzugehen, dass es richtig war. Ich habe immer noch kein gutes Gefühl dabei.).

Die checkForInteraction-Methode war ein schrecklich komplexer Code. In jeder der Affects-Klassen (dh ChangeHP) muss der Code festgelegt werden, ob dieser durch den Eingabeeffekt geändert werden soll. Also zum Beispiel, wenn Sie so etwas hatten ...

  • Buff 1: Verursacht beim Angriff 10 Feuerschaden
  • Buff 2: Erhöht jeglichen Feuerschaden um 25%.
  • Buff 3: Erhöht jeglichen Feuerschaden um 15.

Die checkForInteraction-Methode würde alle diese Auswirkungen behandeln. Um dies zu tun, musste jeder Effekt auf ALLE Spieler in der Nähe überprüft werden !! Dies ist darauf zurückzuführen, dass ich über einen bestimmten Bereich hinweg mit mehreren Spielern zusammengearbeitet hatte. Dies bedeutet, dass der Code NIEMALS spezielle Aussagen wie oben hatte - "Wenn wir gerade gestorben sind, sollten wir nach Teleportern nach dem Tod suchen". Dieses System würde es automatisch zum richtigen Zeitpunkt richtig handhaben.

Der Versuch, dieses System zu schreiben, dauerte ungefähr 2 Monate und ließ den Kopf mehrmals explodieren. JEDOCH war es WIRKLICH mächtig und konnte unglaublich viel bewirken - besonders wenn man die folgenden zwei Fakten für die Fähigkeiten in meinem Spiel berücksichtigt: 1. Sie hatten Zielbereiche (dh: Single, Self, Group Only, PB AE Self , PB AE-Ziel, zielgerichtete AE und so weiter). 2. Fähigkeiten können mehr als einen Einfluss auf sie haben.

Wie ich oben erwähnte, war dies das 2. oder 3. Affektsystem für dieses Spiel. Warum bin ich davon weggezogen?

Dieses System hatte die schlechteste Leistung, die ich je gesehen habe! Es war furchtbar langsam, da es so viel Zeit in Anspruch nehmen musste, um alles zu überprüfen, was vor sich ging. Ich habe versucht, es zu verbessern, hielt es jedoch für gescheitert.

Also kommen wir zu meiner dritten Version (und einer anderen Art von Buff-System):


Komplexe Affektklasse mit Handlern

Das ist also so ziemlich eine Kombination der ersten beiden: Wir können statische Variablen in einer Affect-Klasse haben, die viele Funktionen und zusätzliche Daten enthält. Rufen Sie dann einfach Handler auf (für mich sind es eher statische Dienstprogrammmethoden als Unterklassen für bestimmte Aktionen. Aber ich bin sicher, Sie können auch Unterklassen für Aktionen verwenden, wenn Sie dies möchten), wenn wir etwas tun möchten.

Die Affekt-Klasse würde alle wichtigen Dinge enthalten, wie Zieltypen, Dauer, Anzahl der Verwendungen, Möglichkeit zur Ausführung und so weiter und so fort.

Wir müssten noch spezielle Codes hinzufügen, um mit den Situationen fertig zu werden, zum Beispiel beim Tod teleportieren. Wir müssten dies immer noch manuell im Kampfcode überprüfen, und wenn es existiert, würden wir eine Liste der Auswirkungen erhalten. Diese Liste der Effekte enthält alle aktuell auf den Spieler angewendeten Effekte, die sich mit dem Teleportieren nach dem Tod befasst haben. Dann schauten wir uns jeden einzelnen an und überprüften, ob er ausgeführt wurde und erfolgreich war (wir hörten beim ersten erfolgreichen auf). Wenn es erfolgreich war, haben wir einfach den Handler angerufen, um dies zu erledigen.

Interaktion kann gemacht werden, wenn Sie auch wollen. Es müsste nur der Code geschrieben werden, um nach bestimmten Stärkungen für die Spieler / etc. Zu suchen. Da es eine gute Leistung hat (siehe unten), sollte es ziemlich effizient sein, dies zu tun. Es würde einfach komplexere Handler und so weiter brauchen.

So hat es eine Menge der Leistung des ersten Systems und immer noch viel Komplexität wie das zweite (aber nicht so viel). Zumindest in Java können Sie einige knifflige Dinge tun, um in den meisten Fällen die Leistung von fast der ersten zu erreichen (z. B .: eine Aufzählungskarte ( http://docs.oracle.com/javase/6/docs/api/java) /util/EnumMap.html ) mit Enums als Schlüsseln und ArrayList von Affects als Werten: So können Sie schnell feststellen, ob Sie Affects haben (da die Liste 0 wäre oder die Map die Enumeration nicht hätte) und nicht Es macht mir nichts aus, über die Affektlisten der Spieler zu iterieren, wenn wir sie zu diesem Zeitpunkt benötigen. Ich optimiere sie später, wenn es zu einem Problem wird.

Ich öffne gerade mein MUD, das im Jahr 2005 endete, erneut (und schreibe das Spiel in Java anstatt in der FastROM-Codebasis, in der es ursprünglich enthalten war). Vor kurzem habe ich festgestellt, wie ich mein Buff-System implementieren möchte. Ich werde dieses System verwenden, weil es in meinem vorherigen fehlgeschlagenen Spiel gut funktioniert hat.

Hoffentlich findet jemand irgendwo ein paar dieser Erkenntnisse nützlich.

Dayrinni
quelle
6

Eine andere Klasse (oder adressierbare Funktion) für jeden Buff ist kein Overkill, wenn sich das Verhalten dieser Buffs voneinander unterscheidet. Eine Sache wäre, + 10% oder + 20% Buffs zu haben (was natürlich besser als zwei Objekte derselben Klasse dargestellt werden würde), die andere wäre, völlig andere Effekte zu implementieren, die ohnehin benutzerdefinierten Code erfordern würden. Ich glaube jedoch, dass es besser ist, Standardmethoden zum Anpassen der Spiellogik zu haben jeden Buff tun zu lassen, was ihm gefällt (und sich möglicherweise auf unvorhergesehene Weise gegenseitig zu stören, wodurch das Gleichgewicht des Spiels gestört wird).

Ich würde vorschlagen, jeden "Angriffszyklus" in Schritte zu unterteilen, wobei jeder Schritt einen Basiswert, eine geordnete Liste von Modifikationen, die auf diesen Wert angewendet werden können (möglicherweise begrenzt), und eine endgültige Obergrenze hat. Jede Änderung hat standardmäßig eine Identitätsumwandlung und kann durch null oder mehr Buffs / Debuffs beeinflusst werden. Die Einzelheiten jeder Modifikation hängen von dem angewendeten Schritt ab. Wie der Zyklus implementiert wird, liegt bei Ihnen (einschließlich der Option einer ereignisgesteuerten Architektur, wie Sie bereits besprochen haben).

Ein Beispiel für einen Angriffszyklus könnte sein:

  • Spielerangriff berechnen (Basis + Mods);
  • gegnerische Verteidigung berechnen (Basis + Mods);
  • Mach den Unterschied (und wende Modifikationen an) und bestimme den Grundschaden.
  • Berechnen Sie alle Parier- / Rüstungseffekte (Modifikationen für Grundschaden) und richten Sie Schaden an.
  • Berechnen Sie den Rückstoßeffekt (Modifikationen des Grundschadens) und wenden Sie ihn auf den Angreifer an.

Das Wichtigste ist, dass je früher im Zyklus ein Buff angewendet wird, desto mehr Wirkung hat er auf das Ergebnis . Wenn Sie also einen "taktischeren" Kampf wollen (bei dem die Spielerfähigkeiten wichtiger sind als die Charakterebene), können Sie viele Buffs / Debuffs für die Basisstatistiken erstellen. Wenn Sie einen "ausgewogeneren" Kampf wollen (bei dem das Level wichtiger ist - wichtig bei MMOGs, um die Fortschrittsrate zu begrenzen), verwenden Sie Buffs / Debuffs erst später im Zyklus.

Die Unterscheidung zwischen "Modifikationen" und "Buffs", die ich zuvor erwähnt habe, hat einen Zweck: Entscheidungen über Regeln und Ausgewogenheit können auf dem ersteren implementiert werden, sodass Änderungen an diesen nicht in Änderungen an jeder Klasse des letzteren berücksichtigt werden müssen. OTOH, die Anzahl und Arten von Buffs sind nur durch Ihre Vorstellungskraft begrenzt, da jeder von ihnen sein gewünschtes Verhalten ausdrücken kann, ohne jegliche mögliche Interaktion zwischen ihnen und den anderen (oder sogar der Existenz anderer überhaupt) berücksichtigen zu müssen.

Beantworten Sie also die Frage: Erstellen Sie keine Klasse für jeden Buff, sondern eine für jede (Art von) Modifikation und binden Sie die Modifikation an den Angriffszyklus und nicht an den Charakter. Die Buffs können einfach eine Liste von (Modifikations-, Schlüssel-, Wert-) Tupeln sein, und Sie können einen Buff auf einen Charakter anwenden, indem Sie ihn einfach zum Buff-Satz des Charakters hinzufügen / daraus entfernen. Dies reduziert auch das Fenster für Fehler, da die Statistiken des Charakters Werte der Stärkungszauber überhaupt nicht geändert werden müssen (so besteht weniger Risiko, dass ein Wert nach Ablauf eines Stärkungszauber auf den falschen Wert zurückgesetzt wird).

mgibsonbr
quelle
Dies ist ein interessanter Ansatz, da er irgendwo zwischen den beiden Implementierungen liegt, die ich in Betracht gezogen hatte - das heißt, entweder nur Buffs auf relativ einfache Modifikatoren für Status- und Ergebnisschaden zu beschränken oder ein sehr robustes System mit hohem Overhead zu erstellen, das mit allem umgehen kann. Dies ist eine Art Erweiterung der ersteren, um die "Dornen" unter Beibehaltung einer einfachen Schnittstelle zu ermöglichen. Ich glaube nicht, dass es das Wundermittel für das ist, was ich brauche, aber es macht das Balancieren auf jeden Fall viel einfacher als andere Ansätze. Danke für deinen Beitrag!
gkimsey
3

Ich weiß nicht, ob Sie es noch lesen, aber hier ist, wie ich es jetzt mache (Code basiert auf UE4 und C ++). Nachdem ich mehr als zwei Wochen (!!) über das Problem nachgedacht hatte, fand ich schließlich Folgendes:

http://gamedevelopment.tutsplus.com/tutorials/ using-the-composite-design-pattern-for-an-rpg-attributes-system--gamedev-243

Und ich dachte, dass es gar nicht so schlecht ist, ein einzelnes Attribut innerhalb von class / struct zu kapseln. Beachten Sie jedoch, dass ich das in UE4 integrierte Code-Reflektionssystem sehr gut nutzen kann. Ohne einige Nacharbeiten ist dies möglicherweise nicht überall geeignet.

In jedem Fall habe ich damit begonnen, Attribute in eine einzelne Struktur zu packen:

USTRUCT(BlueprintType)
struct GAMEATTRIBUTES_API FGAAttributeBase
{
    GENERATED_USTRUCT_BODY()
public:
    UPROPERTY()
        FName AttributeName;
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Value")
        float BaseValue;
    /*
        This is maxmum value of this attribute.
    */
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Value")
        float ClampValue;
protected:
    float BonusValue;
    //float OldCurrentValue;
    float CurrentValue;
    float ChangedValue;

    //map of modifiers.
    //It could be TArray, but map seems easier to use in this case
    //we need to keep track of added/removed effects, and see 
    //if this effect affected this attribute.
    TMap<FGAEffectHandle, FGAModifier> Modifiers;

public:

    inline float GetFinalValue(){ return BaseValue + BonusValue; };
    inline float GetCurrentValue(){ return CurrentValue; };
    void UpdateAttribute();

    void Add(float ValueIn);
    void Subtract(float ValueIn);

    //inline float GetCurrentValue()
    //{
    //  return FMath::Clamp<float>(BaseValue + BonusValue + AccumulatedBonus, 0, GetFinalValue());;
    //}

    void AddBonus(const FGAModifier& ModifiersIn, const FGAEffectHandle& Handle);
    void RemoveBonus(const FGAEffectHandle& Handle);

    void InitializeAttribute();

    void CalculateBonus();

    inline bool operator== (const FGAAttributeBase& OtherAttribute) const
    {
        return (OtherAttribute.AttributeName == AttributeName);
    }

    inline bool operator!= (const FGAAttributeBase& OtherAttribute) const
    {
        return (OtherAttribute.AttributeName != AttributeName);
    }

    inline bool IsValid() const
    {
        return !AttributeName.IsNone();
    }
    friend uint32 GetTypeHash(const FGAAttributeBase& AttributeIn)
    {
        return AttributeIn.AttributeName.GetComparisonIndex();
    }
};

Es ist noch nicht fertig, aber die Grundidee ist, dass diese Struktur ihren internen Zustand verfolgt. Attribute können nur von Effects geändert werden. Der Versuch, sie direkt zu ändern, ist unsicher und für Designer nicht zugänglich. Ich gehe davon aus, dass alles, was mit Attributen interagieren kann, Effekt ist. Einschließlich Pauschalboni von Gegenständen. Wenn ein neuer Gegenstand ausgerüstet ist, wird ein neuer Effekt (zusammen mit dem Griff) erstellt und der speziellen Karte hinzugefügt, die Boni mit unbegrenzter Dauer enthält (die vom Spieler manuell entfernt werden müssen). Wenn ein neuer Effekt angewendet wird, wird ein neues Handle für diesen Effekt erstellt (das Handle ist nur int, umbrochen mit struct). Dieses Handle wird dann als Mittel zur Interaktion mit diesem Effekt herumgereicht und überwacht, ob es sich um einen Effekt handelt immer noch aktiv. Wenn der Effekt entfernt wird, wird sein Handle an alle interessierten Objekte gesendet.

Der wirklich wichtige Teil davon ist TMap (TMap is Hashed Map). FGAModifier ist eine sehr einfache Struktur:

struct FGAModifier
{
    EGAAttributeOp AttributeMod;
    float Value;
};

Es enthält die Art der Änderung:

UENUM()
enum class EGAAttributeOp : uint8
{
    Add,
    Subtract,
    Multiply,
    Divide,
    Set,
    Precentage,

    Invalid
};

Und Wert, der der endgültige berechnete Wert ist, den wir auf das Attribut anwenden werden.

Wir fügen neue Effekte mit einfachen Funktionen hinzu und rufen dann auf:

void FGAAttributeBase::CalculateBonus()
{
    float AdditiveBonus = 0;
    auto ModIt = Modifiers.CreateConstIterator();
    for (ModIt; ModIt; ++ModIt)
    {
        switch (ModIt->Value.AttributeMod)
        {
        case EGAAttributeOp::Add:
            AdditiveBonus += ModIt->Value.Value;
                break;
            default:
                break;
        }
    }
    float OldBonus = BonusValue;
    //calculate final bonus from modifiers values.
    //we don't handle stacking here. It's checked and handled before effect is added.
    BonusValue = AdditiveBonus; 
    //this is absolute maximum (not clamped right now).
    float addValue = BonusValue - OldBonus;
    //reset to max = 200
    CurrentValue = CurrentValue + addValue;
}

Diese Funktion soll den gesamten Bonusstapel neu berechnen, jedes Mal, wenn ein Effekt hinzugefügt oder entfernt wird. Die Funktion ist noch nicht abgeschlossen (wie Sie sehen können), aber Sie können sich einen Überblick verschaffen.

Mein derzeit größter Kritikpunkt ist die Behandlung des Schadens- / Heilungsattributs (ohne dass der gesamte Stapel neu berechnet werden muss). Ich denke, ich habe dies etwas gelöst, aber es sind noch weitere Tests erforderlich, um 100% zu erreichen.

In jedem Fall werden Attribute wie folgt definiert (+ unwirkliche Makros, hier weggelassen):

FGAAttributeBase Health;
FGAAttributeBase Energy;

usw.

Ich bin mir auch nicht 100% sicher, wie ich mit dem Attribut CurrentValue umgehen soll, aber es sollte funktionieren. So wie es jetzt ist.

In jedem Fall hoffe ich, dass es einigen Leuten Kopf-Caches erspart, nicht sicher, ob dies die beste oder sogar gute Lösung ist, aber ich mag es mehr, als Effekte unabhängig von Attributen zu verfolgen. In diesem Fall ist es viel einfacher, für jedes Attribut einen eigenen Status festzulegen, und es sollte weniger fehleranfällig sein. Es gibt im Wesentlichen nur einen Punkt des Scheiterns, der ziemlich kurz und einfach ist.

Łukasz Baran
quelle
Vielen Dank für den Link und die Erklärung Ihrer Arbeit! Ich denke, Sie bewegen sich im Wesentlichen auf das zu, wonach ich gefragt habe. Ein paar Dinge, die in den Sinn kommen, sind die Reihenfolge der Operationen (zum Beispiel 3 "Addieren" und 2 "Multiplizieren" von Effekten auf dasselbe Attribut, die zuerst auftreten sollten?), Und dies ist reine Attributunterstützung. Es gibt auch die Idee, Trigger (wie "1 AP verlieren, wenn getroffen") zu behandeln, aber das wäre wahrscheinlich eine separate Untersuchung.
Gkimsey
Die Reihenfolge, in der der Attributbonus berechnet wird, ist einfach. Du kannst hier sehen, dass ich da für und umsteige. Durchlaufen Sie alle aktuellen Boni (die addiert, subtrahiert, multipliziert, dividiert usw. werden können) und akkumulieren Sie sie anschließend. Dann machst du so etwas wie BonusValue = (BonusValue * MultiplyBonus + AddBonus-SubtractBonus) / DivideBonus, oder wie auch immer du diese Gleichung sehen willst. Aufgrund des zentralen Einstiegspunkts ist es einfach, damit zu experimentieren. Bezüglich der Auslöser habe ich nicht darüber geschrieben, weil das ein weiteres Problem ist, über das ich nachdenke, und ich habe es bereits mit 3-4 (Limit) versucht
Łukasz Baran
Lösungen, von denen keine so funktioniert hat, wie ich es wollte (mein Hauptziel ist, dass sie designerfreundlich sind). Meine allgemeine Idee ist, Tags zu verwenden und eingehende Effekte mit Tags zu vergleichen. Wenn die Tags übereinstimmen, kann der Effekt einen anderen Effekt auslösen. (Tag ist ein einfacher menschlicher Readble-Name, wie Damage.Fire, Attack.Physical usw.). Im Kern geht es um ein sehr einfaches Konzept, bei dem es darum geht, Daten zu organisieren, leicht zugänglich zu sein (schnell zu suchen) und neue Effekte hinzuzufügen. Sie können prüfen , Code hier github.com/iniside/ActionRPGGame (GameAttributes ist das Modul Sie interessieren)
Łukasz Baran
2

Ich habe an einem kleinen MMO gearbeitet und alle Gegenstände, Kräfte, Buffs usw. hatten 'Effekte'. Ein Effekt war eine Klasse, die Variablen für 'AddDefense', 'InstantDamage', 'HealHP' usw. hatte. Die Kräfte, Gegenstände usw. würden die Dauer für diesen Effekt handhaben.

Wenn Sie eine Kraft wirken oder einen Gegenstand aktivieren, wird der Effekt für die angegebene Dauer auf den Charakter angewendet. Dann würden die Hauptangriffs- usw. Berechnungen die angewendeten Effekte berücksichtigen.

Zum Beispiel hast du einen Buff, der die Verteidigung erhöht. Es würde mindestens eine EffectID und eine Duration für diesen Buff geben. Beim Wirken wird die EffectID für die angegebene Dauer auf den Charakter angewendet.

Ein anderes Beispiel für einen Artikel hätte die gleichen Felder. Aber die Dauer wäre unendlich oder bis der Effekt beseitigt ist, indem der Gegenstand aus dem Charakter entfernt wird.

Mit dieser Methode können Sie eine Liste der aktuell angewendeten Effekte durchlaufen.

Hoffe, ich habe diese Methode klar genug erklärt.

Grad
quelle
So wie ich es mit meiner minimalen Erfahrung verstehe, ist dies die traditionelle Art, Stat-Mods in RPG-Spielen zu implementieren. Es funktioniert gut und ist leicht zu verstehen und zu implementieren. Der Nachteil ist, dass ich anscheinend keinen Platz mehr habe, um Dinge wie den "Dornen" -Verbesserungsbonus oder etwas Fortgeschritteneres oder Situativeres zu tun. Es war auch historisch die Ursache für einige Exploits in RPGs, obwohl sie ziemlich selten sind, und da ich ein Einzelspieler-Spiel mache, bin ich nicht wirklich besorgt, wenn jemand einen Exploit findet. Danke für die Eingabe.
gkimsey
2
  1. Wenn Sie ein Unity-Benutzer sind, können Sie hier loslegen: http://www.stevegargolinski.com/armory-a-free-and-unfinished-stat-inventory-and-buffdebuff-framework-for-unity/

Ich benutze ScriptableOjects als Buffs / Zaubersprüche / Talente

public class Spell : ScriptableObject 
{
    public SpellType SpellType = SpellType.Ability;
    public SpellTargetType SpellTargetType = SpellTargetType.SingleTarget;
    public SpellCategory SpellCategory = SpellCategory.Ability;
    public MagicSchools MagicSchool = MagicSchools.Physical;
    public CharacterClass CharacterClass = CharacterClass.None;
    public string Description = "no description available";
    public SpellDragType DragType = SpellDragType.Active; 
    public bool Active = false;
    public int TargetCount = 1;
    public float CastTime = 0;
    public uint EffectRange = 3;
    public int RequiredLevel = 1;
    public virtual void OnGUI()
    {
    }
}

mit UnityEngine; using System.Collections.Generic;

öffentliche Aufzählung BuffType {Buff, Debuff} [System.Serializable] öffentliche Klasse BuffStat {public Stat Stat = Stat.Strength; public float ModValueInPercent = 0.1f; }

public class Buff : Spell
{
    public BuffType BuffType = BuffType.Buff;
    public BuffStat[] ModStats;
    public bool PersistsThroughDeath = false;
    public int AmountPerTick = 3;
    public bool UseTickTimer = false;
    public float TickTime = 1.5f;
    [HideInInspector]
    public float Ticktimer = 0;
    public float Duration = 360; // in seconds
    public float ModifierPerStack = 1.1f;
    [HideInInspector]
    public float Timer = 0;
    public int Stack = 1;
    public int MaxStack = 1;
}

BuffModul:

using System;
using RPGCore;
using UnityEngine;

public class Buff_Modul : MonoBehaviour
{
    private Unit _unit;

    // Use this for initialization
    private void Awake()
    {
        _unit = GetComponent<Unit>();
    }

    #region BUFF MODUL

    public virtual void RUN_BUFF_MODUL()
    {
        try
        {
            foreach (var buff in _unit.Attr.Buffs)
            {
                CeckBuff(buff);
            }
        }
        catch(Exception e) {throw new Exception(e.ToString());}
    }

    #endregion BUFF MODUL

    public void ClearBuffs()
    {
        _unit.Attr.Buffs.Clear();
    }

    public void AddBuff(string buffName)
    {
        var buff = Instantiate(Resources.Load("Scriptable/Buff/" + buffName, typeof(Buff))) as Buff;
        if (buff == null) return;
        buff.name = buffName;
        buff.Timer = buff.Duration;
        _unit.Attr.Buffs.Add(buff);
        foreach (var buffStat in buff.ModStats)
        {
            switch (buff.BuffType)
            {
                case BuffType.Buff:
                    _unit.Attr.AddBuffStatValue(buffStat.Stat, Mathf.RoundToInt((_unit.Attr.StatsBase[buffStat.Stat] + _unit.Attr.StatsItem[buffStat.Stat]) * buffStat.ModValueInPercent));
                    break;
                case BuffType.Debuff:
                    _unit.Attr.RemoveBuffStatValue(buffStat.Stat, Mathf.RoundToInt((_unit.Attr.StatsBase[buffStat.Stat] /*+ unit.character.StatsItem[_stat.stat]*/) * buffStat.ModValueInPercent));
                    break;
            }
            Core.StatController(_unit.Attr, buffStat.Stat);
        }
    }

    public void RemoveBuff(Buff buff)
    {
        foreach (var buffStat in buff.ModStats)
        {
            switch (buff.BuffType)
            {
                case BuffType.Buff:
                    _unit.Attr.RemoveBuffStatValue(buffStat.Stat, Mathf.RoundToInt((_unit.Attr.StatsBase[buffStat.Stat] + _unit.Attr.StatsItem[buffStat.Stat]) * buffStat.ModValueInPercent));
                    break;
                case BuffType.Debuff:
                    _unit.Attr.AddBuffStatValue(buffStat.Stat, Mathf.RoundToInt((_unit.Attr.StatsBase[buffStat.Stat]  /*+ unit.character.StatsItem[_stat.stat]*/) * buffStat.ModValueInPercent));
                    break;
            }
            Core.StatController(_unit.Attr, buffStat.Stat);
        }
        _unit.Attr.Buffs.Remove(buff);
    }

    void CeckBuff(Buff buff)
    {
        buff.Timer -= Time.deltaTime;
        if (!_unit.IsAlive && !buff.PersistsThroughDeath)
        {
            if (buff.ModStats != null)
                foreach (var stat in buff.ModStats)
                {
                    _unit.Attr.StatsBuff[stat.Stat] = 0;
                }

            RemoveBuff(buff);
        }
        if (_unit.IsAlive && buff.Timer <= 0)
        {
            RemoveBuff(buff);
        }
    }
}
user22475
quelle
0

Dies war eine aktuelle Frage für mich. Ich habe eine Idee dazu.

  1. Wie bereits erwähnt, müssen wir eine BuffListe und einen Logik-Updater für die Buffs implementieren .
  2. Wir müssen dann alle spezifischen Player-Einstellungen für jedes Bild in Unterklassen der BuffKlasse ändern .
  3. Wir erhalten dann die aktuellen Player-Einstellungen aus dem Feld "Änderbare Einstellungen".

class Player {
  settings: AllPlayerStats;

  private buffs: Array<Buff> = [];
  private baseSettings: AllPlayerStats;

  constructor(settings: AllPlayerStats) {
    this.baseSettings = settings;
    this.resetSettings();
  }

  addBuff(buff: Buff): void {
    this.buffs.push(buff);
    buff.start(this);
  }

  findBuff(predcate(buff: Buff) => boolean): Buff {...}

  removeBuff(buff: Buff): void {...}

  update(dt: number): void {
    this.resetSettings();
    this.buffs.forEach((item) => item.update(dt));
  }

  private resetSettings(): void {
    //some way to copy base to settings
    this.settings = this.baseSettings.copy();
  }
}

class Buff {
    private owner: Player;        

    start(owner: Player) { this.owner = owner; }

    update(dt: number): void {
      //here we change anything we want in subclasses like
      this.owner.settings.hp += 15;
      //if we need base value, just make owner.baseSettings public but don't change it! only read

      //also here logic for removal buff by time or something
    }
}

Auf diese Weise kann es einfach sein, neue Spielerstatistiken hinzuzufügen, ohne die Logik der BuffUnterklassen zu ändern .

DantaliaN
quelle
0

Ich weiß, dass dies ziemlich alt ist, aber es wurde in einem neueren Beitrag verlinkt und ich habe einige Gedanken dazu, die ich gerne teilen möchte. Leider habe ich meine Notizen im Moment nicht bei mir, daher versuche ich, einen allgemeinen Überblick über das zu geben, worüber ich spreche, und bearbeite die Details und einige Beispielcodes, wenn ich sie vor mir habe mich.

Erstens denke ich, dass die meisten Leute aus gestalterischer Sicht zu beschäftigt sind, welche Arten von Buffs erstellt werden können und wie sie angewendet werden, und die Grundprinzipien der objektorientierten Programmierung vergessen.

Was meine ich? Es ist eigentlich egal, ob etwas ein Buff oder ein Debuff ist, beide Modifikatoren wirken sich nur positiv oder negativ auf etwas aus . Dem Code ist es egal, welcher welcher ist. Abgesehen davon spielt es letztendlich keine Rolle, ob etwas Statistiken hinzufügt oder multipliziert, das sind nur verschiedene Operatoren, und auch hier ist es dem Code egal, welche welche sind.

Also, wohin gehe ich damit? Dass das Entwerfen einer guten (sprich: einfachen, eleganten) Buff / Debuff-Klasse nicht allzu schwierig ist, liegt daran, die Systeme zu entwerfen, die den Spielstatus berechnen und aufrechterhalten.

Wenn ich ein Buff / Debuff-System entwerfe, würde ich Folgendes in Betracht ziehen:

  • Eine Buff / Debuff-Klasse, die den Effekt selbst darstellt.
  • Eine Buff / Debuff-Typklasse, die Informationen darüber enthält, was und wie sich der Buff auswirkt.
  • Charaktere, Gegenstände und möglicherweise Orte müssten über eine Listen- oder Sammlungseigenschaft verfügen, um Buffs und Debuffs zu enthalten.

Einige Besonderheiten, welche Buff / Debuff-Typen enthalten sollten:

  • Auf wen / was kann es angewendet werden, IE: Spieler, Monster, Ort, Gegenstand usw.
  • Welche Art von Effekt ist es (positiv, negativ), ob es multiplikativ oder additiv ist, und welche Art von Status beeinflusst es, IE: Angriff, Verteidigung, Bewegung usw.
  • Wann sollte es überprüft werden (Kampf, Tageszeit, etc.).
  • Ob und wie es entfernt werden kann.

Das ist nur ein Anfang, aber von da an definieren Sie einfach, was Sie wollen, und handeln danach unter Verwendung Ihres normalen Spielzustands. Angenommen, Sie möchten einen verfluchten Gegenstand erstellen, der die Bewegungsgeschwindigkeit verringert ...

Solange ich die richtigen Typen eingerichtet habe, ist es einfach, einen Buff-Datensatz zu erstellen, der Folgendes enthält:

  • Typ: Fluch
  • ObjectType: Item
  • StatCategory: Dienstprogramm
  • StatAffected: Bewegungsgeschwindigkeit
  • Dauer: Unendlich
  • Auslöser: OnEquip

Und so weiter, und wenn ich einen Buff erstelle, ordne ich ihm einfach den BuffType of Curse zu und alles andere hängt von der Engine ab ...

Aithos
quelle