ゴマちゃんフロンティア

アザラシが大好きなエンジニアの開発日記です

【Unity】マウスクリックした地点の3D空間上での位置を取得する方法

time 2019/10/08

今回はUnityのマウスクリックに関するお話です。
RPGや戦略系のゲームでは「クリックした地点の3D空間での位置情報」が必要な場合があります。キャラクターの移動先を指定したり、物の配置位置を決めたりとかですね。

この件、さっくり実装できるかと思いきや、予想以上に詰まってしまったので、備忘も兼ねて欠きます!

マウスクリック地点の取得

Input.mousePositionでマウスクリックした地点をVector3型で取得できます。

Vector3型ではありますが、スクリーンに奥行きはないので、Zの値は常に0になります。

クリック地点を3D空間上の位置を取得

Camera.ScreenToWorldPoint()を使います。
Input.mousePositionの値をそのまま使うとZ軸が0のため上手く取得できません。カメラからの距離を測ってZ軸に入れてあげるのがポイントです。

using UnityEngine;

public class MousePositionTest : MonoBehaviour
{
    private Transform player;
    private Camera mainCamera;

    private Vector3 currentPosition = Vector3.zero;

    void Start()
    {
        player = GameObject.FindGameObjectWithTag("Player").transform;
        mainCamera = Camera.main;
    }

    void Update()
    {
        if (Input.GetMouseButton(0)) {
            var distance = Vector3.Distance(player.transform.position, mainCamera.transform.position);
            var mousePosition = new Vector3(Input.mousePosition.x, Input.mousePosition.y, distance);

            currentPosition = mainCamera.ScreenToWorldPoint(mousePosition);
        }
    }

    void OnDrawGizmos()
    {
        if (currentPosition != Vector3.zero) {
            Gizmos.color = Color.blue;
            Gizmos.DrawSphere(currentPosition, 0.5f);
        }
    }
}

ここではZ軸として「メインカメラからプレイヤーキャラクターまでの距離」を測り、その値を入れました。なので大体の地点がプレイヤーのZ軸近くになります。

Z軸に何の値を指定するべきかはゲームによって異なるので、開発内容に応じて変える必要がありそうです。

クリック地点の地面の位置を取得

↑の応用で「クリックした地面の位置を取得」もできます。
Input.mousePositionを元にCamera.ScreenPointToRay()を使い、取得したRayでレイキャストを行います。
RaycastHit.pointでレイの衝突位置が得られるので、そことメインカメラとの距離を測り、ScreenToWorldPoint()のZ軸として使用します。

using System.Linq;
using UnityEngine;

public class MousePositionTest : MonoBehaviour
{
    private Camera mainCamera;
    private Vector3 currentPosition = Vector3.zero;

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

    void Update()
    {
        if (Input.GetMouseButton(0)) {
            var ray = mainCamera.ScreenPointToRay(Input.mousePosition);
            var raycastHitList = Physics.RaycastAll(ray).ToList();
            if (raycastHitList.Any()) {
                var distance = Vector3.Distance(mainCamera.transform.position, raycastHitList.First().point);
                var mousePosition = new Vector3(Input.mousePosition.x, Input.mousePosition.y, distance);

                currentPosition = mainCamera.ScreenToWorldPoint(mousePosition);
                currentPosition.y = 0;
            }
        }
    }

    void OnDrawGizmos()
    {
        if (currentPosition != Vector3.zero) {
            Gizmos.color = Color.blue;
            Gizmos.DrawSphere(currentPosition, 0.5f);
        }
    }
}

すると以下のような感じになります。地面をクリックした場所にGizmoが表示されていますね。

取得した位置のY軸には直接0を代入していますが、これを対象オブジェクトの高さに合わせて調整するともっとしっくりくるかと。
その場合はRaycastHit.transformからゲームオブジェクトやコンポーネントを参照してみましょう。

down

コメントする