マガジン

30分で作る「影を当てる」ゲーム:シルエットから形を選ぶ

遊びをつくる

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

一つのボールと短い足場だけで作られた小さなゲーム模型。
記事のテーマをもとに制作したAI生成イメージ。実際のゲーム画面ではありません。

真っ黒に塗りつぶした図形が、少し傾いて画面に出ています。下には色のついた見本が5つ並んでいて、どれの影かを1回で選びます。画像ファイルは1枚も使わず、図形の輪郭を数行で定義するだけで、この出題ができます。 影当てが面白くなるのは、色も模様も消えて輪郭だけが残るからです。見本は正面向きなのに、影は傾いているので、頭の中で見本を回して照らし合わせることになります。この「頭の中で回す」一手間が遊びの中心です。題名の問いに答えるなら、30分で作れる最小の形は、図形の定義5つ、回転して黒く塗る描画関数、見本ボタン、正誤判定の4部品です。 この記事は、その4部品を手順どおりに組み、難しさを調整し、手応えを自分で確かめるところまでを扱います。時間配分は目安で、合計30分を想定しています。

この記事の目次
  1. 手順0:遊びの条件を先に決める(目安3分)
  2. 手順1〜3:コードを貼って動かす(目安10分)
  3. 手順4:難しさを一つずつ足す(目安8分)
  4. 手順5:遊んで手応えを記録する(目安5分)
  5. 手順6:公開前の確認と投稿(目安4分)
  6. 次の行動

手順0:遊びの条件を先に決める(目安3分)

コードを書く前に、次の3つを決めておきます。ここが曖昧だと、あとで調整する基準がなくなります。

  • 出題:黒一色の影を1つ出します。毎回、回転角をランダムにします。
  • 回答:影の下に見本を並べ、1つ選ばせます。見本は正面向きの色つき図形です。
  • 終わり:今回は終わりを作らず、正解数と出題数だけを表示します。

最初から形を増やしたり、制限時間を足したりしないでください。まず1問が成立する状態を作り、足すのは遊んで物足りなさを確かめてからにします。

手順1〜3:コードを貼って動かす(目安10分)

新しい空のファイルを作り、index.html という名前で保存します。次のコードを丸ごと貼ってください。外部ライブラリは使わず、1ファイルで動きます。

html
<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>影を当てる</title>
<style>button{margin:4px;padding:4px}canvas{background:#e8e2d0}</style>
<canvas id="c" width="240" height="240"></canvas>
<div id="opts"></div>
<p id="msg">どの形の影でしょう?</p>
<script>
const S = {
  丸: p => p.arc(0, 0, 70, 0, 7),
  四角: p => p.rect(-60, -60, 120, 120),
  三角: p => { p.moveTo(0, -75); p.lineTo(70, 55); p.lineTo(-70, 55); p.closePath(); },
  星: p => { for (let i = 0; i < 10; i++) { const r = i % 2 ? 32 : 78, a = i * Math.PI / 5; p.lineTo(Math.sin(a) * r, -Math.cos(a) * r); } p.closePath(); },
  十字: p => { p.rect(-25, -70, 50, 140); p.rect(-70, -25, 140, 50); },
};
const names = Object.keys(S);
let answer, hit = 0, total = 0;
function draw(cv, name, angle, color, k) {
  const g = cv.getContext("2d"), p = new Path2D();
  S[name](p);
  g.clearRect(0, 0, cv.width, cv.height);
  g.save(); g.translate(cv.width / 2, cv.height / 2);
  g.scale(k, k); g.rotate(angle);
  g.fillStyle = color; g.fill(p); g.restore();
}
function next() {
  answer = names[Math.floor(Math.random() * names.length)];
  draw(c, answer, Math.random() * 6.28, "#111", 1);
  opts.innerHTML = "";
  names.forEach(n => {
    const b = document.createElement("button"), cv = document.createElement("canvas");
    cv.width = cv.height = 70; draw(cv, n, 0, "#c0562b", 0.4);
    b.append(cv); b.setAttribute("aria-label", n);
    b.onclick = () => judge(n);
    opts.append(b);
  });
}
function judge(n) {
  total++; if (n === answer) hit++;
  msg.textContent = (n === answer ? "正解" : "正解は" + answer) + `(${hit}/${total})`;
  next();
}
next();
</script>

保存したらブラウザで開き、次の順に確かめてください。

  1. 画面に黒い図形が1つ出て、その下に色つきの見本が5つ並んでいるか。
  2. 見本を押すと、「正解」または「正解は〜」と正解数が表示され、影が次の図形に変わるか。
  3. 何度か繰り返して、同じ向きの影ばかりにならず、回転角が毎回変わっているか。

動かないときは、まずブラウザの開発者ツールのコンソールにエラーが出ていないかを見ます。多いのは、貼るときに { や } が欠けた場合です。

仕組みの要点は、図形を Path2D(輪郭を覚えておく入れ物)として作り、draw 関数で「中心に移動 → 拡大縮小 → 回転 → 塗る」の順に描いているところです。影は色を #111 にしただけで、見本は同じ関数に色と倍率だけ変えて渡しています。出題用と見本用で描画の仕組みが共通なので、形を足すときは S に1行足すだけで済みます。

また、見本のボタンには名前を文字で出さず、aria-label として持たせています。画面上の見本は図形だけにして、文字から答えを引けないようにするためです。

手順4:難しさを一つずつ足す(目安8分)

最初のままだと、5つの形は輪郭の違いが大きく、慣れるとほぼ全問を答えられる可能性があります。手応えを調整するための部品は4つあります。一度に全部ではなく、1つ足して遊び、また次を足すようにしてください。

形を増やす

S に1行足すだけです。たとえば半円なら、次のように書けます。

js
半円: p => p.arc(0, 0, 70, Math.PI, 0),

増やすなら、既存の形と「似ているが違う」ものを選ぶのがコツです。四角と菱形、三角と半円のように、回転すると見間違えやすい組み合わせを足すと、判断に迷いが生まれます。

影を歪める

影は光の角度で伸び縮みするものです。draw の中の g.scale(k, k) を g.scale(k, k * 0.7) のように縦だけ縮めると、影が平たくなります。ただし、歪めると回転した輪郭が見本の輪郭と一致しなくなります。歪めた分は難しさが急に上がるので、0.8 のような弱い値から試してください。

見本の並び順を毎回入れ替える

今のコードは見本の順番が固定です。固定のままだと、「星は左から4番目」と位置で覚えられてしまい、輪郭を見る必要がなくなります。names を表示前に並べ替える処理を足すと、この抜け道を塞げます。

回転をやめる、または段階化する

回転角を0にすると、影と見本の向きが揃うので、ほぼ見たままの照合になります。0度・90度刻み・自由角度の3段階を用意して比べると、難しさのどこが回転に由来するかがわかります。

手順5:遊んで手応えを記録する(目安5分)

調整の良し悪しは、感覚だけで決めず、記録を残して判断します。手元で次のとおりに行ってみてください。

  • 準備:紙かメモに、「出題ごとに、答えた形・正解の形・自分が迷ったか」を書く欄を作ります。
  • 回数:1条件につき10問を1セットとし、同じ条件で3セット遊びます。
  • 比べる条件:回転なし、90度刻み、自由角度のうち2つ以上を、同じ人が続けて遊んで比べます。順番による慣れの影響を避けるため、条件の順は入れ替えて遊びます。
  • 記録の単位:1問ごとに、正誤と、迷って3秒以上かかったかどうかだけを書きます。正確な時間計測は不要です。

結果は次のように読みます。

  • どの条件でも全問正解に近い:難しさが足りません。似た形を足すか、影の歪みを強めます。
  • 誤答が特定の組み合わせに集中する:その2つの輪郭が近すぎる可能性があります。組み合わせの片方を差し替えるか、片方だけ少し大きさを変えます。
  • 正解数は高いのに、迷った問題が多い:判断に手間がかかる設計になっています。遊びとして成立していることもあるので、「迷う楽しさ」と受け取れるか、他の人にも遊んでもらって確かめてください。
  • 全体的に正解が低く、ほとんどが勘になる:難しすぎます。回転を段階化して、最初は90度刻みにします。

起こりやすい誤りは、自分で作った人が遊んで簡単だと感じることです。作った本人は、回転した輪郭と形の対応を既に覚えています。判断は、初めて遊ぶ人の記録で行ってください。1人でも構いませんので、作り手以外の人に10問だけ遊んでもらうのがおすすめです。

手順6:公開前の確認と投稿(目安4分)

完成した index.html は、アソバースにそのままアップロードできます。投稿の流れと注意点を整理します。

  • 投稿はHTMLの直接アップロードか、ZIPです。入力、表示の確認、公開の3段階で進みます。
  • 公開にはメールアドレスの確認が必要です。ログインしていない状態では公開できません。
  • アップロードは20MiB以内です。このゲームは画像を使わないので、容量の心配はほぼありません。
  • ZIPで出す場合は、直下に index.html を置いてください。素材を足すときは相対パスで参照します。
  • 下書き保存ができるので、一度入れて表示確認だけして、あとで公開することもできます。

もう一つ、記録の扱いに注意が要ります。アソバースにはゲーム内スコアの共通ランキングがなく、アップロード作品の中の localStorage は一時メモリーの互換処理です。今回のコードのように、正解数を画面に出すだけの作りなら問題ありませんが、「自己ベストをずっと残す」仕組みを足したい場合は、公式の案内で保存まわりの仕様を確認してから設計してください。

投稿画面には、公開済みのURLや検証済みのアップロードから紹介文と遊び方の下書きを作る機能もあります。下書きは「この文章を使う」を押したときだけ反映されます。影当ての遊び方は短く書けるので、自分の言葉で書くのも十分現実的です。

次の行動

まず index.html を保存して、1問遊べる状態にしてください。次に、形を1つだけ足すか、回転を段階化するかのどちらかを選び、10問×3セットで手応えを記録します。そのうえで、作り手以外の人に10問遊んでもらい、迷いが多かった組み合わせを見直してから投稿してください。