🤖

GPT-4 にゲームのステージを生成させてみた―AIからの挑戦状 powered by GPT-4

2023/12/07に公開

LLM Advent Calendar 2023 の 7 日目の記事です。

https://qiita.com/advent-calendar/2023/llm

はじめに

まずはこちらの GIF をご覧ください。

これは、GPT-4 [1] を使ってゲームのステージを生成し、実際にプレイしてみる様子です。

先日 @mido_app さんと「GPT-3.5/GPT-4 を使って何か作ってみたいね」と話して、2 人で 1〜2 日ほどで試しに実装してみたものになります。

この記事では、この LLM によるゲームのステージ生成について紹介します。

なお、このゲームは unityroom で公開しており、ブラウザ上でプレイ可能です。
実際に GPT-4 が生成したステージを 10 個遊べるので、ぜひプレイしてみてください。
シンプルなゲームですが、AI が生成したと思うとなかなか面白いです。

https://unityroom.com/games/2d-platformer-powered-by-gpt4

事前調査

「ゲームのステージを LLM に生成させてみよう」というアイデアを実装するにあたり、まずは Web 上で参考になりそうな取り組みがないか調べてみました。

「ゲームのステージを LLM に生成させてみよう」というアイデア自体はよくありそうなものですが、意外とそれらしい記事などは多くありませんでした。

MarioGPT

唯一しっかり見つかったのが、「MarioGPT」という論文です。

https://arxiv.org/abs/2302.05981

MarioGPT は、その名の通り、ゲーム「スーパーマリオブラザーズ」のステージを LLM で生成するものです。

ソースコードも GitHub で公開されています。

https://github.com/shyamsn97/mario-gpt

Video Game Level Corpus (VGLC)

MarioGPT は、Video Game Level Corpus (VGLC) というデータセットを使っています。

VGLC は、いくつかのテレビゲームのステージデータを収集したデータセットであり、GitHub で公開されています。

https://github.com/TheVGLC/TheVGLC

VGLC のデータの例は次のようになります。

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------E-----------------------------------------------------------------------------------------------------------------------
----------------------Q---------------------------------------------------------SSSSSSSS---SSSQ--------------?-----------SSS----SQQS--------------------------------------------------------XX------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------XXX------------
-------------------------------------------------------------------------------E----------------------------------------------------------------------------------------------------------XXXX------------
----------------------------------------------------------------S------------------------------------------------------------------------------------------------------------------------XXXXX------------
----------------Q---S?SQS---------------------<>---------<>------------------S?S--------------S-----SS----Q--Q--Q-----S----------SS------X--X----------XX--X------------SSQS------------XXXXXX------------
--------------------------------------<>------[]---------[]-----------------------------------------------------------------------------XX--XX--------XXX--XX--------------------------XXXXXXX------------
----------------------------<>--------[]------[]---------[]----------------------------------------------------------------------------XXX--XXX------XXXX--XXX-----<>--------------<>-XXXXXXXX------------
---------------------E------[]--------[]-E----[]-----E-E-[]------------------------------------E-E--------E-----------------EE-E-E----XXXX--XXXX----XXXXX--XXXX----[]---------EE---[]XXXXXXXXX--------X---
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX--XXXXXXXXXXXXXXX---XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX--XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

引用元: https://github.com/TheVGLC/TheVGLC/blob/master/Super Mario Bros/Processed/mario-1-1.txt

このような形式で LLM に level のテキストを生成させれば、そのテキストにしたがってオブジェクトを配置して遊べる、そんなアクションゲームを作ることができそうです。

MarioGPT での level 生成のヒント

VGLC 以外にも、MarioGPT には level 生成のヒントがいくつもありました。

たとえば、次のようなものです。

  • プロンプトに「敵の数」や「難易度」といった level の特徴を含める
  • 生成されたステージの多様性を向上させるため、ステージのランダムな一部を言語モデルで変異させる
  • 変異させたステージには、真新しさのスコアをつけて評価する
  • ゴール可能かエージェントに自動評価させている

今回簡単に実装してみた例ではここまでの工夫はできませんでしたが、より本格的に挑戦する際には参考になりそうです。

実装したゲームと生成処理の概要紹介

さて、MarioGPT の話はここまでとして、ここから実際に実装してみたゲームと生成処理について紹介します。

簡単に言うと、VGLC のような形式で LLM に level のテキストを生成させて、そのテキストをもとに遊べるアクションゲームを作ってみました。

構成図

実装したしくみは簡単で、次の図のようになります。

まず、Python で生成した level のテキストデータを Supabase に保存します。
Unity で実装したゲームでは、Supabase から level のテキストデータを取得し、その内容通りオブジェクトを配置して遊べるようにしています。

Supabase は PostgREST によって REST API を自動生成してくれるため、Unity 組み込みの HTTP クライアントからでも扱いやすくて便利でした。

LLM アドベントカレンダーの記事なので、ここからはとくに level 生成処理について紹介します。

level の生成処理

level の生成のため、次のような system プロンプトを作成しました。
(実際には英語に翻訳したプロンプトを使っていますが、この記事には日本語で掲載します)

あなたは素晴らしいゲームクリエイターです。
アクションゲームのステージを考えてください。

出力はVGLCの形式にしてください。

次の記号を使うことができます。
- '-': 何もない。移動可能で空。
- 'X': 地面。プレイヤーが移動できる。
- 'E': 敵。敵は左に動く。
- 'F': 飛ぶ敵。飛ぶ敵は上下に動く。
- 'P': プレイヤー。プレイヤーの開始位置。
- 'G': ゴール。このステージのゴール。

ステージは以下のルールに従います。
- <略>

説明は不要です。VGLCの形式のテキストのみを出力してください。

さらにいくつかの level の例を Few-shot プロンプティングとして与えて GPT-4 Turbo を呼び出したところ、ある程度それらしい応答を得られました。

こちらが考えたアクションゲームのステージです。VGLCの形式で表現しています。

```
------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------F----------------E---------------------------------------E-----------
--------------------------------------------E---XXXX----XXXXXX----XXXX----XXXXXX----XXXX------------XXXXXX----XXXXX----XXXX-----
---------------------------------------------------------------------------------------------------------------------XX----------
---------------------------------------------------------------------------------------------------------------------XX----------
---------------------------------------------------------------------------------------------------------------XXXX----------
--------------------------------------------------------------------------------------------------------------------XXX----------
---P------X----X--E---X----X----X----X-----X----X----X---XXXXXXXXXXXXXXXXX---XXX-----X-----X----X----X----X----XXXXXXX----------
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```

生成された level の問題点とルールベースでの対応

生成された level のテキストを見て、「ある程度いけそうだ!」と思いましたが、まず大きく 2 つ問題点がありました。

  1. 「こちらが考えたアクションゲームのステージです。」といった余計な文字列まで出力してくる
  2. 横幅が一定にならない(ステージの一番右が凸凹になる)

上記 2 つの問題点の対応として、ルールベースで 2 つの処理を実装しました。

  1. VGLC の箇所を正規表現で抽出する
  2. 横幅を一番短い行に合わせて切り詰める

個人的に、このあたりの処理は LLM への指示をがんばったりするよりも、ルールベースで対応するほうが楽だと思います。

この 2 つの処理をほどこせば、それらしい level のテキストになりました。

-----------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------F----------------E---------------------------------------E---------
--------------------------------------------E---XXXX----XXXXXX----XXXX----XXXXXX----XXXX------------XXXXXX----XXXXX----XXXX--
---------------------------------------------------------------------------------------------------------------------XX------
---------------------------------------------------------------------------------------------------------------------XX------
---------------------------------------------------------------------------------------------------------------XXXX----------
--------------------------------------------------------------------------------------------------------------------XXX------
---P------X----X--E---X----X----X----X-----X----X----X---XXXXXXXXXXXXXXXXX---XXX-----X-----X----X----X----X----XXXXXXX-------
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

画像にしてみると...

level のテキストを見るだけでは分かりにくかったので、level のテキストを画像に変換する処理を実装しました。

たいした工夫なしに生成させた結果としては、なかなか良いのではないでしょうか?
さすが GPT-4 です。

課題

このように GPT-4 で level の生成ができましたが、いくつか課題もあります。
想像できると思いますが、ゴール不可能な level が生成される場合があるというのが一番大きな課題です。

今回実装してみた例では、ゴール不可能なケースは 2 種類あります。

  • ゴール地点のシンボル G をどこにも配置してくれない場合がある
  • ゴール地点のシンボル G は生成してくれたが、そこまでたどり着くのが不可能な場合がある

今回は時間も限られていたため、この課題への対処までは取り組めませんでしたが、解決のためにどんな方針が考えられそうか議論しました。
せっかくなので、議論した内容をこちらで紹介します。

ゴール地点のシンボル G をどこにも配置してくれない場合がある

まず 1 つ目の「ゴール地点のシンボル G をどこにも配置してくれない場合がある」ことについてです。

この点については、たとえば一度 level を生成したあとで、もう一度 GPT-4 を呼び出してゴール地点を付与させる、といった多段階の生成にすればうまくいくかもしれません。

または、そもそも「画面一番右までいったらゴール」のように、ルールベースでゴールを設定しておくのもいいかもしれません。

ゴール地点のシンボル G は生成してくれたが、そこまでたどり着くのが不可能な場合がある

続いて 2 つ目の「ゴール地点のシンボル G は生成してくれたが、そこまでたどり着くのが不可能な場合がある」という問題についてです。

まず、ゴールまで到達可能な level を確実に自動で生成させたい場合、ゴールまで到達可能か自動評価するしくみを実装する必要があると思います。
この点で、MarioGPT で使われている手法がかなり参考になりそうでした。

また、ゴールできない level が生成されてしまった場合、その level を単に捨てる以外に、「ゴールできるようになるまで GPT-4 に修正させる」といった対応も考えられそうです。

さらに、そもそもゴール不可能な level が生成される場合もあるという前提で、「level を生成した人がゴール可能か判定したくなる仕組みを導入すればいいのでは?」という話も出ました。

感想

以上、課題も残っていますが、ちょっとした挑戦として LLM でゲームのステージを生成してみたお話を紹介しました。

自分たちで実装したからかもしれませんが、実際に遊んでみてもなかなか面白かったです。
ぜひ少しでも遊んでみてください。

https://unityroom.com/games/2d-platformer-powered-by-gpt4

LLM を使うアプリの方向性について

最近、個人的に、LLM の用途としてチャットボット以外の方向性を考えるのを大事にしています。

今回実装してみたようなゲームのステージ生成には、チャットボットなどと比較して以下のようなメリットがあります。

  • LLM を呼び出す回数がユーザのリクエスト数に比例しない
    • そのため、コストやレートリミットが問題になる可能性が低い
    • コスト面のリスクが低いため、GPT-4 などのリッチな LLM を使いやすい
  • 夜間であったりバックグラウンドのジョブとして、ユーザのプレイと関係なく生成すればよい
    • そのため、生成にかかる時間が長くても問題ない
    • さらに、もしも LLM の呼び出しがエラーになっても、ユーザに即座に影響しない

とくに、コスト面のリスクが低いことにより GPT-4 を使えることは大きなメリットで、「GPT-3.5 でも動くよう色々工夫する」といった開発コストを削減できる可能性も高いと思います。

このように、「LLM を呼び出す回数がユーザのリクエスト数に比例しないようにする」「夜間であったりバックグラウンドのジョブで実行可能なユースケースを考える」といった方向性で LLM の応用を考えてみるのも面白いかもしれません。

脚注
  1. 正確には GPT-4 Turbo を使用しています。 ↩︎

Discussion