マガジン

「予測線」を作り込む:未来を少しだけ見せる

遊びをつくる

執筆にClaudeを使用しています。

定番の盤面に一つだけ赤い通り道を加え、選択が変わる様子を描いた版画風イラスト。
記事のテーマをもとに制作したAI生成イラスト。

発射角度と初速を決めて、引力で曲がる軌道の先にあるゲートを狙うゲームを考えてみます。スライダーを動かすたびに、これから通る道が点線で描かれたら、遊び手は何を得るでしょうか。逆に、点線がなければ何が変わるでしょうか。この「先を示す表示」を、どこまで見せるか、どう描くかを、手を動かして決めていくのがこの記事の目的です。 アソバースの公式作品「小さな軌道」には、3つの星系、全軌道予測(点線)、前回の軌道(細い実線)、予測を消して挑む選択肢があります。設計から読み取れるのは、予測線が「答えを教える機能」ではなく「見せる量を遊び手が選べる機能」として置かれていることです。点線が出る遊び方と、出さない遊び方の両方が用意されているからです。細かな見え方や数値は、作品を開いて確かめてください。 ここでは、同じ考え方の予測線を、HTML1枚で作ります。手順は5つです。最後に、予測の長さを自分で比べるための記録の取り方を示します。

この記事の目次
  1. 手順1:予測と実際を「同じ関数」で動かす
  2. 手順2:「どこまで見せるか」を長さで決める
  3. 手順3:前回の軌道を「細い実線」で残す
  4. 手順4:予測を切り替えられるようにする
  5. 手順5:描画の細部を整える
  6. 次の行動

手順1:予測と実際を「同じ関数」で動かす

予測線づくりで最も重要なのは、予測用の計算と実際に飛ぶ計算を別々に書かないことです。別々に書くと、少しずつ食い違い、点線どおりに飛ばない原因になります。

そこで、発射位置・角度・初速から、通る点の並びを返す関数を1本だけ作ります。予測線はその点の並びを点線で描き、実際の飛行は同じ点の並びを順にたどって描きます。

次のコードは、星が1つだけの最小構成です。ファイル名はindex.htmlにして、ブラウザで開けば動きます。外部ライブラリは使いません。

html
<canvas id="c" width="480" height="320" style="background:#112"></canvas><br>
角度<input id="a" type="range" min="0" max="360" value="300">
初速<input id="v" type="range" min="1" max="4" step="0.1" value="2.2">
先<input id="n" type="range" min="0" max="400" value="120">
<label><input id="p" type="checkbox" checked>予測</label>
<button id="go">発射</button>
<script>
const $=i=>document.getElementById(i),g=$('c').getContext('2d');
const S={x:240,y:160},P={x:60,y:260},GM=900;
let last=[],shot=[],k=0;
function path(steps){
  const t=$('a').value*Math.PI/180,s=+$('v').value;
  let x=P.x,y=P.y,vx=Math.cos(t)*s,vy=Math.sin(t)*s;
  const out=[[x,y]];
  for(let i=0;i<steps;i++){
    const dx=S.x-x,dy=S.y-y,r=Math.hypot(dx,dy);
    if(r<12)break;
    const f=GM/(r*r*r);
    vx+=dx*f;vy+=dy*f;x+=vx;y+=vy;
    out.push([x,y]);
  }
  return out;
}
function line(pts,dash,w,col){
  g.setLineDash(dash);g.lineWidth=w;g.strokeStyle=col;g.beginPath();
  pts.forEach((q,i)=>i?g.lineTo(q[0],q[1]):g.moveTo(q[0],q[1]));g.stroke();
}
function draw(){
  g.clearRect(0,0,480,320);
  g.fillStyle='#fc6';g.beginPath();g.arc(S.x,S.y,10,0,7);g.fill();
  g.fillStyle='#fff';g.fillRect(P.x-3,P.y-3,6,6);
  if(last.length)line(last,[],1,'#ccd');
  if($('p').checked)line(path(+$('n').value),[3,6],2,'#8cf');
  if(shot.length)line(shot.slice(0,k),[],2,'#fff');
}
$('go').onclick=()=>{last=shot;shot=path(1500);k=0;};
function tick(){if(k<shot.length)k+=2;draw();requestAnimationFrame(tick);}
tick();
</script>

path関数が心臓部です。星までの距離から引力を求め、速度を更新してから位置を進めています。予測線はpath(先の長さ)、発射はpath(1500)で、呼び出す数が違うだけで計算は同じです。

まず動作を確かめます。次の順で操作します。

  1. 角度と初速のスライダーを動かし、点線が追従することを見ます。
  2. 「発射」を押し、白い線が点線の上をなぞることを確かめます。
  3. 点線の上から白い線が外れる場合は、path以外の場所で計算していないかを疑います。

手順2:「どこまで見せるか」を長さで決める

コードの「先」スライダーは、予測で何ステップ先まで描くかを決めています。値を0にすれば点線は消え、400にすれば遠くまで伸びます。

小さな軌道には全軌道を点線で出す遊び方があります。一方、自作では長さを絞る選択もできます。短い予測は「出だしの曲がり方」だけを教え、その先の読みは遊び手に残します。ゲームの狙いによって、置き場所が変わります。

  • 0(なし)、遊び手に残る仕事:曲がり方を頭の中で組み立てる、向いているゲーム:慣れた人向けの高難度
  • 短い、遊び手に残る仕事:出だし以降の曲がりを読む、向いているゲーム:練習と本番の中間
  • 長い(全体)、遊び手に残る仕事:ゲートに合わせる微調整、向いているゲーム:入門、操作の楽しさ重視

次に、どの長さが自分のゲームに合うかを調べる方法です。題名の問いは、読者自身の手で答えが出る形にしておきます。

  • 自分で試すときは、長さを0、短め、長めの3通りにします。
  • それぞれで、同じ配置のゲートを何回で当てられるかを記録します。
  • 回数の記録は、1設定につき最低5回は取ります。1回だけでは、たまたまの当たりと区別できません。

結果の読み方は次のとおりです。長さ0でも回数が変わらないなら、点線が操作の理解に効いていません。短めと長めで回数がほぼ同じなら、短い方が読みの余地を残せます。長めだけ回数が大きく減るなら、長さがゲートの位置を教えすぎている可能性があります。この3通りの比較には、慣れの影響も出ます。順番を入れ替えて試すか、別の人にも遊んでもらうと判断しやすくなります。

手順3:前回の軌道を「細い実線」で残す

予測線は未来を示しますが、「前回どう飛んだか」という過去も、次の調整の手がかりになります。コードでは、発射のたびに直前のshotをlastに移し、細い実線で描いています。

線の種類を分けるのは、役割を区別するためです。

  • 点線は、これから通る予定の道です。
  • 細い実線は、前回通った道です。
  • 太い白線は、今飛んでいる道です。

この3つが重なる場面では、見分けがつくことが重要です。点線と実線では線の形が違うので、色が似ていても区別できます。ただし、背景との明暗差が小さいと、どちらも埋もれます。色は、色覚の違いがあっても見分けやすいよう、形(点線か実線か)と太さの違いにも頼る設計にしておくのが安全です。

前回の軌道は、記録として残しすぎないことも大切です。コードでは1回前だけを持っています。何本も残すと画面が線で埋まり、予測線が読めなくなります。まず1本から始めて、足りないと感じたら増やしてください。

手順4:予測を切り替えられるようにする

コードのチェックボックスは、予測を出すか消すかを遊び手が選べるようにします。小さな軌道にも予測を消して挑める選択肢があり、設計から読み取れるのは、予測線を「常に付いてくる補助」ではなく「選べる補助」として扱っていることです。

切り替えを付けるときは、次の点を決めます。

  1. 初期状態をどちらにするか。初めて遊ぶ人には、点線が出ている方が操作を理解しやすくなります。
  2. 切り替えの場所。プレイ中に押しやすい位置か、設定画面の奥かで、遊ばれ方が変わります。
  3. 切り替えの結果を記録に混ぜるか。予測ありと予測なしで同じ記録を競わせると、条件の違いが見えなくなります。

記録を混ぜるかどうかは、ゲームの作りによって判断が分かれます。分けるなら、記録の欄を別にするか、記録に「予測なし」の印を付けます。これは自作ゲームで決めることです。公式作品の記録の扱いについては、作品を開いて確かめてください。

手順5:描画の細部を整える

最低限動いたら、読みやすさを整えます。

点線の間隔

コードの[3,6]は、3ピクセル描いて6ピクセル空ける指定です。間隔が詰まると実線に見え、開きすぎると形が読めなくなります。画面のサイズを変えて、曲がりの強い部分でも途切れすぎないか見てください。

動きを抑える設定

アソバースの公式作品は「動きを抑える」設定に対応しています。自作でも、飛行を少しずつ描くアニメーションがある場合は、設定を尊重する余地があります。次の1行で、ブラウザ側の設定を読み取れます。

js
const calm=matchMedia('(prefers-reduced-motion: reduce)').matches;

calmが真のときは、k+=2のところをk=shot.lengthにして、飛行を一気に描く方法があります。予測線そのものは動かないので、そのまま使えます。

起こりやすい失敗

  • 予測の計算と実際の計算を別々に書き、少しずつ食い違う。手順1の一本化で防げます。
  • 予測線がゲートの位置を完全に教えてしまい、狙う楽しさが減る。手順2の3通りの比較で確認できます。
  • 星の近くで速度が極端に大きくなり、点がまばらになる。コードでは星に近づきすぎたら止めています。刻みを細かくしたい場合は、1回あたりの進みを小さくして、ステップ数を増やします。
  • 画面外まで線を描き続け、重くなる。予測の長さに上限を置けば防げます。

次の行動

まずコードをindex.htmlとして保存し、「先」スライダーを動かして、点線の長さで狙いやすさがどう変わるかを自分の目で見てください。次に、ゲートを1つ足し、手順2の3通りの長さで当てるまでの回数を、各5回ずつ記録します。そのうえで、アソバースで「小さな軌道」を開き、点線あり・なしの両方で遊んで、自作の予測線の見せ方と比べてみてください。作ったものを公開したいときは、直下のindex.htmlを入口にして投稿できます。