reckfellENGINE

Координаты 2D: локальные, мир, viewport и HUD

Преобразование точек, масштаб, камера, ввод и координаты HUD без двойных смещений.

На этой странице

Преобразование координат нужно, когда точка приходит из другого пространства: щелчок во viewport, смещение дула внутри оружия или метка игрового объекта на HUD. Простого сложения и вычитания позиций недостаточно при повороте, масштабе или работе камеры.

Определите исходное и конечное пространство

ПространствоЧто означаетДля чего нужно
ЛокальноеОтносительно начала координат и осей самого элементаТочки крепления спрайта, локальное рисование и геометрия.
РодителяЛокальные координаты родителяEntity2D.Position размещает здесь начало координат дочернего объекта.
Canvas / мирКоординаты в canvas элемента с учётом предков CanvasItemEntity2D.GlobalPosition, ToGlobal и ToLocal. У разных CanvasLayer могут быть разные canvas.
Логический viewport, этап canvasПосле преобразования основного canvas или соответствующего CanvasLayerGetGlobalTransformWithCanvas и MakeCanvasPositionLocal. Этот этап используется в примере ниже.
Окно / экранПосле дополнительных преобразований показа, растяжения и встраиванияНе смешивайте позиции курсора или окна ОС с логическими координатами viewport из примера.

В обычных координатах 2D ось +X направлена вправо, +Y — вниз. Положительный поворот на четверть оборота переводит Right в Down. После масштабирования логическая единица не обязательно равна физическому пикселю дисплея.

Преобразуйте точку в иерархии

item.ToGlobal(localPoint) применяет глобальное преобразование элемента. item.ToLocal(canvasPoint) применяет обратное аффинное преобразование. «Глобальное» здесь относится к canvas элемента: это не координаты экрана ОС и не результат преобразования canvas камерой Camera2D.

Например, у родителя столбцы базиса (0, 2) и (-3, 0), начало координат (100, 50). Position дочернего объекта — (10, 4). Его GlobalPosition равна (88, 70), а не (110, 54): на смещение действуют поворот и масштаб родителя. Локальная точка дочернего объекта (2, 1) переходит в точку canvas (85, 74). ToLocal возвращает её обратно.

Чтобы разместить начало координат дочернего объекта в известной точке canvas, присвойте GlobalPosition. ToGlobal принимает точку, уже заданную локально относительно самого элемента; передача его Position обычно повторно применяет дочернее смещение.

В пределах одного canvas точка из A переводится в B выражением b.ToLocal(a.ToGlobal(pointInA)). Для разных CanvasLayer одного viewport используйте помощник ниже, учитывающий преобразование canvas.

Переведите ввод viewport или точку мира в координаты HUD

Сохраните помощник в Coordinates2D.cs:

C#
using System;
using Reckfell;

public static class Coordinates2D
{
    public static Vector2 LocalToViewport(CanvasItem item, Vector2 point)
    {
        RequireTree(item);
        return item.GetGlobalTransformWithCanvas() * point;
    }

    public static Vector2 ViewportToLocal(CanvasItem item, Vector2 point)
    {
        RequireTree(item);
        return item.GetGlobalTransformWithCanvas().AffineInverse() * point;
    }

    public static Vector2 BetweenItems(CanvasItem source, CanvasItem target, Vector2 point)
    {
        RequireTree(source);
        RequireTree(target);
        if (source.GetViewport() != target.GetViewport())
            throw new ArgumentException("Items must belong to the same Viewport.");
        return ViewportToLocal(target, LocalToViewport(source, point));
    }

    private static void RequireTree(CanvasItem item)
    {
        if (!item.IsInsideTree())
            throw new InvalidOperationException("Add the item to the tree first.");
    }
}

Вызовите Coordinates2D.ViewportToLocal(item, viewportPoint) для точки в логическом viewport элемента. Встроенный метод item.MakeCanvasPositionLocal(viewportPoint) выполняет соответствующее преобразование. MakeInputLocal возвращает преобразованное событие ввода, оставляя исходное без изменений. Не преобразуйте позицию повторно, если обработчик уже получает локальные координаты элемента, как при GUI-вводе Control.

Для метки HUD вызовите Coordinates2D.BetweenItems(worldItem, hudParent, pointInWorldItem). Результат можно присвоить Position метки, если она задаётся относительно hudParent. Помощник учитывает преобразования предков обоих элементов и их canvas/слоёв. Оба элемента должны находиться в дереве и одном viewport. У Control внутри Container позицию может перезаписать раскладка; для меток выберите подходящего родителя со свободным позиционированием.

Если CanvasTransform viewport масштабирует в 2 раза и смещает на (7, 11), точка canvas (85, 74) из примера становится (177, 159). Слой HUD с CanvasLayer.Offset (20, 30) и родителем в (5, 7) переводит её в локальную точку родителя HUD (152, 122). Вычитание только позиции камеры пропустило бы масштаб и преобразование HUD.

Помощник работает на этапе canvas. Он не переводит точку экрана ОС или другого SubViewport через преобразования окна, растяжения и встраивания. Учитывайте конечные преобразования показа отдельно: см. масштабирование окна.

Различайте точки, направления и обратные преобразования

Transform2D хранит столбцы базиса X и Y и смещение Origin. transform * point учитывает Origin. transform.BasisXform(direction) исключает перенос, но сохраняет действие масштаба и скоса; нормализуйте результат, только если нужно единичное направление.

parentTransform * childTransform сначала применяет преобразование дочернего объекта, затем родителя. Обратный порядок даёт другой результат.

Для базиса с масштабом или скосом используйте AffineInverse. Inverse и обратный порядок операндов point * transform предполагают ортонормированный базис; это не универсальная замена обращения масштабированной матрицы. У BasisXformInv то же ограничение. Для направления с масштабом используйте transform.AffineInverse().BasisXform(direction).

Для единичной матрицы в C# используйте Transform2D.Identity. Вызов new Transform2D() без аргументов и default(Transform2D) дают нулевые компоненты, а не единичный базис.

Базис должен быть обратимым. Нулевой масштаб по оси схлопывает измерение; C#-метод AffineInverse выбрасывает InvalidOperationException при определителе, точно равном нулю. Почти нулевой масштаб способен усилить погрешность даже при ненулевом определителе. Избегайте вырожденных и неконечных преобразований вместо подстановки выдуманной точки при ошибке.

Найдите причину смещения

СимптомВероятная причина
До изменения зума камеры всё правильноСмешаны координаты canvas/мира и логический ввод viewport.
Ошибка только у повёрнутого или масштабированного родителяВместо преобразования использовано вычитание позиций.
Метка HUD неправильно следует за камеройМир и HUD находятся в разных canvas; переводите точку через общий viewport.
Смещение применено дваждыPosition передана в ToGlobal самого элемента либо уже локальный ввод преобразован повторно.
TopLevel-объект продолжает реагировать на камеруTopLevel отключает наследование преобразования родительского CanvasItem, но не преобразование canvas.
Помощник отклоняет элементыДобавьте их в дерево и проверьте, что viewport совпадает.

Пример проверен в движке численно: обращение масштаба/скоса, иерархия, преобразования canvas/слоёв, позиции в копии события ввода и поведение TopLevel. Координаты ОС/DPI, вложенные окна, доставка настоящего указателя и сочетания конечных преобразований показа не входят в проверенный охват этого примера.

Документация Reckfell EngineНаверх ↑
БЫСТРЫЙ ПЕРЕХОДНачало работыEntity и иерархияC# & .NET 10Сцены и префабы
Руководства и полный API Reckfell K
reckfellENGINE
RECKFELL ACCOUNT

Вход в аккаунт

Войдите в аккаунт Reckfell.

Один аккаунт для всей платформы Reckfell.