前の記事では、最大ヒープを使用して最も遠い敵を効率的に追跡する方法を示しました。この記事では、それをゲームの仕組みに組み込む方法を見ていきます。
既存の実装では、イベント駆動型のアーキテクチャが使用されています。この記事では、敵のイベントに焦点を当てます。これらのイベントはスピンオフ アクションをトリガーします。
各敵はさまざまなイベントを経験できます。以下は、敵が通過する可能性のあるライフサイクルの例です:
この記事に関しては、次の 2 つのイベントに興味があります:
(※敵は様々な理由で削除される可能性があるため、イベント名は今後調整する予定です。)
さまざまなイベントがどのように相互作用するかを視覚化するために、イベント モデル図を作成しました。これは、物事がどのように接続されているかを理解するのに役立ちます。
すべてのイベントに対して、それをトリガーするコマンドがあります。 (つまり、イベントはコマンドの結果です。) 場合によっては、イベントの結果として、データを更新する必要があります (緑色の付箋がこれを示しています)。 3 つすべてを組み合わせると、垂直スライスとなります。
私は緑色の付箋「タワーの範囲内の敵」に注目します。
目標は、敵がタワーの範囲内にいるときはいつでも利用可能な敵を更新し、そうでない場合は敵を除去することです。
タワークラスを使用して作業します。このクラスには、敵を格納する変数があります。
export class Tower implements ITower { public enemies = new MaxHeap() constructor(id: number, coords: Coordinate) { this.id = id this.coords = coords // listeners will go here... }
Tower クラスにイベント リスナーを配置すると、ロジックが集中化され、タワーと敵の間のマッピングを維持する必要性が減ります。これにより、クラスがいくらか複雑になりますが、カプセル化が確実に強化され、デバッグが簡素化されます。現時点では、これが採用しやすい方向です。
まず、範囲内の敵がタワーの敵の山に追加されることを確認するテストを作成します。
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); } });
emoneMoved がトリガーされるたびに、敵をヒープに追加する必要があるかどうかをチェックします。すでにemoneWithinRange関数があるので、insertOrUpdate呼び出しを追加するだけです。
次に、タワーの範囲外の敵が追加されないようにします。
export class Tower implements ITower { public enemies = new MaxHeap() constructor(id: number, coords: Coordinate) { this.id = id this.coords = coords // listeners will go here... }
このシナリオは、emoneWithinRange を使用した以前のチェックですでにカバーされているため、追加のコードは必要ありません。
今度は、射程外に出た敵がタワーの視界から取り除かれることをテストします:
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); } });
敵が範囲内にいた場合は、敵を排除できます。
最後に、ゲームから削除された敵がタワーのヒープからも削除されることを確認します。
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); });
イベントがトリガーされるたびに、敵が範囲内にいる場合は敵を排除できます。
イベント駆動型のアプローチと最大ヒープを組み合わせることで、タワーが敵に動的に優先順位を付ける効率的な方法を実現しました。この実装はゲームのイベント システムとシームレスに連携し、リアルタイムの更新と応答性を保証します。
さらに、テストに関しては、イベント駆動型のアプローチを使用すると、内部コードをテストに結び付ける必要がなくなります。したがって、テストの脆弱性が軽減されます。動作の背後にあるコードを任意の方法でリファクタリングでき、イベント/リスナーが正しく設定されている限り、テストは合格するはずです。
この実装により、次のことが可能になります。
このアプローチを独自のタワーディフェンス ゲームに自由に応用してください。
以上が範囲内の最も遠い敵を攻撃する (タワーディフェンス)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。