📑

[Claudia][Unity]自然言語によって動く2Dプラットフォーム

2024/04/13に公開

はじめに

CysharpからAnthropic Claude APIの.NET用、非公式クライアントライブラリClaudiaが登場しました。このライブラリの登場により、Unity上で簡単にClaude3のAPIを呼び出すことができます。また、Function Callingと呼ばれるLLMの出力から関数を呼び出す機能も簡単に実装できます。今回はClaudiaのチュートリアルとして、自然言語の入力からプレイヤーを操作できる2Dプラットフォームを実施します。
https://github.com/Cysharp/Claudia?tab=readme-ov-file#readme
要所のコードを解説したのちに、最後は簡単に試せるサンプルコードを添付しています。

使用するライブラリ

今回使用するライブラリとして、Cysharpから登場したAnthropic Claude APIの.NET用、非公式クライアントライブラリClaudiaを使用します。Claudiaの導入方法に関しては、今回は触れませんがClaudiaリポジトリのReadMeを読むことで導入できます。
https://github.com/Cysharp/Claudia

使用するアセット

2Dプラットフォームを作成するために、今回はUnity Technologiesが作成しているPlatformer Microgameを使用します。こちらのアセットは、2Dプラットフォームの基本が実装されているアセットになります。
https://assetstore.unity.com/packages/templates/platformer-microgame-151055

成果物

今回作成する成果物は、2Dプラットフォーマーにおけるプレイヤーの操作を、自然言語による入力で可能としたものになります。
図1:自然言語による2Dプラットフォーマーのプレイヤー操作
図1:自然言語による2Dプラットフォーマーのプレイヤー操作

実装概要

実装概要としては、キャラクターの操作に必要な関数を用意します。ユーザーからClaude3に質問文(自然言語による操作)を投げます。Claude3は用意された関数から、最適な関数と引数を返します。これらの情報を元に、手元で関数を呼び出す形になります。
次の章で、実装の詳細を紹介しています。

実装詳細

まず、ユーザーの自然言語の入力と、利用可能な関数一覧と説明をClaude3に渡します。Claude3は、利用可能な関数で最適な関数を返します。
ここで該当する処理がなけれ関数がなければ関数の呼び出しはありません。
図2:実装概要
図2:実装概要
関数をClaude3が返す際に、関数に引数があればClaude3は最適な引数を渡します。これによって得られた関数と、引数を手元で実行する形になります。

具体例で考えてみましょう。移動の関数を定義した際に、移動方向(-1,+1)と移動速度(0~1)を引数に持たせます。ユーザーから右に走れという指示があれば、引数として+1と1が渡されます。ゆっくり左に歩けと指示があれば、引数として-1と0.1が渡されます。
図3:FunctionCalling概要
図3:FunctionCalling概要

実際に実行される関数内部で、プレイヤーの移動速度を変更してあげることで動作する様になります。

FunctionCallingで呼び出す関数

2Dプラットフォームの操作を自然言語で実現する上で、FunctionCallingで呼び出すためのいくつかの関数を定義する必要があります。
今回のチュートリアルにおいて、適切な関数を用意することが重要になります。今回は、Move関数、Stop関数、Jump関数、RunAndJump関数を用意しました。

Move関数

まず、Move関数では、移動方向と移動速度を引数に渡されると、PlayerControllermoveを変更します。

Move関数
/// <summary>
/// 移動プログラム
/// </summary>
/// <param name="moveDirection">移動方向、左-1、右+1</param>
/// <param name="speed">移動速度0~1</param>
/// <returns></returns>
[ClaudiaFunction]
static bool Move(int moveDirection, float speed)
{
    PlayerMoveController.move.x = moveDirection * speed;
    return true;
}

図4:Move
図4:Move

Stop関数

Stop関数が呼び出されるとプレイヤーの動作を停止させます。

Stop関数
/// <summary>
/// 移動を止まる
/// </summary>
/// <returns></returns>
[ClaudiaFunction]
static bool Stop()
{
    PlayerMoveController.move.x = 0;
    return true;
}

図5:Stop
図5:Stop

Jump関数

Jump関数が呼び出されるとプレイヤーのジャンプフラグをONにして、ジャンプします。

Jump関数
/// <summary>
/// ジャンプ処理
/// </summary>
/// <returns></returns>
[ClaudiaFunction]
static bool Jump()
{
     PlayerMoveController.jump = true;
     return true;
}

図6:Jump
図6:Jump

RunAndJump関数

RunAndJump処理では走りと、ジャンプの処理を同時に行います。

RunAndJump処理
/// <summary>
/// 走りながらジャンプ
/// </summary>
/// <param name="moveDirection">移動方向、左-1、右+1</param>
/// <param name="speed">移動速度0~1</param>
/// <returns></returns>
static bool RunAndJump(int moveDirection, float speed)
{
    PlayerMoveController.jump = true;
    PlayerMoveController.move.x = moveDirection * speed;
    return true;
}

図7:RunAndJump
図7:RunAndJump

Claude3との連携

適切な関数が用意できたら、Claude3 APIを呼び出す処理が必要になります。
Claude3への命令関数が、InstructionClaude3になります。anthropicにはAPIキーなどの情報を代入。inputにはClaude3へのユーザーの入力を代入されます。messageはLLMからの応答になります。FunctionTools.SystemPromptには先ほど作成した関数の情報があり、それとinputがClaude3に渡されることで適切な関数をmessageとして返します。

返されたmessageを元に、FunctionCallingで呼び出す関数を手元で呼び出します。適切な関数が呼び出されていれば、moveと、jumpが変化します。変化した変数をplayerControllerに渡してあげることで、playerController側で移動処理や、ジャンプ処理が実行されます。

Claude3への命令関数
private async Task InstructionClaude3()
{
    var anthropic = new Anthropic()
    {
        ApiKey = "APIキー"
    };
    var input = new Message
    {
        Role = Roles.User,
        Content = instructionField.text,
    };
    var message = await anthropic.Messages.CreateAsync(new()
    {
        Model = Models.Claude3Haiku,
        MaxTokens = 1024,
        System = FunctionTools.SystemPrompt,
        StopSequences = new[] { StopSequnces.CloseFunctionCalls },
        Messages = new[] { input },
    });
    await FunctionTools.InvokeAsync(message);
    playerController.Move = move;
    playerController.Jump = jump;
    instructionField.text = "";
}

PlayerControllerの修正

Claudiaとの連携に合わせて、Platformer MicrogamePlayerControllerを修正する必要があります。具体的な修正点は以下のとおりです。

  • Spaceボタンを押したときにJumpさせないで、FunctionCallingによってジャンプ処理へ変更。
  • 十字キーによる移動を行わないで、FunctionCallingによる移動処理へ変更。
  • moveやjumpをFunctionCallingから呼び出すためのプロパティの作成。
    それでは、実際の行数と共に修正点を紹介します。

39行目FunctionCallinで呼び出すためのプロパティ作成。

public Vector2 Move
{
    get => move;
    set => move = value;
}
public bool Jump
{
    get => jump;
    set => jump = value;
}

58行、67~70行目十字キーによる移動を行わない為、処理の削除。

58行目
- move.x = Input.GetAxis("Horizontal"); 

67~70行目
- else
-{
-    move.x = 0;
-d}

59行目Spaceボタンを押したときにJumpさせないで、FunctionCallingの結果でジャンプを行う。

-if (jumpState == JumpState.Grounded && Input.GetButtonDown("Jump"))
+if (jumpState == JumpState.Grounded && jump)

完全版のサンプルコード

簡単に今回のチュートリアルが試せるサンプルコードを提示します。

PlayerInstruction
using Claudia;
using System.Threading.Tasks;
using Platformer.Mechanics;
using UnityEngine;
using UnityEngine.UI;

public class PlayerInstruction : MonoBehaviour
{
    [SerializeField]
    public InputField instructionField;
    
    private Anthropic anthropic;
    [SerializeField]private PlayerController playerController;
    [SerializeField]private Button button;
    public static Vector2 move;
    public static bool jump;
    void Start()
    {
        button.onClick.AddListener(OnDecisionButtonClicked);
    }
    
    private async void OnDecisionButtonClicked()
    {
        await InstructionClaude3();
    }
    
    private async Task InstructionClaude3()
    {
        var anthropic = new Anthropic()
        {
            ApiKey = "APIキー"
        };
        
        var input = new Message
        {
            Role = Roles.User,
            Content = instructionField.text,
        };
        var message = await anthropic.Messages.CreateAsync(new()
        {
            Model = Models.Claude3Haiku,
            MaxTokens = 1024,
            System = FunctionTools.SystemPrompt,
            StopSequences = new[] { StopSequnces.CloseFunctionCalls },
            Messages = new[] { input },
        });

        await FunctionTools.InvokeAsync(message);

        playerController.Move = move;
        playerController.Jump = jump;
        instructionField.text = "";
    }
}

public static partial class FunctionTools
{
    /// <summary>
    /// 移動プログラム
    /// </summary>
    /// <param name="moveDirection">移動方向、左-1、右+1</param>
    /// <param name="speed">移動速度0~1</param>
    /// <returns></returns>
    [ClaudiaFunction]
    static bool Move(int moveDirection, float speed)
    {
        PlayerMoveController.move.x = moveDirection * speed;
        return true;
    }
    
    /// <summary>
    /// 移動を止まる
    /// </summary>
    /// <returns></returns>
    [ClaudiaFunction]
    static bool Stop()
    {
        PlayerMoveController.move.x = 0;
        return true;
    }

    /// <summary>
    /// ジャンプ処理
    /// </summary>
    /// <returns></returns>
    [ClaudiaFunction]
    static bool Jump()
    {
         PlayerMoveController.jump = true;
         return true;
    }

    /// <summary>
    /// 走りながらジャンプ
    /// </summary>
    /// <param name="moveDirection">移動方向、左-1、右+1</param>
    /// <param name="speed">移動速度0~1</param>
    /// <returns></returns>
    static bool RunAndJump(int moveDirection, float speed)
    {
        PlayerMoveController.jump = true;
        PlayerMoveController.move.x = moveDirection * speed;
        return true;
    }
}
PlayerController

39行目FunctionCalling呼び出しのためのプロパティ作成。

public Vector2 Move
{
    get => move;
    set => move = value;
}
public bool Jump
{
    get => jump;
    set => jump = value;
}

58行、67~70行目十字キーによる移動を行わない為、処理の削除。

58行目
- move.x = Input.GetAxis("Horizontal"); 

67~70行目
- else
-{
-    move.x = 0;
-d}

59行目Spaceボタンを押したときにJumpさせないで、FunctionCallingの結果でジャンプを行う。

-if (jumpState == JumpState.Grounded && Input.GetButtonDown("Jump"))
+if (jumpState == JumpState.Grounded && jump)

おわりに

今回は、Claudiaを使って自然言語で2Dプラットフォーマーを操作するためのチュートリアルを紹介しました。ClaudiaはC#でClaude3を利用する上で、非常に優れたOSSになっています。
想像力次第でなんでもできると思います。この機会にお試しください。
この記事が良いと思った方は、いいねとフォローをお願いします!

Discussion