U
17 / 22 · 10 分
FAQ

レイキャスト:目の前にあるものを世界に尋ねる

短い答え

プレイヤーが何を目指し、何に乗っているか判定するには?

Physics.Raycast は見えない線を飛ばし、最初に当たった collider をヒット地点・距離・法線つきで返します。LayerMask と最大距離は必ず指定——でないと無限先まで検査し、三人称カメラからだと手前のプレイヤーに当たります。

Raycast とは一つの質問です——ここからこの向きに見えない線を放ったら、最初に何に当たるか?射撃、地面チェック、プレイヤーの指の下にあるもの、カメラの視線の先、敵から見えるかどうか——すべては引数 4 つの関数一つの話です。

アニメーション図

見えない線一本。最初にぶつかった collider が、ヒット地点、面の法線、距離を返してくれます。

その形

Every raycast you will ever write looks like this
using UnityEngine;

public class Shooter : MonoBehaviour
{
    public float range = 50f;
    public LayerMask hittable;      // set this in the Inspector

    void Fire()
    {
        // origin, direction, out result, how far, what counts
        if (Physics.Raycast(transform.position, transform.forward,
                            out RaycastHit hit, range, hittable))
        {
            Debug.Log("hit " + hit.collider.name + " at " + hit.distance + "m");
        }
    }
}

ヒットが教えてくれること

  • hit.point——世界空間での正確な位置です。弾痕、火花、デカールはここに置きます。

  • hit.normal——面が向いている方向です。エフェクトをこの向きに合わせると、火花は壁から飛び出さず面にぴったり沿います。

  • hit.distance——ray が進んだ長さです。距離によるダメージ減衰や、正しい長さのトレーサーを描くのに役立ちます。

  • hit.collider——当たった対象です。そこから GetComponent を呼んで体力スクリプトを探したり、CompareTag で味方と壁を区別したりします。

Placing an effect so it sits on the surface
// Quaternion.LookRotation(hit.normal) makes the effect's forward axis point
// straight out of the wall.
Instantiate(impactPrefab, hit.point, Quaternion.LookRotation(hit.normal));

LayerMask を正しく使う

LayerMask は layer の番号ではありません。32 ビットの値で、layer に一つずつビットが対応し、判定に含める layer のビットが立ちます。フィールドを LayerMask 型で宣言すれば、Inspector にはチェックボックス付きのドロップダウンが出て、自力でビット計算をすることもなくなります。

The bug, and the two right ways
// WRONG: layer 8 as a number means bit 3, which is layer 3.
Physics.Raycast(origin, dir, out hit, range, 8);

// Right, in code:
int mask = 1 << LayerMask.NameToLayer("Enemy");

// Right, and better: let the Inspector do it.
public LayerMask hittable;

// Everything EXCEPT the player's own layer:
int allButPlayer = ~(1 << LayerMask.NameToLayer("Player"));

カメラから、あるいは指から

ScreenPointToRay — the same line for mouse and touch
void Update()
{
    // Mouse. On a phone, Input.mousePosition follows the first touch too,
    // so this works on both without changing anything.
    if (!Input.GetMouseButtonDown(0)) return;

    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

    if (Physics.Raycast(ray, out RaycastHit hit, 100f, selectable))
    {
        hit.collider.GetComponent<Selectable>()?.Select();
    }
}

線を見えるようにする

Raycast は見えないものなので、描き出さない限りデバッグは当てずっぽうになります。たった 2 行で、ゲーム実行中の Scene view で確かめられるようになります。

Draw what you cast
// Green to where it hit, red for the full length when it missed.
if (Physics.Raycast(origin, dir, out RaycastHit hit, range, mask))
    Debug.DrawLine(origin, hit.point, Color.green, 1f);
else
    Debug.DrawRay(origin, dir * range, Color.red, 1f);

線では細すぎるとき

  • SphereCast は ray に沿って球を転がす判定です。地面チェックも太い弾も望むのはこちらで、物体なら通れない隙間を線だとすり抜けてしまうことがあります。

  • RaycastAll は線上のすべてを返しますが、順不同です。複数の敵を貫通する射撃に使えますが、距離順の並び替えは自分で行ってください。順序は保証されません。

  • RaycastNonAlloc は新しい配列を確保する代わりに、自分で用意した配列に結果を詰めます。スマホで Update のたびに RaycastAll を呼び続けるとゴミが溜まり、いつかガベージコレクションがゲームを止めて掃除することになります。

  • Trigger を無視するか含めるかは Physics の設定次第です。誰かが変えかねないプロジェクト全体の既定値に頼らず、QueryTriggerInteraction は明示的に渡してください。

これを作るレッスン

弾、ダメージ、エフェクト

この記事はそれだけで完結するレシピです。コースでは同じものを、5 つのタブを貫くひとつのプロジェクトの一部として作ります — キャラクターと戦闘 のレッスン 05。