
Was ist das und warum?
Beim Entwerfen kann ein Entwickler auf ein Problem stoßen: Kreaturen und Objekte haben möglicherweise unterschiedliche Fähigkeiten in unterschiedlichen Kombinationen. Frösche springen und schwimmen, Enten schwimmen und fliegen, aber nicht mit einem Gewicht, und Frösche können mit einem Ast und Enten fliegen. Daher ist es praktisch, von der Vererbung zur Komposition zu wechseln und Fähigkeiten dynamisch hinzuzufügen. Die Notwendigkeit, fliegende Frösche zu animieren, führte zu einer ungerechtfertigten Ablehnung der Fähigkeitsmethoden und der Übertragung ihres Codes an Teams in einer der Implementierungen. Da ist sie:
class CastSpellCommand extends Command { constructor (source, target, spell) { this.source = source; this.target = target; this.spell = spell; } execute () { const spellAbility = this.source.getAbility(SpellCastAbility);
Was kann getan werden?
Betrachten Sie verschiedene Ansätze unterschiedlicher Art:
Beobachter
class Executor extends Observer {} class Animator extends Observer {}
Eine klassische Lösung, die Programmierern bekannt ist. Sie müssen es nur minimal ändern, um die von Beobachtern zurückgegebenen Werte zu überprüfen:
this.listeners.reduce((result, listener) => result && listener(action), true)
Nachteil: Beobachter müssen Ereignisse in der richtigen Reihenfolge abonnieren.
Wenn Sie Fehler behandeln, kann der Animator auch Animationen fehlgeschlagener Aktionen anzeigen. Sie können den vorherigen Wert an die Beobachter übergeben, konzeptionell bleibt die Lösung dieselbe. Ob Beobachtermethoden oder Rückruffunktionen aufgerufen werden, ob anstelle der Faltung eine reguläre Schleife verwendet wird, die Details sind nicht so wichtig.
Lass es wie es ist
Und in der Tat. Der derzeitige Ansatz hat sowohl Nachteile als auch Vorteile:
- Um die Fähigkeit zum Ausführen eines Befehls zu testen, muss ein Befehl ausgeführt werden
- Argumente in wechselnder Reihenfolge, Bedingungen und Methodenpräfixe sind fest verdrahtet
- Schleifenabhängigkeiten (Befehl <Zauber <Befehl)
- Zusätzliche Entitäten für jede Aktion (die Methode wird durch die Methode, die Klasse und ihren Konstruktor ersetzt)
- Übermäßiges Wissen und Handeln eines einzelnen Teams: von der Spielmechanik über Synchronisationsfehler bis hin zur direkten Manipulation der Eigenschaften anderer Personen
- Die Schnittstelle ist irreführend (nicht nur Aufrufe ausführen, sondern auch Befehle über addChildren hinzufügen; was natürlich das Gegenteil bewirkt)
- Zweifelhafte Notwendigkeit und Implementierung von rekursiven Anweisungen an sich
- Die Dispatcher-Klasse führt, falls vorhanden, ihre Funktionen nicht aus
- [+] Angeblich die einzige Möglichkeit, in der Praxis zu animieren, wenn die Animationen vollständige Daten benötigen (als Hauptgrund angegeben)
- [+] Wahrscheinlich andere Gründe
Einige der Mängel können separat behandelt werden, der Rest erfordert jedoch drastischere Änderungen.
ad hoc
- Die Bedingungen für die Ausführung des Teams, insbesondere die Spielmechanik, müssen aus den Teams herausgenommen und separat ausgeführt werden. Die Bedingungen können sich zur Laufzeit ändern, und das Hervorheben inaktiver Schaltflächen in Grau erfolgt in der Praxis lange vor Beginn der Animationsarbeiten, ganz zu schweigen von der Logik. Um ein Kopieren zu vermeiden, kann es sinnvoll sein, allgemeine Bedingungen in Fähigkeitsprototypen zu speichern.
- Rückgabemethoden in Kombination mit dem vorherigen Absatz werden solche Überprüfungen nicht mehr erforderlich sein:
const spellAbility = this.source.getAbility(SpellCastAbility);
Die Javascript-Engine selbst zeigt den richtigen TypeError an, wenn die Methode fälschlicherweise aufgerufen wird. - Das Team braucht auch kein solches Wissen:
healthAbility.health = Math.max( 0, resultHealth );
- Um das Problem von Argumenten zu lösen, die Orte ändern, können sie vom Objekt übergeben werden.
- Obwohl der aufrufende Code nicht für das Studium verfügbar ist, scheinen die meisten Mängel aufgrund der nicht optimalen Art des Aufrufs von Spielaktionen zuzunehmen. Beispielsweise greifen Schaltflächenhandler auf bestimmte Entitäten zu. Daher erscheint es ganz natürlich, sie in Handlern durch bestimmte Befehle zu ersetzen. Wenn Sie einen Dispatcher haben, ist es viel einfacher, eine Animation nach der Aktion aufzurufen. Sie können dieselben Informationen darauf übertragen, damit keine Daten fehlen.
Warteschlange
Um die Animation der Aktion nach Abschluss der Aktion anzuzeigen, müssen Sie sie der Warteschlange hinzufügen und ungefähr wie in Lösung 1 ausführen.
[ [ walkRequirements, walkAction, walkAnimation ], [ castRequirements, castAction, castAnimation ],
Es spielt keine Rolle, welche Entitäten sich im Array befinden: Funktionen, die mit den erforderlichen Parametern gesperrt wurden, Instanzen von benutzerdefinierten Klassen oder gewöhnlichen Objekten.
Der Wert einer solchen Lösung liegt in der Einfachheit und Transparenz. Es ist einfach, ein Schiebefenster zum Anzeigen der N letzten Befehle zu erstellen.
Gut geeignet für Prototyping und Debugging.
Understudy-Klasse
Wir machen eine Animationsklasse für die Fähigkeit.
class MovementAbility { walk (...args) {
Wenn es nicht möglich ist, Änderungen an der aufrufenden Klasse vorzunehmen, erben wir davon oder dekorieren die gewünschte Methode so, dass sie die Animation aufruft. Oder wir übertragen Animationen anstelle von Fähigkeiten, sie haben die gleiche Oberfläche.
Gut geeignet, wenn Sie praktisch die gleichen Methoden benötigen, können diese automatisch überprüft und getestet werden.
Methodenkombinationen
const AnimatedMovementAbility = combinedClass(MovementAbility, { ['*:before'] (method, ...args) {
Es wäre eine interessante Gelegenheit mit Unterstützung für Muttersprachen.
Es ist gut, es zu verwenden, wenn diese Option produktiver ist, obwohl tatsächlich ein Proxy benötigt wird.
Proxies
Wir wickeln Fähigkeiten in Proxys ein, fangen Methoden in Getter.
new Proxy(new MovementAbility, {})
Nachteil: um ein Vielfaches langsamer als normale Anrufe, was für die Animation nicht so wichtig ist. Auf einem Server, der Millionen von Objekten verarbeitet, ist die Verlangsamung spürbar, aber der Server benötigt keine Animation.
Versprich es mir
Sie können Ketten aus Promise erstellen, es gibt jedoch eine andere Option (ES2018):
for await (const action of actionDispatcher.getActions()) {
getActions gibt einen asynchronen Aktionsiterator zurück. Die nächste Methode des Iterators gibt das verzögerte Versprechen der nächsten Aktion zurück. Nachdem wir Ereignisse vom Benutzer und vom Server verarbeitet haben, rufen wir resolve () auf und erstellen ein neues Versprechen.
Besseres Team
Erstellen Sie Objekte wie folgt:
{actor, ability, method, options}
Der Code besteht darin, die Fähigkeitsmethode mit Parametern zu überprüfen und aufzurufen. Die einfachste und produktivste Option.
Hinweis