推しアイデア
ゲームマスターが撮影した写真からAIが特徴を抽出し、ハンターはその文章のヒントだけを頼りに同じ場所を見つけて写真を撮る。写真の一致率によってスコアが決まり、最も高いスコアを出したハンターが勝利するゲーム。
ゲームマスターが撮影した写真からAIが特徴を抽出し、ハンターはその文章のヒントだけを頼りに同じ場所を見つけて写真を撮る。写真の一致率によってスコアが決まり、最も高いスコアを出したハンターが勝利するゲーム。
AIを活用したゲームを作りたかったから! ハッカソンで終わり!じゃないプロダクトを作りたかったから!
フロントエンドもバックエンドも全部Cloudflareで動かしています!
ゲームマスターが撮影した写真からAIが特徴を抽出し、ハンターはその文章のヒントだけを頼りに同じ場所を見つけて写真を撮る。写真の一致率によってスコアが決まり、最も高いスコアを出したハンターが勝利するゲーム。
↓ ここから技術的な説明です!
本ゲームは2つの遊び方を提供する
ローカル環境で集まって遊ぶパーティゲーム 1つの部屋や会場に複数人が集まって遊ぶ形態。 この場合は機器はスマートフォンになる。
VR上で遊ぶリモートプレイ VRChatのような空間を共有して遊ぶ形態。 この場合は機器はHMDやPCとなる。
scene-hunter/ ├── api/ # API定義 │ ├── spec/ # TypeSpec │ └── openapi/ # OpenAPI ├── server/ # バックエンドサーバー │ ├── game/ # ゲームロジック (TS/Hono, Workers) │ ├── user/ # ユーザー管理・認証 (TS/Hono, Supabase) │ ├── notify/ # 通知 (TS/Hono, Workers) │ ├── image/ # 画像処理 (TS/Hono, Workers) │ └── match/ # 特徴マッチング (Python, AppRun) ├── web/ # フロントエンド (TS/RR7, Workers) └── docs/ # ドキュメント
今回のハッカソンでは様々なところで沢山AIを活用した。
1つ目はCopilot, Cursor, Cline等で、活用してコードの自動生成やテストの自動生成を行なった。
バックエンドではCopilot、CursorやClineでコードをほぼ自動生成している。ただし何もないところからプロンプトだけいじっても満足のいくコードが生成できるわけではないので、ドメイン知識としてゲームのルールをまとめた docs/rule.md
や ソフトウェアアーキテクチャをまとめた docs/architectrure.md
を整備した。そして ユビキタス言語として docs/rule.md
や OpenAPIとして、api/spec
の整備を行なった。これによりゲームの知識を取り入れて適切なコードを提案してくれるエージェントを作成できた。
また特にフロントエンド担当のメンバーは完全に未経験だった。前日まで環境構築もやってなかったぐらいの未経験で、当日もルーティングを知らなかったり、Gitの使い方を知らずに複数人で同じブランチで作業していたり、npmやpnpmがわからずにいたが、AIのおかげで午後にはバリバリ実装できる人材に成長しており、フロントのデザインからAPIを叩くまで成長した。
他にもGitHub上でPRを出すとCopilotとGeminiからコードレビューを受けることができ、品質の向上に寄与した。
また、アイコンの生成にChatGPTを使用したりもした。
2つ目はゲームそのものの仕組みとしてGeminiを導入して使用した。
ゲームマスターが送ってきた写真を画像解析して5つのヒントとして文章を生成する部分で使用した。ここで単にプロンプトを工夫するだけではLLMはランダム性が高い出力を行うので、不安定である。そこStructured Outputsを適用することで安定してJSONを出力させるようにした。またプロンプト自体もハンターが正解を探しやすいようなヒントを出力するように調整した。具体的には、要素のレイアウトと視覚的配置を中心に出力するようにし、天候や照明などの情報を含めないようにした。
特に今回工夫した・こだわった点で、ほぼ全てのインフラをCloudflareでホスティングしている。 理由は大きく2つあり、1つ目はコストをかけずにサービスを運用できることである。サーバー、フロントエンド、データベース、オブジェクトストレージ、状態管理を無料で使うことができ、さらに全てサーバーレスなためスケールも容易で、無料枠も大きいため今回採用を決めた。 ただそれによる苦労点も多く、サーバーは10ms以内に処理を行う必要があり、画像変換も今回行っているが普通にsharpのようなライブラリを使うと時間制限オーバーになった。そこでwasmを使用して、高速に変換をおこなえるようにすることで対処した。 またフロントエンドもNext.jsがメジャーだが、Cloudflareで動くか怪しかったので今回は技術的な挑戦も含めReact Router v7を使用した。ただv6とv7とで互換性がかなりなく古い情報がインターネットには多いため、AIも古い情報を提案してくるので苦労した。さらに今回フロントエンドを担当しているのが完全に未経験なメンバーに担当してもらったためさらに苦労した。 2つ目はありきたりなインフラ構成にしたくないというわがままである。正直AWS LambdaやCloud Runで構築すれば無料枠でDockerも使え、IaCもあり、豊富な前例もあるので楽だが、いつも聞くような せっかくのハッカソンという今まで触ってこなかったものに最大限触れられるチャンスということで今回全面的に採用した。
インフラ構成図
APIドキュメントを整備するにあたって、TypeSpecによる生成を行った。
TypeSpecはTypeScriptライクにAPIを書くことができ、生のyamlやjsonを書くより遥かに書きやすく、使いやすいため採用した。
また生成AIのためにドメイン知識としてゲームのルールをまとめた docs/rule.md
や ソフトウェアアーキテクチャをまとめた docs/architectrure.md
を整備した。そして ユビキタス言語として docs/rule.md
や OpenAPIとして、api/spec
の整備を行なった。これによりゲームの知識を取り入れて適切なコードを提案してくれるエージェントを作成できた。
TypeSpecの様子
Cloudflare Workersで動かすためにNext.jsを選択するのではなく、React Router v7を採用した。 ただし、Workers用に用意されている素のReact Router v7だとshadcn/uiが使えなかったり、honoが使えなかったりするので別途導入することにした。ただしこれも簡単にはいかずvite.config.tsをいじったり、プラグインを導入したり苦労したが、先端的な技術スタックを持ったWebアプリケーションにすることができた。 また、UI全体のデザインについても、おしゃれで直感的な見た目になるように細部まで配慮した。配色やフォントサイズ、余白の取り方、コンポーネントの並びなどを丁寧に調整し、見た目の印象が柔らかく親しみやすくなるよう工夫した。さらに、撮影画面に関しては、ユーザー体験を向上させるためにさまざまなインタラクションを導入した。具体的には、撮影前のカウントダウンタイマーを単なる数値表示ではなく、シークバーとして画面上部に表示することで、残り時間を視覚的に把握できるようにした。このシークバーは進捗状況に合わせてスムーズに伸縮するアニメーションを実装し、視覚的にも自然で心地よい体験となるよう工夫した。また、撮影時に表示されるヒントについても、コメントのように吹き出し形式で表示・非表示を切り替える仕組みを導入した。これにより、プレイヤーが必要に応じてヒントを見ることができ、UIとしても邪魔にならず、没入感を損なわないデザインとした。
各サービスをマイクロサービスアーキテクチャにすることで、AIによる支援を受けやすくした。 マイクロサービスアーキテクチャだと一つ一つのサービスが小さいのでコンテキストに収まるし、レビューも影響範囲も小さくなる デメリットであるサービス維持管理も、サービス間の技術スタックの違いもLLMが吸収してくれるからほぼ無くなりつつあるため今回採用した。 またバックエンドのラインタイムにCloudflare Workers、フレームワークにHonoを使うことによりHono RPCでフロントエンドのとの型共有が行えより安全かつスマートに開発できるようにもなった。
バックエンドサーバーを動かしている様子
画像アップロード/取得/画像削除/バケットの削除を司るサーバー。初期段階ではWorkersに10msの制限があるため別途GoでCloudRunでホストしていたが、これだけCloudRunにすると管理が煩雑になるのでどうにかWorkersに移行できないかと調査とPoCの作成をした。その結果WebAssembly (WASM)だと高速に画像変換が行えると確認できたのでかなり事例が少なかったがWasmでの画像変換を行うことにした。これはLLMの提案もあまり当てにならないのでドキュメントを読み込み作成した。おかげでWorkersでも動作する画像変換サーバーを動かすことができた。
画像サーバーを動かしている様子
ユーザーの管理と認証認可を行うサーバー。認証自体はSupabase Authを利用している。これを採用した理由はゲームなのでぱっと遊べる体験が重要だが個人の特定は行いたいため匿名認証を行えるものを必須として剪定した。また同時にシェア率の高いGoogleとゲーマー向けDiscord両方をサポートし、JSのSDKが用意されているものを選択した。その結果Supabase Authとなった。25000人までは無料で認証できるが、匿名ログイン対応のためユーザーが多くなりがちなので、若干の不安があるので、将来的にはSupabaseからも脱却してD1オンリーでユーザの管理・認証認可を行えるようにしたい。
ゲームに入っているユーザーの状態を更新・同期するためのリアルタイムイベント状態更新を行うサーバー。技術的にはWebSocketを使用している。ただしステートレスがうりなサーバーレスでWebSocketを使うのは本来想定された用途ではないので簡単にはいかなかった。そこでWorkersで使える強整合性をもつストレージであるDurable Objectsを導入した。ちなみに4月に無料化されたばかりのプロダクトである。これによりサーバーレスでありながら状態を持つことができ、WebSocketも運用することができた。さらにこれをサーバーレスで管理することでスケーリングの問題も保守の問題も解決できるので今回気合を入れて実装した部分である。
通知サーバーを動かしている様子
ゲームサーバーでは、各ゲームルームの状態を管理するために、RoomObjectというDurable Objectを導入している。ルームの初期化、ハンターの参加・退出、ゲームマスターの変更、ハンター名の更新、ラウンドの開始・終了、ルーム情報やランキングの取得といった内部処理をすべてこのオブジェクト内で扱っている。
APIのルーティングにはHonoを使用しており、/rooms や /rooms/:room_id/join、などの外部向けエンドポイントを定義している。これらのリクエストは、対応するRoomObject内のハンドラ関数に転送され、実際のロジックが実行される。
RoomObject内部では、ルームへの参加、ラウンドの開始、写真の投稿、リーダーボードの取得など、各ゲームアクションに対応するハンドラ関数を実装している。これにより、ゲームの状態をDurable Objectのストレージに保持しながら進行できるようにしている。
ハンターはラウンド中に写真を投稿できるようになっており、handleRoundPhotoハンドラでは、外部の画像比較APIを用いてスコアを算出する。この際、残り時間も加味して最終的なスコアが決定され、投稿データと共に保存される。また、/uploadルートではGoogle GenAI APIと連携し、画像からヒントを生成する機能も実装している。
画像からヒントを生成している様子
さらに、ルームやラウンドの状態はDurable Object内で一元的に管理されており、notifyRoomEventというユーティリティ関数を使って、ゲームイベントを外部の通知APIに送信する仕組みも整えている。
2つの画像間の類似度を計算するAPIを提供するサーバー。FastAPI
を使用して構築されており、画像のベクトル化と距離計算にはimgsim
ライブラリを使用している。
画像類似度推定している様子
imgsim
ライブラリを採用した理由は、他のアルゴリズム(ORB特徴量抽出やSSIMによる構造的類似度計算)よりも高精度かつ高速であったためである。
256x256
にリサイズ。imgsim.Vectorizer
を使用して画像をベクトル化。imgsim.distance
で計算する。類似度スコアは以下の手順で計算される:
距離計算
imgsim.distance
を使用して、2つの画像ベクトル間の距離を計算する。
カスタム減衰関数
距離に基づいて以下の減衰関数を適用:
def custom_decay(x): n = 2.17 # 減衰の指数 k = 0.00241 # 減衰のスケール係数 return math.exp(-k * (x ** n))
この関数は、距離が大きいほどスコアが急激に減少するように設計されている。
** スコア計算 ** 減衰関数の結果に100を掛けて、最終的な類似度スコアを計算:
score = 100 * custom_decay(dist)
** スコアの丸め ** スコアは小数点以下4桁に丸められ、クライアントに返される。
減衰関数のパラメータnとkは、画像の特性や用途に応じて調整可能となっている。これらの値を変更することで、スコアの感度や減衰の挙動をカスタマイズできる。
デプロイにはさくらのクラウドのAppRunを使用している。 使用した理由としては無料でDockerのサーバーレスアプリケーションをデプロイできる点が魅力点だった。また国産クラウドサービスの普及を後押ししたい点があり、採用した。
今後はVRで遊べるようなゲームにしたいと考えている。
VRにすると現実世界の欠点であるゲームマスターが撮影している場面を目撃されたり、人や車等の移動物体が含まれることにより結果が変わってしまうといったことがなくなるため、より一層楽しく遊べるゲームになると思う。
VRChatのような空間を共有して遊ぶ形態で、この場合は機器はHMDやPCとなる。
また、サービスを継続的に運用することで末長く愛されるゲームにしていきたいと思う。