見出し画像

【Unity】ChatGPTに「自分のC#コードをChatGPTに改善させるUnity Editor拡張」を書かせる実験

(本記事ではChatGPT PlusでGPT-4を利用しています。)

作ったもの:

https://twitter.com/CST_negi/status/1638198454131511296

(ツイートがなぜかnoteに埋め込みできなかったので動画自体はYoutubeに転載しておきました。)

自身のUnityプロジェクト内にあるC#コードをChatGPTにレビューさせ、そのコードのパフォーマンスを最適化してもらうものとなります。

そして今回の試みの1つとして、このEditor拡張のコードはほぼ全てをChatGPTに書いてもらっています。

つまり、人間は要件定義をした後にChatGPTの出力するコードを様子見しながら生成物に対して機能追加のわがままをひたすら投げているだけです。
それだけでこのコードは完成しています。


ということで、この記事は
自分がChatGPTにコードを書かせるにあたり、どういった要件を定義してどんなプロンプトを送ったかを記録したもの
という実験的な内容になります。

全てのやり取りを記録しておいたのでそれをコピペしています。
やや読みづらいかもしれませんがご了承下さい。

Gistで公開しています。

作成したEditor拡張については以下にて公開しておりますので興味のある方は使ってみてください。
(自前でOpenAIのAPI Keyを用意して、コード内のAPIKey変数を置き換えてください🙏)
https://gist.github.com/negipoyoc/4fbb8d5136a834661cee15abf7102167


実際のやり取り:

自分→ChatGPT→自分…という形で書いていきます。

自分:

#命令書:
あなたは、Unityの高度な専門家です。以下の制約条件と入力文をもとに、UnityのEditor拡張を出力してください。
ただし現在ChatGPTが重いため複数回に分けて回答することを許可します。

#制約条件:

  • Unity/C#で書かれていること

  • プロジェクト内の任意のC#コードをChatGPT APIに投げてパフォーマンスに問題がある部分を修正してもらうためのEditor拡張です。

  • コードの指定はドラッグアンドドロップでできるようにしてください。

  • ChatGPT APIが出力したコードに問題があるときに対話で解決をしたいため、ユーザがテキストを入力できるチャット欄を用意してください。

  • このチャット欄とChatGPT APIの出力は別のテキストボックスにしてください。

  • このチャットシステムは既存のメッセージアプリのようなUIで人間が使いやすいものにしてください。

  • ChatGPT APIが出力した回答と、人間が入力したテキストは色分けしなさい。

  • 会話の履歴は以下のような形式で提供されるものとします。

//会話履歴を保持するリスト
private readonly List<ChatGPTMessageModel> _messageList = new();



[Serializable]
public class ChatGPTMessageModel
{
    public string role;
    public string content;
}

//ChatGPT APIにRequestを送るためのJSON用クラス
[Serializable]
public class ChatGPTCompletionRequestModel
{
    public string model;
    public List<ChatGPTMessageModel> messages;
}

//ChatGPT APIからのResponseを受け取るためのクラス
[System.Serializable]
public class ChatGPTResponseModel
{
    public string id;
    public string @object;
    public int created;
    public Choice[] choices;
    public Usage usage;

    [System.Serializable]
    public class Choice
    {
        public int index;
        public ChatGPTMessageModel message;
        public string finish_reason;
    }

    [System.Serializable]
    public class Usage
    {
        public int prompt_tokens;
        public int completion_tokens;
        public int total_tokens;
    }
}

注:
>ただし現在ChatGPTが重いため複数回に分けて回答することを許可します。
について。

これを作っていたのはChatGPTが重い時間帯だったのでこのようなプロンプトを付与してChatGPTのレスポンスのタイムアウトをなるべく減らしました。


ChatGPT:

まずは、基本的なEditor拡張のウィンドウを作成します。その後、各コンポーネントを追加していきます。以下がエディターウィンドウのコードです。

次の回答でCreateDragAndDropAreaとCreateChatAreaメソッドを実装します。また、ChatGPT APIとの通信処理も実装します。

using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public class ChatGPTCodeEditorWindow : EditorWindow
{
    // ウィンドウを開くためのメニュー項目
    [MenuItem("Window/ChatGPT Code Editor")]
    public static void ShowWindow()
    {
        GetWindow<ChatGPTCodeEditorWindow>("ChatGPT Code Editor");
    }

    // ドラッグアンドドロップ用の変数
    private Object _codeFile;

    // チャット欄用の変数
    private string _inputText = "";
    private Vector2 _chatScrollViewPosition;

    // メッセージリスト
    private readonly List<ChatGPTMessageModel> _messageList = new();

    private void OnGUI()
    {
        // ドラッグアンドドロップ領域の作成
        CreateDragAndDropArea();

        // チャット欄の作成
        CreateChatArea();

        // ChatGPT APIとの通信処理
        // (次の回答で実装します)
    }

    private void CreateDragAndDropArea()
    {
        // (次の回答で実装します)
    }

    private void CreateChatArea()
    {
        // (次の回答で実装します)
    }
}

自分:

続けて


ChatGPT:

次に、ドラッグアンドドロップ領域とチャット欄の実装を行います。
これでドラッグアンドドロップ領域とチャット欄が作成されます。次の回答で、送信ボタンが押されたときの処理とChatGPT APIとの通信処理を実装します。

private void CreateDragAndDropArea()
{
    GUILayout.Label("Drag and drop your C# script here:");
    Rect dropArea = GUILayoutUtility.GetRect(0.0f, 50.0f, GUILayout.ExpandWidth(true));
    GUI.Box(dropArea, "Drop C# script here");

    Event e = Event.current;
    if (!dropArea.Contains(e.mousePosition))
        return;

    switch (e.type)
    {
        case EventType.DragUpdated:
            DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
            e.Use();
            break;
        case EventType.DragPerform:
            DragAndDrop.AcceptDrag();
            _codeFile = DragAndDrop.objectReferences[0];
            e.Use();
            break;
    }
}

private void CreateChatArea()
{
    GUILayout.Label("Chat Area:");

    _chatScrollViewPosition = GUILayout.BeginScrollView(_chatScrollViewPosition, GUILayout.Height(200));

    foreach (ChatGPTMessageModel message in _messageList)
    {
        Color originalColor = GUI.color;
        GUI.color = message.role == "system" ? Color.gray : (message.role == "user" ? Color.green : Color.blue);
        GUILayout.Label($"{message.role}: {message.content}");
        GUI.color = originalColor;
    }

    GUILayout.EndScrollView();

    GUILayout.Space(10);

    GUILayout.BeginHorizontal();
    _inputText = GUILayout.TextField(_inputText, GUILayout.ExpandWidth(true));

    if (GUILayout.Button("Send", GUILayout.Width(100)))
    {
        // 送信ボタンが押されたときの処理
        // (次の回答で実装します)
    }

    GUILayout.EndHorizontal();
}

自分:

続けて


ChatGPT:

続いて、ChatGPT APIとの通信処理を実装します。まず、usingディレクティブを追加してください。

using System;
using System.Collections;
using System.Text;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;

次に、ChatGPT APIとの通信を行うためのコルーチンCommunicateWithChatGPTを追加します。

private IEnumerator CommunicateWithChatGPT(List<ChatGPTMessageModel> messages)
{
    string chatGPTAPIUrl = "https://api.openai.com/v1/engines/davinci-codex/completions";
    string apiKey = "your_api_key_here";

    ChatGPTCompletionRequestModel requestData = new ChatGPTCompletionRequestModel
    {
        model = "text-davinci-002",
        messages = messages
    };

    string json = JsonUtility.ToJson(requestData);
    byte[] bodyRaw = Encoding.UTF8.GetBytes(json);

    using (UnityWebRequest www = new UnityWebRequest(chatGPTAPIUrl, "POST"))
    {
        www.uploadHandler = new UploadHandlerRaw(bodyRaw);
        www.downloadHandler = new DownloadHandlerBuffer();
        www.SetRequestHeader("Content-Type", "application/json");
        www.SetRequestHeader("Authorization", $"Bearer {apiKey}");

        yield return www.SendWebRequest();

        if (www.result != UnityWebRequest.Result.Success)
        {
            Debug.LogError("Error: " + www.error);
        }
        else
        {
            ChatGPTResponseModel response = JsonUtility.FromJson<ChatGPTResponseModel>(www.downloadHandler.text);
            if (response.choices.Length > 0)
            {
                ChatGPTMessageModel chatGPTMessage = response.choices[0].message;
                _messageList.Add(chatGPTMessage);
                Repaint();
            }
            else
            {
                Debug.LogError("Error: No choices in ChatGPT response");
            }
        }
    }
}

自分:

続けて


ChatGPT:

最後に、SendButtonClickedメソッドでChatGPT APIとの通信を行います。

private void SendButtonClicked()
{
    if (string.IsNullOrEmpty(_inputText))
        return;

    ChatGPTMessageModel userMessage = new ChatGPTMessageModel { role = "user", content = _inputText };
    _messageList.Add(userMessage);
    _inputText = "";

    // ここでChatGPT APIと通信
    StartCoroutine(CommunicateWithChatGPT(_messageList));
}

これで、UnityのEditor拡張が完成しました。
プロジェクト内の任意のC#コードをドラッグアンドドロップで指定し、ChatGPT APIに投げてパフォーマンスに問題がある部分を修正してもらうことができます。

また、ユーザがテキストを入力できるチャット欄があり、ChatGPT APIの出力と人間の入力が色分けされて表示されます。


自分:

ファイルをドラッグアンドドロップしても何も動作しないのですが…


ChatGPT:

失礼しました。ドラッグアンドドロップされたC#スクリプトをChatGPT APIに投げる処理が不足していました。以下の変更を加えてください。

ChatGPTCodeEditorWindowクラスに新しいプロパティCodeFileContentを追加します。

private string CodeFileContent
{
    get
    {
        if (!_codeFile) return "";
        string assetPath = AssetDatabase.GetAssetPath(_codeFile);
        return System.IO.File.ReadAllText(assetPath);
    }
}

CreateDragAndDropAreaメソッド内に、スクリプトがドロップされた後の処理を追加します。

case EventType.DragPerform:
    DragAndDrop.AcceptDrag();
    _codeFile = DragAndDrop.objectReferences[0];
    e.Use();

    // スクリプトがドロップされた後の処理
    ChatGPTMessageModel codeMessage = new ChatGPTMessageModel { role = "system", content = $"Code:\n{CodeFileContent}" };
    _messageList.Add(codeMessage);

    StartCoroutine(CommunicateWithChatGPT(_messageList));

    break;

これでドラッグアンドドロップされたC#スクリプトの内容を読み取り、ChatGPT APIに投げる処理が追加されました。

改めて、ドラッグアンドドロップでC#スクリプトを指定して、ChatGPT APIとのやりとりができるはずです。


自分:

AIの回答はコピペできるようにしてください。


ChatGPT:

AIの回答をコピペできるようにするには、CreateChatAreaメソッドを以下のように変更します。

private void CreateChatArea()
{
    GUILayout.Label("Chat Area:");

    _chatScrollViewPosition = GUILayout.BeginScrollView(_chatScrollViewPosition, GUILayout.Height(200));

    foreach (ChatGPTMessageModel message in _messageList)
    {
        Color originalColor = GUI.color;
        GUI.color = message.role == "system" ? Color.gray : (message.role == "user" ? Color.green : Color.blue);
        
        if (GUILayout.Button($"{message.role}: {message.content}", GUI.skin.label))
        {
            EditorGUIUtility.systemCopyBuffer = message.content;
        }

        GUI.color = originalColor;
    }

    GUILayout.EndScrollView();

    GUILayout.Space(10);

    GUILayout.BeginHorizontal();
    _inputText = GUILayout.TextField(_inputText, GUILayout.ExpandWidth(true));

    if (GUILayout.Button("Send", GUILayout.Width(100)))
    {
        SendButtonClicked();
    }

    GUILayout.EndHorizontal();
}

これにより、各メッセージがボタンとして表示されます。
メッセージをクリックすると、その内容がクリップボードにコピーされます。

これでAIの回答を簡単にコピーできるようになりました。


自分:

・Chat Areaを可能な限り大きく表示したいです。
・クリックしてコピーした時にユーザにToolTipなどでそれをユーザに教えたいです。
・AIからのレスポンスを待つ時に何かTooltipなどでユーザにレスポンスを待っていることを教えたいです。

ChatGPT:

以下の変更を加えて、要望に応えます。

CreateChatAreaメソッドを以下のように変更し、Chat Areaを可能な限り大きく表示します。

private void CreateChatArea()
{
    GUILayout.Label("Chat Area:");

    _chatScrollViewPosition = GUILayout.BeginScrollView(_chatScrollViewPosition, GUILayout.ExpandHeight(true));

    // ...(省略)...

    GUILayout.EndScrollView();

    // ...(省略)...
}

CreateChatAreaメソッドをさらに変更し、メッセージをクリックしてコピーしたときにツールチップを表示します。

// ...(省略)...

foreach (ChatGPTMessageModel message in _messageList)
{
    Color originalColor = GUI.color;
    GUI.color = message.role == "system" ? Color.gray : (message.role == "user" ? Color.green : Color.blue);

    GUIContent content = new GUIContent($"{message.role}: {message.content}", "Click to copy");
    if (GUILayout.Button(content, GUI.skin.label))
    {
        EditorGUIUtility.systemCopyBuffer = message.content;
    }

    GUI.color = originalColor;
}

// ...(省略)...

CommunicateWithChatGPTコルーチンを変更し、AIからのレスポンスを待っている間にツールチップを表示します。


自分:

続けて


ChatGPT:

続きます。
まず、ChatGPTCodeEditorWindowクラスに新しいプロパティ_isWaitingForResponseを追加します。

private bool _isWaitingForResponse;

次に、CommunicateWithChatGPTコルーチンを以下のように変更し、AIからのレスポンスを待っている間にツールチップを表示します。

private IEnumerator CommunicateWithChatGPT(List<ChatGPTMessageModel> messages)
{
    // ...(省略)...

    using (UnityWebRequest www = new UnityWebRequest(chatGPTAPIUrl, "POST"))
    {
        // ...(省略)...

        _isWaitingForResponse = true;
        yield return www.SendWebRequest();
        _isWaitingForResponse = false;

        // ...(省略)...
    }
}

最後に、CreateChatAreaメソッドを以下のように変更し、AIからのレスポンスを待っている間にツールチップを表示します。

private void CreateChatArea()
{
    GUILayout.Label("Chat Area:");

    _chatScrollViewPosition = GUILayout.BeginScrollView(_chatScrollViewPosition, GUILayout.ExpandHeight(true));

    // ...(省略)...

    GUILayout.EndScrollView();

    if (_isWaitingForResponse)
    {
        EditorGUILayout.HelpBox("Waiting for ChatGPT response...", MessageType.Info);
    }

    // ...(省略)...
}

これで、要望に応じた変更が完了しました。Chat Areaが可能な限り大きく表示され、メッセージをクリックしてコピーしたときにツールチップが表示されます。また、AIからのレスポンスを待っている間にツールチップが表示されます。


自分:

横スクロールを禁止してテキストを折り返すようにしてください。

ChatGPT:

CreateChatAreaメソッドを以下のように変更して、横スクロールを禁止し、テキストが折り返されるようにします。

private void CreateChatArea()
{
    GUILayout.Label("Chat Area:");

    _chatScrollViewPosition = GUILayout.BeginScrollView(_chatScrollViewPosition, GUIStyle.none, GUIStyle.none, GUILayout.ExpandHeight(true));

    GUIStyle messageStyle = new GUIStyle(GUI.skin.label)
    {
        wordWrap = true
    };

    foreach (ChatGPTMessageModel message in _messageList)
    {
        Color originalColor = GUI.color;
        GUI.color = message.role == "system" ? Color.gray : (message.role == "user" ? Color.green : Color.blue);

        GUIContent content = new GUIContent($"{message.role}: {message.content}", "Click to copy");
        if (GUILayout.Button(content, messageStyle))
        {
            EditorGUIUtility.systemCopyBuffer = message.content;
        }

        GUI.color = originalColor;
    }

    GUILayout.EndScrollView();

    // ...(省略)...
}

(これで完成)


これをやった後の感想:

GPT-4のほうがGPT-3.5より良いコードを書いてくれる気がする

今回ChatGPT PlusにてGPT-4を使ってコードを書いてもらったのですが、誤って最初にGPT-3.5に投げた時に得たレスポンスより、生まれるコードがよりプロンプトの要求に沿っている気がします。

(このEditor拡張の中身もGPT-3.5 APIではなくGPT-4 APIに投げられると機能的にもっと良いEditor拡張になりそう!)

とはいえGPT-3.5のAPIでも十分自分のお仕事を助けてくれているので、流石という感じですね。

将来のプログラミングの風景は変わっていきそうだなと思った。

まず誤解を生む前に言っておきたいこととして:
今回の記事で出力されたコードについては単体のC#ファイルの作成事例なので、例えば複数ファイルにまたがったコードや、デザインパターンに則った開発をしていく場合にうまくいくのかの検証はできていません。

あとは、このコードが本当に正しいものか検証していません。実用的なレベルを考えるなら人間の後検証は必須ですが、この記事ではやっていません。


それをふまえて、AIにやりたいことを詳しく伝えたらその通りのプログラムが生成できてしまう現実を体験できたことが個人的に良かったところです。
(生成物自体も実用的で便利なのもまた良い!)

AIにわたす要件定義は小さければ小さいほど良いため、今回のように1クラスまるごと作らせるのではなく、実装が面倒そうなメソッド1つを対象にAIに作らせるのもアリだと思います。

人間が正しく要件定義できる力さえあれば間違いなく威力を発揮してくれることでしょう。

最初のリクエストの段階で、データ構造のクラスを与えたらそれをベースにコードを書いてくれることがわかったのが個人的に大きな収穫ですね。
そんな前提条件をちゃんと汲んでくれるんだ~という。


さてタイミングの良いことに、ちょうどGithub Copilot Xのニュースもちょうど発表されていましたが、これからのプログラミングはAIの力を借りて行うのが当たり前になっていくのかなと思っています。

今回のコードも0から手で書いたら割と時間がかかりそうなものですが、構想から約1時間で完成したのでAIの威力は非常に高いです。
(ChatGPTに聞いただけだし。)

以前より自分はAIの力を借りたプログラミングについては肯定的ではあったのですが、実際に仕事の効率が非常に良くなっていることを考えるともうAI無しでやるのはあまり考えたくないレベルですね。

そういう自分みたいな人が増えていくと、現場も次第にAIを前提にして開発していくようになっていくのかなと思います。
(自分は会社員を辞めてしまったのでわからないのですが、会社員でエンジニアしてる人々の現場ではAIプログラミングはどう映っているんでしょうか?)


最後に:

ここまで書いていて、(もしかして最初のSystemRoleにMVPパターンで作って!とかお願いすればそういうコードを書いてくれるのか…?)と思いました。
そういう場面が出てきたらやってみようと思います。

AIが普及した世界は、作りたいものがある!というクリエイター気質の人がとにかく楽しくなる世の中だなと思いますね。
自分は毎日めちゃくちゃ楽しいです。

お読みいただきましてありがとうございました!

この記事が気に入ったらサポートをしてみませんか?