← コラム一覧へ
GAME2026.06.28読了 2 分 · デモあり

20 秒で終わるゲームを作るときの設計

小さいループほど、入力の手触りが全てになります。判定・演出・音の優先順位を整理しました。

このサイトに置いてある「格子ハンター」は 1 プレイ 20 秒です。 短いゲームを作ってみて分かったことを書きます。

短いと、システムを説明する余地がない

20 秒しかないので、ルール説明を読ませる時間はありません。 プレイヤーは最初の 1 秒で何をすればいいか分かる必要があります。

格子ハンターでは、盤面のセルのうち 1 つだけが色付きで脈打ちます。 それ以外は全部同じ灰色です。この差だけで「光っているものを押す」と伝わります。 チュートリアルは書いていません。

逆に言うと、ルールを 1 つに絞れないゲームは 20 秒には収まりません。 思いついた要素を足したくなったら、その都度「これは説明なしで伝わるか」で切りました。

優先順位は 判定 > 演出 > 音

限られた時間で作るとき、この順に手をかけると失敗しません。

判定が最優先です。押した場所と当たり判定がずれていると、それだけでゲームが壊れます。 特に六角形のグリッドは、矩形と違って座標からセル番号を出す式が素直ではありません。 奇数行を半セルずらす方式にしたので、逆算はこうなります。

const row = Math.floor(y / cellH);
const col = Math.floor((x - (row % 2 ? cellW / 2 : 0)) / cellW);

演出が次点。当たったかどうかが、スコアの数字を見なくても分かる必要があります。 クリック地点から広がるリングを 0.6 秒かけて消しています。これがないと、 「押したのに反応がない」と感じられてしまいます。

音は最後です。あると気持ちいいですが、無くてもゲームは成立します。 実際、公開時点ではまだ入れていません。

タイマーは描画ループと分ける

残り時間のカウントは setInterval で 1 秒ごとに減らし、描画は requestAnimationFrame で回しています。

最初は描画ループの中で経過時間から残り秒数を計算していたのですが、 タブが裏に回ると requestAnimationFrame が止まるため、戻ってきたときに 時間が飛びませんでした。「裏に回している間は時間が止まる」のは、 ゲームとしては都合が良すぎます。

とはいえ setInterval もタブが裏だと間隔が伸びます。厳密にやるなら 開始時刻を記録して実時間との差分で計算すべきで、そこは今後直す予定です。

ミスに罰を置くかどうか

外したときにスコアを 1 減らす仕様にしています。これがないと、 連打が最適解になってゲームが成立しません。

ただし 0 未満にはしません。序盤に外し続けたプレイヤーが、 挽回不可能な気分になるのを避けるためです。

this.setState(st => ({ score: Math.max(0, st.score - 1) }));

短いゲームでは、1 回のミスの重みが相対的に大きくなります。 罰は「連打を止める」のに必要な最小限にとどめるのがちょうどよさそうでした。