【Unity】Console のエラー文を Ctrl+C するだけで ChatGPT 向けプロンプトに自動変換する Editor 拡張

コンソールのエラー文を Ctrl + C したら
クリップボードの内容が ChatGPT の質問文に
自動で置き換わる Editor 拡張を考えました。

![タイトルなし.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3752886/070b2d35-4726-4aa1-b4ee-d745fb81aca5.png)

エラー文から自動的に .cs ファイルを検出し、
ソースコードも添付します。

これで ChatGPT の活用が楽になるかもしれません。
よろしければどうぞ！

サクっと使用したい場合は
[unitypackage 版](https://drive.google.com/file/d/1VeadeySI-YAi0GPMwtcVabLzSM87ZotI/view?usp=sharing) をご利用ください。

***

```csharp:SephirothConsoleCopyInterceptor.cs
#if UNITY_EDITOR

using UnityEditor;
using UnityEngine;
using System;
using System.Runtime.InteropServices;

[InitializeOnLoad]
public static class SephirothConsoleCopyInterceptor
{
    [DllImport("user32.dll")]
    private static extern short GetAsyncKeyState(int vKey);

    private const int VK_CONTROL = 0x11;
    private const int C_KEY = 0x43;

    private static double lastCopyTime = 0;
    private static double triggerTime = 0;
    private static bool shouldProcessClipboard = false;

    static SephirothConsoleCopyInterceptor()
    {
        EditorApplication.update -= OnEditorUpdate;
        EditorApplication.update += OnEditorUpdate;
    }

    private static void OnEditorUpdate()
    {
        // Ctrl + C を検出
        bool ctrl = (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0;
        bool c = (GetAsyncKeyState(C_KEY) & 0x8000) != 0;


        if (!UnityEditorInternal.InternalEditorUtility.isApplicationActive)
        return;

         // Console ウィンドウ以外では無効化
        var focused = EditorWindow.focusedWindow;
        if (focused == null || !focused.titleContent.text.Equals("Console"))
            return;

        if (ctrl && c && EditorApplication.timeSinceStartup - lastCopyTime > 0.5f)
        {
            lastCopyTime = EditorApplication.timeSinceStartup;
            triggerTime = lastCopyTime + 0.1; // 0.1 秒後に実行
            shouldProcessClipboard = true;
        }

        if (shouldProcessClipboard && EditorApplication.timeSinceStartup >= triggerTime)
        {
            shouldProcessClipboard = false;

            try
            {
                string originalText = GUIUtility.systemCopyBuffer;
                string transformed = TransformToChatGPTPrompt(originalText);

                if (!string.IsNullOrWhiteSpace(transformed))
                {
                    GUIUtility.systemCopyBuffer = transformed;
                    Debug.Log("📋 ChatGPT 用に変換されたエラー文がコピーされました");
                }
            }
            catch (Exception ex)
            {
                Debug.LogError("クリップボード変換中にエラー: " + ex.Message);
            }
        }
    }

    private static string TransformToChatGPTPrompt(string errorText)
    {
        if (string.IsNullOrWhiteSpace(errorText)) return null;

        var lines = errorText.Split("¥n");
        var scriptEntries = new System.Collections.Generic.List<(string relativePath, string absolutePath, string code)>();
        var seenPaths = new System.Collections.Generic.HashSet<string>(StringComparer.OrdinalIgnoreCase); // 重複防止

        foreach (var line in lines)
        {
            int assetIndex = line.IndexOf("Assets");
            int csIndex = line.IndexOf(".cs");

            if (assetIndex >= 0 && csIndex > assetIndex)
            {
                string partial = line.Substring(assetIndex + 6);
                partial = partial.Substring(0, partial.IndexOf(".cs") + 3);
                string relativePath = partial.Replace("\\", "/");
                string absolutePath = Application.dataPath + relativePath;

                // 重複チェック
                if (seenPaths.Contains(absolutePath))
                    continue;

                if (System.IO.File.Exists(absolutePath))
                {
                    try
                    {
                        string code = System.IO.File.ReadAllText(absolutePath);
                        scriptEntries.Add((relativePath, absolutePath, code));
                        seenPaths.Add(absolutePath); // 記録
                    }
                    catch (Exception ex)
                    {
                        Debug.LogWarning("スクリプトの読み込みに失敗: " + ex.Message);
                    }
                }
            }
        }

        // 各ファイルのコードを整形
        System.Text.StringBuilder codeBuilder = new();
        foreach (var entry in scriptEntries)
        {
            codeBuilder.AppendLine($"以下は関連するスクリプトファイルの内容です: `Assets{entry.relativePath}`");
            codeBuilder.AppendLine("```");
            string[] codeLines = entry.code.Split('\n');
            for (int i = 0; i < codeLines.Length; i++)
            {
                codeBuilder.AppendLine($"{i + 1}: {codeLines[i]}");
            }
            codeBuilder.AppendLine("```");
            codeBuilder.AppendLine();
        }

        return
    $@"以下の Unity エラーについて、原因と解決方法を教えてください。

    {errorText}

    {(codeBuilder.Length > 0 ? codeBuilder.ToString() : "")}
    
    エラーの要点や注意点があればそれも合わせて教えてください。";
    }
}

#endif
```

例えばクリップボードの内容が
下記のような内容になります。

```cs:クリップボードの内容
以下の Unity エラーについて、原因と解決方法を教えてください。

    Assets\SephirothChatGPTPromptCreator\SephirothChatGPTPromptCreator.cs(17,43): error CS1002: ; expected



    以下は関連するスクリプトファイルの内容です: `C:\Users\User\SephirothChatGPTPromptCreater\Assets\SephirothChatGPTPromptCreator\SephirothChatGPTPromptCreator.cs`

1: #if UNITY_EDITOR
2: 
3: using UnityEngine;
4: using UnityEditor;
5: using System.IO;
6: using System.Text;
7: using System.Collections.Generic;
8: using UnityEditor.SceneManagement;
9: using UnityEngine.SceneManagement;
10: 
11: public static class SephirothChatGPTPromptCreator
12: {
13:     [MenuItem("Assets/Tools/SephirothChatGPTPromptCreator/All", true)]
14:     private static bool ValidateSelectedUnityFile()
15:     {
16:         string path = AssetDatabase.GetAssetPath(Selection.activeObject);
17:         return path.EndsWith(".unity");aaa
18:     }
19: 
20:     [MenuItem("Assets/Tools/SephirothChatGPTPromptCreator/All")]
21:     private static void GeneratePromptFromScene()
22:     {
23:         string scenePath = AssetDatabase.GetAssetPath(Selection.activeObject);
24:         if (!scenePath.EndsWith(".unity"))
25:         {
26:             Debug.LogWarning("選択されたファイルは .unity ファイルではありません。");
27:             return;
28:         }
29: 
30:         var builder = new StringBuilder();
31: 
32:         // .unity ファイルの内容を追加
33:         string fullScenePath = Path.Combine(Directory.GetCurrentDirectory(), scenePath);
34:         builder.AppendLine(Path.GetFileName(scenePath));
35:         builder.AppendLine(File.ReadAllText(fullScenePath));
36:         builder.AppendLine();
37: 
38:         // シーンを開いてすべての MonoBehaviour を取得
39:         var openedScene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single);
40:         var allObjects = openedScene.GetRootGameObjects();
41: 
42:         var uniqueScripts = new HashSet<MonoScript>();
43: 
44:         foreach (var go in allObjects)
45:         {
46:             foreach (var mb in go.GetComponentsInChildren<MonoBehaviour>(true))
47:             {
48:                 if (mb == null) continue; // Missing Script の場合
49:                 var script = MonoScript.FromMonoBehaviour(mb);
50:                 if (script != null)
51:                     uniqueScripts.Add(script);
52:             }
53:         }
54: 
55:         // 各スクリプトファイルを読み込み（Assetsフォルダ内のスクリプトのみ対象）
56:         foreach (var monoScript in uniqueScripts)
57:         {
58:             string scriptPath = AssetDatabase.GetAssetPath(monoScript);
59:             if (!scriptPath.StartsWith("Assets/")) 
60:                 continue; // Unity内部やパッケージのスクリプトは除外
61: 
62:             string fullScriptPath = Path.Combine(Directory.GetCurrentDirectory(), scriptPath);
63:             builder.AppendLine(Path.GetFileName(scriptPath));
64:             builder.AppendLine(File.ReadAllText(fullScriptPath));
65:             builder.AppendLine();
66:         }
67: 
68:         // クリップボードにコピー
69:         EditorGUIUtility.systemCopyBuffer = builder.ToString();
70:         Debug.Log("シーンと関連付けられた .cs スクリプトがクリップボードにコピーされました。");
71:     }
72: 
73:     [MenuItem("Assets/Tools/SephirothChatGPTPromptCreator/CSOnly", true)]
74:     private static bool ValidateSelectedUnityFile2()
75:     {
76:         string path = AssetDatabase.GetAssetPath(Selection.activeObject);
77:         return path.EndsWith(".unity");
78:     }
79: 
80:     [MenuItem("Assets/Tools/SephirothChatGPTPromptCreator/CSOnly")]
81:     private static void GeneratePromptFromScene2()
82:     {
83:         string scenePath = AssetDatabase.GetAssetPath(Selection.activeObject);
84:         if (!scenePath.EndsWith(".unity"))
85:         {
86:             Debug.LogWarning("選択されたファイルは .unity ファイルではありません。");
87:             return;
88:         }
89: 
90:         var builder = new StringBuilder();
91: 
92:         // シーンを開いてすべての MonoBehaviour を取得
93:         var openedScene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single);
94:         var allObjects = openedScene.GetRootGameObjects();
95: 
96:         var uniqueScripts = new HashSet<MonoScript>();
97: 
98:         foreach (var go in allObjects)
99:         {
100:             foreach (var mb in go.GetComponentsInChildren<MonoBehaviour>(true))
101:             {
102:                 if (mb == null) continue; // Missing Script の場合
103:                 var script = MonoScript.FromMonoBehaviour(mb);
104:                 if (script != null)
105:                     uniqueScripts.Add(script);
106:             }
107:         }
108: 
109:         // 各スクリプトファイルを読み込み（Assetsフォルダ内のスクリプトのみ対象）
110:         foreach (var monoScript in uniqueScripts)
111:         {
112:             string scriptPath = AssetDatabase.GetAssetPath(monoScript);
113:             if (!scriptPath.StartsWith("Assets/")) 
114:                 continue; // Unity内部やパッケージのスクリプトは除外
115: 
116:             string fullScriptPath = Path.Combine(Directory.GetCurrentDirectory(), scriptPath);
117:             builder.AppendLine(Path.GetFileName(scriptPath));
118:             builder.AppendLine(File.ReadAllText(fullScriptPath));
119:             builder.AppendLine();
120:         }
121: 
122:         // クリップボードにコピー
123:         EditorGUIUtility.systemCopyBuffer = builder.ToString();
124:         Debug.Log("シーンと関連付けられた .cs スクリプトがクリップボードにコピーされました。");
125:     }
126: }
127: 
128: #endif

    エラーの要点や注意点があればそれも合わせて教えてください。
```

***

以上、楽に ChatGPT を活用するための
Editor 拡張を紹介しました。

これで皆さんの開発が楽になる事があれば
非常に嬉しいです。

稀にエラー文にバグが発生しているコードの
パスが載っていない事がありますが、
その場合は動作しませんのでご容赦下さい。

今回はここまで、
閲覧ありがとうございました。

以下で ChatGPT に出力頂いた説明文を載せます。
興味のある方はどうぞ。

***

Unity Console のエラーを ChatGPT にそのまま相談できたら便利だと思いませんか？

このエディタ拡張は、Unity の Console で発生したエラー文を Ctrl + C でコピーするだけで、自動的に ChatGPT に貼り付けられる形式へと変換してくれるツールです。

コピー後わずか 0.1 秒で、クリップボード内のエラーメッセージが以下のようなフォーマットに変換されます：

```:生成される文章
以下の Unity エラーについて、原因と解決方法を教えてください。

    [コピーしたエラー文]

    以下は関連するスクリプトファイルの内容です:

[該当ファイルのコード全文]

    エラーの要点や注意点があればそれも合わせて教えてください。

```
🔧 主な機能：

Ctrl + C を押すだけでエラー文を ChatGPT 向けに自動整形

エラー文から自動的に .cs ファイルを検出し、ソースコードも添付

Unity エディタ拡張としてシームレスに動作

無駄な手間ゼロでエラーの原因調査が効率化！

📝 対応環境：

Unity 2020.3 以降

Windows 専用（※ macOS には未対応）

エディターモードのみ対応（Play中は無効）

💡 こんな人におすすめ：

Unity のエラーを毎回手動で ChatGPT に貼っている方

プログラミング初心者の方

デバッグ効率を最大化したい開発者

使い方はとても簡単！スクリプトをプロジェクトに導入するだけです。