ChatGPTとStable Diffusionを使ってAIにUnityのゲームを作らせる

# はじめに
[Life is Tech! Advent Calender 2022](https://qiita.com/advent-calendar/2022/life-is-tech-kanto) 18日目の記事になります。
Life is Tech! 12期UnityメンターのTKです。
[Life is Tech!](https://life-is-tech.com/)は中学生・高校生〜社会人向けのIT・プログラミング教育サービスです。
</br>
最近話題のChatGPTとStable Diffusionという二つのAIを使ってAIはどこまでUnityのゲームを作ることが出来るのかに挑戦してみました。
今回はAIの専門的な部分はほとんど触れずに、誰でも試してみることのできる内容なので少しでも興味を持った人は是非最後まで読んでみて下さい。

# 目標と作戦
先ほど書いた通り今回の目標は**なるべくAIのみでどこまでUnityのゲームを作ることが出来るのか挑戦してみること**です。

そして今回の目標はAIだけでどこまでのUnityのゲームを作ることが出来るかの挑戦なので可能な限り僕自身の指図の要素を省きました。
（僕がAIに比較的具体的な指示を出せばかなり高精度のゲームを作らせることは出来るのですが、今回はそのようなことはせずあくまで「Unityでゲームを作って」というような基本的な指示しかしないようにしました）
</br>
## ChatGPT
今回の目標に向けてまず中心になってくるAIが今流行りの[ChatGPT](https://chat.openai.com)です。
ChatGPTは対話形式でこちらの質問に対して適切な返事をしてくれるAIです。

https://chat.openai.com

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255633/fa47ed73-1576-d38b-d771-b4184e9d17b6.png)
まだ発展途上なところもありますが、かなり自然な会話をしてくれる上に非常に精度の高い返事をしてくれるということで今話題になっているAIです。

実はこのChatGPTですが、色々会話してくれるだけでなく以下のように簡単なプログラムであれば自動で生成してくれるのです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255633/eb31f703-7bb4-1702-e301-8374ec7fcc5b.png)
すごいですね！
この機能を使って簡単なゲームをAIに作らせた人などもおり、非常に革命が起こっています。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ChatGPTにブラウザで動くゲームを作ってという指示をしてみたところ、そのまんまコピペでブラウザ上で動く(ちゃんと自分で操作できる)ブロック崩しゲームのコードを出力してくれました<br>(なお、具体的な内容の指示やデバッグはゼロ)<br>日本語ができればプログラミングできなくてもゲームが作れる <a href="https://t.co/kdixag0VvC">pic.twitter.com/kdixag0VvC</a></p>&mdash; 今井翔太 / Shota Imai@えるエル (@ImAI_Eruel) <a href="https://twitter.com/ImAI_Eruel/status/1599710313146126340?ref_src=twsrc%5Etfw">December 5, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</br>
今回はこのChatGPTを使ってゲームの企画(ゲームデザイン)、そして必要なUnityのC#のスクリプト生成をやっていきます。

## Stable Diffusion
そして、次にもう一つ別のAIとして[Stable Diffusion](https://huggingface.co/spaces/stabilityai/stable-diffusion)というものを使っていきます。
Stable Diffusionはここ最近話題の、いくつかの単語を入れるとその単語に応じた画像を生成してくれるAIです。
これまで様々な画像生成のAIが作られてきたのですが、Stable Diffusionは学習に必要な時間が比較的少ない割に非常に高い精度の画像を生成することから非常に注目されているAIです。

https://huggingface.co/spaces/stabilityai/stable-diffusion

例えば、「education, programming, high school」って単語で画像を生成してみると以下のような画像が生成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255633/86dd284f-6901-70b6-b70e-9d8336ddf939.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255633/6279b89c-8c31-8c9e-dbbc-0f42e1658f56.png)

よく見ると顔の造形などが少し崩れていますが、Life is Tech!の宣材写真にあってもおかしくないような画像が簡単に生成されました。
凄すぎて少し怖いくらいですね。

今回はChatGPTのゲーム企画パートで生成された単語をこのStable Diffusionの入力として用いて画像生成を行い、ゲーム内で用いるデザイン画像(背景、キャラクター)の生成を行います。

# ゲームデザイン
ゲームデザインとはゲーム製作において最も大切な要素と言っても過言ではありません。
どのようなゲームを作るかというゲームの企画は今後の開発のすべてを左右します。
今回は上記で説明したChatGPTをもちいて、ゲームの企画案を教えてもらいました。
以下に行った会話の画像をまとめます。
![スクリーンショット (444).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255633/b03d913d-383b-2fe1-1721-8ffa655e7e94.png)
![スクリーンショット (445).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255633/29af23a0-d490-c5ea-415d-864782521a5f.png)
![スクリーンショット (446).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255633/e0b2b279-9a6b-d195-cda7-95415fb44c33.png)
![スクリーンショット (447).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255633/14ec5767-e8d9-aeaf-46c9-ee5c6566a407.png)

以上の結果、作るゲームは「**探検家の冒険(Adventures of an Explorer)**」というタイトルのアクションゲームになりました。
以降の章ではここでChatGPTに教えてもらったゲームの企画の単語を使って画像を生成したりプログラムを生成したりしていきます。

※AIのみでどこまでできるかを試すためになるべく僕の意思を入れないようにしたのですが、今回の企画上アクションゲームは2Dアクションゲームということにさせてもらいました

# スクリプト生成
スクリプト生成でも同様にChatGPTを用いていきます。
以下にスクリプトを生成してもらった会話の画像をまとめます。
![スクリーンショット (448).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255633/7d04e39b-8389-a1e5-f43f-f712c679424f.png)
![スクリーンショット (449).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255633/d0e14ec0-5aeb-a5c3-a0fc-9994e6ed7d16.png)
![スクリーンショット (450).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255633/73ac7d43-d451-8580-5e46-e1c4974c9899.png)
![スクリーンショット (451).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255633/47aaa6a1-b400-07f5-12fb-a5eb2556ed37.png)
![スクリーンショット (452).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255633/d9beadb1-6aad-ea15-f87b-c556b00744f1.png)

生成されたそれぞれのスクリプトも載せておきます。
（長いので折りたたまれています、クリックで開けます）

<details><summary>生成されたPlayerController.cs</summary><div>


```C#:PlayerController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    // プレイヤーの移動スピード
    public float speed = 5.0f;

    // プレイヤーの攻撃力
    public int attackPower = 1;

    // プレイヤーの体力
    public int hp = 3;

    // 敵との当たり判定用のコライダー
    private Collider2D collider;

    // Rigidbodyコンポーネント
    private Rigidbody2D rb;

    // 移動方向
    Vector2 direction = Vector2.zero;

    // Use this for initialization
    void Start() {
        // コンポーネントを取得
        collider = GetComponent<Collider2D>();

        // Rigidbodyコンポーネントを取得
        rb = GetComponent<Rigidbody2D>();
    }

    // Update is called once per frame
    void Update() {
        // 移動方向を更新
        direction.x = Input.GetAxisRaw("Horizontal");
        direction.y = Input.GetAxisRaw("Vertical");

        // スペースキーを押すと攻撃
        if(Input.GetKeyDown(KeyCode.Space)) {
            Attack();
        }
    }

    void FixedUpdate() {
        // 移動方向に応じて移動
        rb.velocity = direction * speed;
    }

    // 攻撃する
    void Attack() {
        // プレイヤーの前方に攻撃
        Vector2 front = transform.TransformDirection(Vector2.up);
        // コライダーの半径
        float radius = collider.bounds.extents.x;
        // コライダーの中心座標
        Vector2 center = collider.bounds.center;
        // 攻撃する距離
        float attackRange = 1.0f;
        // 攻撃の範囲を取得
        var hit = Physics2D.OverlapCircle(center + front * radius, attackRange, 1 << LayerMask.NameToLayer("Enemy"));
        // 攻撃の範囲に敵がいる場合はダメージを与える
        if(hit) {
            hit.GetComponent<EnemyController>().Damage(attackPower);
        }
    }

    // ダメージを受ける
    public void Damage(int damage) {
        hp -= damage;

        // HPがまだ残っている場合は何もしない
        if(hp > 0) return;

        // HPが0以下になった場合はゲームオーバー
        GameManager.instance.GameOver();
    }
}

```
</div></details>


<details><summary>生成されたEnemyController.cs</summary><div>

```C#:EnemyController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyController : MonoBehaviour
{
    // 敵の移動スピード
    public float speed = 5.0f;

    // 敵の攻撃力
    public int attackPower = 1;

    // 敵の体力
    public int hp = 3;

    // 敵がプレイヤーを追いかけるかどうか
    public bool chasePlayer = true;

    // プレイヤーへの参照
    private GameObject player;

    // Use this for initialization
    void Start()
    {
        // プレイヤーのオブジェクトを取得
        player = GameObject.FindWithTag("Player");
    }

    // Update is called once per frame
    void Update()
    {
        // プレイヤーが存在し、敵がプレイヤーを追いかける場合はプレイヤーに向かって移動
        if (player != null && chasePlayer)
        {
            var direction = player.transform.position - transform.position;
            transform.position += direction.normalized * speed * Time.deltaTime;
        }
    }

    // ダメージを受ける
    public void Damage(int damage)
    {
        hp -= damage;

        // HPがまだ残っている場合は何もしない
        if (hp > 0) return;

        // HPが0以下になった場合は敵を削除
        Destroy(gameObject);
    }

    // プレイヤーからの攻撃に当たる
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.tag == "Player")
        {
            // プレイヤーからの参照を取得
            var player = collision.GetComponent<PlayerController>();
            // ダメージを与える
            player.Damage(attackPower);
        }
    }
}
```
</div></details>

<details><summary>生成されたGameManager.cs</summary><div>

```C#:GameManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour
{
    // シングルトンインスタンス
    public static GameManager instance;

    // 現在のステージ
    public int stage = 1;

    // 現在のスコア
    public int score = 0;

    // ゲームオーバー時に表示するUI
    public GameObject gameOverUI;

    // ステージクリア時に表示するUI
    public GameObject stageClearUI;

    // Use this for initialization
    void Start() {
        // シングルトンインスタンスを取得
        instance = this;
    }

    // Update is called once per frame
    void Update() {
        // ステージクリア条件を満たしたかどうかを判定
        if(IsStageClear()) {
            // ステージクリア条件を満たしている場合はステージクリア
            StageClear();
        }
    }

    // ステージクリア条件を満たしているかどうかを判定
    bool IsStageClear() {
        // ここでは単純に、敵キャラクターが全滅しているかどうかを判定する
        return GameObject.FindGameObjectsWithTag("Enemy").Length == 0;
    }

    // ステージクリア時の処理
    void StageClear() {
        // ステージ番号を更新
        stage++;

        // ステージクリア時のUIを表示
        stageClearUI.SetActive(true);

        // ステージクリア時の処理（ここでは何もしない）
    }

// ゲームオーバー処理
    public void GameOver() {
        // ゲームオーバー時のUIを表示
        gameOverUI.SetActive(true);

        // ゲームオーバー時の処理（ここでは何もしない）
    }
}
```

</div></details>

以上のようにPlayerController, EnemyController, GameManagerの三つのスクリプトが生成されました。
凄いですね、Unityのプログラムもこんなに簡単に生成されてしまいました。
# デザイン画像生成
ゲームの企画のところでAIによって生成された単語をStable Diffusionに入力してゲームに用いる単語を生成していきます。
生成した画像は主に以下の四つです。
* プレーキャラクター
* 敵キャラクター
* 背景
* タイトル画像
それぞれの生成に用いた単語と画像は以下のようになります。
* プレーキャラクター
  * 「Adventures of an Explorer, Video game, character design of player」
  * ![trimedcharacter.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255633/f3b1578f-3358-c232-39e7-02219a761391.png)

* 敵キャラクター
    * 「Adventures of an Explorer, Video game, character design of enemy」
    * ![enemytrimed.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255633/f9d8b3c8-8f3e-44c1-e614-390e353d97a1.png)
* 背景
    * 「Adventures of an Explorer, Video game, background design of dungeon」
    * ![background.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255633/6bea9c23-40ab-ca1d-7dca-9ae64489827e.png)
* タイトル画像
    * 「Adventures of an Explorer, Video game, title」
    * ![Title_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255633/21ea48e7-4976-43ed-6447-eaddd7cdc47f.png)

よく見ると怪しい部分があったりしますが、とてもAIが自動生成した画像とは思えないような高いクォリティーの画像が生成されました。
なんとなく世界観も見えてきました。

# UnityEditor上での最終調整
最後にここまでAIに作ってもらったスクリプトと画像を用いてUnityでゲームを完成させます。

この部分は今のところAIで行うことが難しかったので今回は仕方なく人間の手によって行いました。これからのAIの進化に期待しましょう。
正直なことをいうとUnityは人間がゲームを作るのに特化したゲームエンジンなのでAIがわざわざそれに合わせる必要はないように感じます。
完全にスクリプトだけで完結するゲームエンジンであればこの作業は必要なかったかもしれません。

スクリプト生成の章で実はUnityの手順についても簡単に説明を作ってくれていたのでそれも参考にしながら、Unity上でGameObjectを生成して画像を張り付けスクリプトを張り付ける作業を行いました。
また、スクリプトの中でプレイヤーの当たり判定のためにプレイヤーと敵キャラクターにPlayerタグとEnemyタグをつけてあげる必要があったのでその作業も行いました。
逆に言えばそれ以外の作業はほぼ行わずに、生成されたスクリプトをそのまま張り付ければなんとゲームが動いてしまいました。
# 出来上がったゲーム
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255633/1c572c35-25c9-4341-6935-6b8eab357321.png)
[こちらのサイト](https://unityroom.com/games/aiiiiiiiiiiiiiiiii)で公開をしています。是非遊んでみて下さい。

https://unityroom.com/games/aiiiiiiiiiiiiiiiii

生成されたスクリプトから僕が読み取った操作方法は以下のようなものです。
* キーボードのWASDか十字キーでプレイヤーを操作できます。
* 敵キャラクターが追いかけてくるので、逃げましょう！
* 敵キャラクターに三回ぶつかったらゲームオーバーです。

* スペースキーでプレイヤーの上部に攻撃を繰り出します
    * （攻撃のエフェクトなどはなく、見えないのでわかりにくいですがそこはＡＩなのでご了承を）
* スペースキーの攻撃を三回当てればゲームクリアです。
# まとめ
まだまだゲームとしては不十分ですが、AIが自動で作ったとは思えないレベルのゲームが出来上がりました。
画像のクォリティーが高いだけでなく、プレイヤーの操作、敵キャラクターの自動追尾、スペースキーでの攻撃判定など、プログラムの生成の精度も非常に高くて驚かされました。
ゲームの世界観の企画に関してもハチャメチャなものではなくしっかりとゲームとして面白そうなものを作ってくれています。

#### 今回やってみてまだAIにも進歩が必要だなと思った部分について
* 完全に斬新なゲームシステムやアイデアはまだ出てこない
* デザインや世界観は欧米の人が好みそうなものが出てくる(AIの学習に使ったデータセットの問題)
* 文字を含んだ画像の生成

#### AIが凄すぎて驚かされた部分
* スクリプトの生成の精度の高さ
    * 普通にちゃんと動くスクリプトを生成するしすべてにしっかりとコメントが付いている
* 少なくとも僕が作れないクォリティー以上の素材画像の生成精度
* しっかりとした世界観のゲームデザイン
* 僕の適当な日本語を理解してちゃんと生成してくれている点


プログラムの生成に関しては本当に精度が高いし、あいまいな日本語でもそれっぽいプログラムをすぐ生成出来てしまいます。
最近ではgoogleで調べるまえにChatGPTで質問することのが多いくらいです（笑）
googleだとちゃんと単語を入れないとわかりやすい記事が出てこないですが、ChatGPTは割と適当に質問しても答えてくれます。
僕が生徒に教えてるときも、「一回ChatGPTで聞いてみな」ってやってるくらいです。

Stable Diffusionに関しても、僕はデザインが苦手なこともあり僕はデザインに関してもうAIに負けていそうです。

(果たして、今のメンターで今回ChatGPTが生成したスクリプト以上のものを書ける人やStable Diffusionが生成した画像以上のデザインができる人はどのくらいいるのでしょうか？)
**本当にAIに仕事を乗っ取られる日は近いのかもしれません**