敵の追いかけ方:まっすぐ・予測・遠回りの3種類
遊びをつくる執筆にClaudeを使用しています。

同じ速さで動く敵が3体いて、どれもプレイヤーを追いかけているのに、動きの印象がまるで違う。そんなゲームを作る場面を考えてみます。1体は背中に張りつき、1体は行く先に現れ、1体は横から回り込んでくる。この3体の違いは、速さでも見た目でもありません。多くの場合、違いは「どこを目指して進むか」の一点にあります。 この記事の問いは、追跡の性格は何を変えると作れるのか、です。答えを先に言えば、敵が毎フレーム選ぶ「目標点」を変えると作れます。目標点まで進む処理は3体で共通にして、目標点の決め方だけを差し替えます。以下では3種類の目標点の決め方を整理し、40行足らずのコードで軌跡を並べて比べる手順を示します。結果は書きません。コードを動かして自分の目で確かめる形にしています。
この記事の目次
追跡を「目標点を決める」と「進む」に分ける
追いかける敵の処理は、2つに分けて考えると扱いやすくなります。
- 目標点を決める(今フレームでどこを目指すか)
- 目標点へ向かって一定の速さで進む
2つ目はどの敵でも同じです。目標点への向きを求め、長さ1に揃えて速さを掛け、位置に足します。1つ目だけが敵ごとに違います。
この分け方には実用上の利点があります。敵の種類を増やしたいとき、移動処理には手を付けず、目標点を返す関数を1つ書き足すだけで済みます。調整のときも、速さの問題なのか目標点の問題なのかを切り分けられます。「この敵は強すぎる」と感じたとき、速さを落とす前に目標点の置き方を疑う、という順番が取れます。
3種類の目標点の決め方
3種類を表にまとめます。座標はプレイヤーの位置を P、プレイヤーの1フレームあたりの速度を V、敵の位置を E と書きます。
- まっすぐ、目標点:P(今いる場所)、動きの見え方:背後にぴたりとつく、弱点:プレイヤーが曲がり続けると常に一歩遅れる
- 予測、目標点:P + V × T(Tフレーム先の位置)、動きの見え方:進行方向の先に回り込む、弱点:急な方向転換で外れる。Tが大きいと外れ方も大きい
- 遠回り、目標点:Pから敵の進行方向に垂直にずらした点。近づいたら P、動きの見え方:横から膨らんで接近する、弱点:近づく直前に目標が切り替わる。切り替え距離の調整が要る
まっすぐ
目標点をプレイヤーの現在位置にします。最も単純で、基準になる動きです。プレイヤーが一定の速さで逃げ続けるなら、この敵は後ろから離れずについてくる形になります。他の2種類の性格は、この動きとの差として見えます。
予測
プレイヤーの速度に時間を掛けた分だけ、先の位置を目標点にします。T を30にすると、30フレーム先まで今の速度で進むと仮定した位置を狙うことになります。プレイヤーが直進しているあいだは進路を塞ぐ動きになり、方向を変えた瞬間に目標点が大きく外れます。この「裏をかける余地」が、予測型の面白さの源になります。
注意したいのは、プレイヤーが止まった瞬間に速度が0になり、予測型がまっすぐ型と同じ動きに戻る点です。仕様として許容できるか、止まっている相手にも別の工夫が要るかは、ゲームごとに判断が分かれます。
遠回り
遠回りには複数の作り方があります。ここでは最小のものを選びます。敵からプレイヤーへの向きに対して90度ずらした方向へ、プレイヤーの位置から一定距離ずらした点を目標にします。ただしプレイヤーに十分近づいたら、目標点を P に戻して一気に詰めます。
横から膨らんで近づくので、まっすぐ型と挟み撃ちの形を作りやすくなります。壁や障害物を避けて回り込む本格的な遠回りは、次の節以降で触れる経路探索の領域です。ここで扱うのは、あくまで「性格としての遠回り」です。
動かして比べる最小コード
3種類を同じ画面に置いて動かします。矢印キーでプレイヤーを動かし、3体が追います。敵の速さをプレイヤーより遅くしてあるので捕まりません。捕まらない設計にしたのは、軌跡を長く観察するためです。背景を少しずつ塗り重ねるので、軌跡が残ります。
<canvas id="c" width="480" height="320" style="background:#123"></canvas>
<script>
const g=document.getElementById('c').getContext('2d'),k={};
addEventListener('keydown',e=>{k[e.key]=1;if(e.key.startsWith('Arrow'))e.preventDefault()});
addEventListener('keyup',e=>k[e.key]=0);
const P={x:240,y:160,vx:0,vy:0};
const E=[{col:'#f66',x:20,y:20},{col:'#fc4',x:460,y:20},{col:'#6cf',x:240,y:300}];
const T=40, SIDE=90, NEAR=110;
function target(i,e){
if(i==0)return[P.x,P.y];
if(i==1)return[P.x+P.vx*T,P.y+P.vy*T];
const dx=P.x-e.x,dy=P.y-e.y,d=Math.hypot(dx,dy)||1;
if(d<NEAR)return[P.x,P.y];
return[P.x-dy/d*SIDE,P.y+dx/d*SIDE];
}
function step(){
P.vx=((k.ArrowRight?1:0)-(k.ArrowLeft?1:0))*2.2;
P.vy=((k.ArrowDown?1:0)-(k.ArrowUp?1:0))*2.2;
P.x=Math.max(8,Math.min(472,P.x+P.vx));
P.y=Math.max(8,Math.min(312,P.y+P.vy));
g.fillStyle='rgba(17,34,51,.15)';g.fillRect(0,0,480,320);
g.fillStyle='#fff';g.beginPath();g.arc(P.x,P.y,6,0,7);g.fill();
E.forEach((e,i)=>{
const[tx,ty]=target(i,e),dx=tx-e.x,dy=ty-e.y,d=Math.hypot(dx,dy)||1;
e.x+=dx/d*1.6;e.y+=dy/d*1.6;
g.fillStyle=e.col;g.beginPath();g.arc(e.x,e.y,6,0,7);g.fill();
g.strokeStyle=e.col;g.strokeRect(tx-3,ty-3,6,6);
});
requestAnimationFrame(step);
}
step();
setInterval(()=>console.log(E.map(e=>Math.round(Math.hypot(P.x-e.x,P.y-e.y)))),1000);
</script>敵とプレイヤーの動きのほかに、各敵の目標点が小さな四角として描かれます。敵の円と四角の位置関係を見ると、「いまどこを狙っているか」が目で追えます。1秒ごとに、3体それぞれとプレイヤーの距離(ピクセル)がブラウザの開発者ツールのコンソールに出ます。
このコードで先に知っておきたい点が2つあります。
- 予測型の目標点は、画面の外に出ることがあります。プレイヤーが画面の端へ向かって動いているときに起きます。気になる場合は、目標点の座標を画面内に収める処理(クランプ)を足すと変わります。
- 遠回り型は、プレイヤーとの距離が NEAR を下回った瞬間に目標点が横の点からプレイヤーへ飛びます。この切り替わりで動きが折れる様子が見えたら、それが仕様の癖です。後で調整する対象になります。
比べ方の設計書:何を、どの単位で、何回見るか
動かして眺めるだけでも違いは分かりますが、性格の差を言葉にするには、見る場面と記録を決めておくと確実です。次の手順で、読者自身が答えを出せます。
準備
- 上のコードを1つのHTMLファイルとして保存し、ブラウザで開きます。
- 開発者ツールのコンソールを開き、距離のログが流れることを確認します。
場面と記録
次の3つの場面を、それぞれ3回ずつ行います。
- 右へ直進を続ける(約10秒)
- 画面中央で小さな円を描くように動き続ける(約10秒)
- 直進したあと、突然逆向きに折り返す
記録するのは次の項目です。
- 1秒ごとの距離: コンソールの出力を3体分、場面ごとに書き写します。単位はピクセルです。
- 軌跡の形: 場面の終了時に画面を見て、3体の軌跡が背後に集まるのか、先に回るのか、横に膨らむのかを一言で書きます。
- 3回のばらつき: 同じ場面でも、動かし方は毎回少し変わります。3回の距離がどれくらい散らばるかも記録しておきます。
結果の読み方
- 場面1で予測型の距離がまっすぐ型より小さく推移するなら、予測が先回りとして働いていると読めます。差が見えない場合は、T が小さすぎる可能性があります。T を2倍にして再確認します。
- 場面2で予測型の距離がまっすぐ型より大きくなる時間帯があるなら、方向が変わり続ける相手には予測が外れやすい、という性格の表れです。
- 場面3で折り返した直後、予測型の距離が一時的に大きくなるなら、予測の「外れ」が見えています。これが、プレイヤーが敵の裏をかける瞬間です。
- 遠回り型の軌跡が直線より外へ膨らんでいるなら、横からの接近が働いています。ただし膨らみが小さければ SIDE を大きくします。NEAR を大きくすると、早めに直進へ切り替わります。
起こりやすい誤り
- 敵の速さとプレイヤーの速さの比を変えてしまうと、性格の差と速さの差が混ざります。比べるあいだは速さを固定します。
- 1回の操作だけで結論を出さないでください。キー操作の癖で、結果が偏ることがあります。
- 予測型で「画面外の目標」を失敗と見なさないでください。クランプの有無で結果が変わるので、有無を決めて記録します。
性格を作るつまみと、組み合わせ方
3種類の違いは、3つのつまみに整理できます。
- T(予測の先読み時間): 大きいほど先回りが強くなり、外れたときのズレも大きくなります。
- SIDE(横へのずらし幅): 大きいほど大きく膨らみ、到達が遅くなります。
- NEAR(直進へ切り替える距離): 大きいほど早く詰めに入ります。小さいと、最後まで横から膨らみます。
つまみを動かす基準は、「プレイヤーが何を学べるか」です。まっすぐ型は、逃げ方の基本(距離を取る、曲がる)を教えます。予測型は、方向転換のタイミングを学ばせます。遠回り型は、一方向だけ見ていると危ないことを教えます。3体を同時に出すときは、全員を強くするより、役割を分ける方が遊びの層が増えます。たとえば、まっすぐ型を後ろ、遠回り型を横、予測型を前に配置すると、逃げ道の取り方を考える場面が生まれます。
経路探索へ進む前に:公式作品から読み取れること
壁や障害物のある盤面では、目標点へ「まっすぐ進む」だけでは壁にぶつかります。そこで必要になるのが、盤面上の最短の道を調べる経路探索です。格子状の盤面なら、幅優先探索(いまの位置から近い順にマスをたどっていく方法)がよく知られています。
アソバースの公式作品のうち、この考え方に近い題材は「月夜の配達」です。確認済みの範囲では、小包3つ・バッグ2枠・有限の燃料(1区間で燃料1)という条件の経路パズルで、「残り最短距離」の表示と、次の一歩を照らすヒントがあります。ここから読み取れるのは、「あと何歩か」を測る処理と「次にどこへ進むか」を示す処理が、プレイヤーに別々の情報として見えている、という点です。先ほどの「目標点を決める」と「進む」を分けた考え方と通じます。ただし、内部でどんな探索を使っているかは確認済みの範囲にありません。仕組みは作品を開いて、ヒントの出方や距離の変わり方を観察して確かめてください。
もう1つ、「かたちのあいだ」のヒントは「最短とは限らない」と案内されています。次の一手を示すことと、全体の最短を保証することは別の話だ、という設計の割り切りとして読めます。敵の追跡でも同じで、毎フレームの目標点が正しくても、全体として最短で捕まえるとは限りません。性格を作るのが目的なら、むしろ最短でない方が個性になる場面もあります。
次の行動
まず上のコードをそのまま動かし、3つの場面の距離と軌跡を記録してみてください。次に T、SIDE、NEAR のどれか1つだけを変えて、同じ場面でもう一度比べます。差が見えたつまみが、その敵の性格を決めるつまみです。壁のある盤面に広げたくなったら、「月夜の配達」を開き、距離表示とヒントがどう変わるかを観察すると、経路探索の入口になります。

