Im vorherigen Artikel habe ich gezeigt, wie man den am weitesten entfernten Feind mithilfe eines maximalen Heaps effizient verfolgen kann. In diesem Artikel werden wir sehen, wie man das in eine Spielmechanik integriert.
Die bestehende Implementierung verwendet eine ereignisgesteuerte Architektur. In diesem Artikel konzentrieren wir uns auf die Feindereignisse. Diese Ereignisse werden Spin-off-Aktionen auslösen.
Jeder Feind kann verschiedene Ereignisse erleben. Nachfolgend finden Sie ein Beispiel für einen Lebenszyklus, den der Feind durchlaufen kann:
Für den Artikel interessieren mich die beiden Veranstaltungen:
(* Ich habe vor, die Ereignisnamen in Zukunft anzupassen, da ein Feind aus verschiedenen Gründen entfernt werden kann.)
Ich habe ein Ereignismodelldiagramm erstellt, um zu visualisieren, wie verschiedene Ereignisse interagieren. Dies hilft beim Verständnis, wie die Dinge zusammenhängen.
Für jedes Ereignis habe ich einen Befehl, der es auslöst. (Ein Ereignis ist also das Ergebnis eines Befehls.) In einigen Fällen müssen wir als Ergebnis eines Ereignisses Daten aktualisieren (grüne Haftnotizen zeigen dies). Eine Kombination aller drei zusammen ist ein vertikaler Schnitt.
Mein Fokus wird auf der grünen Haftnotiz „Feinde in Reichweite des Turms“ liegen.
Das Ziel besteht darin, die verfügbaren Feinde zu aktualisieren, wenn sich ein Feind in der Reichweite des Turms befindet, und sie zu entfernen, wenn nicht.
Wir werden mit einer Turmklasse arbeiten. In dieser Klasse haben wir eine Variable zum Speichern von Feinden.
export class Tower implements ITower { public enemies = new MaxHeap() constructor(id: number, coords: Coordinate) { this.id = id this.coords = coords // listeners will go here... }
Durch die Platzierung von Ereignis-Listenern in der Tower-Klasse wird die Logik zentralisiert, wodurch die Notwendigkeit verringert wird, Zuordnungen zwischen Türmen und Feinden aufrechtzuerhalten. Dies erhöht zwar die Komplexität der Klasse, gewährleistet jedoch eine bessere Kapselung und vereinfacht das Debuggen, was vorerst eine einfachere Richtung darstellt.
Zuerst schreiben wir einen Test, um zu überprüfen, ob ein Feind in Reichweite zum Feindhaufen des Turms hinzugefügt wird:
it('should add an enemy to the tower when enemy is within range', () => { const tower = new Tower(1, { col: 0, row: 1 }); const enemy = new TinyEnemy(); enemy.currentPosition = { col: 0, row: 1 }; triggerEnemyMovedEvent(enemy); expect(tower.enemies.length()).toBe(1); });
Hier ist die entsprechende Implementierung:
window.addEventListener("enemyMoved", event => { const enemy: Enemy = event.detail.enemy; if (enemyWithinRange(this, enemy)) { this.enemies.insertOrUpdate(enemy.id, enemy.distanceTraveled); } });
Immer wenn „feindMoved“ ausgelöst wird, prüfen wir, ob ein Feind zum Heap hinzugefügt werden soll. Ich habe bereits die Funktion „feindWithinRange“, es geht nur darum, den Aufruf „insertOrUpdate“ hinzuzufügen.
Als nächstes stellen wir sicher, dass keine Feinde außerhalb der Reichweite des Turms hinzugefügt werden:
export class Tower implements ITower { public enemies = new MaxHeap() constructor(id: number, coords: Coordinate) { this.id = id this.coords = coords // listeners will go here... }
Dieses Szenario wird bereits durch unsere frühere Prüfung mit „feindWithinRange“ abgedeckt, daher ist kein zusätzlicher Code erforderlich.
Jetzt testen wir, ob Feinde, die die Reichweite verlassen, aus der Sichtbarkeit des Turms entfernt werden:
it('should add an enemy to the tower when enemy is within range', () => { const tower = new Tower(1, { col: 0, row: 1 }); const enemy = new TinyEnemy(); enemy.currentPosition = { col: 0, row: 1 }; triggerEnemyMovedEvent(enemy); expect(tower.enemies.length()).toBe(1); });
window.addEventListener("enemyMoved", event => { const enemy: Enemy = event.detail.enemy; if (enemyWithinRange(this, enemy)) { this.enemies.insertOrUpdate(enemy.id, enemy.distanceTraveled); } });
Wenn der Feind früher in Reichweite war, können wir ihn entfernen.
Zuletzt stellen wir sicher, dass aus dem Spiel entfernte Feinde auch vom Haufen des Turms entfernt werden:
it('should not add an enemy to the tower if enemy is out of range', () => { const tower = new Tower(1, { col: 0, row: 1 }); const enemy = new TinyEnemy(); enemy.currentPosition = { col: 0, row: 99 }; triggerEnemyMovedEvent(enemy); expect(tower.enemies.length()).toBe(0); });
it('should remove an enemy from the tower when it moves out of range', () => { const tower = new Tower(1, { col: 0, row: 1 }); const enemy = new TinyEnemy(); enemy.currentPosition = { col: 0, row: 1 }; // enemy within range triggerEnemyMovedEvent(enemy); expect(tower.enemies.length()).toBe(1); // enemy outside of the range enemy.currentPosition = { col: 0, row: 99 }; triggerEnemyMovedEvent(enemy); expect(tower.enemies.length()).toBe(0); });
Immer wenn ein Ereignis ausgelöst wird und sich der Feind in Reichweite befindet, können wir ihn entfernen.
Durch die Kombination eines ereignisgesteuerten Ansatzes mit einem maximalen Heap haben wir eine effiziente Möglichkeit für Türme erreicht, Feinde dynamisch zu priorisieren. Die Implementierung fügt sich nahtlos in das Event-System des Spiels ein und gewährleistet Echtzeit-Updates und Reaktionsfähigkeit.
Außerdem entfällt beim Testen durch die Verwendung eines ereignisgesteuerten Ansatzes die Notwendigkeit, internen Code mit dem Test zu verknüpfen. Dadurch werden Tests weniger spröde. Wir können den Code hinter dem Verhalten nach Belieben umgestalten, und solange Ereignisse/Listener korrekt eingerichtet sind, sollten die Tests trotzdem bestehen.
Diese Implementierung kann nun den Weg ebnen für:
Sie können diesen Ansatz gerne für Ihre eigenen Tower-Defense-Spiele anpassen.
Das obige ist der detaillierte Inhalt vonAngriff auf den am weitesten entfernten Feind (Tower Defense). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!