【Godot】RayCast2Dの基本的な使い方

この記事では RayCast2D の基本的な使い方を説明ます。

Raycast2Dの使い方

Raycast2Dとは

RayCast2Dとは、指定の方向に光線(Ray)を発射し、その直線上に存在するオブジェクトを検知するノードとなります。

これを使用すると、キャラクターが向いている方向にオブジェクトが存在するかどうかをチェックできるようになるため、NPCに話しかけられるかどうか判定、銃やレーザーなど直線距離の長い攻撃の判定、などが実現できます。

今回は、プレイヤーの正面に存在する NPCと会話するようなサンプルを作成します。

素材データ

今回使用する素材データです。

素材の内容は、Raycast2Dを可視化するための矢印データです。

ray.png
ray256.png

この2つのファイルをプロジェクトに追加しておきます。

プレイヤーの作成

まずはプレイヤーを作成します。

「+その他のノード」を選び「KinematicBody2D」ノードを作成します。

ノードの名前は「Player」に変更しておきます。

そしてプレイヤー画像として「icon.png」をSpriteとして登録しておきます。

さらに「CollisionShape2D」ノードを作成します。

インスペクターから「CollisionShape2D > Shape > [空]」をクリックして「新規 RectangleShape2D」を選びます。

作成された「RectangleShape2D」をクリックするとサイズ設定が表示されるので、「Extents の (x, y) を (32, 32)」に設定すると画像サイズにピッタリおさまるコリジョンサイズとなります。

Raycast2Dノードの作成

次に Raycast2Dを制御する親ノードとして「Node2D」を作成します。

名前は「Point」にリネームしておきます。

そしてこの「Point」ノードの子として「Raycast2D」ノードを作成します。

そしてインスペクターから「Raycast2D > Enabled」にチェックを入れ、「Cast Toの(x, y) を (64, 0)」に設定します。

これにより Raycast2D の色が水色(有効)になり、0度方向に 64px で設定されます。

それとこのRaycast2Dの表示は実行時には表示されないので、”ray.png” も Pointノードの下にぶら下げておきます。

ただ、このままだと回転の原点が中央なので Sprite の調整が必要となります。

Rayスプライトのインスペクターから「Offset > Offset > (x, y) を (32, 0)」に設定し、「Transform > (x, y) を (0, 0)」にリセットします。

すると「Point」ノードを選択した状態にしておくと…

インスペクターの「Transform > Rotation Degrees」から回転できるようになります。

なお少し細かいことですが、Raycast2Dで仮表示されている矢印の先端の▶部分は長さには含まれていないようです。

Playerスクリプトの作成

Playerノードを選択して以下のスクリプトをアタッチします。

extends KinematicBody2D

# 移動速度
const SPEED = 20000

# レイ
onready var ray = $Point/RayCast2D
# レイの親ノード
onready var ray_root = $Point

func _physics_process(delta: float) -> void:
	
	# 移動速度を計算する
	var vel = Vector2()
	vel.x = int(Input.is_action_pressed("ui_right")) - int(Input.is_action_pressed("ui_left"))
	vel.y = int(Input.is_action_pressed("ui_down")) - int(Input.is_action_pressed("ui_up"))

	vel = vel.normalized() * SPEED * delta
	
	# 移動実行
	move_and_slide(vel)
	
	if vel.length() > 0:
		# キーを入力した場合のみRayを回転する
		ray_root.rotation = vel.angle()
	
	if ray.is_colliding():
		# Rayに衝突しているオブジェクトが存在する
		# 衝突しているオブジェクトを取得する
		var col = ray.get_collider()
		if col.has_method("_on_interact"):
			# インタラクトメッセージがあれば呼び出す
			col._on_interact()

KinematicBody2D なので、”_physics_process()” で移動処理を行います。

レイの回転は親ノードの「Point」を使って回転します。ただ今回はわかりやすくするために階層化しただけなので、レイを直接回転させても問題ありません。

Raycast2D は “is_colliding()” で衝突しているかどうかをチェックできます。そして “get_collider()” で衝突しているオブジェクトを取得します。

まだ未実装なのですが、NPCには会話イベントを発生させるための関数 “_on_intaract()” を実装します。その関数が実装されているオブジェクトであれば、”_on_interact()” を呼び出す処理となっています。

NPCの作成

Raycast2Dにより検知する NPCオブジェクトを作成します。

新規にシーンを作成し、ノードの型は「KinematicBody2D」で作成します。

ノードの名前は 「Npc」 にリネームしておきます。

“icon.png” をSpriteとして登録します。

ただこのままだと Playerオブジェクトとの違いがわかりにくいので、インスペクターの「Visibility > Modulate」から頂点カラーを変更しておきます。(違いが分かればどの色でも問題ありません)

そして「CollisionShape2D」ノードを作成します。

インスペクターから「CollisionShape2D > Shape > [空]」をクリックして、「新規 RectangleShape2D」を選びます。

サイズを 32×32 px に設定します。

そして会話メッセージ表示用の Label ノードを追加します。

メッセージをわかりやすくするため Label ノードはNpcの頭上に移動させておきます。

Npcスクリプトの作成

Npcノードを選択して、以下のスクリプトをアタッチします。

extends KinematicBody2D

# 表示するメッセージ
export var message:String

# メッセージ表示時間
var message_timer = 0.0

onready var label = $Label

func _process(delta: float) -> void:
	# メッセージ表示の更新
	if message_timer > 0:
		message_timer -= delta
		if message_timer <= 0:
			# 一定時間経過でメッセージを消す
			label.text = ""

# インタラクト開始
func _on_interact() -> void:
	label.text = message
	message_timer = 3.0 # 3秒間メッセージを表示する

Mainシーンの作成

新しく2Dシーン (Node2D) を作成して、名前を Main にリネームしておきます。

そして、このシーンに Player を1体、Npcをいくつか配置します。

そして配置した Npc のインスペクターから「Script Variables > Message」にメッセージを指定します。

メッセージには何を入れても問題ありませんが、日本語はフォントが設定されておらず表示ができないので「英数字」のみを指定します。

では、実行して動作を確認します。

しかし、レイがNpcオブジェクトに衝突しても何も起きません。これは Raycast2D の仕様通りの挙動であるためです。

というのも Raycast2D は一番近くで衝突したオブジェクトを優先して取得します。

「……? NPCオブジェクトしか衝突していないのでは……?」と思うかもしれませんが、実は レイを発生させている Playerオブジェクトが一番近くにいるのが原因となります。

これだと判定ができないので、コリジョンレイヤー・マスクの仕組みを使って Raycast2D の判定から Playerオブジェクトを除外します。

なお、コリジョンレイヤー・マスクについては以下のページで解説しています。

この記事では詳しく説明しませんので、詳細が知りたい場合はこちらのページが参考になると思います。

【Godot】コリジョンレイヤーとマスクについて

コリジョンレイヤーとマスクを設定する

まずはメニューから「プロジェクト > プロジェクト設定」を開きます。

「一般」タブの Layer Names > 2d Physics から レイヤー名を設定します。

  • Layer 1: player
  • Layer 2: npc

レイヤーの1番を「player」とし、レイヤーの2番を「npc」としました。

次に Playerシーンを開き Playerノードのインスペクターから「Collision > Mask」を “1” と “2” になるように修正します。

これは、Playerノードは “NPC(2)” のコリジョンと衝突を行う指定となります。”Player(1)” と衝突を行う必要はないのですがひとまず残しておきました。

次に “RayCast2D” ノードを選択します。

「Collision Mask」の項目を「2」のみに設定します。

これで Raycast2D は NPC(2) のみと衝突を行うようになります。

最後に Npcオブジェクトの設定です。Npcシーンを開いてインスペクターから以下のように設定します。

  • Collision > Layer: 2
  • Collision > Mask: 1 と 2

これで、Npcが “2” 番のコリジョンレイヤーに所属するようになりました。

では実行して動作を確認します。

Raycast2Dで判定したオブジェクトがメッセージを表示できるようになりました。

Raycast2Dの射程を伸ばしてみる

Raycast2Dの動作検証として、射程を伸ばしてみます。

Playerシーンを開き、Raycast2Dのインスペクターから「Cast To の xの値を “256”」に修正します。

そして、Rayスプライトのインスペクターから、画像を “ray256.pngにして、「Offset > Offset > x」の値を「128に修正します。

そして Mainシーンを開き、レイの重なりを実験できるように NPCオブジェクトの配置を調整します。

では実行して動作を確認します。

このように Raycast2D は一番手前のオブジェクトを優先して衝突判定を行います。

完成プロジェクト

今回作成したプロジェクトを添付しておきます。

補足: Area2Dと衝突を行う場合

Raycast2Dはデフォルトは “Body (KinematicBodyやRigidBody) のみと衝突を行うようになっています。

これを “Area2D” と衝突させるには、Raycast2Dのインスペクターから「Collide With > Areas」にチェックを入れます。

参考

今回の記事を参考にするにあたって以下の動画を参考にしました

Using Raycasts to Interact in Godot (Tutorial)

RayCast2Dを使用したインタラクションシステムの作り方を解説している動画です

Godot Nodes 101: Raycast2D Gun Tutorial

Raycast2Dを使用した無限距離のガンショットの命中判定について解説している動画です。