mcpField
P
player-controller

player-controller

github.com/jame581開発889171 回インストール4 日前に更新
リポジトリ

player-controller について

Godot 4.3以降でプレイヤー移動コントローラーを作るためのスキルです。GDScriptとC#の例で、見下ろし型、プラットフォーマー、FPS操作に加え、ダッシュや壁ジャンプの実装を扱います。

主な機能

  1. 012D見下ろし型コントローラー
  2. 022Dプラットフォーマー用コントローラー
  3. 033D一人称コントローラー
  4. 04物理フレームでの移動処理
  5. 05ダッシュと壁ジャンプのレシピ

活用シーン

  1. 01反応のよい見下ろし型移動の構築
  2. 02プラットフォーマーのジャンプと移動の実装
  3. 033D一人称コントローラーの作成
  4. 04ダッシュや壁ジャンプの追加

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jame581/godotprompter --skill player-controller

インストール後、Claude Code で/player-controllerを入力して読み込まれたか確認します。

同梱ファイルreferences/common-movement-recipes.md

詳細

Godot 4.3+ のプレイヤーコントローラー

すべての例は、非推奨の API を使用せずに Godot 4.3+ を対象としています。まず GDScript、次に C# を示します。

関連スキル: RigidBody、Area、レイキャスト、コリジョンシェイプについては physics-system、TileMaps、パララックス、2D ライティングについては 2d-essentials、CharacterBody3D と 3D 移動の設定については 3d-essentials、移動状態の管理については state-machine、カメラの追従とシェイクについては camera-system、ヒットボックス/ハートボックスの統合については component-system、移動状態に応じたアニメーションについては animation-system、InputMap アクションとコントローラー対応については input-handling、敵の移動と経路探索については ai-navigation。


1. 基本概念

CharacterBody と RigidBody
ボディタイプ用途物理制御備考
CharacterBody2D/3Dプレイヤー、敵、NPC手動(完全)速度を制御します。move_and_slide() が衝突を処理します
RigidBody2D/3D飛び道具、オブジェクト、破片エンジン駆動物理エンジンが力を適用します。精密な制御は困難です
RigidBody2D/3D跳ね返る飛び道具エンジン駆動linear_velocity を一度設定し、跳ね返りの処理は物理エンジンに任せます
CharacterBody2D/3Dプラットフォーマー、見下ろし型、FPS手動(完全)安定して予測可能で、応答性の高いゲームフィールに最適です

基本ルール: きびきびとした応答性の高い操作が必要な場合は CharacterBody を使います。リアルな物理シミュレーションを求める場合は RigidBody を使います。

移動ループ

各物理フレームは、次の順序で処理されます。

1. Read input          → get axis/action values
2. Apply forces        → gravity, friction, acceleration
3. Modify velocity     → move_toward, lerp, clamp
4. move_and_slide()    → engine resolves collisions, updates position
5. Post-movement state → check is_on_floor(), is_on_wall(), landing events

このループは必ず _physics_process(delta) に記述し、_process(delta) には記述しないでください。


2. 2D 見下ろし型コントローラー

GDScript
extends CharacterBody2D

@export var speed: float = 200.0
@export var acceleration: float = 1500.0
@export var friction: float = 1200.0

func _physics_process(delta: float) -> void:
    # 1. Read input (analog vector, length capped at 1)
    var input_dir: Vector2 = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")

    # 2 & 3. Apply acceleration or friction to velocity
    if input_dir != Vector2.ZERO:
        velocity = velocity.move_toward(input_dir * speed, acceleration * delta)
    else:
        velocity = velocity.move_toward(Vector2.ZERO, friction * delta)

    # 4. Move and resolve collisions
    move_and_slide()
C#
using Godot;

public partial class TopDownPlayer : CharacterBody2D
{
    [Export] public float Speed { get; set; } = 200.0f;
    [Export] public float Acceleration { get; set; } = 1500.0f;
    [Export] public float Friction { get; set; } = 1200.0f;

    public override void _PhysicsProcess(double delta)
    {
        // 1. Read input (analog vector, length capped at 1)
        Vector2 inputDir = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down");

        // 2 & 3. Apply acceleration or friction
        if (inputDir != Vector2.Zero)
            Velocity = Velocity.MoveToward(inputDir * Speed, Acceleration * (float)delta);
        else
            Velocity = Velocity.MoveToward(Vector2.Zero, Friction * (float)delta);

        // 4. Move and resolve collisions
        MoveAndSlide();
    }
}

3. 2D プラットフォーマーコントローラー

GDScript
extends CharacterBody2D

@export var speed: float = 200.0
@export var jump_velocity: float = -400.0
@export var acceleration: float = 1200.0
@export var deceleration: float = 900.0

# Coyote time and jump buffer
@export var coyote_time: float = 0.12
@export var jump_buffer_time: float = 0.12

var _gravity: float = ProjectSettings.get_setting("physics/2d/default_gravity")
var _coyote_timer: float = 0.0
var _jump_buffer_timer: float = 0.0
var _was_on_floor: bool = false

func _physics_process(delta: float) -> void:
    # Coyote time: allow jump briefly after walking off a ledge
    if is_on_floor():
        _coyote_timer = coyote_time
        _was_on_floor = true
    else:
        _coyote_timer -= delta

    # Jump buffer: register jump input before landing
    if Input.is_action_just_pressed("ui_accept"):
        _jump_buffer_timer = jump_buffer_time
    else:
        _jump_buffer_timer -= delta

    # Apply gravity when airborne
    if not is_on_floor():
        velocity.y += _gravity * delta

    # Jump: consume coyote time and buffer together
    var can_jump: bool = _coyote_timer > 0.0
    if _jump_buffer_timer > 0.0 and can_jump:
        velocity.y = jump_velocity
        _coyote_timer = 0.0
        _jump_buffer_timer = 0.0

    # Variable jump height: cut velocity when button released early
    if Input.is_action_just_released("ui_accept") and velocity.y < 0.0:
        velocity.y *= 0.5

    # Horizontal movement with deceleration
    var input_x: float = Input.get_axis("ui_left", "ui_right")
    if input_x != 0.0:
        velocity.x = move_toward(velocity.x, input_x * speed, acceleration * delta)
    else:
        velocity.x = move_toward(velocity.x, 0.0, deceleration * delta)

    move_and_slide()
C#
using Godot;

public partial class PlatformerPlayer : CharacterBody2D
{
    [Export] public float Speed { get; set; } = 200.0f;
    [Export] public float JumpVelocity { get; set; } = -400.0f;
    [Export] public float Acceleration { get; set; } = 1200.0f;
    [Export] public float Deceleration { get; set; } = 900.0f;
    [Export] public float CoyoteTime { get; set; } = 0.12f;
    [Export] public float JumpBufferTime { get; set; } = 0.12f;

    private float _gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();
    private float _coyoteTimer;
    private float _jumpBufferTimer;

    public override void _PhysicsProcess(double delta)
    {
        float dt = (float)delta;

        // Coyote time
        if (IsOnFloor())
            _coyoteTimer = CoyoteTime;
        else
            _coyoteTimer -= dt;

        // Jump buffer
        if (Input.IsActionJustPressed("ui_accept"))
            _jumpBufferTimer = JumpBufferTime;
        else
            _jumpBufferTimer -= dt;

        // Gravity
        if (!IsOnFloor())
        {
            Vector2 vel = Velocity;
            vel.Y += _gravity * dt;
            Velocity = vel;
        }

        // Jump
        if (_jumpBufferTimer > 0f && _coyoteTimer > 0f)
        {
            Vector2 vel = Velocity;
            vel.Y = JumpVelocity;
            Velocity = vel;
            _coyoteTimer = 0f;
            _jumpBufferTimer = 0f;
        }

        // Variable jump height
        if (Input.IsActionJustReleased("ui_accept") && Velocity.Y < 0f)
        {
            Vector2 vel = Velocity;
            vel.Y *= 0.5f;
            Velocity = vel;
        }

        // Horizontal movement
        float inputX = Input.GetAxis("ui_left", "ui_right");
        Vector2 velocity = Velocity;
        if (inputX != 0f)
            velocity.X = Mathf.MoveToward(velocity.X, inputX * Speed, Acceleration * dt);
        else
            velocity.X = Mathf.MoveToward(velocity.X, 0f, Deceleration * dt);

        Velocity = velocity;
        MoveAndSlide();
    }
}

4. 3D 一人称視点コントローラー

GDScript
extends CharacterBody3D

@export var move_speed: float = 5.0
@export var jump_velocity: float = 5.0
@export var mouse_sensitivity: float = 0.002

@onready var head: Node3D = $Head  # Child Node3D that holds Camera3D

var _gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")

func _ready() -> void:
    Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

func _unhandled_input(event: InputEvent) -> void:
    if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
        # Horizontal look: rotate the body (yaw)
        rotate_y(-event.relative.x * mouse_sensitivity)
        # Vertical look: rotate the head (pitch), clamped to ±90°
        head.rotate_x(-event.relative.y * mouse_sensitivity)
        head.rotation.x = clamp(head.rotation.x, -PI / 2.0, PI / 2.0)

    if event.is_action_pressed("ui_cancel"):
        Input.mouse_mode = Input.MOUSE_MODE_VISIBLE

func _physics_process(delta: float) -> void:
    # Gravity
    if not is_on_floor():
        velocity.y -= _gravity * delta

    # Jump
    if Input.is_action_just_pressed("ui_accept") and is_on_floor():
        velocity.y = jump_velocity

    # Movement relative to the direction the player is facing
    var input_dir: Vector2 = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
    var direction: Vector3 = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()

    if direction != Vector3.ZERO:
        velocity.x = direction.x * move_speed
        velocity.z = direction.z * move_speed
    else:
        velocity.x = move_toward(velocity.x, 0.0, move_speed)
        velocity.z = move_toward(velocity.z, 0.0, move_speed)

    move_and_slide()
C#
using Godot;

public partial class FPSController : CharacterBody3D
{
    [Export] public float MoveSpeed { get; set; } = 5.0f;
    [Export] public float JumpVelocity { get; set; } = 5.0f;
    [Export] public float MouseSensitivity { get; set; } = 0.002f;

    private float _gravity = ProjectSettings.GetSetting("physics/3d/default_gravity").AsSingle();
    private Node3D _head;

    public override void _Ready()
    {
        _head = GetNode<Node3D>("Head");
        Input.MouseMode = Input.MouseModeEnum.Captured;
    }

    public override void _UnhandledInput(InputEvent @event)
    {
        if (@event is InputEventMouseMotion motion
            && Input.MouseMode == Input.MouseModeEnum.Captured)
        {
            // Horizontal look (yaw on body)
            RotateY(-motion.Relative.X * MouseSensitivity);
            // Vertical look (pitch on head), clamped to ±90°
            _head.RotateX(-motion.Relative.Y * MouseSensitivity);
            Vector3 rot = _head.Rotation;
            rot.X = Mathf.Clamp(rot.X, -Mathf.Pi / 2f, Mathf.Pi / 2f);
            _head.Rotation = rot;
        }

        if (@event.IsActionPressed("ui_cancel"))
            Input.MouseMode = Input.MouseModeEnum.Visible;
    }

    public override void _PhysicsProcess(double delta)
    {
        float dt = (float)delta;
        Vector3 vel = Velocity;

        // Gravity
        if (!IsOnFloor())
            vel.Y -= _gravity * dt;

        // Jump
        if (Input.IsActionJustPressed("ui_accept") && IsOnFloor())
            vel.Y = JumpVelocity;

        // Movement relative to facing direction
        Vector2 inputDir = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down");
        Vector3 direction = (Transform.Basis * new Vector3(inputDir.X, 0, inputDir.Y)).Normalized();

        if (direction != Vector3.Zero)
        {
            vel.X = direction.X * MoveSpeed;
            vel.Z = direction.Z * MoveSpeed;
        }
        else
        {
            vel.X = Mathf.MoveToward(vel.X, 0f, MoveSpeed);
            vel.Z = Mathf.MoveToward(vel.Z, 0f, MoveSpeed);
        }

        Velocity = vel;
        MoveAndSlide();
    }
}

5. よく使う移動レシピ

上記の基本的な移動パターンに加えて、頻繁に使われる次の 2 つのレシピは、それぞれ独立した項目として扱う価値があります。ダッシュ(クールダウンがあり、空中にいる間は 1 回だけ使える、タイマー式の短時間の速度上書き)と、ウォールジャンプ(垂直な壁を滑り、ジャンプボタンが押されたときに GetWallNormal() を使って壁から跳ね返る動作)です。どちらも CharacterBody2D に適用でき、重力や水平方向の移動とともに標準の _physics_process ループに組み込めます。

両方のレシピを実装した GDScript と C# の全コードについては、references/common-movement-recipes.md を参照してください。


6. よくある問題

症状原因修正
プレイヤーが急な壁を登るfloor_block_on_wall がオフになっているtrue(デフォルト)のままにします。床から壁を登る移動を防ぎ、ボディが壁を滑り降りるようにします
移動が不安定、またはフレームレートに依存する_process 内で移動処理をしている物理処理と速度に関するコードをすべて _physics_process(delta) に移します
ジャンプの高さを変えられないジャンプ時に固定の速度を設定している可変ジャンプを使います(ボタンを離したときに velocity.y を下げます)
プレイヤーが斜面を滑り降りるfloor_stop_on_slope がオフになっている、または斜面が floor_max_angle より急になっているfloor_stop_on_slope = true(デフォルト)のままにし、floor_max_angle を調整します。下り坂でボディを斜面上に保つには floor_snap_length を使います
マウス視点操作が反転している回転の差分の符号が間違っているヨーには event.relative.x、ピッチには event.relative.y の符号を反転させます

7. 実装チェックリスト

  • すべての移動ロジックが _process ではなく _physics_process(delta) 内にある
  • 入力アクション名が Project > Project Settings > Input Map で定義された名前と完全に一致している
  • 重力の値をハードコードせず、ProjectSettings(physics/2d/default_gravity または physics/3d/default_gravity)から読み取っている
  • 毎フレーム、すべての速度変更の後に move_and_slide() を呼び出している
  • プラットフォーマーでは、操作の応答性を高めるためにコヨーテタイムとジャンプバッファリングを実装している
  • FPS コントローラーでは、_ready() でマウスをキャプチャし、エスケープキーで解放している
  • ジャンプボタンを早く離したときに速度を減少させ、可変ジャンプの高さに対応している

コメント