Custom 2D drawing: _Draw and QueueRedraw
Draw a marker, redraw when its data changes and apply the correct transform.
Complete example without textures
LocalMarker draws a circle and two lines in its own coordinates. Its center is Vector2.Zero; the entity's Position moves the result. DrawCircle does not create a physics body, clickable region or Control. Implement interaction separately.
Use Reckfell.NET.Sdk 1.0.0 and net10.0. Save LocalMarker.cs and CanvasExample.cs in your C# project, run dotnet build, create a level with an Entity2D root, attach CanvasExample.cs and save the .rscene. Run it: a white world marker and a blue HUD marker appear. The sample click handler moves the white marker.
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());
}
}How drawing updates
The engine calls _Draw to record drawing commands. Call DrawCircle and DrawLine inside _Draw, not _Process. Recorded commands are reused on subsequent frames: _Draw is not a callback for every frame.
SetStyle validates the radius, updates data and calls QueueRedraw. This requests a deferred redraw rather than immediately calling _Draw. Requests made before processing are coalesced. DrawCount observes this callback; it does not measure GPU draw calls.
For an animated shape, update shape data in _Process using delta and request QueueRedraw. Changing only Position, Rotation or Scale transforms retained commands; the same circle does not need to be generated again.
Configure the shape
Radius uses local units. Parent scale changes the circle and line widths; nonuniform scale turns the circle into an ellipse. Keep a fixed-size HUD marker in an independent CanvasLayer with an appropriate scale.
Positive DrawLine width specifies thickness. Negative width selects a different line mode with different scaling behavior. The example explicitly uses 2. Antialiasing and thin-line sharpness depend on the drawing mode, coordinates and Viewport stretching; this sample does not configure project-wide AA.
Diagnose missing changes
- Tried to assign Radius or Ink directly: these properties have private setters. Use SetStyle, which requests a redraw.
- SetStyle returned false: radius must be finite and in (0,256]. This is a sample limit.
- Called QueueRedraw outside the tree: the request is not kept in an unlimited queue. Adding a visible CanvasItem schedules its initial drawing.
- Expected changed pixels immediately: wait for a frame. Callback execution, submission and presentation are different stages.
- Called QueueRedraw from _Draw: move state changes outside the callback instead of building a redraw loop inside it.