物理の紹介

ゲーム開発では、多くの場合、ゲーム内の2つのオブジェクトが交差または接触するタイミングを知る必要があります。これは衝突検出として知られています。衝突が検出された場合、通常は何かが発生します。これは衝突応答として知られています。

Godotは、衝突の検出と応答の両方を提供するために、2Dおよび3Dで多数のコリジョン(衝突)オブジェクトを提供します。プロジェクトに使用するものを決定しようとすると、混乱する可能性があります。それぞれがどのように機能し、その長所と短所が何であるかを理解すれば、問題を回避して開発を簡素化できます。

このガイドでは、次の内容について学習します:

  • Godotの4つのコリジョンオブジェクトタイプ
  • 各コリジョンオブジェクトの仕組み
  • 別のタイプよりもあるタイプを選択するべきタイミングと理由

注釈

このドキュメントの例では、2Dオブジェクトを使用します。すべての2D物理オブジェクトとコリジョン形状は3Dでもまったく同等であり、ほとんどの場合、ほぼ同じように機能します。

オブジェクトのコリジョン

Godotは4種類の物理ボディを提供し、CollisionObject2D を拡張します:

  • Area2D
    Area2D ノードは 検出影響 を提供します。オブジェクトの重なり合いを検出し、物体が出入りするときにシグナルを発することができます。`` Area2D`` を使用して、定義された領域で重力や減衰などの物理特性をオーバーライドすることもできます。

他の3つのボディは PhysicsBody2D を拡張します:

  • StaticBody2D
    静的ボディとは、物理エンジンによって動かされないものです。コリジョン検出に参加しますが、衝突に応じて移動しません。これらは、環境の一部であるオブジェクトや動的な動作を必要としないオブジェクトに最もよく使用されます。
  • RigidBody2D
    これは、シミュレートされた2D物理を実装するノードです。RigidBody2D を直接制御するのではなく、それに力(重力、衝撃など)を適用し、物理エンジンが結果の動きを計算します。:ref:`リジッドボディの使用に関する詳細をお読みください。 <doc_rigid_body> `
  • KinematicBody2D
    コリジョン検出を提供しますが、物理は提供しません。すべての移動および衝突応答はコードで実装する必要があります。

コリジョン形状

物理ボディは、任意の数の Shape2D オブジェクトを子として保持できます。これらの形状は、オブジェクトのコリジョン境界を定義し、他のオブジェクトとの接触を検出するために使用されます。

注釈

コリジョンを検出するには、少なくとも1つの Shape2D をオブジェクトに割り当てる必要があります。

シェイプを割り当てる最も一般的な方法は、CollisionShape2D または CollisionPolygon2D をオブジェクトの子として追加することです。これらのノードを使用すると、エディタのワークスペースでシェイプを直接描画できます。

重要

エディタでコリジョン形状をスケーリングしないように注意してください。インスペクタの”Scale”プロパティは (1, 1) `` のままにしてください。コリジョン形状のサイズを変更するときは、\ ``Node2D スケールハンドルではなく、常にサイズハンドルを使用する必要があります。シェイプをスケーリングすると、予期しない衝突が発生する場合があります。

../../_images/player_coll_shape1.png

物理プロセスのコールバック

物理エンジンは、パフォーマンスを向上させるために複数のスレッドを生成する場合があるため、物理を処理するために最大でフルフレームを使用できます。このため、positionlinear velocity などのボディの状態変数の値は、現在のフレームに対して正確でない場合があります。

In order to avoid this inaccuracy, any code that needs to access a body's properties should be run in the Node._physics_process() callback, which is called before each physics step at a constant frame rate (60 times per second by default). This method will be passed a delta parameter, which is a floating-point number equal to the time passed in seconds since the last step. When using the default 60 Hz physics update rate, it will typically be equal to 0.01666... (but not always, see below).

注釈

It's recommended to always use the delta parameter when relevant in your physics calculations, so that the game behaves correctly if you change the physics update rate or if the player's device can't keep up.

コリジョンレイヤーとマスク

最も強力ですが、誤解されることが多い衝突機能の1つは、コリジョンレイヤーシステムです。このシステムにより、さまざまなオブジェクト間の複雑な相互作用を構築できます。重要な概念は レイヤーマスク です。各 CollisionObject2D には、相互作用できる20の異なる物理層があります。

各プロパティを順番に見てみましょう:

  • collision_layer
    これは、オブジェクトが **in ** で表示されるレイヤーを記述します。デフォルトでは、すべてのボディはレイヤー 1 上にあります。
  • collision_mask
    これは、ボディが衝突を スキャン するレイヤーを記述します。オブジェクトがマスクレイヤーのいずれかにない場合、ボディはそれを無視します。デフォルトでは、すべてのボディがレイヤー 1 をスキャンします。

これらのプロパティは、コードを介して、またはインスペクタで編集して構成できます。

各レイヤーの使用目的を追跡するのは難しい場合があるため、使用しているレイヤーに名前を付けると便利な場合があります。プロジェクト設定 → Layer Names で名前を割り当てることができます。

../../_images/physics_layer_names.png

GUI example

ゲームには、Walls、Player、Enemy、およびCoinの4つのノードタイプがあります。 PlayerとEnemyの両方がWallsと衝突するはずです。 PlayerノードはEnemyとCoinの両方との衝突を検出する必要がありますが、EnemyとCoinは互いに無視する必要があります。

レイヤー1〜4に"walls"、"player"、"enemies"、"coins"という名前を付けて開始し、"Layer" プロパティを使用して各ノードタイプをそれぞれのレイヤーに配置します。次に、相互作用するレイヤーを選択して、各ノードの"Mask"プロパティを設定します。たとえば、プレイヤーの設定は次のようになります:

../../_images/player_collision_layers.png ../../_images/player_collision_mask.png

Code example

In function calls, layers are specified as a bitmask. Where a function enables all layers by default, the layer mask will be given as 0x7fffffff. Your code can use binary, hexadecimal, or decimal notation for layer masks, depending on your preference.

The code equivalent of the above example where layers 1, 3 and 4 were enabled would be as follows:

# Example: Setting mask value for enabling layers 1, 3 and 4

# Binary - set the bit corresponding to the layers you want to enable (1, 3, and 4) to 1, set all other bits to 0.
# Note: Layer 20 is the first bit, layer 1 is the last. The mask for layers 4,3 and 1 is therefore
0b00000000000000001101
# (This can be shortened to 0b1101)

# Hexadecimal equivalent (1101 binary converted to hexadecimal)
0x000d
# (This value can be shortened to 0xd)

# Decimal - Add the results of 2 to the power of (layer be enabled-1).
# (2^(1-1)) + (2^(3-1)) + (2^(4-1)) = 1 + 4 + 8 = 13
pow(2, 1) + pow(2, 3) + pow(2, 4)

Area2D

エリア(Area)ノードは 検出 および 影響 を提供します。オブジェクトの重なり合いを検出し、物体が出入りするときにsシグナルを発します。エリアは、定義されたエリアで重力や減衰などの物理特性をオーバーライドするためにも使用できます。

Area2D には主に3つの用途があります:

  • 特定の領域での物理パラメータ(重力など)のオーバーライド。
  • 他のボディが領域に出入りするとき、または現在どのボディが領域にあるかを検出します。
  • 他の領域のオーバーラップを確認します。

デフォルトでは、エリアはマウスとタッチスクリーンの入力も受け取ります。

StaticBody2D

静的(Static)ボディとは、物理エンジンによって動かされないものです。コリジョン検出に参加しますが、衝突に応じて移動しません。ただし、constant_linear_velocity および constant_angular_velocity プロパティを使用して、衝突している物体に動きや回転をあたかも移動しているかのように伝えることができます。

StaticBody2D ノードは、環境の一部であるか、動的な動作を必要としないオブジェクトに最もよく使用されます。

StaticBody2D の使用例:

  • プラットフォーム(移動プラットフォームを含む)
  • コンベヤベルト
  • 壁やその他の障害物

RigidBody2D

これは、シミュレートされた2D物理を実装するノードです。RigidBody2D を直接制御することはできません。代わりに、それに力を適用し、物理エンジンは、他のボディとの衝突、跳ね返り、回転などの衝突応答を含む結果の動きを計算します。

インスペクタで設定できる"Mass(質量)"、"Friction(摩擦)"、"Bounce(反発)"などのプロパティを使用して、リジッド ボディの動作を変更できます。

ボディの動作は、プロジェクト設定 → Physics で設定された世界のプロパティ、またはグローバルな物理学プロパティをオーバーライドする Area2D を入力することによっても影響を受けます。

リジッドボディが静止していて、しばらく動かない場合、スリープ状態になります。眠っている体は静的な体のように動作し、その力は物理エンジンによって計算されません。衝突またはコードを介して力が加えられると、ボディが起動します。

リジッドボディのモード

リジッドボディは、次の4つのモードのいずれかに設定できます:

  • Rigid - ボディは物理的なオブジェクトとして動作します。それは他のボディと衝突し、それに加えられた力に反応します。これがデフォルトのモードです。
  • Static - ボディは StaticBody2D のように動作し、移動しません。
  • Character - "Rigid"モードに似ていますが、ボディは回転できません。
  • Kinematic - ボディは KinematicBody2D のように動作し、コードによって移動する必要があります。

RigidBody2Dを使用する

リジッドボディを使用する利点の1つは、コードを記述せずに多くの動作を「無料」で行えることです。たとえば、落下するブロックを持つ「Angry Birds」スタイルのゲームを作成している場合、RigidBody2Dを作成してプロパティを調整するだけで済みます。スタッキング、落下、および跳ね返りは、物理エンジンによって自動的に計算されます。

ただし、ボディを制御したい場合は注意が必要です - リジットボディの positionlinear_velocity、またはその他の物理プロパティを変更すると、予期しない動作が発生する可能性があります。物理関連のプロパティを変更する必要がある場合は、_ physics_process() の代わりに _integrate_forces() コールバックを使用する必要があります。このコールバックでは、ボディの Physics2DDirectBodyState にアクセスできます。これにより、プロパティを安全に変更し、物理エンジンと同期させることができます。

たとえば、"Asteroids(Atariのゲーム)"スタイルの宇宙船のコードは次のとおりです:

extends RigidBody2D

var thrust = Vector2(0, 250)
var torque = 20000

func _integrate_forces(state):
    if Input.is_action_pressed("ui_up"):
        applied_force = thrust.rotated(rotation)
    else:
        applied_force = Vector2()
    var rotation_dir = 0
    if Input.is_action_pressed("ui_right"):
        rotation_dir += 1
    if Input.is_action_pressed("ui_left"):
        rotation_dir -= 1
    applied_torque = rotation_dir * torque
class Spaceship : RigidBody2D
{
    private Vector2 _thrust = new Vector2(0, 250);
    private float _torque = 20000;

    public override void _IntegrateForces(Physics2DDirectBodyState state)
    {
        if (Input.IsActionPressed("ui_up"))
            SetAppliedForce(_thrust.Rotated(Rotation));
        else
            SetAppliedForce(new Vector2());

        var rotationDir = 0;
        if (Input.IsActionPressed("ui_right"))
            rotationDir += 1;
        if (Input.IsActionPressed("ui_left"))
            rotationDir -= 1;
        SetAppliedTorque(rotationDir * _torque);
    }
}

linear_velocity または angular_velocity プロパティを直接設定するのではなく、力(`` thrust`` および torque)をボディに適用し、物理エンジンに結果の動きを計算させることに注意してください。

注釈

リジッドボディがスリープ状態になると、_integrate_forces() 関数は呼び出されません。この振る舞いをオーバーライドするには、コリジョンを作成するか、それに力を加えるか、can_sleep プロパティを無効にすることで、ボディを起こしたままにする必要があります。これはパフォーマンスに悪影響を及ぼす可能性があることに注意してください。

接触のレポート

デフォルトでは、リジッドボディは接触を追跡しません。これは、シーンに多くのボディがある場合、膨大な量のメモリが必要になる可能性があるためです。接触のレポートを有効にするには、contacts_reported プロパティをゼロ以外の値に設定します。接触は、Physics2DDirectBodyState.get_contact_count() および関連する関数を介して取得できます。

シグナルによる接触の監視は、contact_monitor プロパティを使用して有効にできます。使用可能なシグナルのリストについては、RigidBody2D を参照してください。

KinematicBody2D

KinematicBody2D ボディは、他のボディとの衝突を検出しますが、重力や摩擦などの物理特性の影響を受けません。代わりに、ユーザーがコードを介して制御する必要があります。物理エンジンはキネマティックボディを移動しません。

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

運動学的衝突応答

衝突後、ボディをバウンスさせたり、壁に沿ってスライドさせたり、ヒットしたオブジェクトのプロパティを変更したりすることができます。コリジョンレスポンスの処理方法は、KinematicBody2Dの移動に使用した方法によって異なります。

move_and_collide

move_and_collide() を使用すると、関数は KinematicCollision2D オブジェクトを返します。このオブジェクトには、コリジョンと衝突するボディに関する情報が含まれます。この情報を使用して、応答を判別できます。

たとえば、衝突が発生した空間内のポイントを検索する場合:

extends KinematicBody2D

var velocity = Vector2(250, 250)

func _physics_process(delta):
    var collision_info = move_and_collide(velocity * delta)
    if collision_info:
        var collision_point = collision_info.position
class Body : KinematicBody2D
{
    private Vector2 _velocity = new Vector2(250, 250);

    public override void _PhysicsProcess(float delta)
    {
        var collisionInfo = MoveAndCollide(_velocity * delta);
        if (collisionInfo != null)
        {
            var collisionPoint = collisionInfo.GetPosition();
        }
    }
}

または、衝突するオブジェクトから跳ね返るには:

extends KinematicBody2D

var velocity = Vector2(250, 250)

func _physics_process(delta):
    var collision_info = move_and_collide(velocity * delta)
    if collision_info:
        velocity = velocity.bounce(collision_info.normal)
class Body : KinematicBody2D
{
    private Vector2 _velocity = new Vector2(250, 250);

    public override void _PhysicsProcess(float delta)
    {
        var collisionInfo = MoveAndCollide(_velocity * delta);
        if (collisionInfo != null)
            _velocity = _velocity.Bounce(collisionInfo.Normal);
    }
}

move_and_slide

スライディングは一般的な衝突応答です。トップダウンゲームで壁に沿って移動するプレイヤー、またはプラットフォーマーで坂を上下に走るプレイヤーを想像してください。move_and_collide() を使用した後、この応答を自分でコーディングすることは可能ですが、move_and_slide() は、多くのコードを書かずにスライド移動を実装する便利な方法を提供します。

警告

move_and_slide() は計算にタイムステップを自動的に含めるため、速度ベクトルに delta掛けないでください。

たとえば、次のコードを使用して、地面(坂を含む)に沿って歩き、地面に立っているときにジャンプできるキャラクターを作成します:

extends KinematicBody2D

var run_speed = 350
var jump_speed = -1000
var gravity = 2500

var velocity = Vector2()

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 is_on_floor() and jump:
        velocity.y = jump_speed
    if right:
        velocity.x += run_speed
    if left:
        velocity.x -= run_speed

func _physics_process(delta):
    velocity.y += gravity * delta
    get_input()
    velocity = move_and_slide(velocity, Vector2(0, -1))
class Body : KinematicBody2D
{
    private float _runSpeed = 350;
    private float _jumpSpeed = -1000;
    private float _gravity = 2500;

    private Vector2 _velocity = new Vector2();

    private void GetInput()
    {
        _velocity.x = 0;

        var right = Input.IsActionPressed("ui_right");
        var left = Input.IsActionPressed("ui_left");
        var jump = Input.IsActionPressed("ui_select");

        if (IsOnFloor() && jump)
            _velocity.y = _jumpSpeed;
        if (right)
            _velocity.x += _runSpeed;
        if (left)
            _velocity.x -= _runSpeed;
    }

    public override void _PhysicsProcess(float delta)
    {
        _velocity.y += _gravity * delta;
        GetInput();
        _velocity = MoveAndSlide(velocity, new Vector2(0,-1));
    }
}

move_and_slide() の使い方に関する詳細については、詳細なコードを含むデモプロジェクトを含んだ キネマティックキャラクター(2D) を参照してください。