キネマティックキャラクター(2D)

はじめに

はい、奇妙な名前に聞こえますね。「キネマティック・キャラクター」それは、何ですか?名前の理由は、物理エンジンが出てきたときに、それらが(主に衝突応答を扱ったため)「ダイナミクス」エンジンと呼ばれたからです。ダイナミクスエンジンを使ってキャラクターコントローラーを作ろうとする試みは数多く行われましたが、思ったほど簡単ではありませんでした。Godotには、(2d/platformerのデモで見ることができるように)色々と発見できるダイナミックキャラクターコントローラーの最高の実装の1つがありますが、それを使用するには、かなりのレベルのスキルと物理エンジンの理解(または試行錯誤の忍耐)が必要です。

Havokなどの一部の物理エンジンは、最適なオプションとしてダイナミックキャラクターコントローラーに誓いをたてているように見えますが、他の物理エンジン(PhysX)は、むしろキネマティックコントローラーを促進します。

だから、違いは何ですか?:

  • ダイナミックキャラクターコントローラーは、無限慣性テンソルを持つリジットボディを使用します。 回転できないリジットボディです。 物理エンジンは常にオブジェクトの移動と衝突を許可し、その後、すべて一緒に衝突を解決します。これにより、ダイナミックキャラクターコントローラーは、プラットフォーマーデモで見られるように、他の物理オブジェクトとシームレスに対話できます。 ただし、これらの相互作用は常に予測できるとは限りません。 衝突は解決するために複数のフレームを必要とする可能性があるため、いくつかの衝突はわずかにずれているように見える場合があります。 これらの問題は修正できますが、ある程度のスキルが必要です。
  • キネマティックキャラクターコントローラーは、常に非衝突状態で始まり、常に非衝突状態に移行すると想定されています。 衝突状態で開始した場合、リジットボディのように自身を解放しようとしますが、これは例外であり、ルールではありません。 これにより、制御と動作がより予測可能になり、プログラムが簡単になります。 ただし、欠点としては、コードで手動で行わない限り、他の物理オブジェクトと直接やり取りすることはできません。

この短いチュートリアルでは、キネマティックキャラクターコントローラーに焦点を当てます。 基本的に、衝突を処理する昔ながらの方法です(内部では必ずしも単純ではないが、よく隠されており、素晴らしくシンプルなAPIとして提示されています)。

Physics process

キネマティックボディまたはキャラクターのロジックを管理するには、常に物理プロセスを使用することをお勧めします。これは、物理ステップの前に呼び出され、その実行が物理サーバーと同期しているため、常に毎秒同じ回数とも呼ばれるためです。 これにより、通常のプロセスを使用するよりも物理学と動きの計算が予測可能な方法で機能します。フレームレートが高すぎたり低すぎたりするとスパイクが発生したり、精度が低下したりします。

extends KinematicBody2D

func _physics_process(delta):
    pass
using Godot;
using System;

public class PhysicsScript : KinematicBody2D
{
    public override void _PhysicsProcess(float delta)
    {
    }
}

シーンの設定

テスト用に、ここにシーンがあります(タイルマップチュートリアルから): kbscene.zip。 キャラクターの新しいシーンを作成します。 ロボットスプライトを使用して、次のようなシーンを作成します:

../../_images/kbscene.png

CollisionShape2Dノードの横に警告アイコンがあります。 それは、形状を定義していないためです。 CollisionShape2Dのshapeプロパティで新しいCircleShape2Dを作成します。 <CircleShape2D>をクリックしてオプションに移動し、半径を30に設定します:

../../_images/kbradius.png

注: 物理のチュートリアルで前述したように、物理エンジンはほとんどの種類の形状のスケールを処理できないため(コリジョンポリゴン、プレーン、セグメントのみが機能します)、シェイプのパラメータ(半径など)を拡大縮小するのではなく、常に値を変更してください。スケールが形状のスケールに影響するため、同じことがキネマティック/リジッド/スタティックボディ自体にも当てはまります。

ここで、キャラクターのスクリプトを作成します。上記の例として使用したスクリプトがベースとして機能するはずです。

最後に、タイルマップでそのキャラクターシーンをインスタンス化し、マップシーンをメインのシーンにします。再生を押すと実行されます。

../../_images/kbinstance.png

キネマティック キャラクタの移動

キャラクターシーンに戻り、スクリプトを開くと、魔法が始まります! キネマティックボディはデフォルトでは何もしませんが、KinematicBody2D.move_and_collide() <class_KinematicBody2D_method_move_and_collide>` という便利な関数があります。 この関数は、Vector2 を引数として取り、その運動を キネマティックボディに適用しようとします。 衝突が発生した場合、衝突の瞬間に停止します。

それでは、スプライトが床に当たるまで下に移動しましょう:

extends KinematicBody2D

func _physics_process(delta):
    move_and_collide(Vector2(0, 1)) # Move down 1 pixel per physics frame
using Godot;
using System;

public class PhysicsScript : KinematicBody2D
{
    public override void _PhysicsProcess(float delta)
    {
        // Move down 1 pixel per physics frame
        MoveAndCollide(new Vector2(0, 1));
    }
}

その結果、キャラクターは移動しますが、床に当たるとすぐに停止します。 かなりクールですね?

次のステップでは、ミックスに重力を追加します。これにより、通常のゲームキャラクターのように動作します:

extends KinematicBody2D

const GRAVITY = 200.0
var velocity = Vector2()

func _physics_process(delta):
    velocity.y += delta * GRAVITY

    var motion = velocity * delta
    move_and_collide(motion)
using Godot;
using System;

public class PhysicsScript : KinematicBody2D
{
    const float gravity = 200.0f;
    Vector2 velocity;

    public override void _PhysicsProcess(float delta)
    {
        velocity.y += delta * gravity;

        var motion = velocity * delta;
        MoveAndCollide(motion);
    }
}

これでキャラクターがスムーズに落ちます。 方向キーをタッチして、左右に歩いてみましょう。(少なくとも速度の場合)使用されている値はピクセル/秒であることに注意してください。

これにより、左右キーを押すことで簡単な歩行サポートが追加されます:

extends KinematicBody2D

const GRAVITY = 200.0
const WALK_SPEED = 200

var velocity = Vector2()

func _physics_process(delta):
    velocity.y += delta * GRAVITY

    if Input.is_action_pressed("ui_left"):
        velocity.x = -WALK_SPEED
    elif Input.is_action_pressed("ui_right"):
        velocity.x =  WALK_SPEED
    else:
        velocity.x = 0

    # We don't need to multiply velocity by delta because "move_and_slide" already takes delta time into account.

    # The second parameter of "move_and_slide" is the normal pointing up.
    # In the case of a 2D platformer, in Godot, upward is negative y, which translates to -1 as a normal.
    move_and_slide(velocity, Vector2(0, -1))
using Godot;
using System;

public class PhysicsScript : KinematicBody2D
{
    const float gravity = 200.0f;
    const int walkSpeed = 200;

    Vector2 velocity;

    public override void _PhysicsProcess(float delta)
    {
        velocity.y += delta * gravity;

        if (Input.IsActionPressed("ui_left"))
        {
            velocity.x = -walkSpeed;
        }
        else if (Input.IsActionPressed("ui_right"))
        {
            velocity.x = walkSpeed;
        }
        else
        {
            velocity.x = 0;
        }

        // We don't need to multiply velocity by delta because "MoveAndSlide" already takes delta time into account.

        // The second parameter of "MoveAndSlide" is the normal pointing up.
        // In the case of a 2D platformer, in Godot, upward is negative y, which translates to -1 as a normal.
        MoveAndSlide(velocity, new Vector2(0, -1));
    }
}

そして、それを試してみてください。

これはプラットフォーマーにとって良い出発点です。 より完全なデモは、エンジンとともに配布されるデモzip、または https://github.com/godotengine/godot-demo-projects/tree/master/2d/kinematic_character にあります。