キャラクタコントローラー

課題

Godot環境でリグ設定済みのアニメーション3Dキャラクターをインポートし、AnimationTreeを使ってそのアニメーションを設定したところです。次は移動機能を実装が必要です。キャラクターコントローラーが必要です。

解決策

このレシピでは、既にキャラクターモデルとアニメーションをインポートしており、アニメーションの遷移やブレンド処理用に AnimationTree が適切に設定されていることを前提に進めます。まだ準備ができていない場合は、アセットのインポート方法 および キャラクターアニメーションについて を参照してください。参考までに、ここでは セクション説明 でリンクされているアートパックを使用しています。

衝突の追加

インポートしたシーンのルートノードとして CharacterBody3D を選択しましたが、衝突形状が欠けているとエラーが出ています。まずはこれを修正します。以下の手順に従ってください。

  1. CollisionShape3D 子要素を追加します
  2. その [プロパティ]CapsuleShape3D を選択します

カプセルのサイズと位置を調整し、キャラクターの全身を覆うようにします。参考までに、使用した数値は以下の通りです。

alt alt

インポートしたリグは、足部分が「地面」に位置するように配置されています(つまり身体の中心座標に合わせた設定です)。この方法は後で便利になります。プレイヤーが中央に立つ場合、空中に浮いた状態ではなく、実際に地面に立っている状態で表示されるようになるからです。

Godot の 3D 操作に慣れている方なら、キャラクターが画面後方の +Z 方向に向けていることにもお気づきでしょう。 ノードを選択し、Y軸回転180 度に変更して修正してください。

入力操作

以下のキー操作を使用しています。forwardbackleftrightjump。お好みで任意のキー/ボタンに割り当ててください。

カメラ機能

プレイヤーを追従する3Dカメラの実装方法には様々なものがあります。この例では、カメラ用マウントとして SpringArm3D を採用します。

SpringArm3Dノードはレイキャストを実行した後、その子オブジェクトを衝突点に移動させることで動作します。これをカメラに応用すると、プレイヤーとカメラの間に障害物が一切入らない状態を実現でき、この長さを調整することでズーム機能を実装することもできます。

ルートノードの子として追加し、その後に Camera3D をその子要素として追加してください。

春季アームのプロパティで、スプリング長5マージン0.1位置(0, 2.5, 0) に設定します。

We don’t want the spring arm to collide with the player’s capsule shape, so in the root CharacterBody3D set the collision layer to 2. Since the spring arm is checking collision layer 1, that will prevent the camera hitting the player’s head.

衝突レイヤーの整理

プレイヤーオブジェクト、環境要素、敵キャラクターなど、さまざまなゲームオブジェクトに対する衝突レイヤーを、最終的には適切に管理することが必要です。

移動

これでプレイヤーにスクリプトを追加する準備が整いました。まずは必要な変数から始めてください。

extends CharacterBody3D
class_name Knight

@export var speed = 5.0
@export var acceleration = 4.0
@export var jump_speed = 8.0

var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
var jumping = false

その後で、アクセスに必要なノードへの参照を示します。

@onready var spring_arm = $SpringArm3D
@onready var model = $Rig
@onready var anim_tree = $AnimationTree
@onready var anim_state = $AnimationTree.get("parameters/playback")

ここではanim_treeリファレンスを使用して、アイドル/歩行/ランニングブレンドスペースのブレンド位置と、ジャンプトリガー条件を設定します。まずAnimationTreeを選択し、これらのプロパティがインスペクターに表示されることを確認してください。

alt alt

anim_state はアニメーション状態マシンへの参照で、これを使用して異なるアニメーション間の遷移を呼び出すことができます。設定方法については、キャラクターアニメーション レシピを参照してください。

移動処理は、プレイヤーの入力を取得してmove_and_slide()関数を呼び出すことで実現されます。

func _physics_process(delta):
    velocity.y += -gravity * delta
    get_move_input(delta)

    move_and_slide()

プレイヤーの入力は水平方向の動きにのみ適用すべきです(X軸Z軸)。重力が作用しているのはY軸であるためです。このため、一時的にvelocity.yをゼロに設定し、必要な入力を適用した後、処理終了後に元の値に戻します。

注: 入力ベクトルはカメラの回転に基づいて回転されます。つまり、キャラクターはカメラが向いている方向に前進します。

func get_move_input(delta):
    var vy = velocity.y
    velocity.y = 0
    var input = Input.get_vector("left", "right", "forward", "back")
    var dir = Vector3(input.x, 0, input.y).rotated(Vector3.UP, spring_arm.rotation.y)
    velocity = lerp(velocity, dir * speed, acceleration * delta)
    velocity.y = vy

その前に、これは機能をテストする絶好の機会です。地面に大きな StaticBody3D を使ったクイックテストシーンを作成するか、ダンジョンパックアセットを使用してシーン制作を開始してください。

前進/後退/左移動/右移動ができるようになります(まだアニメーションはありません)。

カメラ操作

それでは、カメラ機能を動作させます。マウス操作でカメラ制御ができるようにします。感度を調整できる変数を追加してください。

@export var mouse_sensitivity = 0.0015

その後、マウスの動きを検知し、それに応じてスプリングアームを回転させます。アームをX軸周りに回転させると上下に傾きます(マウスのy軸方向の動きが反映されます)。また、Y軸周りの回転は向きを変えます(マウスのx軸方向の動きが反映されます)。さらに、カメラの傾斜角度が過度にならないように制限を設けます。

func _unhandled_input(event):
    if event is InputEventMouseMotion:
        spring_arm.rotation.x -= event.relative.y * mouse_sensitivity
        spring_arm.rotation_degrees.x = clamp(spring_arm.rotation_degrees.x, -90.0, 30.0)
        spring_arm.rotation.y -= event.relative.x * mouse_sensitivity

実際に操作してみると、「前進」を押すとキャラクターがカメラの向き方向に移動することを確認できるはずです。

現在は、キャラクターを回転させて、移動方向を向くようにさせる必要があります。

回転速度用の変数を追加してください。これにより、新しい方位に瞬時にスナップするのを防ぎます。

@export var rotation_speed = 12.0

次に、_physics_process() 関数の move_and_slide() 呼び出し後に以下を追加してください。

    if velocity.length() > 1.0:
        model.rotation.y = lerp_angle(model.rotation.y, spring_arm.rotation.y, rotation_speed * delta)

lerp_angle() を使用することで、常に最短方向に新しい角度に回転できるようになります(例えば、359度から1度に回す場合のように、遠回りせずに済みます)。

IWR(アイドル/ウォーク/ラン) アニメーション

移動と回転が実装できたところで、次はアニメーションの選択に進みます。基本的な考え方は、キャラクターの水平速度(x/z軸方向の動き)を取得し、それを使って作成したIWRブレンドスペース内のブレンド位置を設定することです。

get_move_input() では、プレイヤーの移動速度を設定しています。その直後にブレンド位置を指定できます。

    velocity = lerp(velocity, dir * speed, acceleration * delta)
    var vl = velocity * model.transform.basis
    anim_tree.set("parameters/IWR/blend_position", Vector2(vl.x, -vl.z) / speed)

velocity はグローバル座標系ですが、キャラクターモデルが回転しているため、モデルの basis を使用して velocity をローカル座標系に変換する必要があります。変換後、3Dベクトルをブレンドスペースの2Dベクトルにマッピングし、speed で割ることで -1 から 1 の範囲の値を得ます。また、-z方向は前方ですが、+y方向はブレンドスペースにおける前進アニメーションを表すため、値が一致するよう符号を調整します。

注意: このパラメーターパスは、AnimationTreeインスペクターを確認することで取得できます。実際にスクリプトウィンドウにドラッグ&ドロップして入力することもできます。

攻撃方法

攻撃動作については、まず「attack」という入力アクションを追加してください。このコマンドは左マウスボタンに割り当てています。

AnimationTree には3つの異なる攻撃が存在するため、それらをリスト化します。

var attacks = [
    "1h_slice_diagonal",
    "1h_slice_horizontal",
    "1h_attack_chop"
]

それから、_unhandled_input() 関数内で、アクションが押されたときにリストからランダムなアニメーションを選択するようにします。

    if event.is_action_pressed("attack"):
        anim_state.travel(attacks.pick_random())

ジャンプ動作

ジャンプモーションは複数のアニメーションが連動するため、やや複雑です。参考までに、状態マシンの設定手順を以下に示します。

alt alt

まず、「ジャンプ開始」アニメーションに移行するために jumping = true を設定します。これにより状態機械での遷移がトリガーされます。

    if is_on_floor() and Input.is_action_just_pressed("jump"):
        velocity.y = jump_speed
        jumping = true
        anim_tree.set("parameters/conditions/grounded", false)
    anim_tree.set("parameters/conditions/jumping", jumping)

次に、地面に接地したタイミングを把握する必要があります。これにより、「Jump_Idle」アニメーションから移行可能になります。これを実現するためには、前フレームと比較することで接地状態を追跡する必要があります。上部に新しい変数を追加してください。

var last_floor = true

そして最初のif文の後に、このステートメントが続きます。

    # We just hit the floor after being in the air
    if is_on_floor() and not last_floor:
        jumping = false
        anim_tree.set("parameters/conditions/grounded", true)
    last_floor = is_on_floor()

最終的に、段差から飛び降りた際に「Jump_Idle」に直接移行する仕組みがあります。

    # We're in the air, but we didn't jump
    if not is_on_floor() and not jumping:
        anim_state.travel("Jump_Idle")
        anim_tree.set("parameters/conditions/grounded", false)

まとめ

機能する制御可能なキャラクターにチェイスカメラと複数のアニメーションを追加しました。次は何が必要でしょうか?

セクションの説明 を参照すると、3D作業のさらなる事例や、ダウンロード可能なGodotプロジェクトなどの例を確認できます。

関連動画