OpenBone Runtime for Unity
Play OpenBone skeletal animations and interactive state machines in Unity 2021.3 or newer.
- Importer for
.obone(JSON) and.obonb(binary): drop the file inAssets/, get anOpenBoneAsset. Images and atlas pages embedded in the file become textures; others are found next to the file (theirsrcpath, or<name>.pngbeside it or in animages/folder). - OpenBone Animation component: play and queue animations, cross-fades, skins; animation events, starts and completions as UnityEvents.
- OpenBone State Machine component: inputs (bool, number, string, trigger) from code or the inspector, pointer listeners fed by the EventSystem (old or new Input System) or by mouse polling, events and state changes as UnityEvents, scripts (JavaScript, run by Jint).
- Mesh generator with the four blend modes, clipping masks (stencil), two-colour tint, premultiplied atlas pages, particles and shape strokes, in one mesh per skeleton (a submesh per texture/blend run). Shader
OpenBone/Skeletonis unlit and works in the built-in pipeline and URP. - OpenBone Audio component: plays the runtime’s audio commands (play, stop, fade) through pooled AudioSources with the document’s buses and master volume.
The posing, constraints, particles, text templates, state machines and audio commands come from OpenBone.Runtime, the C# port of the reference runtime, which passes the cross-language conformance suite (runtimes/csharp).
Status: tested in Unity 6 (6000.6.8f1, built-in render pipeline) on Linux with every OpenBone example: import, state machines, rendering, pointer input and sound. Unity 2021.3–2023 and URP are compile-checked only; please report what breaks.
Install
Section titled “Install”Package Manager → + → Add package from git URL…, then one of:
https://github.com/UXCentury/open-bone.git#upm # latest releasehttps://github.com/UXCentury/open-bone.git#upm/v0.2.0 # a specific versionor add it to Packages/manifest.json:
"com.openbone.runtime": "https://github.com/UXCentury/open-bone.git#upm/v0.2.0"The upm branch holds the released package with its prebuilt DLLs (CI builds it from runtimes/csharp for every
release). The package is also on OpenUPM
(openupm add com.openbone.runtime), and each GitHub release has com.openbone.runtime-<version>.tgz attached
(Add package from tarball…).
The DLLs in Plugins/:
| DLL | What | Licence |
|---|---|---|
OpenBone.Runtime.dll |
the runtime (netstandard2.1) | MIT |
OpenBone.Scripting.Jint.dll |
script host | MIT |
Jint.dll |
JavaScript interpreter | BSD-2-Clause |
Esprima.dll |
JavaScript parser used by Jint | BSD-3-Clause |
From source
Section titled “From source”The package folder in the repository (runtimes/csharp/unity/com.openbone.runtime) has no DLLs; build them first:
-
Install the .NET 8 SDK.
-
From the repository root:
Terminal window python3 runtimes/csharp/unity/build-package.py --packThis builds the DLLs into
runtimes/csharp/unity/com.openbone.runtime/Plugins/, writes the.metafiles and createsruntimes/csharp/unity/com.openbone.runtime-<version>.tgz. (--out DIRassembles the complete package intoDIRinstead, leaving the source tree untouched: what CI publishes.) -
In Unity: Window → Package Manager → + → Add package from tarball… and pick the
.tgz(or Add package from disk… and pickcom.openbone.runtime/package.jsonto work on the package in place).
- Put
hero.obone(and its images, unless they are embedded) inAssets/. - Create a GameObject, add OpenBone → OpenBone Animation (or OpenBone State Machine), and set Asset to the imported file.
- Set Start Animation (animation component) or State Machine (state-machine component), and Pixels Per Unit (skeleton units per Unity unit, default 100).
- Add OpenBone → OpenBone Audio to the same object to hear the document’s sounds.
- Point an orthographic camera at
StageBounds(the document’s stage, as framed in the OpenBone editor):cam.transform.position = b.center + Vector3.back * 10; cam.orthographicSize = b.extents.y;.
using OpenBone.Unity;using UnityEngine;
public class Hero : MonoBehaviour{ public OpenBoneAnimation anim; public OpenBoneStateMachine machine;
void Start() { anim.Play("walk"); // loop on track 0 anim.Queue("jump", loop: false); // after walk completes anim.onEvent.AddListener((name, i, f, s) => Debug.Log($"event {name}"));
machine.SetBool("walk", true); machine.Fire("attack"); machine.onStateChanged.AddListener((layer, state) => Debug.Log($"{layer}: {state}")); }}The full runtime is available underneath: component.Instance (OpenBoneInstance), component.Skeleton (bones, slots, FindBone, SetSkin, SetText), OpenBoneStateMachine.Machine (StateMachineInstance). BoneWorldPosition("hand") gives a bone’s world position (to attach weapons, effects…).
Pointer input
Section titled “Pointer input”With Pointer Source = EventSystem (default) the component receives IPointerDown/Up/Enter/Exit/MoveHandler callbacks: the scene needs an EventSystem, the camera a Physics 2D Raycaster (or Physics Raycaster with a 3D collider), and the object a collider; a BoxCollider2D around the skeleton is added at start if it has none. The runtime then hit-tests the listeners’ slots itself (bounding boxes, else the attachment’s outline), exactly like the other runtimes. MousePolling reads the legacy Input manager’s mouse instead (no EventSystem; not available when only the new Input System is enabled). None leaves it to you: machine.Pointer("down", worldPosition).
Loading at runtime
Section titled “Loading at runtime”var bytes = File.ReadAllBytes(path); // .obone or .obonbvar asset = OpenBoneAsset.Create(bytes, new Dictionary<string, Texture2D> { ["img_head"] = headTexture });component.asset = asset;component.Initialize();Texture keys are image ids, or atlas page ids for packed documents.
Rendering notes
Section titled “Rendering notes”- Vertex colours are straight (not premultiplied); the shader outputs premultiplied colour, so the blend modes are: normal
One, OneMinusSrcAlpha; additiveOne, One; multiplyDstColor, OneMinusSrcAlpha; screenOne, OneMinusSrcColor. Atlas pages packed with premultiplied alpha are marked by the importer and divided back in the shader. - Clipping uses stencil value 1 (written by the mask, tested by the clipped run, cleared after it). The camera’s target needs a stencil buffer (the default).
- Two-colour tint:
dark + (light − dark) × textureper channel; without a dark colour it is the usual multiply. - Text attachments are drawn with legacy
TextMeshchildren on top of the skeleton mesh, in Unity’s font. For TextMesh Pro, turn Render Text off and drawDrawItem.Text(content, size, alignment, colour, matrix) from theDrawListReadyevent.
Scripts
Section titled “Scripts”State-machine scripts run in Jint with the same ob API, hooks (onStart, onUpdate, onPointer, onEvent) and limits (16 ms per call, 50 ms to load, 8 MB) as the web runtimes. Override CreateScriptHost() to change the limits, or return null to turn scripts off. Runtime/link.xml keeps Jint from being stripped in IL2CPP builds (copy it into Assets/ if your Unity version ignores link.xml files in packages).
Not done / known limits
Section titled “Not done / known limits”- Not yet run in the Unity Editor or a player build (see Status above).
- Embedded audio: WAV only (decoded at runtime); MP3/OGG need the file next to the document so Unity imports it as an AudioClip.
- Text is drawn above the skeleton (no interleaving with draw order) and with Unity’s font metrics.
- No inspector preview of animations (the component shows the setup pose in edit mode).
OpenBone is MIT licensed and made by UXCentury. The example art, voices and sounds are credited in the repository.