Своя отрисовка 2D: _Draw и QueueRedraw
Нарисовать метку, обновлять её только при изменении данных и правильно применять трансформацию.
Полный пример без текстур
LocalMarker рисует круг и две линии в собственных координатах. Центр — Vector2.Zero; Position самой Entity перемещает готовый рисунок. DrawCircle не создаёт физическое тело, область клика или Control: взаимодействие задаётся отдельно.
Нужен C#-проект Reckfell.NET.Sdk 1.0.0, net10.0. Сохраните LocalMarker.cs и CanvasExample.cs в проект, выполните dotnet build, создайте уровень с корнем Entity2D, прикрепите CanvasExample.cs и сохраните .rscene. Запустите: видны белая метка мира и синий маркер HUD. Обработчик щелчка в примере переносит белую метку.
LocalMarker.cs
using Reckfell;
public partial class LocalMarker : Entity2D
{
public float Radius { get; private set; } = 12;
public Color Ink { get; private set; } = Colors.White;
public int DrawCount { get; private set; }
public bool SetStyle(float radius, Color ink)
{
if (!float.IsFinite(radius) || radius <= 0 || radius > 256)
return false;
Radius = radius;
Ink = ink;
QueueRedraw();
return true;
}
public override void _Draw()
{
DrawCount++;
DrawCircle(Vector2.Zero, Radius, Ink);
DrawLine(new Vector2(-Radius, 0), new Vector2(Radius, 0), Colors.Black, 2);
DrawLine(new Vector2(0, -Radius), new Vector2(0, Radius), Colors.Black, 2);
}
}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());
}
}Как обновляется рисунок
_Draw вызывается движком для записи команд рисования. Вызывайте DrawCircle и DrawLine внутри _Draw; не переносите их в _Process. Сохранённые команды используются при следующих кадрах: _Draw не является callback каждого кадра.
SetStyle сначала проверяет радиус, затем меняет данные и вызывает QueueRedraw. Это запрос отложенной перерисовки, а не немедленный вызов _Draw. Несколько запросов до обработки объединяются. DrawCount здесь нужен для наблюдения за callback, а не для измерения количества draw calls GPU.
Для анимируемой формы меняйте её данные в _Process с учётом delta и запрашивайте QueueRedraw. Если меняется только Position, Rotation или Scale, трансформация применяется к сохранённым командам: заново генерировать тот же круг не требуется.
Настроить форму
Radius задан в локальных единицах. Масштаб родителя изменяет и круг, и толщину его линий; неравномерный масштаб превращает круг в эллипс. Чтобы метка оставалась постоянного размера в HUD, держите её в независимом CanvasLayer с подходящим масштабом.
Положительная width в DrawLine задаёт толщину. Отрицательная ширина использует другой режим линии, который иначе ведёт себя при масштабировании. Пример явно задаёт 2. Сглаживание и чёткость тонких линий зависят от выбранного режима, координат и растяжения Viewport; этот пример не настраивает общий AA проекта.
Если изменения не видны
- Поменяли поле Radius или Ink напрямую: свойства закрыты для записи; используйте SetStyle, который вызывает QueueRedraw.
- SetStyle вернул false: радиус должен быть конечным и находиться в (0,256]. Это ограничение примера.
- Вызвали QueueRedraw вне дерева: запрос не сохраняется как бесконечная очередь. При добавлении видимого CanvasItem движок организует начальную отрисовку.
- Ждёте новые пиксели сразу после вызова: дождитесь кадра. Callback, отправка и отображение — разные этапы.
- Вызываете QueueRedraw из _Draw: перенесите изменение состояния наружу. Не стройте цикл перерисовки внутри callback.