【Unity】クリックしたオブジェクトの位置を取得する Raycast入門

広告

クリックした先の3Dオブジェクトを知りたいときは、ScreenToWorldPoint ではなく Raycast を使います。

マウスでクリックしたオブジェクトの位置を取得しようとして、次のようなコードを書いたことはないでしょうか。

var pos = Camera.main.ScreenToWorldPoint(Mouse.current.position.value);
Debug.Log(pos);

エラーにはなりませんが、期待した値は出ません。この記事では、Raycast で「クリックしたオブジェクトの名前」「そのオブジェクトの位置」「クリックした表面の座標」を取得する方法を、実際の Console の出力を見ながら解説します。

動作確認は Unity 6.3 LTS(6000.3.25f1)、Input System を使ったプロジェクトで行いました。

ScreenToWorldPoint では取れない理由

ScreenToWorldPoint は「画面上の点」を「カメラから指定した距離にある位置」に変換する関数で、どのオブジェクトをクリックしたかは分かりません。

引数の Vector3 の z 成分が「カメラからの距離」を表します。Mouse.current.position が返す Vector2 をそのまま渡すと、z は 0 になります。

  • Perspective(3D)カメラ: z = 0 は「カメラからの距離 0」なので、カメラ自身の位置が返ります。マウスの位置は結果に反映されません。
  • Orthographic(2D)カメラ: x と y は正しく得られますが、z はカメラの z 座標(通常 -10)になります。

つまり、このコードが得意なのは「2D で、z = 0 の平面にオブジェクトを置きたい」場面です。3D のオブジェクトをクリックして選ぶ用途には向きません。

Raycast でクリックしたオブジェクトを取得する

カメラからマウス位置に向けて光線(Ray)を飛ばし、最初に当たった Collider の情報を受け取ります。

using UnityEngine;
using UnityEngine.InputSystem;  // 入力を検知するために必要

public class ClickPick : MonoBehaviour
{
    Camera cam;

    void Start()
    {
        cam = Camera.main;
    }

    void Update()
    {
        if (Mouse.current == null) return;

        if (Mouse.current.leftButton.wasPressedThisFrame)
        {
            Ray ray = cam.ScreenPointToRay(Mouse.current.position.ReadValue());

            if (Physics.Raycast(ray, out RaycastHit hit))
            {
                Debug.Log(hit.collider.name);        // クリックしたオブジェクト名
                Debug.Log(hit.transform.position);   // そのオブジェクトの位置
                Debug.Log(hit.point);                // レイが当たった表面の座標
            }
        }
    }
}

ポイントは3つです。

  1. ScreenToWorldPoint ではなく ScreenPointToRay を使い、画面上の点から Ray を作ります。
  2. Physics.Raycast(ray, out hit) が true を返したときだけ、何かに当たっています。
  3. 当たった結果は RaycastHit 型の hit に入ります。

このスクリプトは、シーン内の任意の GameObject(空のオブジェクトでも可)に付ければ動きます。Camera.main の仕組みや null になるケースは、UnityのCamera.mainとは? GetComponent<Camera>()との違いを理解しようで解説しています。

実行結果を読む

的(target)をクリックすると、Console に次の3行が出ました。

target
(0.00, -5.00, 14.00)
(0.30, 2.51, 13.60)
Unity の Console に出力された、クリックしたオブジェクト名・位置・当たった座標の3行のログ
ログ値意味
hit.collider.nametargetクリックしたオブジェクト名
hit.transform.position(0.00, -5.00, 14.00)target の原点の位置
hit.point(0.30, 2.51, 13.60)レイが当たった表面上の点

使ったオブジェクトの設定は次のとおりです。

項目値
Transform の Position(0, -5, 14)
Box Collider の Center(0, 6.5, 0)
Box Collider の Size(3.8, 3.8, 0.8)
target の Inspector。Transform の Position と Box Collider の Center・Size

この設定から、値を読み解けます。

  • hit.transform.position は Position と一致します。Transform の値そのものです。
  • hit.point の z = 13.60 は、的の手前の面です。Collider の中心は z = 14 で、奥行き(Size Z)が 0.8 なので、手前の面は 14 − 0.4 = 13.6 になります。カメラ側の表面に Ray が当たったことが分かります。
  • hit.point の y = 2.51 は、Collider の範囲内です。Collider の中心は y = −5 + 6.5 = 1.5 で、高さ(Size Y)が 3.8 なので、y は −0.4 から 3.4 の範囲になります。
  • hit.point の x = 0.30 は、中央よりやや右をクリックした位置です。

hit.point と hit.transform.position の違い

「クリックした場所」が知りたいなら hit.point、「オブジェクトの基準位置」が知りたいなら hit.transform.position を使います。RaycastHit に入っている情報(point・normal・distance・collider)の全体像は、UnityのRaycastHitには何が入っている? hit.point・distance・normal・colliderを理解しようで詳しく解説しています。

今回の的は、原点が棒の根元(地面)にあります。そのため hit.transform.position の y は −5 ですが、見た目の的は y = 1.5 付近にあり、大きくずれます。Transform の位置は、見た目の中心とは限りません。hit.transform.position は世界座標です。親子関係があるときの座標の違いは、Unityのtransform.positionとlocalPositionの違い|世界から見た座標と親から見た座標で解説しています。

取得したいもの使う値使いどころの例
クリックした表面の座標hit.point着弾エフェクトや弾痕を出す位置
オブジェクトの基準位置hit.transform.position選択したオブジェクトを移動・回転させる
クリックしたオブジェクトhit.collider.gameObjectタグや名前で処理を分ける

hit.point を使った実例として、クリックした場所にマーカーを置く方法はUnityでクリックした場所にマーカーを置く|hit.pointとhit.normalを実際に使ってみよう、弾痕を残す方法はUnityでクリックした場所に弾痕を残す|RaycastとDestroyを使ってみようで紹介しています。

よくあるつまずき

  • 何も出力されない: クリックしたオブジェクトに Collider が付いているか確認します。Collider がないオブジェクトには Ray が当たりません。
  • 見た目と判定がずれる: Raycast は見た目の Mesh ではなく、Collider の形で判定します。今回の的のように Collider の Center がずれていると、判定の位置も見た目からずれます。Scene ビューで緑色の枠線を確認してください。
  • 遠くの物に当たってしまう: Physics.Raycast(ray, out hit, 100f) のように第3引数で距離を制限できます。
  • Mouse.current が null になる: マウスが接続されていない環境(スマートフォンなど)では null になります。冒頭の if (Mouse.current == null) return; は、その対策です。
  • UnityEngine.InputSystem が使えない: Player Settings の Active Input Handling が、旧 Input Manager のみになっていないか確認します。Input System の基本はUnity 6でキー入力による移動を実装する ― Input Systemの基本で解説しています。
  • 2D ゲームの場合: Collider2D と Physics2D.Raycast を使います。単にクリック位置にオブジェクトを置きたいだけなら、ScreenToWorldPoint で変換したあと、z を 0 にそろえる方法が使えます。

まとめ

クリックした3Dオブジェクトを調べるには、ScreenPointToRay と Physics.Raycast の組み合わせを使います。

  • ScreenToWorldPoint は座標変換の関数で、クリックしたオブジェクトは分かりません。
  • hit.collider.name でオブジェクト、hit.point で当たった表面の座標、hit.transform.position でオブジェクトの基準位置が得られます。
  • 判定は Collider の形で行われるので、見た目と合わないときは Collider の設定を確認します。

「座標変換」と「Raycast」は別の仕組みです。やりたいことに合わせて使い分けてください。

関連記事

訪問数 24 回, 今日の訪問数 24回

広告