神様は有休消化中です。

Unity関連の技術ネタを書いてます。

【Unity】EventSystemsから受け取った座標をRectTransform.localPositionに設定する方法

uGUIのEventSystems(IBeginDragHandler, IDragHandler, IEndDragHandlerなど)から受け取った座標をそのままRectTransform.localPositionに設定すると、Canvasのscaleによってはぶっ飛んだ位置(描画領域外)に移動してしまうことがあります。
EventSystemsから渡される座標はスクリーン座標のため、scaleが影響してぶっ飛んでしまうわけですが、今回はこのぶっ飛び現象を回避するためにスクリーン座標→ローカル座標へ変換する方法を共有します。


CanvasのRender Modeごとにやり方が違う・・・!

CanvasのRender Modeには、以下の3種類があります。詳しい説明は公式を参照。
・Screen Space - Overlay
・Screen Space - Camera
・World Space

上記それぞれで、ローカル座標への変換方法が異なります。

public class EventHandler : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    private RectTransform rectTransform;

    private void Awake()
    {
       rectTransform = GetComponent<RectTransform>();
    }

    public void OnDrag(PointerEventData eventData)
    {
       Vector2 localPosition = GetLocalPosition(eventData.position);
       rectTransform.localPosition = localPosition;
    }

    private Vector2 GetLocalPosition(Vector2 screenPosition)
    {
       Vector2 result = Vector2.zero;

       // TODO: ここで、スクリーン座標→ローカル座標に変換したい
       
       return result;
    }
}

Screen Space - Overlayの場合

この場合が一番簡単、以下のコードで変換できます。

private Vector2 GetLocalPosition(Vector2 screenPosition)
{
    return transform.InverseTransformPoint(screenPosition);
}

Screen Space - Cameraの場合

こちらの場合は少し面倒。RectTransformUtilityを使って解決します。

private Vector2 GetLocalPosition(Vector2 screenPosition)
{
    Vector2 result = Vector2.zero;
    
    RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPosition, Camera.main, out result);
       
    return result;
}

World Spaceの場合

World Spaceの場合は未確認ですが、調べてみたところ以下の方法で取得できる様子。

private Vector2 GetLocalPosition(Vector2 screenPosition)
{
    Vector2 result = Vector2.zero;
    
    RectTransformUtility.ScreenPointToWorldPointInRectangle(rectTransform, screenPosition, Camera.main, out result);
       
    return result;
}

この現象、いざ発生すると気付きにくいです。普段からちゃんと座標空間を意識してコーディングしないと。。。

参考サイト

qiita.com
docs.unity3d.com