マウスで操作しよう:クリック位置とRaycast【Unityスタートガイド6-12】

「クリックした敵を選びたい」「マウスの位置にオブジェクトを置きたい」。マウスで操作するゲームでは、画面のどこをクリックしたかを、ゲームの世界の位置やオブジェクトに変換する必要があります。今回はマウスの位置とクリックの受け取り方と、クリックした先を調べるRaycastをご紹介します!

目次

この記事で学べること

  • Input Systemでマウスの位置とクリックを受け取る方法
  • スクリーン座標とワールド座標の違い
  • ScreenToWorldPointで、マウスの位置をゲームの世界の位置にする方法(2D)
  • Raycastで、クリックした先にあるオブジェクトを調べる方法(3D・2D)
  • レイヤーで、調べる相手を絞り込む方法

前提知識

マウスの位置とクリックを受け取る

マウスを直接調べる

using UnityEngine;
using UnityEngine.InputSystem;

public class MouseCheckExample : MonoBehaviour
{
    void Update()
    {
        // マウスがつながっていないときはnullになる
        if (Mouse.current == null)
        {
            return;
        }

        if (Mouse.current.leftButton.wasPressedThisFrame)
        {
            Vector2 mousePosition = Mouse.current.position.ReadValue();
            Debug.Log("クリックした位置: " + mousePosition);
        }
    }
}
  • using UnityEngine.InputSystem;が必要です
  • Mouse.current.position.ReadValue():マウスの位置(画面の左下が(0, 0)、単位はピクセル)
  • Mouse.current.leftButton.wasPressedThisFrame:左クリックした瞬間(右クリックはrightButton)

InputSystem_Actionsを使う場合

6-7の記事のInputSystem_Actionsでは、Attackアクションにマウスの左クリックが割り当てられています。ゲームパッドのボタンでも同じ操作にしたい場合は、こちらを使います。

if (inputActions.Player.Attack.WasPressedThisFrame())
{
    // 左クリック(またはゲームパッドの攻撃ボタン)
}

マウスの位置は、どちらの場合もMouse.current.position.ReadValue()で読むのが簡単です。

スクリーン座標とワールド座標

マウスの位置はスクリーン座標(画面のピクセルの位置)です。ゲームの世界の位置(ワールド座標)とは別物なので、変換が必要です。

座標基準例
スクリーン座標画面の左下が(0, 0)、単位はピクセルマウスの位置
ワールド座標シーンの原点(0, 0, 0)、単位はUnityの1マスtransform.position

画面の大きさを変えると、同じ場所をクリックしてもスクリーン座標は変わります。

2D:クリックした位置にオブジェクトを動かす

2Dゲーム(カメラがOrthographic)では、Camera.main.ScreenToWorldPoint()でワールド座標に変換できます。

using UnityEngine;
using UnityEngine.InputSystem;

public class MouseClickMover2D : MonoBehaviour
{
    void Update()
    {
        if (Mouse.current == null || !Mouse.current.leftButton.wasPressedThisFrame)
        {
            return;
        }

        Vector3 screenPosition = Mouse.current.position.ReadValue();
        Vector3 worldPosition = Camera.main.ScreenToWorldPoint(screenPosition);

        // カメラのZ位置(-10など)が入ってしまうので、0にそろえる
        worldPosition.z = 0f;

        transform.position = worldPosition;
    }
}
  • Camera.main:タグが「MainCamera」のカメラ(最初からあるMain Camera)
  • zを0に戻すのを忘れない:変換した位置のzはカメラと同じ位置になるので、そのままだとカメラに重なって見えなくなります

Raycast:クリックした先のオブジェクトを調べる

3Dゲームでは、画面の点がゲームの世界のどこに当たるかは、奥行きによって変わります。そこで、カメラからマウスの方向にまっすぐ線(レイ)を飛ばして、最初に当たったColliderを調べます。これがRaycastです。

using UnityEngine;
using UnityEngine.InputSystem;

public class MouseRaycastPicker : MonoBehaviour
{
    [SerializeField] private float maxDistance = 100f;

    void Update()
    {
        if (Mouse.current == null || !Mouse.current.leftButton.wasPressedThisFrame)
        {
            return;
        }

        // カメラからマウスの位置に向かうレイを作る
        Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());

        // レイが何かのColliderに当たったら、その情報がhitに入る
        if (Physics.Raycast(ray, out RaycastHit hit, maxDistance))
        {
            Debug.Log(hit.collider.gameObject.name + "をクリックした");
            Debug.Log("当たった場所: " + hit.point);
        }
    }
}

空のゲームオブジェクトにアタッチして、シーンにCubeやSphereを置いて再生し、クリックしてみましょう。

  • Camera.main.ScreenPointToRay(マウスの位置):カメラからマウスの方向へ向かうレイ
  • Physics.Raycast(レイ, out RaycastHit hit, 距離):当たればtrue。当たった情報がhitに入る
    • hit.collider:当たったCollider(.gameObjectでオブジェクト)
    • hit.point:当たった場所(ワールド座標)
    • hit.distance:カメラからの距離
  • Colliderが付いていないオブジェクトには当たりません

レイを見えるようにする

Debug.DrawRay(ray.origin, ray.direction * maxDistance, Color.red, 1f);

Raycastの前にこの1行を足すと、シーンビューに赤い線が1秒間表示されます。「どこに向かってレイを飛ばしているか」を確認するのに便利です。

2Dの場合

2DのCollider(Box Collider 2Dなど)には、Physics.Raycastは当たりません。2Dでは次のように書きます。

Vector2 worldPoint = Camera.main.ScreenToWorldPoint(Mouse.current.position.ReadValue());

// その位置に2DのColliderがあるか調べる
Collider2D hitCollider = Physics2D.OverlapPoint(worldPoint);

if (hitCollider != null)
{
    Debug.Log(hitCollider.gameObject.name + "をクリックした");
}
  • クリックした点に何があるかを調べるならPhysics2D.OverlapPointが簡単です
  • 「ある点から、ある方向に線を飛ばす」ときはPhysics2D.Raycast(開始位置, 方向, 距離)を使います

レイヤーで調べる相手を絞り込む

床と敵が重なっているとき、敵だけに当てたい、ということがあります。そんなときはレイヤーで絞り込みます。

レイヤーを作る

  1. オブジェクトを選び、Inspector右上のLayerのプルダウンを開く
  2. 一番下のAdd Layer…を選ぶ
  3. 空いている欄(User Layer 6以降)に名前(例:Enemy)を入力
  4. もう一度オブジェクトを選び、Layerのプルダウンから作ったレイヤーを選ぶ

タグ(6-10の記事)は「何のオブジェクトか」の目印、レイヤーは「Raycastや当たり判定、カメラで、まとめて扱うグループ」と考えると分かりやすいです。

LayerMaskで絞り込む

using UnityEngine;
using UnityEngine.InputSystem;

public class MouseLayerPicker : MonoBehaviour
{
    [SerializeField] private LayerMask targetLayers;   // Inspectorで当てたいレイヤーを選ぶ
    [SerializeField] private float maxDistance = 100f;

    void Update()
    {
        if (Mouse.current == null || !Mouse.current.leftButton.wasPressedThisFrame)
        {
            return;
        }

        Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());

        if (Physics.Raycast(ray, out RaycastHit hit, maxDistance, targetLayers))
        {
            Debug.Log("ねらったレイヤーの " + hit.collider.gameObject.name + " に当たった");
        }
    }
}
  • LayerMask型の変数を[SerializeField]にすると、Inspectorでチェックボックス付きのプルダウンになり、当てたいレイヤーを選べます
  • 選んだレイヤー以外のオブジェクトは、レイが通り抜けます
スライム

床をクリックすると、奥の敵じゃなくて床が選ばれちゃう・・・

フェアリー

そんなときこそLayerMask!敵にEnemyレイヤーを付けて、Raycastで絞り込めばOKだよ

UIの上をクリックしたとき

Raycastは、ボタンなどのUI(Canvas)には当たりません。UIのボタンを押したつもりが、後ろのオブジェクトまでクリックされてしまうことがあります。気になる場合は、UIの上にマウスがあるかどうかをEventSystem.current.IsPointerOverGameObject()(using UnityEngine.EventSystems;が必要)で調べて、UIの上ならRaycastしないようにします。

よくあるエラーと対処法

エラー:NullReferenceException(Camera.mainの行)

  • 原因:シーンのカメラのTagが「MainCamera」になっていない、またはカメラがない
  • 対処法:カメラのInspectorでTagをMainCameraにする

エラー:クリックしても何も当たらない

  • 原因:クリックしたいオブジェクトにColliderが付いていない。2DのColliderなのにPhysics.Raycastを使っている。LayerMaskで何も選んでいない
  • 対処法:Colliderを付ける。2DはPhysics2Dを使う。Debug.DrawRayでレイの向きを確認する

エラー:2Dでクリックした位置にオブジェクトが見えない

  • 原因:ScreenToWorldPointで変換した位置のzが、カメラと同じ位置になっている
  • 対処法:worldPosition.z = 0f;にする

エラー:The name ‘Mouse’ does not exist in the current context

  • 原因:using UnityEngine.InputSystem;を書いていない
  • 対処法:スクリプトの一番上に足す

まとめ

  • マウスの位置はMouse.current.position.ReadValue()、クリックはleftButton.wasPressedThisFrame(またはInputSystem_ActionsのAttack)
  • マウスの位置はスクリーン座標。2DはScreenToWorldPointで変換してzを0に
  • 3Dでクリックした先を調べるのはScreenPointToRay+Physics.Raycast
  • 2DはPhysics2D.OverlapPointやPhysics2D.Raycast
  • 調べる相手はレイヤーとLayerMaskで絞り込む

次のステップ

プレハブを使ってクリックした場所にオブジェクトを出したい場合は、第7章のスクリプトからプレハブを生成しようと組み合わせてみてください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次