マガジン

観察ゲームを組み立てる:置く・変わる・見つける、三つの仕組みの設計と実装手順

遊びをつくる

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

小さな庭に石を置き、虫眼鏡で芽や生きものの変化を観察する水彩イラスト
記事のテーマをもとに制作したAI生成イラスト。

アクションで反応を返す、謎を解いて先へ進む。そうでないゲームを作ろうとしたとき、手が止まることがある。「置く・変わる・見つける」という三段階だけで成立する観察ゲームは、短い実装で遊び心地を届けられるジャンルのひとつだ。この記事では、そうした観察ゲームをゼロから組み立てるための設計方針と実装手順を整理する。掲載するコードは動作原理を説明するためのサンプルであり、そのまま動くプロダクトとして検証したものではない。AIを使って設計を進める場合の考え方のヒントも合わせて示す。本文はClaudeが執筆した。人間による取材・プレイテストは行っていない。

この記事の目次
  1. 観察ゲームとは何か
  2. 三つの核となる仕組みを設計する
  3. 状態とデータ構造を決める
  4. 変化ルールと発見条件を書く
  5. UIと手触りを整える
  6. HTMLファイルに仕上げてアソバースへ投稿する

観察ゲームとは何か

観察ゲームとは、プレイヤーが何かを「置く」「触れる」「組み合わせる」操作をすると、場面が変化し、その変化の中に「発見」が生まれるゲームだ。勝敗条件や即時フィードバックではなく、状態の変化を眺めて気づく体験を中心に置く。

アソバースのゲームにも、このジャンルに近い作品がある。「潮だまり」は海藻と小石を並べて8種の観察図鑑を完成させる構造を持ち、「余白の庭」は木・花・池を置いて季節の変化を見守る。どちらも勝敗も時間制限もなく、プレイヤーが場を整えていく中で発見が生まれる。「こだまの森」は光と音の反応を観察する仕組みを持ち、「プリズムの道」は鏡と色ガラスの組み合わせが光の経路を変える。共通しているのは、置く・変わる・見つけるという三段構造だ。

この記事はClaudeが執筆した設計・実装の手順説明です。掲載するコード例は動作原理を説明するためのサンプルであり、動作を保証するものではありません。人間による取材・プレイテストは行っていません。

三つの核となる仕組みを設計する

小さな観察ゲームを設計するとき、まず「置く」「変わる」「見つける」それぞれの範囲を決めることで、実装の全体量を見通せる。

  • 置く(Input):プレイヤーが取れる操作の種類と場所。グリッドのセルにオブジェクトを配置する、スライダーで値を変える、オブジェクト同士をドラッグして隣に置く、など。操作の選択肢が少ないほど、発見のための集中度が上がる。
  • 変わる(Rule):配置によって場面の状態がどう変わるか。隣接するオブジェクトが反応する、時間経過で状態が推移する、特定の組み合わせが別の状態を生む、などがある。ルールは少なくとも一つ、多くても三〜四種にとどめると設計と実装が整理しやすい。
  • 見つける(Discovery):どの状態の組み合わせが「発見」として扱われるか。全マスが特定の状態になる、図鑑の条件を満たす組み合わせが出現する、隠れた経路がつながる、といった条件を事前に列挙しておく。発見条件の総数がゲームの「深さ」に直結する。

AIツールを設計の補助に使う場合、この三要素を一度テキストで整理してから対話を始めると有効だ。「5×5のグリッドに3種のオブジェクトを置け、隣接すると色が変わる、8種の発見条件がある」という仕様を自然言語で書き出しておくと、コード生成の精度が上がり、後から仕様が膨らんだときも変更箇所が特定しやすい。

状態とデータ構造を決める

観察ゲームの実装は「状態を持つグリッド」と「オブジェクトの定義」から始まる。状態が明示的であれば変化ルールも発見条件も書きやすい。以下は5×5のグリッドを二次元配列で管理し、セルに複数の状態を持たせる最小構成の説明用サンプルだ。

javascript
// 説明用サンプル。動作確認済みではありません。
const GRID_SIZE = 5;

// セルの状態を表すオブジェクト型(例)
// { type: 'empty' | 'moss' | 'stone' | 'water', activated: boolean }

function createGrid(size) {
  return Array.from({ length: size }, () =>
    Array.from({ length: size }, () => ({
      type: 'empty',
      activated: false
    }))
  );
}

const state = {
  grid: createGrid(GRID_SIZE),
  discoveries: [],    // 発見済みの条件ID
  selectedTool: 'moss' // 現在の置くオブジェクト
};

状態をオブジェクト一つにまとめておくと、「現在の配置をリセットする」「発見条件をチェックする」関数がグリッドだけでなく選択状態なども参照できて整理しやすい。状態を持つオブジェクトとして設計することで、AIに「この状態に対して変化ルールを書いて」と依頼する際も文脈を共有しやすくなる。設計ドキュメントとコードのコメントを同期させておくことも、AIとの対話の中で修正の見通しを保つ実践的な方法だ。

変化ルールと発見条件を書く

変化ルールは「ある操作が加わったとき、状態がどう更新されるか」を関数として表現する。例えば「苔と石が隣接するとセルがactivated状態になる」ルールは、配置のたびに周囲のセルを走査して判定する形になる。

javascript
// 説明用サンプル。動作確認済みではありません。
const NEIGHBORS = [
  [-1, 0], [1, 0], [0, -1], [0, 1]
];

function applyRules(grid) {
  const next = grid.map(row => row.map(cell => ({ ...cell })));
  for (let r = 0; r < grid.length; r++) {
    for (let c = 0; c < grid[r].length; c++) {
      const cell = grid[r][c];
      if (cell.type === 'moss') {
        const hasAdjacentStone = NEIGHBORS.some(([dr, dc]) => {
          const nr = r + dr, nc = c + dc;
          return grid[nr]?.[nc]?.type === 'stone';
        });
        next[r][c].activated = hasAdjacentStone;
      }
    }
  }
  return next;
}

ルールは「隣接判定」のほかに「対角線」「距離2以内」「同種が3つ以上連続」など複数のパターンを組み合わせられる。ただし初期設計では一種類のルールだけで発見条件をすべて設定してみる方が、ゲームとして成立するかの判断が早い。ルールを増やすのは発見条件の多様性が足りないと感じてからでも遅くない。

発見条件を定義する

発見条件は「どの配置パターンが満たされたら、何がアンロックされるか」をリストとして先に書ききっておく。プログラムより先に条件を日本語で列挙することで、「条件が被っている」「一つも達成できない配置しかない」といった設計上の矛盾を早期に見つけられる。

javascript
// 説明用サンプル。動作確認済みではありません。
const DISCOVERY_CONDITIONS = [
  {
    id: 'moss-ring',
    label: '苔の輪',
    // 中心セル(2,2)の周囲4マスがすべて苔
    check: (grid) =>
      [[-1,0],[1,0],[0,-1],[0,1]].every(
        ([dr,dc]) => grid[2+dr]?.[2+dc]?.type === 'moss'
      )
  },
  {
    id: 'water-edge',
    label: '水辺の端',
    // 最上行にwaterが3つ以上
    check: (grid) =>
      grid[0].filter(c => c.type === 'water').length >= 3
  }
  // …条件を必要な数まで列挙する
];

function checkDiscoveries(grid) {
  return DISCOVERY_CONDITIONS
    .filter(cond => cond.check(grid))
    .map(cond => cond.id);
}

発見条件の数は多ければ良いわけではない。完成形に見通しがある数に絞ると、プレイヤーがどこまで来たかを実感しやすい。条件が増えるほど「全パターンを達成できるか」の検証コストも上がるため、最初は4〜6種で動かして調整するのが現実的だ。

AIに発見条件の生成を依頼するときは「グリッドの構造と使えるオブジェクトの種類を渡し、意味のある組み合わせを提案させる」方法が使いやすい。提案をそのまま採用せず、「この条件はゲームの体験として面白いか」を自分で判断してから実装に移すことが品質につながる。

UIと手触りを整える

観察ゲームの手触りは、配置した瞬間に状態が変わって見えるかどうかにかかっている。変化をアニメーションで示す必要は必ずしもないが、「変わった」ことがすぐわかる視覚的な差分は必要だ。activated状態のセルに背景色を付ける、アイコンを入れ替える、といったシンプルな方法でも十分機能する。

発見が達成されたときのフィードバックも、過剰にならない範囲で明確にする。モーダルでポップアップを出す方法はシンプルで実装も短い。画面の端に図鑑リストを置いてアンロック済みにチェックが入る方法は、進捗の見通しを与えやすい。「残りいくつ」を示すだけでもプレイヤーの探索意欲に影響する。

リセットボタンは早めに実装する。観察ゲームはプレイヤーが「もし別の組み合わせだったら」を試したい場面が多く、リセットが手軽でないとその試行が止まる。ブラウザゲームとしてHTMLファイルにまとめる場合は、stateを再初期化してDOMを再描画するだけで実現できる。

HTMLファイルに仕上げてアソバースへ投稿する

観察ゲームはJavaScriptとHTML/CSSだけで完結することが多く、外部サーバーを必要としない単一ファイルとして書き出しやすい。アソバースはHTMLファイルの直接投稿とZIPファイルの投稿のどちらにも対応しているため、まず一枚のHTMLで動作するところまで作り、必要に応じてフォルダ構成に移行するという進め方が取れる。

  1. グリッド・ルール・発見条件の全コードを単一HTMLファイルにまとめ、ブラウザで想定どおりに動くことをローカルで確認する。
  2. 発見条件の全パターンを手動で試し、見逃している条件または達成不可能な条件がないかを確かめる。
  3. ゲームのタイトル、簡単な遊び方説明、発見の総数をわかる範囲で記載したページ内テキストを追加する。
  4. アソバースのスタジオからHTMLファイルを投稿し、タイトルと概要を入力して確認画面で表示を検証してから公開する。

アソバース上での保存まわりの注意

ローカルで動くHTMLファイルをアソバースに投稿すると、ゲームはサンドボックス化されたiframe内で実行される。このときlocalStorageはSecurityErrorで使用できない。iframeにsandbox属性が付与されてallow-same-originが含まれない場合、ブラウザはそのフレームにオペーク(不透明)なオリジンを割り当て、localStorage・sessionStorage・IndexedDBなどのストレージAPIへのアクセスをすべて拒否する。これはブラウザの標準的な動作であり、投稿先のプラットフォームに関わらず適用される制約だ。

ゲーム内で図鑑の達成状況などを保存したい場合は、次の方針を取る。

  • セッション内だけ保持する:JavaScriptの変数やオブジェクトにstateを保持する。ページを離れると消えるが、短い観察ゲームであれば一セッション内の達成記録だけで成立することも多い。
  • プラットフォームのSDKを利用する:アソバースがセーブ機能をSDKとして提供している場合は、そちらを使うことで永続保存が可能になる。利用方法はアソバースの開発者向けドキュメントで確認する。
  • スタンドアロンでも公開する:GitHub PagesやNetlifyなど自己ホストの環境では通常のlocalStorageが使えるため、アソバース外でも公開URLを別途設ける方法もある。

外部ライブラリを使う場合はCDN経由で読み込むか、ファイルをまとめてZIP投稿にする。完成した作品を人に渡して遊んでもらうと、設計時には見えなかった「発見の難易度」の偏りや、ルールの直感的なわかりにくさが出てくることが多い。発見条件の難易度分布を見直し、簡単な条件と難しい条件が混在するように調整することが、遊び続けてもらえる観察ゲームの完成度につながる。

出典・参考資料