Координаты 2D: локальные, мировые и экранные
Как переместить дочернюю Entity в нужную точку при повороте родителя и движении камеры.
Выбрать систему координат
| Значение | Что означает |
|---|---|
| Position | Позиция относительно родительского CanvasItem при обычном наследовании трансформации |
| GlobalPosition | Позиция в пространстве текущего canvas; это не пиксели экрана |
| ToGlobal(point) | Точка из локальных координат этой Entity в пространство canvas |
| ToLocal(point) | Обратное преобразование для этой Entity |
| GetGlobalTransformWithCanvas() | Трансформация из локальных координат в координаты Viewport, включая canvas и камеру |
Для обычного дочернего Entity2D мировая трансформация получается умножением трансформации родителя на локальную. Нельзя заменять это сложением Position: поворот, масштаб и skew меняют результат. Начало 2D направлено вправо по X и вниз по Y; Rotation задаётся в радианах, RotationDegrees — в градусах.
Поставить дочерний объект под курсор
В полном примере ниже Marker — ребёнок Rig. Обработчик получает позицию мыши в пространстве canvas через Rig.GetGlobalMousePosition(), затем переводит её в локальные координаты Rig. Результат присваивается Marker.Position. Альтернатива для этого же canvas — присвоить Marker.GlobalPosition напрямую.
Не используйте Marker.ToLocal(target) для вычисления Marker.Position: тогда вы получите координаты относительно самой метки, а не её родителя. Не передавайте InputEventMouseButton.Position прямо в GlobalPosition: координаты события принадлежат Viewport, и камера может их преобразовывать.
Запустить пример
Нужен C#-проект Reckfell.NET.Sdk 1.0.0, net10.0. Сохраните два файла из руководства по рисованию, соберите проект и прикрепите CanvasExample.cs к корню Entity2D уровня .rscene. Запустите уровень; белая метка появляется в canvas-точке (88, 48), синяя — в точке HUD (24, 24). Щелчок левой кнопкой вне перехватывающего ввод интерфейса переносит белую метку.
CanvasExample.cs
using Reckfell;
public partial class CanvasExample : Entity2D
{
public Entity2D Rig { get; private set; } = null!;
public LocalMarker Marker { get; private set; } = null!;
public Camera2D Camera { get; private set; } = null!;
public CanvasLayer Hud { get; private set; } = null!;
public LocalMarker HudMarker { get; private set; } = null!;
public override void _Ready()
{
Rig = new Entity2D { Position = new Vector2(64, 48) };
AddChild(Rig);
Marker = new LocalMarker { Position = new Vector2(24, 0) };
Rig.AddChild(Marker);
Camera = new Camera2D { Position = new Vector2(160, 120) };
AddChild(Camera);
Camera.MakeCurrent();
Hud = new CanvasLayer { Layer = 1, FollowViewportEnabled = false };
AddChild(Hud);
HudMarker = new LocalMarker { Position = new Vector2(24, 24) };
HudMarker.SetStyle(8, new Color(0.25f, 0.45f, 1));
Hud.AddChild(HudMarker);
var label = new Label { Position = new Vector2(42, 12), Text = "HUD" };
Hud.AddChild(label);
}
public override void _UnhandledInput(InputEvent input)
{
if (input is InputEventMouseButton { Pressed: true, ButtonIndex: MouseButton.Left })
Marker.Position = Rig.ToLocal(Rig.GetGlobalMousePosition());
}
}Проверить расчёт без камеры
У Rig.Position=(64,48), Marker.Position=(24,0). При единичном масштабе и нулевом повороте Marker.GlobalPosition=(88,48). Если Rig.Scale=(2,3), Rig.Rotation=Pi/2, результат — (64,96). ToLocal(ToGlobal(point)) возвращает исходную точку с погрешностью float, если трансформация обратима.
Нулевой масштаб делает обратное преобразование вырожденным. Не используйте его как способ скрыть объект; меняйте Visible. TopLevel=true отключает наследование трансформации родительского CanvasItem, но не превращает координаты в экранные и не заменяет CanvasLayer для HUD. При переключении режима явно задавайте нужную трансформацию.
Если объект попадает не туда
- Ошибка зависит от поворота родителя: смешаны локальные и мировые координаты.
- Ошибка зависит от камеры: координаты Viewport присвоены как мировые.
- HUD и мир дают разные точки: они находятся в разных canvas. Преобразовывайте через соответствующий CanvasItem, а не через случайную Entity.
- Изменение Position не решает проблему интерфейса: Control с anchors и контейнерами требует правил UI, а не только Entity2D.