KinematicBody2Dの使用

はじめに

Godotは、衝突の検出と応答の両方を提供するいくつかのコリジョンオブジェクトを提供します。 プロジェクトに使用するものを決定しようとすると、混乱する可能性があります。 それぞれがどのように機能し、それぞれの長所と短所を理解していれば、問題を回避して開発を簡素化できます。 このチュートリアルでは、KinematicBody2D ノードを見て、その使用方法の例を示します。

注釈

このドキュメントは、あなたがGodotのさまざまな物理ボディに精通していることを前提としています。最初に 物理の紹介 をお読みください。

キネマティック(kinematic)ボディとは何ですか?

``KinematicBody2D``は、コードで制御されるボディを実装するためのものです。 キネマティックボディは、移動時に他のボディとの衝突を検出しますが、重力や摩擦などのエンジンの物理特性の影響を受けません。 これは、動作を作成するためにコードを記述する必要があることを意味しますが、それらがどのように動き、反応するかをより正確に制御できることも意味します。

ちなみに

KinematicBody2D は重力やその他の力の影響を受ける可能性がありますが、コードで動きを計算する必要があります。 物理エンジンは KinematicBody2D を移動しません。

動きと衝突

KinematicBody2D を移動する場合は、position プロパティを直接設定しないでください。代わりに、move_and_collide() または move_and_slide() メソッドを使用します。これらのメソッドは、特定のベクトルに沿ってボディを移動し、別のボディとの衝突が検出された場合は即座に停止します。KinematicBody2D が衝突した後、衝突応答 を手動でコーディングする必要があります。

警告

_physics_process() コールバックでのみキネマティックボディの動きを行う必要があります。

2つの移動方法の目的は異なります。このチュートリアルの後半では、それらの動作方法の例を参照します。

move_and_collide

このメソッドは1つのパラメーターを取ります: ボディの相対的な動きを示す Vector2。 通常、これは速度ベクトルにフレームタイムステップ(delta)を掛けたものです。 エンジンがこのベクトルに沿ったどこかで衝突を検出すると、ボディはすぐに動きを停止します。 この場合、メソッドは KinematicCollision2D オブジェクトを返します。

KinematicCollision2D は、衝突とコリジョンオブジェクトに関するデータを含むオブジェクトです。 このデータを使用して、衝突応答を計算できます。

move_and_slide

move_and_slide() メソッドは、一方のボディをもう一方のボディに沿ってスライドさせたいという一般的なケースで衝突応答を単純化することを目的としています。 たとえば、プラットフォーマーやトップダウンゲームで特に役立ちます。

ちなみに

move_and_slide()delta を使用してフレームベースの動きを自動的に計算します。 速度ベクトルを delta で乗算ぜずに ` move_and_slid()`` に渡します。

速度ベクトルに加えて、move_and_slide() は他の多くのパラメーターを取り、スライドの動作をカスタマイズできます:

  • floor_normal - デフォルト値: Vector2( 0, 0 )

    このパラメーターを使用すると、エンジンが床と見なすサーフェスを定義できます。 これを設定すると、is_on_floor()is_on_wall()、および is_on_ceiling() メソッドを使用して、ボディが接触している表面のタイプを検出できます。 デフォルト値は、すべてのサーフェスが壁と見なされることを意味します。

  • slope_stop_min_velocity - デフォルト値: 5

    このパラメーターは、斜面に立っているときに移動するために必要な最小速度です。 静止しているときに身体が滑り落ちるのを防ぎます。

  • max_bounces - デフォルト値: 4

    このパラメーターは、ボディが移動を停止するまでの衝突の最大数です。 設定が低すぎると、動きが完全に妨げられる場合があります。

  • floor_max_angle - デフォルト値: 0.785398 (ラジアンで、45 度に相当)

    このパラメータは、サーフェスが「床」と見なされなくなる前の最大角度です。

move_and_slide_with_snap

このメソッドは snap パラメータを追加することで move_and_slide() にいくつかの追加機能を追加します。 このベクトルが地面に接触している限り、身体は表面に付着したままになります。 これは、たとえばジャンプするときにスナップを無効にする必要があることを意味します。 これを行うには、snapVector2(0, 0) に設定するか、代わりに move_and_slide() を使用します。

使用する移動方法は?

新しいGodotユーザーからの一般的な質問は、「どの移動関数を使用するのかを、どのように決めればいいですか?」というものですが、 多くの場合、その答えは 「より簡単」な move_and_slide() を使用することですが、必ずしも全てがそうではありません。 判断をする一つの方法は、move_and_slide() は特殊なケースであり、move_and_collide()`` はより一般的であるということです。 たとえば、次の2つのコードスニペットは同じ衝突応答になります:

../../_images/k2d_compare.gif
# using move_and_collide
var collision = move_and_collide(velocity * delta)
if collision:
    velocity = velocity.slide(collision.normal)

# using move_and_slide
velocity = move_and_slide(velocity)
// using MoveAndCollide
var collision = MoveAndCollide(velocity * delta);
if (collision != null)
{
    velocity = velocity.Slide(collision.Normal);
}
// using MoveAndSlide
velocity = MoveAndSlide(velocity);

move_and_slide() で行うことはすべて move_and_collide() でもできますが、もう少しコードが必要になる場合があります。 ただし、以下の例で見るように、 move_and_slide() が希望する応答を提供しない場合があります。

上記の例では、move_and_slide()velocity 変数に戻す速度を割り当てます。 これは、キャラクターが環境と衝突すると、関数が速度を内部的に再計算してスローダウンを反映するためです。

たとえば、キャラクターが床に落ちた場合、重力の影響により垂直速度を蓄積することは望ましくありません。 代わりに、垂直速度をゼロにリセットする必要があります。

move_and_slide() は、キネマティックボディの速度をループ内で数回再計算し、スムーズなモーションを生成するために、キャラクターを移動し、デフォルトで最大5回衝突する場合があります。 プロセスの最後で、関数は velocity 変数に保存し、次のフレームで使用できるキャラクターの新しい速度を返します。

これらの例を実際に見るには、サンプルプロジェクトをダウンロードします: using_kinematic2d.zip

動きと壁

サンプルプロジェクトをダウンロードした場合、この例は"BasicMovement.tscn"にあります。

この例では、SpriteCollisionShape2D の2つの子を持つ KinematicBody2D を追加します。 Godot "icon.png"をSpriteのテクスチャとして使用します(ファイルシステムドックから SpriteTexture プロパティにドラッグします)。CollisionShape2DShape プロパティで、「新規 RectangleShape2D」を選択し、スプライト画像に収まるように長方形のサイズを変更します。

注釈

2D移動スキームの実装例については、2D移動の概要 を参照してください。

KinematicBody2Dにスクリプトをアタッチし、次のコードを追加します:

extends KinematicBody2D

var speed = 250
var velocity = Vector2()

func get_input():
    # Detect up/down/left/right keystate and only move when pressed.
    velocity = Vector2()
    if Input.is_action_pressed('ui_right'):
        velocity.x += 1
    if Input.is_action_pressed('ui_left'):
        velocity.x -= 1
    if Input.is_action_pressed('ui_down'):
        velocity.y += 1
    if Input.is_action_pressed('ui_up'):
        velocity.y -= 1
    velocity = velocity.normalized() * speed

func _physics_process(delta):
    get_input()
    move_and_collide(velocity * delta)
using Godot;
using System;

public class KBExample : KinematicBody2D
{
    public int Speed = 250;
    private Vector2 _velocity = new Vector2();

    public void GetInput()
    {
        // Detect up/down/left/right keystate and only move when pressed
        _velocity = new Vector2();

        if (Input.IsActionPressed("ui_right"))
            _velocity.x += 1;

        if (Input.IsActionPressed("ui_left"))
            _velocity.x -= 1;

        if (Input.IsActionPressed("ui_down"))
            _velocity.y += 1;

        if (Input.IsActionPressed("ui_up"))
            _velocity.y -= 1;
    }

    public override void _PhysicsProcess(float delta)
    {
        GetInput();
        MoveAndCollide(velocity * delta);
    }
}

このシーンを実行すると、move_and_collide() が期待どおりに動作し、ボディが速度ベクトルに沿って移動することがわかります。 障害物を追加するとどうなるか見てみましょう。 長方形の衝突形状を持つ StaticBody2D を追加します。 可視性を確保するには、SpriteまたはPolygon2Dを使用するか、「デバッグ」メニューから「コリジョン形状の表示」をオンにします。

シーンを再度実行し、障害物に移動してみてください。KinematicBody2D が障害物を貫通できないことがわかります。 ただし、障害物に斜めに移動してみてください。障害物が接着剤のように機能することがわかります。ボディが立ち往生しているように感じます。

これは、*collision response * がないために発生します。move_and_collide() は衝突が発生したときに体の動きを止めます。 衝突からの応答をコーディングする必要があります。

関数を move_and_slide(velocity) に変更して、もう一度実行してみてください。 速度計算から delta を削除していることに注意してください。

move_and_slide() は、コリジョンオブジェクトに沿ってボディをスライドさせるデフォルトの衝突応答を提供します。 これは非常に多くの種類のゲームに有用であり、必要な動作を得るために必要なものすべてである場合があります。

反発/反射

スライドする衝突応答は必要ない場合はどうしますか? この例(サンプルプロジェクトの"BounceandCollide.tscn")には、弾丸を発射するキャラクターがあり、弾丸が壁から跳ね返るようにする必要があります。

この例では、3つのシーンを使用しています。 メインシーンには、プレイヤーと壁が含まれています。 BulletとWallは別々のシーンであるため、インスタンス化できます。

プレイヤーはw および s キーで前後に制御されます。 照準はマウスポインターを使用します。 プレイヤーのコードは、move_and_slide() を使用しています:

extends KinematicBody2D

var Bullet = preload("res://Bullet.tscn")
var speed = 200
var velocity = Vector2()

func get_input():
    # Add these actions in Project Settings -> Input Map.
    velocity = Vector2()
    if Input.is_action_pressed('backward'):
        velocity = Vector2(-speed/3, 0).rotated(rotation)
    if Input.is_action_pressed('forward'):
        velocity = Vector2(speed, 0).rotated(rotation)
    if Input.is_action_just_pressed('mouse_click'):
        shoot()

func shoot():
    # "Muzzle" is a Position2D placed at the barrel of the gun.
    var b = Bullet.instance()
    b.start($Muzzle.global_position, rotation)
    get_parent().add_child(b)

func _physics_process(delta):
    get_input()
    var dir = get_global_mouse_position() - global_position
    # Don't move if too close to the mouse pointer.
    if dir.length() > 5:
        rotation = dir.angle()
        velocity = move_and_slide(velocity)
using Godot;
using System;

public class KBExample : KinematicBody2D
{
    private PackedScene _bullet = (PackedScene)GD.Load("res://Bullet.tscn");
    public int Speed = 200;
    private Vector2 _velocity = new Vector2();

    public void GetInput()
    {
        // add these actions in Project Settings -> Input Map
        _velocity = new Vector2();
        if (Input.IsActionPressed("backward"))
        {
            _velocity = new Vector2(-speed/3, 0).Rotated(Rotation);
        }
        if (Input.IsActionPressed("forward"))
        {
            _velocity = new Vector2(speed, 0).Rotated(Rotation);
        }
        if (Input.IsActionPressed("mouse_click"))
        {
            Shoot();
        }
    }

    public void Shoot()
    {
        // "Muzzle" is a Position2D placed at the barrel of the gun
        var b = (Bullet)_bullet.Instance();
        b.Start(GetNode<Node2D>("Muzzle").GlobalPosition, Rotation);
        GetParent().AddChild(b);
    }

    public override void _PhysicsProcess(float delta)
    {
        GetInput();
        var dir = GetGlobalMousePosition() - GlobalPosition;
        // Don't move if too close to the mouse pointer
        if (dir.Length() > 5)
        {
            Rotation = dir.Angle();
            _velocity = MoveAndSlide(_velocity);
        }
    }
}

そして、弾丸のコード:

extends KinematicBody2D

var speed = 750
var velocity = Vector2()

func start(pos, dir):
    rotation = dir
    position = pos
    velocity = Vector2(speed, 0).rotated(rotation)

func _physics_process(delta):
    var collision = move_and_collide(velocity * delta)
    if collision:
        velocity = velocity.bounce(collision.normal)
        if collision.collider.has_method("hit"):
            collision.collider.hit()

func _on_VisibilityNotifier2D_screen_exited():
    queue_free()
using Godot;
using System;

public class Bullet : KinematicBody2D
{
    public int Speed = 750;
    private Vector2 _velocity = new Vector2();

    public void Start(Vector2 pos, float dir)
    {
        Rotation = dir;
        Position = pos;
        _velocity = new Vector2(speed, 0).Rotated(Rotation);
    }

    public override void _PhysicsProcess(float delta)
    {
        var collision = MoveAndCollide(_velocity * delta);
        if (collision != null)
        {
            _velocity = _velocity.Bounce(collision.Normal);
            if (collision.Collider.HasMethod("Hit"))
            {
                collision.Collider.Call("Hit");
            }
        }
    }

    public void OnVisibilityNotifier2DScreenExited()
    {
        QueueFree();
    }
}

アクションは _physics_process() で発生します。move_and_collide() を使用した後、衝突が発生した場合、KinematicCollision2D オブジェクトが返されます(そうでない場合、戻り値は Nil です)。

返されたコリジョンがある場合、コリジョンの normal を使用してVector2.bounce() メソッドで弾丸の velocity を反映します。

衝突するオブジェクト(collider) に hit メソッドがある場合、それも呼び出します。 サンプルプロジェクトでは、これを示すために壁に点滅する色の効果を追加しました。

../../_images/k2d_bullet_bounce.gif

プラットフォームの動き

もう1つの一般的な例を試してみましょう。2Dプラットフォーマーです。move_and_slide() は、機能するキャラクターコントローラーを素早く起動して実行するのに理想的です。 サンプルプロジェクトをダウンロードした場合は、"Platformer.tscn"で見つけることができます。

この例では、レベルが StaticBody2D オブジェクトで構成されていると仮定します。 任意の形状とサイズを使用できます。 サンプルプロジェクトでは、Polygon2D を使用してプラットフォームの形状を作成しています。

プレイヤーの本体のコードは次のとおりです:

extends KinematicBody2D

export (int) var run_speed = 100
export (int) var jump_speed = -400
export (int) var gravity = 1200

var velocity = Vector2()
var jumping = false

func get_input():
    velocity.x = 0
    var right = Input.is_action_pressed('ui_right')
    var left = Input.is_action_pressed('ui_left')
    var jump = Input.is_action_just_pressed('ui_select')

    if jump and is_on_floor():
        jumping = true
        velocity.y = jump_speed
    if right:
        velocity.x += run_speed
    if left:
        velocity.x -= run_speed

func _physics_process(delta):
    get_input()
    velocity.y += gravity * delta
    if jumping and is_on_floor():
        jumping = false
    velocity = move_and_slide(velocity, Vector2(0, -1))
using Godot;
using System;

public class KBExample : KinematicBody2D
{
    [Export] public int RunSpeed = 100;
    [Export] public int JumpSpeed = -400;
    [Export] public int Gravity = 1200;

    Vector2 velocity = new Vector2();
    bool jumping = false;

    public void GetInput()
    {
        velocity.x = 0;
        bool right = Input.IsActionPressed("ui_right");
        bool left = Input.IsActionPressed("ui_left");
        bool jump = Input.IsActionPressed("ui_select");

        if (jump && IsOnFloor())
        {
            jumping = true;
            velocity.y = JumpSpeed;
        }

        if (right)
            velocity.x += RunSpeed;
        if (left)
            velocity.x -= RunSpeed;
    }

    public override void _PhysicsProcess(float delta)
    {
        GetInput();
        velocity.y += Gravity * delta;
        if (jumping && IsOnFloor())
            jumping = false;
        velocity = MoveAndSlide(velocity, new Vector2(0, -1));
    }
}
../../_images/k2d_platform.gif

move_and_slide() を使用すると、この関数は、スライド衝突が発生した後に残った動きを表すベクトルを返します。 その値をキャラクターの velocity に戻すことで、斜面をスムーズに上下できます。``velocity = `` を削除して、これを行わないとどうなるかを確認してください。

また、フロアの法線として Vector2(0, -1) を追加したことに注意してください。 このベクトルはまっすぐ上を指します。 その結果、キャラクターがこの法線を持つオブジェクトと衝突した場合、そのオブジェクトは床と見なされます。

フロア法線を使用すると、is_on_floor() を使用してジャンプ作業を行うことができます。 この関数は、衝突する物体の法線が与えられた床ベクトルの45度以内にある move_and_slide() 衝突後にのみ true を返します。floor_max_angle を設定することで最大角度を制御できます。

この角度により、たとえば is_on_wall() を使用して壁ジャンプなどの他の機能を実装することもできます。