はじめに
前回は、1ターンの処理を Section の階層に組み替えました。
今回は、攻撃の途中で Event を発火し、個別の処理を EventHandler で受け取るようにします。ハンドラを登録しないときは、実行結果は前回のままです。
今回のポイント
- ダメージの補正は
executeActionに直接書かず、beforeDamageのハンドラがevent.damageを変える - 同じ種類のハンドラは登録順に同期実行し、全部終わってから攻撃の続きへ戻る
-
afterDamageは HP を減らしたあと。ここでダメージ値を変えても、すでに引いた HP には戻らない - 登録はバトルごとの
EventBusで行い、実行中の追加登録はしない
ディレクトリ
増えたファイルは event.ts です。Section の並びは前回のままです。
Before
src/
├── main.ts
└── battle/
├── types.ts
├── section.ts
├── battle.ts Battle が子 Section を並べる
├── turn.ts Turn が子 Section を並べる
└── actions.ts executeAction が固定20ダメージを引く
After
src/
├── main.ts
└── battle/
├── types.ts
├── section.ts
├── event.ts 追加。Event と EventHandler の登録・発火
├── battle.ts EventBus を Turn へ渡す
├── turn.ts EventBus を攻撃 Section へ渡す
└── actions.ts executeAction の途中で Event を発火する
シーケンス
変わったのは攻撃の中です。executeAction が HP を直接 20 減らすのではなく、発火とハンドラの完了を挟みます。
Before
After
コード
EventBus は、種類が一致するハンドラだけを登録順に実行します。
export function createEventBus(): EventBus {
const handlers: EventHandler[] = [];
let emitDepth = 0;
return {
register(handler) {
if (emitDepth > 0) {
throw new Error("実行中にEventHandlerは登録できません");
}
handlers.push(handler);
},
emit(event, state) {
const matched = handlers.filter((handler) => handler.kind === event.kind);
emitDepth += 1;
try {
for (const handler of matched) {
handler.handle(event, state);
}
} finally {
emitDepth -= 1;
}
},
};
}
beforeDamage のあとに、ハンドラが残した event.damage を HP から引きます。afterDamage はそのあとです。
export function executeAction(
state: BattleState,
action: Action,
events?: EventBus,
): BattleState {
const actor = findPokemon(state, action.actorId);
const target = findPokemon(state, action.targetId);
state.logs.push(`${actor.name}の${action.move}!`);
const damageEvent: BattleEvent = {
kind: "beforeDamage",
actorId: actor.id,
targetId: target.id,
move: action.move,
damage: BASE_DAMAGE,
};
events?.emit(damageEvent, state);
target.currentHp -= damageEvent.damage;
state.logs.push(`${target.name}に${damageEvent.damage}ダメージ!`);
events?.emit(
{
kind: "afterDamage",
actorId: actor.id,
targetId: target.id,
move: action.move,
damage: damageEvent.damage,
},
state,
);
return state;
}
ハンドラがないときは damage は 20 のままなので、表示も両者 HP 80 のままです。特性や持ち物は、まだこのバスには登録していません。