見出し画像

PyxelとOpenAIで洞窟探索アドベンチャーゲーム作ってみた

OpenAI(ChatGPTなどで使われているエンジン)のチャット機能はゲームマスターの役割も演じてくれるらしいと知ったので、Pyxelと組み合わせて何か作れないかなと考え、簡単なアドベンチャーゲームを作ってみました。

基本的にはAIゲームマスターが状況を説明して選択肢を提示、プレイヤーがそれに答えるというステップを繰り返すだけのゲームなんですが、一応ゲームクリアもあります。(動画のラストをご覧ください。)

ソースコードも1ファイルのみで長くないので、丸ごと掲載します。
(ただし、日本語表示のためPyxel のバージョン1.9.13でリリースされたビットマップフォント表示機能を使っており、サンプルのコードを丸ごとコピペしただけなので、その部分は省略します。)

from dotenv import load_dotenv
import pyxel
import openai
import json
import os

load_dotenv()
openai.api_key = os.environ["OPENAI_APIKEY"]  # OpenAIのAPIキーを.envから読み込む

MODE_GM = 0
MODE_ACTION = 1
MODE_END = 2

# 日本語フォント表示(Pyxel公式サンプル 13_bitmap_font.py より)
class BDFRenderer:
    # (サンプルそのままなので省略します)


# メインクラス
class App:
    def __init__(self):
        pyxel.init(256, 256, title="Pyxel Adventure")
        pyxel.load("./asset.pyxres")
        self.size_x = 22
        self.size_y = 16
        self.texts = []
        self.mode = MODE_GM
        self.bdf = BDFRenderer("umplus_j10r.bdf")
        # チャットメッセージ準備
        self.chat_messages = [
            {
                "role": "user",
                "content": """
                    今からテーブルトークRPGをします。あなたはゲームマスターの役割を果たしてください。

                    ## 舞台設定
                    * プレイヤーの男性(私)は、相棒の女性「ミク」とともに、洞窟の奥深くにある秘宝を探しに行きます。
                    * ミクは楽天的な性格で、くだけた口調で喋ります。
                    * 洞窟には危険な生物が生息しており、また多くの罠が潜んでいます。

                    ## ゲームのルール
                    * あなたは状況を説明し、私に行動の選択肢を最大4つ提示してください。私は数字で行動を選択します。
                    * 道中にはさまざまハプニングが発生します。
                    * 私とミクが秘宝を見つけて洞窟の外に持ち帰れば、私の勝ちです。もし、秘宝を見つけられずに洞窟から撤退するか、2人のうちどちらかが大怪我をすると、私の負けです。

                    それでは、ゲームを始めてください。
                """,
            },
        ]
        # BGM読み込み
        with open(f"bgm1.json", "rt") as fin:
            self.bgm1 = json.loads(fin.read())
        with open(f"bgm2.json", "rt") as fin:
            self.bgm2 = json.loads(fin.read())
        # メイン処理実行
        self.play(self.bgm1)
        pyxel.run(self.update, self.draw)

    def draw(self):
        pyxel.cls(0)
        pyxel.blt(0, 0, 0, 0, 0, 256, 256)
        for y in range(min(self.size_y, len(self.texts))):
            self.bdf.draw_text(2, 16 + y * 14, self.texts[y], 7, 0)

    def update(self):
        if self.mode == MODE_GM:
            answer = self.generate_answer()
            self.texts = []
            self.add_text(answer)
            # GMのメッセージに「おめでとう」が入っていたらクリアとみなす
            if "おめでとう" in answer:
                self.play(self.bgm2)
                self.end_game()
            # それ以外&選択肢の数字がなければゲームオーバーとみなす
            elif not "1" in answer:
                self.end_game()
            else:
                self.mode = MODE_ACTION
        elif self.mode == MODE_ACTION:
            if pyxel.btnp(pyxel.KEY_1):
                prompt = "1"
            elif pyxel.btnp(pyxel.KEY_2):
                prompt = "2"
            elif pyxel.btnp(pyxel.KEY_3):
                prompt = "3"
            elif pyxel.btnp(pyxel.KEY_4):
                prompt = "4"
            else:
                return
            self.add_text("\n私の行動:" + prompt)
            self.chat_messages.append({"role": "user", "content": prompt})
            self.mode = MODE_GM

    # ゲーム終了
    def end_game(self):
        self.add_text("\n[Esc]でゲームを終了します。")
        self.mode = MODE_END

    # BGM再生
    def play(self, bgm):
        for ch, sound in enumerate(bgm):
            pyxel.sound(ch).set(*sound)
            pyxel.play(ch, ch, loop=True)

    # 画面にテキスト追加
    def add_text(self, words):
        self.texts.append("")
        for word in words:
            if len(self.texts) > self.size_y:
                self.texts.pop(0)
            idx = len(self.texts) - 1
            if word == "\n":
                self.texts.append("")
            else:
                if not word in ["。", "、"] and len(self.texts[idx]) >= self.size_x:
                    self.texts.append("")
                    idx += 1
                self.texts[idx] += word

    # チャットメッセージ生成
    def generate_answer(self):
        response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo",
            messages=self.chat_messages,
            temperature=0.6,
            frequency_penalty=1.0,
        )
        answer = response["choices"][0]["message"]["content"]
        self.chat_messages.append({"role": "assistant", "content": answer})
        return answer


App()

GitHubはこちら。画像やBGMなどのリソースも入っています。
ダウンロードすれば実行できますが、OpenAIのAPIキーはご自身で準備いただく必要がありますのでご注意ください。

ソースの説明

ソースコード中に入っている以下の部分がプロンプトです。冒頭でこの指示を入れることで、OpenAIはゲームマスターとして状況説明と選択の提示をしてくれます。
同じプロンプトをChatGPTに入れれば、ChatGPT上で同じことができます。

今からテーブルトークRPGをします。あなたはゲームマスターの役割を果たしてください。

## 舞台設定
* プレイヤーの男性(私)は、相棒の女性「ミク」とともに、洞窟の奥深くにある秘宝を探しに行きます。
* ミクは楽天的な性格で、くだけた口調で喋ります。
* 洞窟には危険な生物が生息しており、また多くの罠が潜んでいます。

## ゲームのルール
* あなたは状況を説明し、私に行動の選択肢を最大4つ提示してください。私は数字で行動を選択します。
* 道中にはさまざまハプニングが発生します。
* 私とミクが秘宝を見つけて洞窟の外に持ち帰れば、私の勝ちです。もし、秘宝を見つけられずに洞窟から撤退するか、2人のうちどちらかが大怪我をすると、私の負けです。

それでは、ゲームを始めてください。

あとは基本的に、OpenAIにメッセージを要求→プレイヤーの入力待ちを繰り返しているだけです。

なおOpenAIリクエスト部分の説明はこの記事ではしません。「openai ChatCompletion」とかでググれば丁寧な解説記事がいろいろ出てくると思います。

また、ゲームクリア・ゲームオーバーの判定を以下のような簡易的な仕組みで実現しています。当然厳密ではないですが、プレイした感覚ではこれで問題なさそうではありました。

  • AIが生成したメッセージに「おめでとう」が入っていたらゲームクリアとみなす

  • 上記以外で、選択肢の数字(1)が入っていなければゲームオーバーとみなす

このようにシンプルな仕様なので、プロンプトを変更して、画像とBGMを差し替えれば簡単に別のゲームにできます。
実際、今回紹介した洞窟探索ゲームの前に以下のツイートのバージョンを試作しています。(この試作品では「ゲームクリア」の概念がなく、ゲームとしては成立していなかったので、改良して今のバージョンにしました。)


画像もAIで生成

僕は絵を描けないのですが、最近は画像の自動生成AIもすごいですよね。今回は DreamStudio にお世話になりました。

参考まで、プロンプトやセッティングのキャプチャを貼っておきます。これで512x512のpngファイルが出力されるので、256x256に縮小した上でPyxelカラーに減色したものがゲーム画面の画像です。

画像
Dream Studioで背景画像を生成

作ってみた所感

OpenAIのAPIがとても使いやすいので、プログラムのスキルがあまり高くなくても苦労せずに使えると思います。すごいです。

チャットのエンジンはGPT-3.5-turboを使っています。現時点(2023.4.14)ではGPT-4が公開されていないので(申請してウエイトリストに加わることはできるようですが)、本当はGPT-4でやりたかったのですが諦めました。このプロンプトはシンプルなのであまり気にならないと思いますが、もっと凝ったゲームを作ろうとすると、3.5と4だと生成の質がかなり違ってきます。

あと、本当はプレイヤーの行動も選択制ではなくフリーテキスト入力のほうが、よりOpenAIの力をフルで実感できるので面白かったんですが、Pyxel上で日本語入力できる仕組みを作るのが困難だったので割り切りました。

(英語版ならフリーテキスト入力バージョンも作れるのですが、私自身が英語できないマンなので自分がプレイするのに苦労する。)

GPT-4のAPIが使えるようになったら、またいろいろ試してみて面白いものが作れないか研究してみたいと思います。

いいなと思ったら応援しよう!

ピックアップされています

人工知能

  • 97本

コメント

1
nemesislivezx
nemesislivezx

素晴らしい!

コメントするには、 ログイン または 会員登録 をお願いします。
PyxelとOpenAIで洞窟探索アドベンチャーゲーム作ってみた|frenchbread
word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1