Unity UI Toolkit での座標変換
初めに
UI Toolkit での座標変換が分かりづらかったのでまとめました。
修正:2025/08/12 一部の関数で、引数が Vector3 になっていたのを Vector2 に変更
修正:2026/03/21 スクリーンサイズとパネルサイズが一致していない場合に正しくないため変更
修正:2026/04/26 スクリーンサイズとパネルサイズが一致していない場合にまだ正しくないため変更
- Unity 6000.4.4f1
参考
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);
}
}
}