Skip to content

Engine and platform exports

The Export dialog’s Engines group packages animations for engines and apps as files they already understand. Every export is a ZIP with a README.

Entry Kind What you get
Unity sprite sheet PNG sheet(s), TexturePacker JSON (hash, with pivots), <name>.unity.json and Editor/OpenBoneSpriteSheetImporter.cs, which slices the sheet (custom pivots at the skeleton origin) and creates one .anim clip per animation
Godot 4 sprite sheet PNG sheet(s), a SpriteFrames .tres (one AtlasTexture per unique frame, margin restores trimmed borders; fps and loop per animation) and an AnimatedSprite2D .tscn whose position is the skeleton origin
Flutter sprite sheet or WebView Sheet + JSON + a dependency-free Dart widget (CustomPainter) and a Flame component; or the interactive WebView package below with a webview_flutter controller
Android WebView Kotlin OpenBoneView (wraps WebView, loads from assets/)
iOS WebView Swift OpenBoneView (WKWebView) and a SwiftUI OpenBoneAnimation wrapper
Sprite sheet sprite sheet PNG sheet(s), TexturePacker JSON hash and array, Phaser 3 multi-atlas (for PixiJS, Phaser, Cocos, …)
flowchart LR
    anim[Chosen animations] --> render["renderFrames<br/>(one shared frame size)"]
    render --> sheets["buildSpriteSheets<br/>trim · dedupe · MaxRects"]
    sheets --> meta["engines.ts<br/>Unity · Godot · Flutter · TexturePacker JSON"]
    meta --> zip[ZIP with README]
  • apps/editor/src/export-engines.ts renders each chosen animation with renderFrames. All clips share one frame size: the artboard, or the union of the content bounds over every frame.
  • buildSpriteSheets (packages/export/src/spritesheet.ts) trims transparent borders (offsets kept in spriteSourceSize), stores pixel-identical frames once (aliasOf), and places them with the MaxRects packer from packages/core/src/atlas.ts on as many sheets as needed.
  • The metadata writers in packages/export/src/engines.ts are pure string/object builders, tested in packages/export/test/engines.test.ts.

Frame names are <animation>_0001, …; fps, loop and the origin are in meta.openbone of the JSON files.

These keep state machines, inputs, listeners, scripts and touch by embedding the web player:

Piece What it is
packages/player/src/bundle/webview.ts The entry of a single classic script (openbone-player.js): player, renderers, runtime and the QuickJS sandbox with its .wasm inlined. @openbone/core is aliased to bundle/core-lite.ts, so the op registry (and zod) stay out: about 0.8 MB, 0.33 MB gzipped
pnpm build:player-bundle Writes packages/player/dist/openbone-player.js. The editor does not need it: a Vite plugin (apps/editor/vite.config.ts) builds the same bundle with esbuild into the virtual module virtual:openbone-player-bundle, loaded lazily by the dialog, so exports work offline with no prebuilt file
index.html Made by webViewHtml (packages/export/src/webview.ts); embeds the .obone/.obonb as base64 so it works from file:// (no fetch)
preview.html Drives the page through the bridge from a desktop browser

packages/player/src/bridge.ts connects the page to the host app.

Host → page: OpenBone.receive(command) with one of:

Command Effect
{type: "setInput", name, value} Sets a state-machine input
{type: "fire", name} Fires a trigger
{type: "play", animation?, loop?} Plays an animation
{type: "pause"} Pauses
{type: "setSkin", name} Switches the skin
{type: "useStateMachine", name?} Starts a state machine
{type: "getInputs"} Asks for the current inputs

iframes can send postMessage({openbone: command}).

Page → host: JSON strings {"source": "openbone", "v": 1, "type": …} with ready, event, stateChange, inputs and error, posted to:

Host Channel
Flutter, Android window.OpenBoneHost.postMessage (Flutter channel / Android interface)
iOS webkit.messageHandlers.openbone
iframes parent.postMessage
Any page an openbone-message DOM event

OpenBone is MIT licensed and made by UXCentury. The example art, voices and sounds are credited in the repository.