Recursive AI

Drawing in 2D

All onegame docs

A 2D game draws from view code: an entity that implements IView2D gets a Draw(Draw2D d) call every frame, and what it draws appears under the UI. No render stage of your own. View code reads state and never writes it (assigning a synced property in Draw is a compile error).

C#
using System.Numerics;
using OneGame.Core;
using OneGame.Host;
using OneGame.UI;
 
[Entity]
public partial class Ship : Entity, IView2D
{
    [Synced] public partial Fixed X { get; private set; }
    [Synced] public partial Fixed Y { get; private set; }
 
    [View]
    public void Draw(Draw2D d) =>
        d.Sprite("art/ship.png", new Vector2(X.ToSingle(), Y.ToSingle()), scale: 2, tint: Color.White);
}

The path is loaded once through the game's asset service and cached, so calling it every frame is cheap, and an edited file shows up while the game runs (assets.md). World units are pixels at zoom 1: +X right, +Y down, rotation in radians, clockwise.

Sprites, shapes, text

Every call takes an optional z (higher draws later); ties keep call order. Sprites take rotation, scale, tint, origin (a fraction of the sprite, default its centre) and flip.

C#
[View]
public void Draw(Draw2D d)
{
    d.FillRect(new Rect(-400, 16, 800, 24), Color.Rgb(0x35506b));
    d.StrokeRect(new Rect(-400, 16, 800, 24), Color.Rgb(0x8fb8de), 2);
    d.Line(new Vector2(-120, 16), new Vector2(-120, -40), Color.Rgb(0xd0d0d0), 2);
    d.FillCircle(new Vector2(60, -8), 8, Color.Rgb(0xf2c14e));
    d.StrokeCircle(new Vector2(60, -8), 12, Color.Rgb(0xf2c14e), 1.5f);
 
    d.Text("hero", new Vector2(0, -24), 12, Color.White, new Vector2(0.5f, 1)); // in the world, anchored bottom-centre
    d.ScreenText("walk with Go", new Vector2(8, 8), 14, Color.Rgb(0xdddddd));     // in window pixels, for a HUD
}

Text is one line, unrotated, in the game's fonts. For anything that wraps or reacts to clicks, use the UI (ui-guide.md). Sprites can also come from a texture you already hold, a pixel rectangle of it, or a hand-built Sprite (d.Sprite(in Sprite)).

Sprite sheets and clips

A sheet cuts one picture into equal frames; clips name runs of frames and play against view time (d.Time), so animation is smooth in play, identical in captures and replays, and frozen while the sim is paused.

C#
private SpriteSheet? _sheet;
 
[View]
public void Draw(Draw2D d)
{
    if (_sheet is null)
    {
        _sheet = SpriteSheet.Grid(d.Texture("art/hero.png", TextureFilter.Nearest), 16, 16);
        _sheet.AddClip("walk", 0, 4, 8);   // first frame, count, frames per second
        _sheet.AddClip("idle", 4, 2, 2);
    }
 
    var at = new Vector2(X.ToSingle(), 0);
    d.Sprite(_sheet, Speed == Fixed.Zero ? "idle" : "walk", at, scale: 2,
        flip: Speed < Fixed.Zero ? SpriteFlip.Horizontal : SpriteFlip.None, z: 1);
}

d.Sprite(sheet, frame, ...) draws a single frame by number. SpriteSheet.Frames(texture, rects) takes explicit rectangles instead of a grid. An unknown clip name throws UnknownClipException. To play a clip from a moment, pass clipTime: d.Time - startedAt.

The camera

d.Camera is a rig that every Draw2D call shares:

C#
d.Camera.Zoom = 2;                                 // physical pixels per world unit
d.Camera.Bounds = new Rect(-400, -300, 800, 600);  // keeps the view inside this world rectangle
d.Camera.Smoothing = 0.15f;                        // follow time constant in seconds; 0 = exact
d.Follow(new Vector2(X.ToSingle(), 0));            // centre on a point (smoothed)
var world = d.ScreenToWorld(new Vector2(320, 200)); // a pixel to a world point
var pixel = d.WorldToScreen(new Vector2(60, -8));   // and back

d.Camera.Snap(position) jumps without smoothing; Rotation turns the view. Smoothing depends on how many frames were drawn, so keep it at 0 where captures must repeat exactly.

Dropping down

When the happy path is not enough, the layers underneath are open. d.Scene is the frame's Scene2D, d.Layer the layer new sprites go on, and d.Scene.AddLayer(name, parallax) makes a scrolling background layer. d.Renderer is the Renderer2D, for textures made from pixels (CreateTexture), tile maps, custom pixel-shader materials and post-processing. Draw them from a render stage's DrawOverUi, alongside the entities' Draw calls. The engine-side contracts are in docs/interfaces/render2d.md.

Look at it

onegame capture --out shots/main.png --size 720p --after 30 --json

Captures and the scenario capture expectation include everything drawn here. The control operation draw_view reports the last frame's sprite and text counts, the tick, and the camera, and converts between screens and worlds: draw_view with {"screen":[160,100]} returns the world point under that pixel, {"world":[60,-8]} the pixel of a world point. tests/Draw2DGame is a worked example: a walking hero with a clip, a follow camera, shapes and text, and a scenario with capture references.