状態で切り替える
判断には「今は追跡モード、今は逃走モード」のように、丸ごと切り替わるものもある。見回す、追う、散る。そういう排他的なモードを状態として持って、条件で次の状態へ遷移させるのが有限状態機械、FSM。
状態の正体は、ただの番号ひとつ。0 が追跡、1 が散開、2 が怯え。タイマーが尽きるたびに次へ進めて、% 3 で 0 に巻き戻す。番号を濃淡に写すと、今どのモードかが色で読める。
横帯が三つの状態で、白丸がいる帯が今のモード。タイマーが尽きるたびに丸が次の帯へ降りて、いちばん下まで来たら一番上へ戻る。遷移はこの (state + 1) % 3 一行で、状態どうしの遷移を一周のリングに固定している。
状態が決めるのは振る舞いの中身。同じ「標的との距離」を見ても、追跡なら近づく向きを選び、散開なら遠ざかる向きを選ぶ。隣のマスを評価する点数の符号を、状態で反転させる。
const dist = Math.hypot(nx - target.x, ny - target.y) // 追跡(0)は距離を縮めたい→ -dist、散開(1)は離れたい→ +dist let score = state === 0 ? -dist : dist // 怯え(2)は乱数を混ぜて、向きが定まらず逃げ惑う if (state === 2) score = -dist + Math.random() * 6
四方向それぞれの点数を出して、いちばん高い向きへ一歩進む。状態が変わると同じ距離から逆の符号が出るので、追っていた点が次の瞬間には逃げ出す。下は一匹を開いた格子に置いて、状態を色で、向いた先を線で示す。
濃い鬼(追跡)は標的の格子マスへ寄っていき、中間調になった瞬間(散開)に向きを変えて離れ、薄い鬼(怯え)はふらふらと定まらずに動く。同じ評点ループが、状態の番号ひとつで追う/逃げる/惑うを切り替えている。標的は濃い四角で、鬼が追える状態のときだけ間合いが詰まる。
壁を加えて鬼を四匹に増やすと、迷路を別々のモードで巡る群れになる。各鬼が自分の状態とタイマーを独立に持つので、追う鬼と散る鬼と惑う鬼が同じ盤に混ざる。
四匹がそれぞれ別のタイマーで状態を回すので、ある鬼が標的の薄い丸へ詰めているあいだに、別の鬼は壁を伝って遠ざかり、輪郭だけの鬼はあてどなく曲がる。壁の open を評点の前提に挟んでいるので、通れる隣だけを候補にして迷路を抜ける。状態の番号と切り替えのタイマー、それと評点の符号だけで、四匹が違うモードで同じ盤を巡る。
有限状態機械は、有限個の状態と、状態どうしをつなぐ遷移で振る舞いを表す。パックマンの鬼が例で、追跡(プレイヤーへ寄る)・散開(盤の隅へ戻る)・怯え(パワーエサで反転して逃げる)の状態を、タイマーやイベントで切り替える。実装は状態を表す整数や enum と、毎フレーム遷移条件を見る分岐。状態が n 個になると遷移は最悪 n×(n−1) 本まで増える。どこからどこへ飛べるかを全部書くと配線が手に負えなくなる。この遷移爆発を、階層型 FSM やビヘイビアツリーが構造で抑える。