Maemaemae

Godot 4.4.1 RayCast2D 実践ガイド

レイキャストは現代ゲーム開発において不可欠の技術です。GodotのRayCast2Dノードを使いこなせば、敵の探知、照準システム、地形検出など多彩な機能を実装できます。この記事では、Godot 4.4.1におけるRayCast2Dの基本から実践的な活用例まで解説します。

RayCast2Dとは

RayCast2Dは指定した方向にレイ(光線)を飛ばし、何かに衝突したかを検出するノードです。障害物の検知、視線の通り具合の確認、射撃の命中判定など、様々な用途に使えます。

主な特徴

基本的な使い方

RayCast2Dノードの追加

まず、シーンにRayCast2Dノードを追加します。

  1. シーンタブで親ノードを選択
  2. 「+」ボタンをクリックまたはCtrl+A
  3. 検索欄に「RayCast2D」と入力
  4. RayCast2Dを選択して追加

主要プロパティの設定

インスペクタでこれらの重要なプロパティを設定します:

# コードでプロパティを設定する例
$RayCast2D.enabled = true
$RayCast2D.target_position = Vector2(100, 0)  # 右方向に100ピクセル
$RayCast2D.collision_mask = 1  # 1番目のコリジョンレイヤーのみ

衝突検出の基本

RayCast2Dの主要なメソッドを使った衝突検出:

func _physics_process(delta):
    # レイの方向を更新(例:マウス方向)
    $RayCast2D.target_position = get_local_mouse_position().normalized() * 100

    # 衝突検出
    if $RayCast2D.is_colliding():
        # 衝突点を取得
        var collision_point = $RayCast2D.get_collision_point()

        # 衝突したオブジェクトを取得
        var collider = $RayCast2D.get_collider()

        # 衝突点の法線(面の向き)
        var normal = $RayCast2D.get_collision_normal()

        print("衝突検出: ", collider.name)
        print("衝突点: ", collision_point)
        print("法線: ", normal)

実践的な活用例

例1: プレイヤーの視界と壁の検出

壁を通り抜けない視界システムの実装:

extends CharacterBody2D

@onready var ray_cast = $RayCast2D
@onready var vision_area = $VisionArea

func _physics_process(delta):
    # プレイヤーの向きに応じてレイの方向を更新
    ray_cast.target_position = Vector2(cos(rotation), sin(rotation)) * 200

    # 壁との衝突をチェック
    if ray_cast.is_colliding():
        # 壁までの距離を計算
        var distance = global_position.distance_to(ray_cast.get_collision_point())

        # 視界の範囲を壁までに制限
        vision_area.scale = Vector2(distance / 200, distance / 200)
    else:
        # 壁がなければ最大視界
        vision_area.scale = Vector2(1, 1)

例2: 射撃システム

シンプルな射撃システムの実装:

extends Node2D

@onready var ray_cast = $RayCast2D
@onready var gun_sprite = $GunSprite
@onready var muzzle_position = $MuzzlePosition

func _process(delta):
    # 銃をマウス方向に向ける
    gun_sprite.look_at(get_global_mouse_position())

    # レイの方向も更新
    ray_cast.global_position = muzzle_position.global_position
    ray_cast.target_position = get_local_mouse_position().normalized() * 1000

func _input(event):
    if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
        shoot()

func shoot():
    if ray_cast.is_colliding():
        var target = ray_cast.get_collider()

        # 敵に当たったか確認
        if target.is_in_group("enemies"):
            target.take_damage(10)

            # ヒットエフェクト
            var hit_effect = preload("res://hit_effect.tscn").instantiate()
            get_tree().root.add_child(hit_effect)
            hit_effect.global_position = ray_cast.get_collision_point()

例3: 地形傾斜の検出

プレイヤーキャラクターが立っている地面の傾斜を検出:

extends CharacterBody2D

@onready var floor_ray = $FloorRayCast2D

func _physics_process(delta):
    # 重力と移動の処理...

    # 地面との接触を確認
    if is_on_floor():
        # 地面の傾斜角度を計算
        var floor_normal = floor_ray.get_collision_normal()
        var slope_angle = rad_to_deg(acos(floor_normal.dot(Vector2.UP)))

        print("地面の傾斜: ", slope_angle, "度")

        # 傾斜に応じた処理(例:急な坂では減速)
        if slope_angle > 20:
            velocity.x *= 0.8

高度なテクニック

複数のレイキャストを使用したセンサー

プレイヤー周囲の状況を把握するためのセンサーシステム:

extends CharacterBody2D

var rays = []
var ray_count = 8
var ray_length = 100

func _ready():
    # 周囲に8方向のレイを配置
    for i in range(ray_count):
        var ray = RayCast2D.new()
        add_child(ray)
        ray.enabled = true

        # レイの角度を計算(円周上に均等配置)
        var angle = i * 2 * PI / ray_count
        ray.target_position = Vector2(cos(angle), sin(angle)) * ray_length

        rays.append(ray)

func _physics_process(delta):
    # 各レイの状態をチェック
    for i in range(ray_count):
        if rays[i].is_colliding():
            var obj = rays[i].get_collider()
            var dist = global_position.distance_to(rays[i].get_collision_point())

            # 方向に応じた処理
            if obj.is_in_group("dangers"):
                # その方向に障害物があれば回避行動
                avoid_danger(i, dist)

レイキャストの結果の可視化

デバッグ用にレイキャストの結果を描画:

extends Node2D

@onready var ray_cast = $RayCast2D

func _draw():
    if ray_cast.enabled:
        var color = Color.GREEN
        var target = ray_cast.target_position

        # レイが何かに当たっていれば赤色に
        if ray_cast.is_colliding():
            color = Color.RED
            # ローカル座標系に変換
            var collision_point = to_local(ray_cast.get_collision_point())
            target = collision_point

        # レイを描画
        draw_line(Vector2.ZERO, target, color, 2)

        # 衝突点に円を描画
        if ray_cast.is_colliding():
            draw_circle(target, 5, Color.YELLOW)

func _process(delta):
    # 描画を更新
    queue_redraw()

よくあるエラーと解決策

レイキャストが機能しない場合

  1. Enabled プロパティの確認

    • enabled = true になっているか確認する
  2. コリジョンマスクの設定

    • 対象オブジェクトのコリジョンレイヤーがレイキャストのマスクに含まれているか確認
  3. ターゲットポジションの設定

    • 長さが0になっていないか(target_position = Vector2.ZERO
  4. 物理エンジンのタイミング

    • レイキャストの結果は _physics_process() で確認する

パフォーマンスの問題

# パフォーマンス最適化の例
var update_interval = 0.1  # 0.1秒ごとに更新
var timer = 0

func _physics_process(delta):
    timer += delta
    if timer >= update_interval:
        update_raycasts()
        timer = 0

func update_raycasts():
    # 重要な処理だけを実行
    # ...

ベストプラクティス

  1. 目的に応じたレイの長さ設定

    • 必要以上に長いレイは避け、目的に合った長さに設定
  2. 適切なコリジョンマスク

    • 検出したいオブジェクトのレイヤーのみをマスクに含める
  3. ローカル・グローバル座標の理解

    • target_positionはローカル座標であることに注意
    • グローバル座標での操作が必要な場合は変換を行う
  4. デバッグ表示の活用

    • 開発中はレイを可視化して動作を確認する

サンプルプロジェクト:敵探知システム

敵を探知してプレイヤーを追跡するAIの例:

extends CharacterBody2D

@export var detection_range = 300
@export var fov_angle = 60  # 視野角(度)
@export var speed = 100

@onready var ray_cast = $RayCast2D
@onready var player = get_tree().get_nodes_in_group("player")[0]

var detected = false

func _physics_process(delta):
    if player:
        # プレイヤーへの方向ベクトル
        var to_player = player.global_position - global_position
        var distance = to_player.length()

        # 探知範囲内かつ視野角内にいるか確認
        if distance < detection_range:
            var angle_to_player = rad_to_deg(to_player.angle())
            var my_angle = rad_to_deg(rotation)
            var angle_diff = abs(angle_to_player - my_angle) % 360
            if angle_diff > 180:
                angle_diff = 360 - angle_diff

            if angle_diff < fov_angle / 2:
                # レイキャストでプレイヤーまでの視線が通るか確認
                ray_cast.target_position = to_player
                ray_cast.force_raycast_update()

                if ray_cast.is_colliding() and ray_cast.get_collider() == player:
                    detected = true
                    # プレイヤー発見!追跡開始
                    velocity = to_player.normalized() * speed
                else:
                    detected = false
                    # 視界が遮られている
                    velocity = Vector2.ZERO
            else:
                detected = false
                # 視野角外
                velocity = Vector2.ZERO
        else:
            detected = false
            # 探知範囲外
            velocity = Vector2.ZERO

        # 状態に応じた表示の更新
        $DetectionIndicator.modulate = Color.RED if detected else Color.GREEN

        # 移動を適用
        move_and_slide()

まとめ

Godot 4.4.1のRayCast2Dノードは、2Dゲーム開発における強力なツールです。本記事で解説した基本から応用までの技術を活用すれば、以下のようなさまざまな機能を実装できます:

効率的なレイキャストの使用は、よりリアルでインタラクティブなゲームプレイを実現する鍵となります。ぜひ自分のプロジェクトに取り入れてみてください。

参考資料


本記事はGodot 4.4.1を基準としています。将来のバージョンでは一部の機能や使い方が変更される可能性があります。

GodotRayCast2D2Dゲーム開発衝突検知