Releases / v0.69.0

v0.69.0 Flat Out

2026-08-19 Engine Latest

A release about making 2D games. A sprite node that knows what a pixel is, sorting that puts a character in front of the table they are standing at, sprite animation whose frames can come from anywhere, and a camera that follows.

A sprite is a node now

✚ New ▸ ▦ 2D ▸ ▫ Sprite.

You could always build one out of a Plane and a Material, and every 2D project did — each of them working out the same three things: that the plane is 1.4 units across and not one, that flipping means a negative scale, and that the sheet cell lives on the Material. A Sprite answers all three once, and carries two things a Plane cannot:

  • A size in pixels. pixels per unit measures one cell, not the sheet, so slicing a sheet finer does not resize every sprite on it.
  • A pivot. The origin defaults to the middle; the feet button puts it at the bottom, which is what a Y-sorted character wants.

Lower on the screen draws in front

Set a node's sorting layer to Y, and nodes level on layer and order sort by how far down the screen they are. Walk behind a table and you are behind it; walk in front and you are in front.

The order is sorting layer, then order, then Y. Y is the tiebreak, not a replacement — a drop shadow on order −1 stays under a Y-sorted crowd, and a lantern on order 1 stays in front of whoever is carrying it.

Two things to know:

  • A node's sorting layer and its collision layer are different things. They were both labelled "layer"; they are not any more. (Nor is the third one, the layers a 2D light reaches.)
  • A Y-sorting layer holds about 64 nodes before two of them tie — that is measured, not estimated. Split crowded scenes across sorting layers.

Backgrounds that move less than the world

parallax is a per-axis scroll factor: 1 moves with the world, 0 pins to the camera, 0.3 is a distant range of hills.

A factor rather than a distance, because the other way of getting parallax — putting a layer further back — only works under a perspective camera, and a flat game wants an orthographic one so its pixels hold still.

Sorting and parallax move the picture. Colliders stay where you put them and node.x reads back what you set.

Sprite animation, from any sheet

A frame names its own art, so one animation can use frames from different sheets and from plain PNGs that were never on a sheet at all.

Three ways in:

  • Right-click a sliced texture in Assets ▸ ▦ New sprite animation — one frame per cell, ready to cut down.
  • Right-click an Aseprite .json ▸ ▦ Import Aseprite sheet — the tags become clips, the grid slices the image, and per-frame timing is kept.
  • ⏱ Animating ▸ ✚ Property ▸ Sprite ▸ frame — a lane beside position and rotation, for when the sprite is only part of what is animated.

A sprite animation is an ordinary clip: drop it in a controller and it crossfades, layers, carries events and plays from Lua like anything else.

ron
// hero_walk.spriteanim.ron
(
  fps: 12,
  loop: true,
  cols: 8, rows: 4,
  texture: "art/hero.png",
  frames: [
    (cell: 0),
    (cell: 1),
    (texture: "art/hero_extra.png", cols: 4, rows: 4, cell: 9),
    (cell: 2, hold: 3.0),
  ],
)

Record picks up what you actually changed

● Record used to notice numbers and nothing else, so changing a material's texture with record on wrote no key — which is the one gesture sprite animation is for. It writes one now.

Materials also gained the rest of their surface: opacity, base colour, emissive, specular, rim, roughness, metallic and the rest are all keyable, so fading something out is a lane rather than a script.

The ✚ Property menu is grouped by component, with the fields people reach for at the top.

A camera that follows

Tick 2D camera on an orthographic Camera:

follow the name of a node to chase
dead zone how far it may move before the camera moves at all
smoothing seconds to close the gap — the same at 30 fps and at 144
limits a rectangle the camera stays inside

With no follow target the camera's position is left to whatever else is moving it — a script, a cutscene, your own placement — and only the limits and the shake are applied. So you can drive the camera by hand and still shake it.

lua
node:setCamera2D{ follow = "Player", smoothing = 0.12, deadZoneX = 1.5 }
node:shake(0.4, 0.25)

Shake is added to what is drawn and never fed back into the follow, so it composes with the chase and with the limits instead of fighting them. Calling it again takes the louder amplitude and the longer time, each independently — a bang during a rumble does not cut the rumble short.

All of this applies to an orthographic camera and to nothing else.

Menus that stay short

✚ New is grouped — ▦ 2D, ■ 3D, ▦ Model shape, ● Lighting, Δ World, ⬚ Navigation, 🖼 UI — so adding a node is a glance rather than a scroll.

Panels fit however thin you drag them

Every form panel now shrinks, then wraps, then stacks. Drag the Model, Tiles, Inspector or Settings panel as narrow as you like and nothing goes past the edge.

Smaller things you will notice

  • Drag a .png into the Scene view and you get a Sprite wearing it. It used to do nothing at all.
  • Sorting, parallax and 2D light settings follow a multi-selection, so putting forty tiles on a background layer is one edit.
  • Greyed-out menu entries now say why they are greyed out. They had the explanation written and could not show it.
  • The ✚ Property menu in the Animating tab is grouped by component, and a material's texture and opacity sit at the top rather than in a wall of surface maps.
  • Duplicating or pasting a node keeps its sorting layer, parallax and 2D camera. It used to drop them.

For package authors

  • json.array(t) / json.isArray(v). {} is both an empty list and an empty object and encodes as an object, so there was no way to send [] at all. json.decode now marks the arrays it builds, so reading a body, emptying a list and sending it back keeps it a list.
  • ed.copy(text) puts a string on the clipboard, so a panel can have a copy button. Write only — nothing can read the clipboard — and the editor shows how much was copied.

Upgrading

Nothing to do. Everything here is opt-in: a scene that does not use sorting layers, parallax, sprites or a 2D camera saves exactly the file it saved before.

Floptle Hub

Linux x86_64 7.5 MB Download → sha256 218714bab327d6e13b8d555d73d53b9b8505eeb97de94ce8f33708bd68ad6fcd
macOS Apple Silicon 5.7 MB Download → sha256 5668f4882fca82328d2f2d4ea044bdb8ecd5f2502f6b83397f8858890f42c59d
macOS Intel 6.2 MB Download → sha256 ee2f1f70f437f53d84dbd708a8b8b23d4903f027bf4c2f7ff1904d8042749eeb
Windows x86_64 7.1 MB Download → sha256 87bd32d0fe8d17951c94f99e30ed5b38eba76f1ed011ad7a5dacbc2e70308437

Engine

Linux x86_64 20.2 MB Download → sha256 76c563ac607a40cf554b7f0ddca5bd34441aaf3b5828561a94a854a295f4c847
macOS Apple Silicon 16.3 MB Download → sha256 7a7f6a0579d3a6a50ae8531b15b60f1d1cb49e05f43ef2a4f88265becf03ef87
macOS Intel 17.6 MB Download → sha256 078ff473710df98bcc6426775a11e1d0825279342a99771932019f2c5936afa3
Windows x86_64 19.9 MB Download → sha256 21603cac933ad371edf8dfcc42d8df3498adc4d194a2d1df09510a35dfbe6b99

The binaries are not signed yet. On macOS, right-click the app and pick Open the first time. On Windows, if SmartScreen appears, pick More info then Run anyway.