30分で作る「止める」ゲーム:動くバーをタイミングで止める
遊びをつくる執筆にClaudeを使用しています。

左右に動くバーが、緑の範囲に重なった瞬間にボタンを押す。ルールはこれだけです。キャラクターも物語も当たり判定もありません。それでも「あと少し早く押せば」と思わせるなら、遊びとして成立しています。この記事では、そのタイミング判定だけのゲームを、30分で動くところまで作ります。 核になるのは、押した瞬間の1回の判定です。バーの位置と緑の範囲の中心との距離を測り、近さに応じて結果を分けます。この1行の計算に、緊張も上達も失敗の悔しさも乗ります。逆に言えば、ここ以外は後回しにできます。 進め方は、まず約50行のHTMLを1ファイル作って動かし、次に速度や範囲の数字を変えて手触りを確かめます。最後に、アソバースへ投稿する前の確認点を押さえます。使うのはブラウザとテキストエディタだけで、外部ライブラリもサーバーも不要です。
この記事の目次
作る前に決める3つのこと
最初に、タイミングゲームを成立させる部品を3つに絞ります。
- 動くもの:左右に往復するバーです。速さは時間で決め、画面の描画回数に左右されないようにします。
- 基準:緑の範囲です。ラウンドごとに位置を変え、位置を覚えただけでは当たらないようにします。
- 結果の返し方:「ぴったり」「ゾーン内」「はずれ」の3段階です。2段階だと上達が見えにくく、4段階以上だと言葉の区別が曖昧になります。
30分の配分は次のとおりです。
- 0〜10分、作業:バーが往復するところまで
- 10〜20分、作業:止める操作と判定
- 20〜30分、作業:数字を変えて手触りを調整
音、演出、ランキングは今回やりません。公開済みのアソバースには、ゲーム内スコアの共通ランキングはまだありません。記録はゲーム内の得点表示にとどめます。
手順1:ファイルを作ってバーを動かす(0〜10分)
テキストエディタで新しいファイルを作り、index.html という名前で保存します。次のコードを全部貼り付けてください。49行で、そのまま単独で動きます。
<!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行です。
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回」と思えるか。
- 初速を 150、250、350 の3通りにします。speed=200 は2か所(変数の初期値と stop 内のリセット)にあるので、両方書き換えます。
- 各設定で、ゲームオーバーまで3回ずつ遊びます。
- 1回ごとに「何ラウンド目で終わったか」と「ぴったりの回数」をメモに書きます。
- 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回遊んでもらい、同じ表に書き足すところまで進めてみましょう。そこまでできれば、投稿の準備は整っています。

