13. 敵の移動パターン
はじめに
敵の動きがワンパターンだったので、敵ごとに動き方を設定しました。
方針
- MovementStrategyComponentを作成します
- createEnemy で敵タイプごとに MovementStrategyComponent を設定します
- 各MovementStrategyごとに必要なパラメータやステートも用意
- EnemyAISystem で MovementStrategy に応じてvelocityを計算してspriteに設定します
フローチャート
前回のフローチャートで流れを確認してみます。
flowchart TD
subgraph EnemyAISystem
subgraph update
A{{すべての敵についてループ}} -->
Call[movementStrategyに応じてupdateXXXMovement呼び出し]
end
Call -.-> updateXXXMovement
subgraph updateXXXMovement
direction TB
velocity計算 -->
movmentStrategy.state更新 -->
sprite.setVelocity
end
end
移動パターン
現在のプレイヤーまで直線で向かう(Direct)に
- ジグザグに動きながら寄ってくる(ZigZag)
- 渦巻き状に近寄ってくる(Spiral)
を追加しました。
コード
MovementStrategyComponent.ts
ジグザグ移動は、直線移動にサイン波を足して計算します。現在のサイン波の位置をphaseというステートに保存しています。
export type MovementType = "direct" | "zigzag" | "spiral";
export interface ZigzagMovementParams {
amplitude: number;
frequency: number;
}
export interface ZigzagMovementState {
phase: number;
}
export interface SpiralMovementParams {
clockwise: boolean;
spiralStrength: number;
}
export type MovementStrategyComponent =
| { type: "direct"; params: {}; state: {} }
| { type: "zigzag"; params: ZigzagMovementParams; state: ZigzagMovementState }
| { type: "spiral"; params: SpiralMovementParams; state: {} };createEnemy.ts
// createEnemy関数に以下を追加
...
// 敵のタイプによって異なる移動戦略を設定
switch (enemyType) {
case "slime":
ComponentManager.set("movementStrategy", entity, {
type: "direct",
params: {},
state: {},
});
break;
case "spider":
ComponentManager.set("movementStrategy", entity, {
type: "zigzag",
params: { amplitude: 50, frequency: 2 },
state: { phase: 0 },
});
break;
case "bat":
ComponentManager.set("movementStrategy", entity, {
type: "spiral",
params: { spiralStrength: 0.8, clockwise: Phaser.Math.Between(0, 1) > 0.5 },
state: {},
});
ComponentManager.set("flyTag", entity, {});
break;
}EnemyAISystem.ts
export class EnemyAISystem {
update(delta: number) {
for (const entity of ComponentManager.entitiesWith("enemyTag", "target")) {
const { targetX, targetY } = ComponentManager.get("target", entity);
const { speed } = ComponentManager.get("speed", entity);
const { sprite: enemySprite } = ComponentManager.get("sprite", entity);
const targetPosision = { x: targetX, y: targetY };
// MovementStrategy
let movementStrategy = ComponentManager.getOptional("movementStrategy", entity);
if (!movementStrategy) continue; // 未設定なら移動しない
switch (movementStrategy.type) {
case "direct":
this.updateDirectMovement(enemySprite, targetPosision, speed);
break;
case "spiral":
this.updateSpiralMovement(enemySprite, targetPosision, speed, movementStrategy);
break;
case "zigzag":
this.updateZigzagMovement(enemySprite, targetPosision, speed, movementStrategy, delta);
break;
}
}
}移動方法ごとのVelocity計算は以下のとおりです。
直線移動 (Direct)
private updateDirectMovement(sprite: Phaser.Physics.Arcade.Sprite, target: { x: number; y: number }, speed: number) {
const { vx, vy } = calcVelocity(sprite, target, speed);
sprite.setVelocity(vx, vy);
}渦巻き状 (Spiral)
private updateSpiralMovement(
sprite: Phaser.Physics.Arcade.Sprite,
target: { x: number; y: number },
speed: number,
strategy: MovementStrategyComponent
) {
const { clockwise, spiralStrength } = strategy.params as SpiralMovementParams;
// targetへのベクトル
const direction = new Phaser.Math.Vector2(target.x - sprite.x, target.y - sprite.y);
const distance = direction.length();
if (distance < 10) {
sprite.setVelocity(0, 0);
return;
}
// 垂直なベクトル
const perpendicular = new Phaser.Math.Vector2(-direction.y, direction.x).scale(clockwise ? 1 : -1);
// スパイラルの強さを考慮して、目標地点に向かうベクトルと垂直なベクトルを組み合わせる
const spiralVector = direction.scale(1 - spiralStrength).add(perpendicular.scale(spiralStrength));
// 速度を計算
const velocity = spiralVector.normalize().scale(speed);
sprite.setVelocity(velocity.x, velocity.y);
}ジグザグ移動 (ZigZag)
private updateZigzagMovement(
sprite: Phaser.Physics.Arcade.Sprite,
target: { x: number; y: number },
speed: number,
strategy: MovementStrategyComponent,
delta: number
) {
// 基本方向を計算
const targetVector = new Phaser.Math.Vector2(target.x, target.y);
const spriteVector = new Phaser.Math.Vector2(sprite.x, sprite.y);
const distance = targetVector.distance(spriteVector);
if (distance < 10) {
sprite.setVelocity(0, 0);
return;
}
const direction = targetVector.subtract(spriteVector).normalize();
// ジグザグ効果を計算(サイン波を使用
const params = strategy.params as ZigzagMovementParams;
const state = strategy.state as ZigzagMovementState;
state.phase += 2 * Math.PI * (delta / 1000) * params.frequency; // 1秒間にfrequency回の周期
let amp = params.amplitude;
if (distance < 100) amp *= Phaser.Math.Linear(0, 1, 1 - distance / 100);
const sideOffset = Math.sin(state.phase) * params.amplitude;
// 方向ベクトルに垂直なベクトルを計算
const perpendicular = new Phaser.Math.Vector2(-direction.y, direction.x);
// 最終的な速度を計算
const velocity = direction.scale(speed).add(perpendicular.scale(sideOffset));
sprite.setVelocity(velocity.x, velocity.y);
}まとめ
敵の移動パターンを追加しました。 さらに、最終ウエーブでボスも登場させました。
次回はボスが火の玉を吐くようにしたいと思います。
