マガジン

ゲームの実績・トロフィー機能の設計

遊びをつくる

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

石や花を自由に配置する小さな庭で、点数に頼らない遊びを表したイラスト。
記事のテーマをもとに制作したAI生成イラスト。

ゲームに実績(アチーブメント)やトロフィー機能を入れると、プレイヤーが「次に何をすればよいか」を見つけやすくなり、クリア後の再訪も起きやすくなります。一方で設計を誤ると、実績の存在をプレイヤーが気づかなかったり、取得条件が不明瞭でストレスになったりします。 この記事では、ブラウザゲームに実績機能を組み込む際の設計方針と実装の選択肢を整理します。「どのデータをどこに持つか」「条件判定をいつ走らせるか」「UIとしてどう見せるか」の三点が、実績機能の設計の核心です。

この記事の目次
  1. 実績が果たす役割を先に決める
  2. データ設計:何を持ち、どこに保存するか
  3. 条件判定の実装パターン
  4. UIの設計:通知・一覧・隠し実績の扱い
  5. 失敗しやすい点と確認事項
  6. 次の行動
  7. 参考資料

実績が果たす役割を先に決める

実装を始める前に「この実績は何のためにあるか」を一言で言えるようにしておくことが重要です。目的が曖昧なまま実績を増やすと、コレクションとしてのボリュームだけが膨らみ、ゲームプレイに関係しない飾りになりがちです。

実績が担う役割は大きく三つに分けられます。

  • 誘導:プレイヤーが見落としがちな機能やルートに気づかせる(「一度も攻撃せずにステージをクリアする」など)
  • 記録:プレイの節目や達成を可視化して満足感を与える(「通算100回ジャンプした」「初めてボスを倒した」など)
  • 挑戦:上級者向けの難しい条件を提示してリプレイを促す(「3分以内にクリアする」「ノーダメージクリア」など)

一つのゲームにこれらが混在していても問題ありませんが、実績ごとにどの役割かを意識して設計すると、条件のバランスや解除タイミングが自然と整います。

データ設計:何を持ち、どこに保存するか

実績機能に必要なデータは「定義データ」と「進捗データ」の二種類です。

定義データは実績そのものの仕様で、ゲームのソースコードまたは静的なJSONファイルとして管理します。各実績に必要な項目は次のとおりです。

  • ID(内部識別子。変更しない)
  • タイトルと説明文
  • 解除条件(数値の閾値、フラグ、複合条件など)
  • 隠し実績かどうか(条件を事前に見せるか否か)
  • アイコンやバッジのリソース

進捗データはプレイヤーごとの取得状況で、こちらをどこに保存するかが実装の分岐点になります。

  • ブラウザのlocalStorage、メリット:実装が最もシンプル、デメリット:端末をまたぐと引き継げない。削除・クリアで消える
  • 外部サーバー(自前)、メリット:端末をまたいで同期できる、デメリット:サーバーの用意と認証の設計が必要
  • アソバースのSDK経由、メリット:プラットフォーム側のアカウントと連携できる、デメリット:作者がSDKに対応させる必要がある。SDKへの対応が前提条件

単独で完結するミニゲームであればlocalStorageで十分です。プレイヤーに長く遊んでもらいたいゲームや、複数端末でプレイされることを想定するなら外部保存を検討してください。アソバースに投稿するゲームでSDKを使った場合は、クラウドセーブの仕組みを実績の進捗保存にも活用できます。

条件判定の実装パターン

実績の解除条件をどのタイミングで評価するかは、ゲームの構造によって選択肢が変わります。

イベント駆動方式は、ゲーム内で「敵を倒した」「アイテムを拾った」などのイベントが発生したタイミングで、その場で実績条件をチェックします。処理の見通しがよく、条件ごとに独立して書けるため、最初に試みるには自然な方法です。

// イメージ(擬似コード)
function onEnemyDefeated(enemy) {
  stats.enemiesDefeated += 1;
  checkAchievement('defeat_100_enemies', stats.enemiesDefeated >= 100);
}

定期チェック方式は、ゲームループの中で一定間隔ごと、またはステージクリア時などの区切りにまとめて全実績を評価します。条件が複合的(「残り時間が60秒以上かつノーダメージでクリア」など)な場合は、この方式のほうが整理しやすいことがあります。

どちらの方式でも、「同じ実績が二重に解除されないよう、取得済みフラグを確認してからロジックを走らせる」という処理は必須です。解除済みならスキップするだけですが、この確認を忘れると通知が繰り返し表示される不具合の原因になります。

UIの設計:通知・一覧・隠し実績の扱い

実績が解除された瞬間の通知は、ゲームプレイの邪魔にならない位置と持続時間で設計します。画面の端に数秒だけ表示するトースト通知が一般的です。アニメーションや音を付ける場合は、ゲームの重要な場面(ボス戦の演出中など)と重ならないよう、通知の表示を一時的に抑制するタイミング制御を入れておくと親切です。

実績の一覧画面では、取得済み・未取得の区別が一目でわかることが最低限の要件です。未取得の実績をすべて隠してしまうと「何が存在するのかわからない」ためプレイヤーが行動を変えにくくなります。隠し実績は名前だけ表示して条件を「???」にする方法が、存在の示唆と謎の両立という点でよく使われます。

進捗を表示できる実績(「100体中37体倒した」など)は、現在値と目標値を一覧で見せることでプレイヤーの次の行動を促します。ただし進捗を表示するためにはカウンター値を保存する必要があるため、単純なフラグより保存データが増えることを意識してください。

失敗しやすい点と確認事項

実績機能の実装でよくある問題を事前に把握しておくと、後から直す手間を減らせます。

条件が曖昧になりやすい実績の例:「たくさんアイテムを集める」→プレイヤーは何個かわからない。数値を明示するか、進捗バーで残りを見せるかのどちらかが必要です。

解除後のデータ整合:実績を取得した後にニューゲームを始めた場合、実績がリセットされるべきか引き継がれるべきかをあらかじめ決めておく必要があります。何も考えずに実装すると、ゲームのリスタート処理が進捗データを上書きして実績が消える、という事態が起きがちです。

localStorage の容量と形式:localStorageは文字列しか保存できないため、オブジェクトはJSON.stringifyで変換する必要があります。また、容量の上限はブラウザによって異なります(一般的に数MB程度とされていますが、公式のMDNなどで確認してください)。実績の定義データをlocalStorageに入れると容量を無駄に使うため、定義はコード側に持ち、進捗データだけを保存するようにします。

バージョンアップ時の互換性:実績のIDや条件を後から変えると、既存のプレイヤーの進捗データと食い違いが生じます。IDは一度決めたら変えない、条件を変えた場合は進捗のマイグレーション処理を入れる、という原則を最初から守っておくと安心です。

次の行動

まずゲームの「誘導・記録・挑戦」の三つの役割に沿って実績を5〜10本書き出し、それぞれの解除条件を数値で明確にしてみてください。次にlocalStorageで動作するプロトタイプを作り、解除通知と一覧表示が意図どおり動くことを確認してから、保存先の拡張を検討するのが効率的な進め方です。アソバースへの投稿を考えている場合は、SDKのクラウドセーブを実績進捗に使う構成が端末をまたいだ体験を提供しやすくなります。

参考資料

  • MDN Web Docs「Window: localStorage」
  • MDN Web Docs「JSON.stringify() / JSON.parse()」
  • アソバース 開発者向けSDKドキュメント