【Unity】マップにプレイヤーの位置と回転を表示する

投稿者: | 2023-08-05

マップ上にプレイヤーの位置と回転を表示します。

画像を作る

マップの画像はシーンビューで真上からスクリーンショットしました。シーンギズモのY軸をクリックして真上から表示します。

ギズモの中央のキューブかテキストをクリックして、Orthographicビューにします。

ディレクショナルライトのShadow Mapやプレイヤーを無効化したり、シーンビューでエフェクトやアイコン、グリッドなどをオフにできます。

マップ画像にしたい部分をスクリーンショットして、Unityにインポートしスプライトにします。

UIを作成

マップのスプライトを表示するImageオブジェクトを追加します。

マップのImageオブジェクトの子として、プレイヤーのアイコンを設定するImageオブジェクトも追加します。プレイヤーのアイコンは上向きの矢印にしました。

ImageコンポーネントのSource Imageにスプライトを設定します。マップのRectTransformでは、元の画像のサイズの比率を変えないようにしてWidthとHeightの値を設定します。

画像をリサイズするツールを使うと簡単に計算できます。http://www.n1-sitemaker.com/resize/

今回はマップの左下を基準にするので、Pivotを(0,0)にします。

プレイヤーアイコンのRectTransfromでは、Pivotを(0.5, 0.5)にします。

アンカーとポジションを0にしたときに、プレイヤーアイコンの中心が、マップ画像の左下に来ます。

プレイヤーはマップの子なので、マップ画像は自由に移動や回転、拡大ができます。

マップ上の位置を計算

シーンでの実際のマップの大きさと画像の大きさの比を、プレイヤーの位置にかけることで、マップ上のアイコンの位置を計算できます。

マップの大きさ

マップの大きさを計算するために、マップの左下と右上の位置をCanvasに付けるスクリプトに保存します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class TestMapCanvas : TestCanvas
{

    Image mapImage;
    Image playerImage;
    Transform player;
    public Vector3 mapBottomLeftPosition;
    public Vector3 mapTopRightPosition;

    // ...
}

この値をシーンビューでハンドルを使って設定できるようにエディタを拡張します。

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(TestMapCanvas))]
public class TestMapCanvasEditor : Editor
{
    private void OnEnable()
    {
        SceneView.duringSceneGui += OnSceneGUI;
    }

    private void OnDisable()
    {
        SceneView.duringSceneGui -= OnSceneGUI;
    }

    private void OnSceneGUI(SceneView sv)
    {
        TestMapCanvas tmc = (TestMapCanvas)target;

        EditorGUI.BeginChangeCheck();
        Vector3 newBottomLeftCorner = Handles.PositionHandle(tmc.mapBottomLeftPosition, Quaternion.identity);
        Vector3 newTopRightCorner = Handles.PositionHandle(tmc.mapTopRightPosition, Quaternion.identity);

        if (EditorGUI.EndChangeCheck())
        {
            Undo.RecordObject(tmc, "Change Positions");
            tmc.mapBottomLeftPosition = newBottomLeftCorner;
            tmc.mapTopRightPosition = newTopRightCorner;
        }
    }
}

Canvasを選択するとシーンビューでハンドルが使えるようになります。

アイコンの位置を計算

Canvasのスクリプトでアイコンの位置を計算します。

    Vector3 CalculateIconImagePosition(Vector3 targetWorldPosition)
    {
        // マップの幅
        var mapWidth = mapTopRightPosition.x - mapBottomLeftPosition.x;
        
        // マップの奥行
        var mapDepth = mapTopRightPosition.z - mapBottomLeftPosition.z;
        
        // 左下を基準にした位置
        var targetRelativePosition = targetWorldPosition - mapBottomLeftPosition;

        // アイコンの位置
        var iconImagePosition = Vector3.zero;
        iconImagePosition.x = targetRelativePosition.x * mapImage.rectTransform.sizeDelta.x / mapWidth;
        iconImagePosition.y = targetRelativePosition.z * mapImage.rectTransform.sizeDelta.y / mapDepth;

        return iconImagePosition;
    }

まず、左下と右上に置いたハンドルの値を使って、3D空間のマップの幅と奥行を計算します。

    public Vector3 mapBottomLeftPosition;
    public Vector3 mapTopRightPosition;

// ----------

        // マップの幅
        var mapWidth = mapTopRightPosition.x - mapBottomLeftPosition.x;
        
        // マップの奥行
        var mapDepth = mapTopRightPosition.z - mapBottomLeftPosition.z;

上と右がプラスなので、右上の位置から左下の位置を引きます。マップの画像の向きが違うと符号などが変わります。

引数にはプレイヤーや敵、ゴール地点などの3D空間での位置が渡されます。これは原点が基準なので、マップの左下を基準とした位置に変換します。

        var targetRelativePosition = targetWorldPosition - mapBottomLeftPosition;

UIでのアイコンの位置は、3D空間での位置に、マップの3D空間での幅や奥行と、マップ画像の幅や高さの比をかけると求められます。

        var iconImagePosition = Vector3.zero;
        iconImagePosition.x = targetRelativePosition.x * mapImage.rectTransform.sizeDelta.x / mapWidth;
        iconImagePosition.y = targetRelativePosition.z * mapImage.rectTransform.sizeDelta.y / mapDepth;

        return iconImagePosition;
    }

アイコンの回転を計算

プレイヤーの向きによって矢印アイコンを回転させます。シーンの真上(Y)方向を軸とした回転を、アイコンのRectTransformのZ軸の回転にします。

    Quaternion CalculateIconImageRotation(Quaternion targetWorldRotation)
    {
        var euler = Quaternion.Inverse(targetWorldRotation).eulerAngles;

        return Quaternion.Euler(new Vector3(0f, 0f, euler.y));
    }

プレイヤーとアイコンが逆に回転したので、Quaternion.Inverseメソッドを使っています。ここでも、マップ画像の向きによって計算が変わります。例えば、マップが上下逆さまだった場合、アイコンのZ回転に180を足します。

敵や目的地のアイコンを表示

リストを使って複数のターゲットをアイコン表示できるようにしました。

    List<Transform> targets;
    [SerializeField] Image targetIconPrefab;

    // ----------------

    public void AddTarget(Transform target)
    {
        if (targets == null) targets = new List<Transform>();

        targets.Add(target);
    }

アイコンのImageオブジェクトはプレハブにしてスクリプトにアタッチします。

スクリプト

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class TestMapCanvas : TestCanvas
{

    Image mapImage;
    Image playerImage;
    Transform player;
    public Vector3 mapBottomLeftPosition;
    public Vector3 mapTopRightPosition;

    List<Transform> targets;
    [SerializeField] Image targetIconPrefab;
    public void Init(Transform player)
    {
        this.player = player;

        mapImage = transform.GetChild(0).GetComponent<Image>();
        playerImage = transform.GetChild(0).GetChild(0).GetComponent<Image>();
    }


    public void Open()
    {
        gameObject.SetActive(true);
        UpdateIconPositionAndRotation();
    }

    public void Close()
    {
        gameObject.SetActive(false);

    }

    void UpdateIconPositionAndRotation()
    {
        // プレイヤーアイコンの位置
        playerImage.rectTransform.localPosition = CalculateIconImagePosition(player.position);
        
        // プレイヤーアイコンの回転
        playerImage.rectTransform.localRotation = CalculateIconImageRotation(player.rotation);

        // 他のターゲットのアイコンをすべて削除
        DeleteAllTargetIcons();

        // ターゲットのアイコンを作成
        if (targets != null)
        {
            foreach(var t in targets)
            {
                // インスタンス化
                var icon = Instantiate(targetIconPrefab, mapImage.transform);

                // アイコンの位置
                icon.rectTransform.localPosition = CalculateIconImagePosition(t.position);
            }
        }
    }

    void DeleteAllTargetIcons()
    {
        for(int i = 0; i < mapImage.transform.childCount; i++)
        {
            var icon = mapImage.transform.GetChild(i);
            if (mapImage.transform.GetChild(i) == playerImage.transform) continue;

            Destroy(icon.gameObject);
            
        }
    }

    Vector3 CalculateIconImagePosition(Vector3 targetWorldPosition)
    {
        // マップの幅
        var mapWidth = mapTopRightPosition.x - mapBottomLeftPosition.x;
        
        // マップの奥行
        var mapDepth = mapTopRightPosition.z - mapBottomLeftPosition.z;
        
        // 左下を基準にした位置
        var targetRelativePosition = targetWorldPosition - mapBottomLeftPosition;

        // アイコンの位置
        var iconImagePosition = Vector3.zero;
        iconImagePosition.x = targetRelativePosition.x * mapImage.rectTransform.sizeDelta.x / mapWidth;
        iconImagePosition.y = targetRelativePosition.z * mapImage.rectTransform.sizeDelta.y / mapDepth;

        return iconImagePosition;
    }

    Quaternion CalculateIconImageRotation(Quaternion targetWorldRotation)
    {
        var euler = Quaternion.Inverse(targetWorldRotation).eulerAngles;

        return Quaternion.Euler(new Vector3(0f, 0f, euler.y));
    }

    public void AddTarget(Transform target)
    {
        if (targets == null) targets = new List<Transform>();

        targets.Add(target);
    }
}

UIや進行を管理するクラス等からpublicメソッドを呼んでいます。マップを開いたときに、アイコンの位置と回転を計算します。

    public void Open()
    {
        gameObject.SetActive(true);
        UpdateIconPositionAndRotation();
    }

    public void Close()
    {
        gameObject.SetActive(false);

    }

プレイヤーアイコンの位置と回転を設定したら、それ以外のターゲットのアイコンをすべて削除してから、新たにインスタンス化して位置を設定しています。アイコンはすべてマップのImageオブジェクトの子にします。

    void UpdateIconPositionAndRotation()
    {
        // プレイヤーアイコンの位置
        playerImage.rectTransform.localPosition = CalculateIconImagePosition(player.position);
        
        // プレイヤーアイコンの回転
        playerImage.rectTransform.localRotation = CalculateIconImageRotation(player.rotation);

        // 他のターゲットのアイコンをすべて削除
        DeleteAllTargetIcons();

        // ターゲットのアイコンを作成
        if (targets != null)
        {
            foreach(var t in targets)
            {
                // インスタンス化
                var icon = Instantiate(targetIconPrefab, mapImage.transform);

                // アイコンの位置
                icon.rectTransform.localPosition = CalculateIconImagePosition(t.position);
            }
        }
    }

アイコンを削除するときは、マップのすべての子を見て行って、プレイヤーアイコンでなければゲームオブジェクトを破壊します。

    void DeleteAllTargetIcons()
    {
        for(int i = 0; i < mapImage.transform.childCount; i++)
        {
            var icon = mapImage.transform.GetChild(i);
            if (mapImage.transform.GetChild(i) == playerImage.transform) continue;

            Destroy(icon.gameObject);
            
        }
    }

コメントを残す

メールアドレスが公開されることはありません。