マガジン

30分で作る「止める」ゲーム:動くバーをタイミングで止める

遊びをつくる

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

週末の明るい机に置かれた、一つの仕組みで遊べる小さな完成済みゲーム。
記事のテーマをもとに制作したAI生成の写真風イメージ。

左右に動くバーが、緑の範囲に重なった瞬間にボタンを押す。ルールはこれだけです。キャラクターも物語も当たり判定もありません。それでも「あと少し早く押せば」と思わせるなら、遊びとして成立しています。この記事では、そのタイミング判定だけのゲームを、30分で動くところまで作ります。 核になるのは、押した瞬間の1回の判定です。バーの位置と緑の範囲の中心との距離を測り、近さに応じて結果を分けます。この1行の計算に、緊張も上達も失敗の悔しさも乗ります。逆に言えば、ここ以外は後回しにできます。 進め方は、まず約50行のHTMLを1ファイル作って動かし、次に速度や範囲の数字を変えて手触りを確かめます。最後に、アソバースへ投稿する前の確認点を押さえます。使うのはブラウザとテキストエディタだけで、外部ライブラリもサーバーも不要です。

この記事の目次
  1. 作る前に決める3つのこと
  2. 手順1:ファイルを作ってバーを動かす(0〜10分)
  3. 手順2:止めた瞬間の判定を読む(10〜20分)
  4. 手順3:手触りを調べる小さな実験(20〜30分)
  5. よくある失敗と直し方
  6. アソバースに出すときの確認点
  7. 次の行動

作る前に決める3つのこと

最初に、タイミングゲームを成立させる部品を3つに絞ります。

  1. 動くもの:左右に往復するバーです。速さは時間で決め、画面の描画回数に左右されないようにします。
  2. 基準:緑の範囲です。ラウンドごとに位置を変え、位置を覚えただけでは当たらないようにします。
  3. 結果の返し方:「ぴったり」「ゾーン内」「はずれ」の3段階です。2段階だと上達が見えにくく、4段階以上だと言葉の区別が曖昧になります。

30分の配分は次のとおりです。

  • 0〜10分、作業:バーが往復するところまで
  • 10〜20分、作業:止める操作と判定
  • 20〜30分、作業:数字を変えて手触りを調整

音、演出、ランキングは今回やりません。公開済みのアソバースには、ゲーム内スコアの共通ランキングはまだありません。記録はゲーム内の得点表示にとどめます。

手順1:ファイルを作ってバーを動かす(0〜10分)

テキストエディタで新しいファイルを作り、index.html という名前で保存します。次のコードを全部貼り付けてください。49行で、そのまま単独で動きます。

html
<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>止める</title>
<style>
body{font-family:sans-serif;text-align:center;background:#102030;color:#fff}
#track{position:relative;width:320px;height:40px;margin:40px auto 16px;background:#234;overflow:hidden}
#zone{position:absolute;top:0;height:100%;background:#3a7}
#bar{position:absolute;top:0;width:6px;height:100%;background:#fc3}
button{font-size:20px;padding:12px 32px}
</style>
<div id="track"><div id="zone"></div><div id="bar"></div></div>
<button id="go">止める</button>
<p id="msg">ボタンかスペースで開始</p>
<p>得点 <span id="score">0</span> / 残り <span id="life">3</span></p>
<script>
const W=320,BW=6,ZW=60,$=id=>document.getElementById(id);
let x=0,dir=1,speed=200,zoneX=0,last=0,score=0,life=3,state="ready";
function newRound(){
 zoneX=20+Math.random()*(W-ZW-40);
 $("zone").style.left=zoneX+"px";
 $("zone").style.width=ZW+"px";
}
function tick(t){
 const dt=Math.min((t-last)/1000,0.05);last=t;
 if(state=="run"){
   x+=dir*speed*dt;
   if(x<0){x=0;dir=1}
   if(x>W-BW){x=W-BW;dir=-1}
   $("bar").style.left=x+"px";
 }
 requestAnimationFrame(tick);
}
function stop(){
 if(state!="run"){score=0;life=3;speed=200;state="run";newRound();}
 else{
   const d=Math.abs(x+BW/2-(zoneX+ZW/2));
   if(d<=6){score+=100;speed*=1.1;$("msg").textContent="ぴったり";}
   else if(d<=ZW/2){score+=50;speed*=1.1;$("msg").textContent="ゾーン内";}
   else{life--;$("msg").textContent="はずれ";}
   if(life<=0){state="over";$("msg").textContent="終了。もう一度どうぞ";}
   else newRound();
 }
 $("score").textContent=score;$("life").textContent=life;
}
$("go").onpointerdown=stop;
addEventListener("keydown",e=>{if(e.code=="Space"&&!e.repeat){e.preventDefault();stop();}});
requestAnimationFrame(tick);
</script>

ファイルをブラウザにドラッグして開き、「止める」を押すと、緑の範囲とバーが現れます。バーが左右に往復すれば、手順1は完了です。

動きの仕組みは tick 関数にあります。前回の描画からの経過時間(dt)に速度を掛けて位置を進めるので、画面の更新頻度が違う端末でも、バーが1秒間に進む距離は同じになります。dt に上限(0.05秒)を付けているのは、別タブから戻った直後にバーが瞬間移動するのを防ぐためです。

手順2:止めた瞬間の判定を読む(10〜20分)

判定の中心は stop 関数の次の1行です。

js
const d=Math.abs(x+BW/2-(zoneX+ZW/2));

x+BW/2 はバーの中心、zoneX+ZW/2 は緑の範囲の中心です。その差の絶対値 d が、止めた位置のずれです。ここで見ているのは「バーの端」ではなく「中心」である点に注意してください。端で判定すると、バーが太いほど有利になり、見た目と結果が食い違います。

結果は d の大きさで分かれます。

  • d が6以下:ぴったり(100点)
  • d が緑の範囲の半分(30)以下:ゾーン内(50点)
  • それ以外:はずれ(残り回数が1減る)

「ぴったり」と「ゾーン内」では、速度が1.1倍になります。うまくいくほど難しくなる、いちばん単純な難易度曲線です。残り回数が0になると state が "over" に変わり、次に押すと最初からやり直せます。

操作は、ボタン(pointerdown)とスペースキーの両方で受けます。click ではなく pointerdown にしたのは、指やマウスが触れた瞬間に反応させるためです。click は指を離すまで発火しないので、タイミングゲームでは体感のずれになります。スペースキーは e.repeat で長押しの連続入力を除いています。

ここまで動いたら、まず10回ほど遊んでください。「ぴったり」と「ゾーン内」の違いが体感できれば、判定の骨組みは完成です。

手順3:手触りを調べる小さな実験(20〜30分)

動くだけでは、おもしろいとは限りません。残りの10分で、数字を1つずつ変えて、自分の手で答えを出します。変更は1回に1つだけにします。2つ同時に変えると、どちらが効いたのか分からなくなるためです。

実験A:速さはどこまで上げられるか

問い:初速(speed=200)と上昇率(1.1)は、どの組み合わせなら「もう1回」と思えるか。

  1. 初速を 150、250、350 の3通りにします。speed=200 は2か所(変数の初期値と stop 内のリセット)にあるので、両方書き換えます。
  2. 各設定で、ゲームオーバーまで3回ずつ遊びます。
  3. 1回ごとに「何ラウンド目で終わったか」と「ぴったりの回数」をメモに書きます。
  4. 3回の平均を設定ごとに比べます。

読み方の目安は次のとおりです。

  • 序盤の数ラウンドで終わる設定は、速すぎる可能性があります。
  • 10ラウンド以上続けても「ぴったり」が半分を超える設定は、易しすぎる可能性があります。
  • その中間で、「あと少しだった」と思って再開してしまう設定が候補です。

目安の数字は、自分の腕前に合わせて決めてください。作った本人は動きに慣れていて、初見の人より上手になっています。可能なら、一度も遊んでいない人に同じ設定で遊んでもらい、同じ表で記録してください。

実験B:範囲の広さと「ぴったり」の幅

問い:緑の範囲(ZW=60)と「ぴったり」の幅(d<=6)の比は、上達の実感に効くか。

  • ZW を 40、60、90 に変えて、各10回遊びます。
  • 記録するのは「ぴったり/ゾーン内/はずれ」の回数です。
  • 「ゾーン内」ばかりで「ぴったり」がほとんど出ない設定は、100点が遠すぎて目標になりません。
  • 逆に「ぴったり」ばかりの設定は、判定が甘すぎます。

ZW を変えると、バーが範囲の外へはみ出さないように newRound の余白(20 と 40)も見直してください。

実験C:入力の遅れを疑う

「押したのに間に合わなかった」と感じる場合は、判定ではなく入力側が原因のこともあります。マウス、タッチ、スペースキーで、同じ設定を各10回遊び、はずれの回数を比べてください。操作方法で明らかに差が出るなら、判定の幅を操作ごとに変えるより先に、どの入力が自分の環境で遅いのかを確かめるほうが先です。

よくある失敗と直し方

  • 速度が上がり続けて誰も遊べない:speed*=1.1 を speed=Math.min(speed*1.1,600) のように上限付きにします。上限の数字は実験Aの記録から決めます。
  • 緑の範囲が端に寄って見えにくい:newRound の 20+ と -40 は、範囲が左右の端から20px以上離れるための余白です。ZW を広げたら、この余白も確認します。
  • バーが同じ動きに見えて飽きる:速度だけでなく、往復の途中で向きを変える、ときどき緑の範囲を細くする、といった変化を足せます。ただし、判定の中心は変えず、一度に足すのは1つにとどめます。
  • 結果が目に入らない:今は文字で「ぴったり」と出すだけです。範囲の色を一瞬変える程度なら、数行で足せます。「動きを抑える」設定を使う人もいるため、点滅は避け、色の変化を中心にしてください。

アソバースに出すときの確認点

この index.html は1ファイルで外部通信もないので、アソバースの投稿条件に収まります。出す前に次を確認してください。

  • 入口は、ZIPまたはアップロードの直下にある index.html です。フォルダに入れたままZIPにすると、直下に見つからなくなります。
  • アップロードは20MiB以内です。このゲームは数KBです。
  • 投稿は「入力→表示の確認→公開」の3段階です。途中で下書き保存ができるので、実験Aの設定を決める前に保存して、あとで更新できます。
  • 公開にはメールアドレスの確認が必要です。
  • 投稿画面の「紹介文と遊び方を作成」は、本人の検証済みアップロードを読み取って下書きを作ります。「この文章を使う」を押した場合だけ反映されます。内容が実際の遊びと合っているか、必ず読んで確認してください。
  • このゲームは記録を保存しません。得点はページを閉じると消えます。最高記録を残したい場合は、アソバースのクラウドセーブ(作品ごとにJSONで128KiBまで)を使う設計が必要です。その場合はSDKの案内を現在のドキュメントで確認してください。

次の行動

まず index.html を保存し、10回遊んで「ぴったり」の手触りを確かめてください。次に実験Aの表を作り、初速を3通りに変えて、ラウンド数とぴったりの回数を記録します。数字が決まったら、未経験の人に1回遊んでもらい、同じ表に書き足すところまで進めてみましょう。そこまでできれば、投稿の準備は整っています。