Releases / v0.69.1
v0.69.1 One Whole Pixel
2026-08-19 Engine Latest
The follow-up to the 2D release. Spritesheets reach the sprites wearing them, a 2D camera can land on whole pixels, a sprite's own numbers can be keyframed, and the panels that had run past the edge of a narrow dock no longer do.
Keyframe what a sprite does with its picture
⏱ Animating ▸ ✚ Property ▸ Sprite ▸ Node.
Sprite ▸ frame says which picture. The new lanes beside it say what this node
does with it:
ppuandsize— squash and stretch.pivotX/pivotY— shift the origin, so a crouch keeps its feet on the ground.flipX/flipY— face the other way on a turn.
Every one of those was already reachable from a script and from no clip, which is the wrong way round in a game where the clip is the character. A whole turn-and-swing is now one clip and none of it needs code.
The flips are stepped, like every other on/off lane: 0 is left alone, 1 is mirrored. An eased flip would turn the sprite round exactly halfway between two keys, at a moment nobody authored.
Turn a node you already have into a Sprite
Inspector ▸ Add Component ▸ Type now offers ▫ Sprite, ▩ Tilemap, ▧ Sprite Batch, ⇄ Nav Link and ▨ Nav Area. They were in ✚ New and not in the Type menu, so the only way to make an existing node into a Sprite was to create a fresh one and move the work across.
Panels that fit a narrow dock
Drag ⚙ Settings or ▦ Model as thin as you like:
- ⚙ Settings ▸ Input wraps. An action with several bindings puts the extra
ones on the next line instead of pushing the ✕ that deletes the action off the
edge. A binding too wide for a whole line —
🖱 Motion x0.006 (hold RMB)— is shortened with the full name on hover. A long action name is shortened the same way rather than taking the row with it. - The collision matrix scrolls sideways. It is one checkbox per pair of layers, so its width is decided by how many layers your project has, not by how wide the panel is.
- ▦ Model ▸ per-slot material fits, including its Surface maps and Retro artefacts sections and the spritesheet cell picker.
- Shortcut keys, the reserved-keys list and the how-to text all wrap.
Icons that mean one thing each
▪ was Capsule and Sprite Batch. ▦ was Model Mesh and Tilemap. A
Reflection Probe wore one glyph in the Inspector and a different one in ✚ New.
Tilemap is ▩, Sprite Batch is ▧, Reflection Probe is ◍, everywhere.
VSCode knows the 2D API
The stub library the extension reads was missing 35 node methods — the whole
2D surface (node:setSprite, node:setCamera2D, node:shake, node:sorting,
node:setParallax, node:tilemap, node:sprites, …) plus a dozen older ones
like node:toWorld, node:lookAt and node:animator. Every call to them was
marked as an error on an unknown field, on the one surface you are looking at
while you write the line. Re-open a project to refresh
.floptle/library/floptle.lua.
The 2D camera can snap to whole pixels
Inspector ▸ Camera 2D ▸ snap to whole pixels, or
node:setCamera2D{ pixelSnap = 32 }. The number is pixels per world unit — the
same one a ▫ Sprite's pixels per unit uses.
A camera that stops between two pixels resamples every sprite in the scene by a fraction of one, and pixel art shimmers along its edges while nothing is moving. Every 2D project ends up writing this by hand. The engine's version snaps what is drawn and keeps the follow's sub-pixel place, so the camera can still creep slower than a pixel a frame instead of sticking.
Fixes
- A spritesheet set from the Assets panel did not reach the sprites already
wearing that texture. The grid lives on the texture — slice the
.pngonce and every material using it inherits the cells — but the only thing that copied it onto a material was the Inspector's material editor, running for the one node you had selected. Every other sprite still believed its texture was a single whole cell, so it drew the entire sheet stretched across its quad and came out sized from the whole image. Re-slicing a texture now re-slices every material that wears it, and a cell past the end of the new grid falls back into range. Opening a scene puts its materials in step too — and says so in the Console, leaving the scene unsaved, because an exported build ships the scene file and a grid corrected only in memory never reaches it. - Clicking a frame in the Material's cell picker did nothing on a ▫ Sprite node. A Sprite carries its own cell — that is what lets one sit on a shared material — and the picker was writing the material's. It now drives the node's, so the control you reach for is the one that draws.
- Selecting a sprite laid a wrongly-sized quad over it. The selection outline was built without the material or the texture size, so it fell back to the authored world size instead of measuring one cell.
camera.pixelsPerUnit()was wrong under an orthographic camera — scaled by the camera's distance from the origin, which has nothing to do with it. A camera 9.33 units back answered 9.33× too small, and onlypixelsPerUnit(1)was usable. An orthographic view is the same height at every distance; the argument is now ignored for one.- Pinning a retro width did not change the framing. The scene was composited into the fixed target while the camera still projected at the panel's aspect, so the picture came out squashed inside the letterbox — defeating the reason to pin a width, which is that the framing stops depending on the window.
- A frame with
hold: 0never appeared. It landed on the same instant as the frame after it and lost the tie. Zero, negative and nonsense now mean one frame; a fraction above zero is kept exactly as written. - A clip file whose name was capitalised resolved to nothing.
Walk.SpriteAnim.ronshowed in Assets, dragged onto a controller state, and played silence — the browser matched the extension loosely and the loader matched it exactly. Extensions are matched case-insensitively everywhere now. - A texture path saved on Windows found its import settings only on Windows. Backslashes are normalised, so pixel art keeps its Pixelated filter when the project moves between machines.
- Importing an Aseprite sheet whose tags name frames it does not have is
refused with a sentence naming the tag, instead of quietly producing a clip
that is short. It happens when the
.jsonand the.pngcome from different exports. - Y-sorted nodes standing at the same height stack the same way after a reload — and after a pooled enemy is destroyed and spawned again. The tiebreak is now left-to-right rather than an internal number.
- A 2D camera following a name that two nodes share picks the same one every frame instead of whichever it met first, so the picture no longer jumps between two places with nothing in the scene changing. The lookup also runs once per frame rather than once per camera.
Nothing to do on upgrade
Scenes, projects, clips and scripts are unchanged. If you had reached for
Sprite ▸ frame in a clip it keeps working exactly as it did; the new lanes sit
beside it.
Floptle Hub
Engine
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.