Unity UI Toolkit での座標変換

初めに

UI Toolkit での座標変換が分かりづらかったのでまとめました。

修正:2025/08/12 一部の関数で、引数が Vector3 になっていたのを Vector2 に変更

修正:2026/03/21 スクリーンサイズとパネルサイズが一致していない場合に正しくないため変更

修正:2026/04/26 スクリーンサイズとパネルサイズが一致していない場合にまだ正しくないため変更

参考

https://discussions.unity.com/t/is-there-runtimepanelutils-paneltoscreen/911004/6

https://light11.hatenadiary.com/entry/2023/05/10/190035

サンプルコード


using UnityEngine;
using UnityEngine.UIElements;

namespace Assets.Scripts
{
    internal static class Extensions
    {
        public static Vector2 FlipY(this Vector2 position)
        {
            position.y = Screen.height - position.y;
            return position;
        }

        public static Vector2 PanelWorldToPanelLocal(this VisualElement ve, Vector2 panelWorldPos)
        {
            return ve.WorldToLocal(panelWorldPos);
        }

        public static Vector2 PanelLocalToPanelWorld(this VisualElement ve, Vector2 panelLocalPos)
        {
            return ve.LocalToWorld(panelLocalPos);
        }

        public static Vector2 ScreenToPanelLocal(this VisualElement ve, Vector2 screenPos)
        {
            var panelWorldPos = ve.ScreenToPanelWorld(screenPos);
            return ve.PanelWorldToPanelLocal(panelWorldPos);
        }

        public static Vector2 PanelLocalToScreen(this VisualElement ve, Vector2 panelLocalPos)
        {
            var panelWorldPos = ve.PanelLocalToPanelWorld(panelLocalPos);
            return ve.PanelWorldToScreen(panelWorldPos);
        }

        public static Vector3 PanelWorldToWorld(this Camera camera, VisualElement ve, Vector2 panelWorldPos)
        {
            var screenPos = ve.PanelWorldToScreen(panelWorldPos);
            return camera.ScreenToWorldPoint(screenPos);
        }

        public static Vector2 ScreenToPanelWorld(this VisualElement ve, Vector2 screenPos)
        {
            return ve.panel.ScreenToPanelWorld(screenPos);
        }

        public static Vector2 ScreenToPanelWorld(this IPanel panel, Vector2 screenPos)
        {
            var screenPosUITK = screenPos.FlipY();

            var panelRect = panel.visualTree.worldBound;

            float panelX = Mathf.Lerp(
                panelRect.xMin,
                panelRect.xMax,
                screenPosUITK.x / Screen.width
            );

            float panelY = Mathf.Lerp(
                panelRect.yMin,
                panelRect.yMax,
                screenPosUITK.y / Screen.height
            );

            return new Vector2(panelX, panelY);
        }

        public static Vector2 PanelWorldToScreen(this VisualElement ve, Vector2 panelWorldPos)
        {
            return ve.panel.PanelWorldToScreen(panelWorldPos);
        }

        public static Vector2 PanelWorldToScreen(this IPanel panel, Vector2 panelWorldPos)
        {
            var panelRect = panel.visualTree.worldBound;

            float screenPosUITKX = Mathf.Lerp(
                0f,
                Screen.width,
                panelWorldPos.x / panelRect.width
            );

            float screenPosUITKY = Mathf.Lerp(
                0f,
                Screen.height,
                panelWorldPos.y / panelRect.height
            );

            return new Vector2(screenPosUITKX, screenPosUITKY).FlipY();
        }

        public static Vector2 WorldToPanelWorld(this Camera camera, VisualElement ve, Vector3 worldPos)
        {
            return camera.WorldToPanelWorld(ve.panel, worldPos);
        }

        public static Vector2 WorldToPanelWorld(this Camera camera, IPanel panel, Vector3 worldPos)
        {
            // 内部でエディタモード用の関数を使っているので不可。プレイモードにおいて改善したという話は出てくるものの、一次情報は見つからなかった
            //return RuntimePanelUtils.CameraTransformWorldToPanel(ve.panel, worldPos, camera);

            // World → Screen
            var screenPos = camera.WorldToScreenPoint(worldPos);//透視投影/平行投影はここで対応されるので考慮不要

            // カメラの背面にある場合は無効
            if (screenPos.z < 0)
                return Vector2.negativeInfinity;

            return panel.ScreenToPanelWorld(screenPos);
        }

        public static Vector3 PanelLocalToWorld(this Camera camera, VisualElement ve, Vector2 palenLocalPos)
        {
            var panelWorldPos = ve.PanelLocalToPanelWorld(palenLocalPos);
            return camera.PanelWorldToWorld(ve, panelWorldPos);
        }

        public static Vector2 WorldToPanelLocal(this Camera camera, VisualElement ve, Vector3 worldPos)
        {
            var panelWorldPos = camera.WorldToPanelWorld(ve, worldPos);
            return ve.PanelWorldToPanelLocal(panelWorldPos);
        }
    }
}

top

その他の投稿

20260502-01 ローカルLLMを構築
20260425-01 Unity UI Toolkit カスタムコントロールサンプル
20260103-01 Unity Entities Tips
20251231-01 Unity UI Toolkit カスタムコントロールサンプル
20250913-01 Unity アプリで Android 16KB ページサイズ確認
20250912-01 Unity 6 で Editor がちらつく問題
20250906-01 Win10サポート終了 ChromeOS Flex を入れてみてつまづいた所
20250812-02 Unity UI Toolkit でのタップ操作検出
20250812-01 Unity UI Toolkit でマップ画面 with RenderTexture
20250721-01 Unityでカメラが平行投影の場合にScreenToWorldPointがズレる