レイキャスティング

はじめに

ゲーム開発で最も一般的なタスクの1つは、レイ(またはカスタム形状のオブジェクト)をキャストし、そのヒットをチェックすることです。 これにより、複雑な動作、AIなどを実行できます。 このチュートリアルでは、2Dおよび3Dでこれを行う方法について説明します。

Godotはすべての低レベルのゲーム情報をサーバーに保存しますが、シーンは単なるフロントエンドです。 そのため、レイキャスティングは一般に低レベルのタスクです。 単純なレイキャストの場合、RayCastRayCast2D などのノードが機能します。これは、レイキャストの結果がフレームごとに返されるためです。

多くの場合、レイキャストはよりインタラクティブなプロセスである必要があるため、コードでこれを行う方法が必要です。

Space

物理世界では、Godotはすべての低レベルのコリジョンと物理の情報をspaceに保存します。CanvasItem.get_world_2d().space にアクセスすると、現在の2D space(2D物理用)を取得できます。 3Dの場合、Spatial.get_world().space です。

結果として得られるspace RID は、3Dおよび2Dの場合、それぞれ PhysicsServer および Physics2DServer で使用できます。

spaceへのアクセス

Godotの物理は、ゲームロジックと同じスレッドでデフォルトで実行されますが、より効率的に動作するために別のスレッドで実行するように設定できます。 このため、spaceへのアクセスが安全なのは Node._physics_process() コールバック中のみです。 この関数の外部からアクセスすると、spaceがロックされているためエラーが発生する場合があります。

物理spaceへのクエリを実行するには、Physics2DDirectSpaceState および PhysicsDirectSpaceState を使用する必要があります。

2Dでは、次のコードを使用します:

func _physics_process(delta):
    var space_rid = get_world_2d().space
    var space_state = Physics2DServer.space_get_direct_state(space_rid)
public override void _PhysicsProcess(float delta)
{
    var spaceRid = GetWorld2d().Space;
    var spaceState = Physics2DServer.SpaceGetDirectState(spaceRid);
}

またはより直接的に:

func _physics_process(delta):
    var space_state = get_world_2d().direct_space_state
public override void _PhysicsProcess(float delta)
{
    var spaceState = GetWorld2d().DirectSpaceState;
}

そして、3Dで:

func _physics_process(delta):
    var space_state = get_world().direct_space_state
public override void _PhysicsProcess(float delta)
{
    var spaceState = GetWorld().DirectSpaceState;
}

レイキャストクエリ

2Dレイキャストクエリを実行するには、メソッド Physics2DDirectSpaceState.intersect_ray() を使用できます。 例えば:

func _physics_process(delta):
    var space_state = get_world_2d().direct_space_state
    # use global coordinates, not local to node
    var result = space_state.intersect_ray(Vector2(0, 0), Vector2(50, 100))
public override void _PhysicsProcess(float delta)
{
    var spaceState = GetWorld2d().DirectSpaceState;
    // use global coordinates, not local to node
    var result = spaceState.IntersectRay(new Vector2(), new Vector2(50, 100));
}

結果はdictionaryです。 光線が何もヒットしなかった場合、dictionaryは空になります。 何かにヒットした場合、衝突情報が含まれます:

if result:
    print("Hit at point: ", result.position)
if (result.Count > 0)
    GD.Print("Hit at point: ", result["position"]);

衝突が発生した場合の result というdictionaryには、次のデータが含まれます:

{
   position: Vector2 # point in world space for collision
   normal: Vector2 # normal in world space for collision
   collider: Object # Object collided or null (if unassociated)
   collider_id: ObjectID # Object it collided against
   rid: RID # RID it collided against
   shape: int # shape index of collider
   metadata: Variant() # metadata of collider
}

データは、Vector3座標を使用した3D空間の物に類似しています。

コリジョンの例外

レイキャスティングの一般的な使用例は、キャラクターが周囲の世界に関するデータを収集できるようにすることです。 これに関する1つの問題は、同じキャラクターにコライダーがあるため、次の画像に示すように、レイは親のコライダーのみを検出することです:

../../_images/raycast_falsepositive.png

自己交差を避けるために、intersect_ray() 関数は例外の配列であるオプションの3番目のパラメーターを取ることができます。 これは、KinematicBody2Dまたはその他のコリジョンオブジェクトノードから使用する方法の例です:

extends KinematicBody2D

func _physics_process(delta):
    var space_state = get_world_2d().direct_space_state
    var result = space_state.intersect_ray(global_position, enemy_position, [self])
class Body : KinematicBody2D
{
    public override void _PhysicsProcess(float delta)
    {
        var spaceState = GetWorld2d().DirectSpaceState;
        var result = spaceState.IntersectRay(globalPosition, enemyPosition, new object[] { this });
    }
}

例外配列には、オブジェクトまたは RID を含めることができます。

コリジョンマスク

例外メソッドは親ボディを除外するためにうまく機能しますが、例外の大きなリストや動的リストが必要な場合は非常に不便になります。 この場合、コリジョンレイヤー/マスクシステムを使用する方がはるかに効率的です。

intersect_ray() のオプションの4番目の引数はコリジョンマスクです。 たとえば、親ボディと同じマスクを使用するには、collision_mask メンバー変数を使用します:

extends KinematicBody2D

func _physics_process(delta):
    var space_state = get_world().direct_space_state
    var result = space_state.intersect_ray(global_position, enemy_position,
                            [self], collision_mask)
class Body : KinematicBody2D
{
    public override void _PhysicsProcess(float delta)
    {
        var spaceState = GetWorld2d().DirectSpaceState;
        var result = spaceState.IntersectRay(globalPosition, enemyPosition,
                        new object[] { this }, CollisionMask);
    }
}

See Code example for details on how to set the collision mask.

画面からの3Dレイキャスティング

画面から3D物理空間への光線の投射は、オブジェクトの選択に役立ちます。CollisionObject には"input_event"シグナルがあり、クリックされたときに通知されるため、これを行う必要はあまりありませんが、手動で行う場合は次のようにします。

画面から光線を投影するには、Camera ノードが必要です。 Camera には、遠近法(perspective )と直交法(orthogonal)の2つの投影モードがあります。 このため、レイの原点と方向の両方を取得する必要があります。 これは、orgin が直交法モードで変化し、bormal が遠近法モードで変化するためです:

../../_images/raycast_projection.png

カメラを使用して取得するには、次のコードを使用できます:

const ray_length = 1000

func _input(event):
    if event is InputEventMouseButton and event.pressed and event.button_index == 1:
          var camera = $Camera
          var from = camera.project_ray_origin(event.position)
          var to = from + camera.project_ray_normal(event.position) * ray_length
private const float rayLength = 1000;

public override void _Input(InputEvent @event)
{
    if (@event is InputEventMouseButton eventMouseButton && eventMouseButton.Pressed && eventMouseButton.ButtonIndex == 1)
    {
        var camera = (Camera)GetNode("Camera");
        var from = camera.ProjectRayOrigin(eventMouseButton.Position);
        var to = from + camera.ProjectRayNormal(eventMouseButton.Position) * rayLength;
    }
}

_input() 中はspaceがロックされる可能性があるため、実際にはこのクエリは _physics_process() で実行する必要があることに注意してください。