UnityからAIを使用する際、OpenAI APIを使うケースが現状最も多く見られます。
が、今回はあえてClaudeを使って画像認識を実装した話を書きます!
なぜなら、OpenAI API で「画像の人物の表情を分析して」と聞くと、頑なに拒否されたためです。。
しかしClaudeでは出来ました!
似たようなケースは他にもありそうなので、複数のAIを組み込んでおくと幅が広がりそうですね。
まずはコードを貼っちゃいます
リクエストおよびレスポンスデータを定義するためのクラス群
using System.Collections.Generic;
using System;
using Newtonsoft.Json;
[Serializable]
public class ClaudeMessage
{
public string role;
public List<Content> content;
}
[Serializable]
public class Content
{
public string type;
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string text; // Textを送る際に使用
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public Source source; // Imageを送る際に使用
}
[Serializable]
public class Source
{
public string type = "base64";
public string media_type;
public string data;
}
[Serializable]
public class ClaudeRequest
{
public string model;
public string system;
public List<ClaudeMessage> messages;
public int max_tokens;
}
[Serializable]
public class ClaudeResponse
{
public string id;
public ResponseContent[] content;
public Usage usage;
}
[Serializable]
public class ResponseContent
{
public string text;
}
[Serializable]
public class Usage
{
public int input_tokens;
public int output_tokens;
}
APIリクエストを管理し、テキストと画像データをAPIに送信するクラス
using System;
using Cysharp.Threading.Tasks;
using UnityEngine.Networking;
using UnityEngine;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Threading;
public class ClaudeApiConnector : MonoBehaviour
{
[SerializeField] private string _apiKey;
[SerializeField] private int _maxTokens = 1024;
private readonly string _apiUrl = "https://api.anthropic.com/v1/messages";
private readonly string _modelName = "claude-3-5-sonnet-20240620";
private List<ClaudeMessage> _messageHistory = new List<ClaudeMessage>();
/// <summary>
/// Claude APIを呼び出す。TextとTexture2Dのどちらか、または両方を引数で渡して使用。
/// </summary>
/// <returns></returns>
public async UniTask<string> CallApi(string prompt, string text = null, Texture2D texture2D = null, CancellationToken token = default)
{
if (text == null && texture2D == null)
{
Debug.LogError("Text and Texture2D are null.", gameObject);
return "unknown";
}
ClaudeMessage[] messages = CreateMessages(text, texture2D);
_messageHistory.AddRange(messages); // 入力の履歴を保持
return await ExecuteApiRequest(prompt, token);
}
private ClaudeMessage[] CreateMessages(string text = null, Texture2D texture2D = null)
{
List<Content> contents = new List<Content>();
if (texture2D != null)
{
Content imageContent = new Content
{
type = "image", // type を設定
source = new Source
{
type = "base64",
media_type = "image/png",
data = Convert.ToBase64String(texture2D.EncodeToPNG())
}
};
contents.Add(imageContent);
Debug.Log("Added images to messages to Claude API.", gameObject);
}
if (!string.IsNullOrEmpty(text))
{
Content textContent = new Content
{
type = "text",
text = text
};
contents.Add(textContent);
Debug.Log("Added Text to messages to Claude API : " + text, gameObject);
}
ClaudeMessage[] messages = new ClaudeMessage[]
{
new ClaudeMessage
{
role = "user",
content = contents
}
};
return messages;
}
private async UniTask<string> ExecuteApiRequest(string prompt, CancellationToken token = default)
{
Debug.Log("Execute a request to Claude API.", gameObject);
ClaudeRequest chatBody = new ClaudeRequest
{
system = prompt,
model = _modelName,
messages = _messageHistory,
max_tokens = _maxTokens
};
string body = JsonConvert.SerializeObject(chatBody);
byte[] jsonToSend = new System.Text.UTF8Encoding().GetBytes(body);
using UnityWebRequest request = new UnityWebRequest(_apiUrl, UnityWebRequest.kHttpVerbPOST);
request.uploadHandler = new UploadHandlerRaw(jsonToSend);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("x-api-key", _apiKey);
request.SetRequestHeader("anthropic-version", "2023-06-01");
request.SetRequestHeader("Content-Type", "application/json");
await request.SendWebRequest().ToUniTask(cancellationToken: token);
if (request.result != UnityWebRequest.Result.Success)
{
Debug.LogError("API request failed : " + request.error);
return null;
}
string response = request.downloadHandler.text;
ClaudeResponse responseJson = JsonConvert.DeserializeObject<ClaudeResponse>(response);
string output = responseJson.content[0].text;
Debug.Log("Claude API response : " + output, gameObject);
return output;
}
#if UNITY_EDITOR
[UnityEditor.CustomEditor(typeof(ClaudeApiConnector))]
private class ClaudeApiConnectorTest : UnityEditor.Editor
{
private const string PromptKey = "ClaudeApiPrompt";
private const string TextKey = "ClaudeApiText";
[SerializeField] private string _prompt;
[SerializeField] private string _text;
[SerializeField] private Texture2D _texture;
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
UnityEditor.EditorGUILayout.Space(10);
UnityEditor.EditorGUILayout.LabelField("--- Debug ---");
_prompt = UnityEditor.EditorPrefs.GetString(PromptKey, _prompt);
_text = UnityEditor.EditorPrefs.GetString(TextKey, _text);
_prompt = UnityEditor.EditorGUILayout.TextField("Prompt", _prompt);
_text = UnityEditor.EditorGUILayout.TextField("Text", _text);
_texture = (Texture2D)UnityEditor.EditorGUILayout.ObjectField("Texture2D", _texture, typeof(Texture2D), allowSceneObjects: false);
UnityEditor.EditorPrefs.SetString(PromptKey, _prompt);
UnityEditor.EditorPrefs.SetString(TextKey, _text);
if (target is not ClaudeApiConnector system) return;
if (GUILayout.Button("Call Claude API"))
{
system.CallApi(_prompt, text: _text, texture2D: _texture).Forget();
_text = null;
_texture = null;
}
}
}
#endif
}
使用方法
UniTask と Newtonsoft.Json を事前にUnityプロジェクトにインポートしておきます。
シーン上に空のGameObjectを作成し、そこにClaudeApiConnectorをアタッチし、そのインスペクターを設定していきます。
・API Key と プロンプトを入力。
・「Text」または「Texture2D」または「Text・Texture2D の両方」を設定。
※ 画像はpng形式で、Import Settings の Read/Write にチェックを入れた状態にしておく。
・シーン再生中に、インスペクターのボタン「Call Claude API」を押すと実行できます。
実装のメモ
・ClaudeApiConnector の CreateMessages にて、media_type = “image/png”としていますが、これを “image/jpeg” にすれば jpeg の画像を送れます。
・画像は Base64 にエンコードして送っています。
[Serializable]
public class Content
{
public string type;
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string text; // Textを送る際に使用
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public Source source; // Imageを送る際に使用
}
・↑ここの定義で[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
とすることで、Claudeへ送るコンテンツの Text か Image を省略した場合はJSONシリアライズ時に省略されるようにしました。これにより、Input内容が柔軟に変更できる仕様になっています。
おわりに
ゲーム画面のスクショを送ってみたり、Webカメラのスクショを送ってみたり、色々実装できそうですね!
今度は Germini Multimodal Live API を調べているので、形になったらまた書こうと思います。

