Skip to main content

Engines

spine-layout is one core behind a narrow Engine interface, with an engine for every Pixi major from 4 to 8. A game picks its Pixi when it creates the engine; the engine then reads a layout's skeletons with the Spine runtime their export version picks, among the runtimes registered for that Pixi. All the skeletons of one layout must be of one Spine version (see One Spine version per layout):

EnginePixiSpine runtimesEntry
pixi8pixi.js v8spine-pixi-v8 4.2 and 4.3; the default entry bundles 4.3@pixijs-userland/spine-layout, @pixijs-userland/spine-layout/pixi8
pixi77.2 or later@pixi-spine/* 4.0.6 runtime-3.7, 3.8 and 4.1 (4.0 through 4.1); spine-pixi-v7 4.2 and 4.3@pixijs-userland/spine-layout/pixi7
pixi66@pixi-spine/* 3.1.2 runtime-3.7, 3.8, 4.0 and 4.1@pixijs-userland/spine-layout/pixi6
pixi55pixi-spine 2.1.14 (3.8) or 2.0.5 (3.7), one per page@pixijs-userland/spine-layout/pixi5
pixi44.8.9pixi-spine 1.6.2 (3.8) or 1.5.23 (3.7), one per page@pixijs-userland/spine-layout/pixi4

SpineLayout and its controllers never import pixi.js or a Spine runtime; they call the engine they are given. A game on any stack gets the same slot naming, state/event animation, texts, skins and pointer bones.

pixi.js and @esotericsoftware/spine-pixi-v8 are optional peer dependencies. The default entry imports both, /pixi8 imports pixi.js alone, and every other entry imports neither: the Pixi 4–7 engines take the PIXI namespace and the Spine runtimes the game loaded as parameters. A Pixi 4–7 build installs neither.

Entry points​

ImportExports
@pixijs-userland/spine-layoutThe Pixi 8 build with spine-pixi-v8 4.3: SpineLayout, pixi8Engine, createPixi8Engine, SpineLayoutCore, ParticlesController, Sounds/sounds, skeletonVersion, isBinarySkeleton, the version helpers of /core, UnsupportedSpineVersionError, Pixi8EngineOptions, Pixi8Runtime, LazyEsotericRuntime, Pixi8Spine, Pixi8Types, the config types and the Engine types
@pixijs-userland/spine-layout/coreEngine-free: SpineLayoutCore, every controller, ManifestParser, Sounds/sounds, skeletonVersion, isBinarySkeleton, UnsupportedSpineVersionError, the config and Engine types, and the version helpers RUNTIME_MATRIX, runtimesOf, PIXI_MAJORS, DEFAULT_PIXI, ONE_RUNTIME_PER_PAGE, JSON_ONLY_MINORS, choosePixi, pickRuntime, pixiMajorsFor, minorOf, compareMinors, UNVERSIONED, PRE_PASSES, applyPrePasses and constraintLists. Imports no rendering stack
@pixijs-userland/spine-layout/pixi8Everything in /core, plus createPixi8Engine, Pixi8EngineOptions, Pixi8Runtime, LazyEsotericRuntime, Pixi8Spine and Pixi8Types. Imports pixi.js and no Spine runtime
@pixijs-userland/spine-layout/pixi7Everything in /core, plus createPixi7Engine, Pixi7Engine, Pixi7EngineOptions, Pixi7Runtime, Pixi7LazyRuntime, Pixi7Namespace, Pixi7Types, LegacyAssetsPort and the Legacy* types
@pixijs-userland/spine-layout/pixi6Everything in /core, plus createPixi6Engine, Pixi6Engine, Pixi6EngineOptions, Pixi6Runtime, Pixi6LazyRuntime, Pixi6AssetsPort and the Pixi6* types
@pixijs-userland/spine-layout/pixi5Everything in /core, plus createPixi5Engine, Pixi5Engine, Pixi5EngineOptions, Pixi5Namespace, Pixi5Spine, Pixi5Types and LegacyAssetsPort
@pixijs-userland/spine-layout/pixi4Everything in /core, plus createPixi4Engine, Pixi4Engine, Pixi4EngineOptions, Pixi4AssetsPort and the Pixi4* / Spine38* types
@pixijs-userland/spine-layout/runtimesesotericRuntime, pixiSpineLegacy, pixiSpineModular and minorOfRuntime, which wrap a runtime's namespace; esotericAdapter, pixiSpineAdapters, runtimeAdapters and lazyRuntime, which bind runtimes to a Pixi; discoverGlobalRuntimes; and their types (SpineRuntime, EsotericRuntime, PixiSpineRuntime, SpineRuntimeAdapter, LazySpineRuntimeAdapter, …). Imports no rendering stack

Every engine is handed to the layout the same way: new SpineLayoutCore({ engine }), or new SpineLayout({ engine }) from the default entry. A game that wraps the layout in a class of its own passes the same { engine }.

Pixi 8​

The default entry is unchanged. new SpineLayout(options) builds on pixi8Engine, whose spine-pixi-v8 4.3 reads every export, whatever its version or none, and warns about nothing:

import { SpineLayout } from '@pixijs-userland/spine-layout';

const layout = new SpineLayout();
await layout.createInstancesFromManifest(manifest, 'spines');

createPixi8Engine builds an engine that reads each export with the runtime its version picks:

Call@pixijs-userland/spine-layout@pixijs-userland/spine-layout/pixi8
createPixi8Engine()The default engine, as pixi8Engine isNot available
createPixi8Engine({ runtimes, pin?, warn? })runtimes, and the bundled 4.3 unless runtimes holds a 4.3 tooruntimes alone
OptionValue
runtimesRequired. spine-pixi-v8 runtimes, one per minor: esotericRuntime(module); a lazy { minor, load } (LazyEsotericRuntime), whose load returns the module or its esotericRuntime; or an adapter (esotericAdapter, lazyRuntime)
pinThe runtime the game reads an export minor with: see Choosing a runtime per skeleton
warnWhere a skeleton read by a runtime other than its own is reported; console.warn by default
import { Application } from 'pixi.js';
import { createPixi8Engine, SpineLayout } from '@pixijs-userland/spine-layout';

const engine = createPixi8Engine({
runtimes: [{ minor: '4.2', load: () => import('spine-pixi-v8-42') }],
});

const app = new Application();
await app.init({ width: 1280, height: 720 });

const layout = new SpineLayout({ engine });
await layout.createInstancesFromManifest(manifest, 'spines');
app.stage.addChild(layout);

spine-pixi-v8-42 is the game's npm alias of @esotericsoftware/spine-pixi-v8@4.2. With a lazy runtime, createInstancesFromManifest returns a promise: the layout loads the runtimes its exports need before it reads any skeleton.

spine-pixi-v8 4.2 and 4.3 both register their render pipe as 'spine', and pixi keeps the first for both. The engine renders each runtime through a pipe of its own, spine-4.2 and spine-4.3, and gives every Spine it creates that renderPipeId. Create the engine before the renderer: a lazy runtime's pipe is reserved then, and the renderer builds it as it starts. A pipe registered after the renderer started is added to it when it first collects one of that runtime's spines. A 4.3 Spine the game creates itself still draws through the shared 'spine' pipe: import spine-layout before any 4.2, or load 4.2 with import(); otherwise the first such spine logs a warning and draws nothing.

On Pixi 8 the runtimes' Assets loaders do not clash: a skeleton in Assets is read by the runtime its export version picks, and each runtime takes an atlas another one's loader parsed.

Pixi 7​

createPixi7Engine(PIXI, options?). PIXI is a pixi.js 7.2 or later namespace: window.PIXI, or import * as PIXI from 'pixi.js' in a bundled game. Another major, or no Spine runtime, throws.

OptionValue
runtimesPixi7Runtimes: one from esotericRuntime or pixiSpineModular for the engine to bind, one bound already (esotericAdapter, pixiSpineAdapters), or a lazy Pixi7LazyRuntime. By default what discoverGlobalRuntimes finds on the page. A runtime RUNTIME_MATRIX[7] does not list is passed over when discovered and refused when given, but for a given runtime-4.0 (below)
particles@pixi/particle-emitter 5, where it is not on PIXI.particles
pinThe runtime the game reads an export minor with
warnWhere a skeleton read by a runtime other than its own, or a runtime left out, is reported; console.warn by default

A script-tag game, its runtimes found on the page:

import { createPixi7Engine, SpineLayoutCore } from '@pixijs-userland/spine-layout/pixi7';

const app = new PIXI.Application({ width: 1280, height: 720 });
const engine = createPixi7Engine(PIXI);

const layout = new SpineLayoutCore({ engine });
app.stage.addChild(layout);

A bundled game, its runtimes passed in:

import * as PIXI from 'pixi.js';
import * as particles from '@pixi/particle-emitter';
import * as base from '@pixi-spine/base';
import * as runtime38 from '@pixi-spine/runtime-3.8';
import * as runtime41 from '@pixi-spine/runtime-4.1';
import * as spine43 from '@esotericsoftware/spine-pixi-v7';
import { createPixi7Engine } from '@pixijs-userland/spine-layout/pixi7';
import { esotericRuntime, pixiSpineModular } from '@pixijs-userland/spine-layout/runtimes';

const pixiSpine = pixiSpineModular(
base,
{ '3.8': runtime38, '4.1': runtime41 },
{ packageVersion: '4.0.6' },
);

const engine = createPixi7Engine(PIXI, {
runtimes: [...pixiSpine, esotericRuntime(spine43)],
particles,
});

Pixi 7 reads 4.0 exports with @pixi-spine/runtime-4.1, after the 4.0 → 4.1 pre-pass, as pixi-spine's own loader does: @pixi-spine/runtime-4.0 4.0.6 turns chain path constraints wrongly, and RUNTIME_MATRIX[7] has no 4.0 runtime. A runtime-4.0 passed in runtimes is left out with a warning. A 4.0 .skel has no reader on Pixi 7; Pixi 6's runtime-4.0 reads one.

The engine reads skeletons from their source, atlas text and page textures: pixi-spine's and spine-pixi-v7's Assets loaders claim each other's files (see Loading on Pixi 4–7).

Pixi 6​

createPixi6Engine(PIXI, options?). PIXI is a pixi.js 6 namespace. Another major, or no Spine runtime, throws.

OptionValue
runtimesPixi6Runtimes, one per minor: from pixiSpineModular or discoverGlobalRuntimes, or a lazy Pixi6LazyRuntime; the engine binds each itself and refuses one bound already. By default the first of each minor the page's scripts loaded
particlespixi-particles 4 or @pixi/particle-emitter 5, where it is not on PIXI.particles
rendererIts interaction manager reports every pointer move, hit or not
loadersLoaders besides PIXI.Loader.shared whose resources count as assets, e.g. an app.loader
pinThe runtime the game reads an export minor with
warnWhere a skeleton read by a runtime other than its own, or a runtime the page loaded twice, is reported; console.warn by default
import { createPixi6Engine, SpineLayoutCore } from '@pixijs-userland/spine-layout/pixi6';

const app = new PIXI.Application({ width: 1280, height: 720 });
const engine = createPixi6Engine(PIXI, { renderer: app.renderer });

const layout = new SpineLayoutCore({ engine });
app.stage.addChild(layout);

@pixi-spine/* 3.1's dist/runtime-*.js scripts read their base from PIXI.spine.base, which dist/base.js does not set: set PIXI.spine.base = PIXI.spine between the two.

Pixi 5​

createPixi5Engine(PIXI, options?). PIXI is a pixi.js 5 namespace with pixi-spine 2.x on it, and pixi-particles 4 on PIXI.particles for particles. Another major, or no pixi-spine, throws.

OptionValue
runtimeThe page's one pixi-spine: 2.1 reads 3.8 exports, 2.0 reads 3.7, and both own PIXI.spine. By default the one there; pixiSpineLegacy(PIXI.spine, { packageVersion }) names its exact version, and discoverGlobalRuntimes finds one kept on a global of its own
rendererIts interaction manager reports every pointer move, hit or not
loadersLoaders besides PIXI.Loader.shared whose resources count as assets, e.g. an app.loader
pinReads the other minor's exports with the page's runtime. Their skins do not survive it: 2.1.14 finds none in a 3.7 export, and 2.0.5 throws on a 3.8 one's
warnWhere such a cross-read is reported; console.warn by default
import { createPixi5Engine, SpineLayoutCore } from '@pixijs-userland/spine-layout/pixi5';

const app = new PIXI.Application({ width: 1280, height: 720 });
const engine = createPixi5Engine(PIXI, { renderer: app.renderer });

const layout = new SpineLayoutCore({ engine });
app.stage.addChild(layout);

Pixi 4​

Load the legacy stack as plain scripts, in this order, so everything lands on window.PIXI:

<script src="pixi.js@4.8.9/dist/pixi.min.js"></script>
<script src="pixi-spine@1.6.2/dist/pixi-spine.js"></script>
<script src="pixi-particles@4.3.0/dist/pixi-particles.min.js"></script>

pixi-spine@1.6.x (npm tag pixi4-spine3.8) is the Spine 3.8 runtime for Pixi v4, and 1.5.23 the 3.7 one. pixi-spine 2.x is for Pixi v5, so a "v4 + Spine 3.8" bundle is 1.6.x. pixi-particles is optional: without it the engine has no particles port.

createPixi4Engine(PIXI, options?). The engine takes the PIXI namespace as a parameter and never resolves pixi.js itself.

OptionValue
rendererIts interaction manager reports every pointer move, hit or not
runtimeThe pixi-spine 1.x to render with, on a page without PIXI.spine: from pixiSpineLegacy or discoverGlobalRuntimes. Where PIXI.spine is loaded too, PIXI.loader reads every skeleton with that one, and readSkeletonFromAssets refuses what it read
pinThe runtime minor the game reads an export minor with, e.g. { '3.7': '3.8' }
warnWhere a skeleton read by a runtime other than its own is reported; console.warn by default
import { createPixi4Engine, SpineLayoutCore } from '@pixijs-userland/spine-layout/pixi4';

const app = new PIXI.Application({ width: 1280, height: 720 });
const engine = createPixi4Engine(PIXI, { renderer: app.renderer });

const layout = new SpineLayoutCore({ engine });
app.stage.addChild(layout);

createPixi4Engine throws when PIXI is not v4, when no runtime is given and PIXI.spine is missing, and when the runtime is not a pixi-spine 1.x whose Spine is a container of this PIXI. The legacy ports add copyFrom to v4's Point and ObservablePoint, which the core uses.

Loading on Pixi 4–7​

The legacy assets port reads, in order: the resources of the shared loader (PIXI.loader on 4, PIXI.Loader.shared on 5 and 6) and of loaders; its own registry; PIXI.Assets on 7; and the texture cache. readSkeletonFromAssets takes the spineData a pixi-spine loader already built from a skeleton .json and its sibling .atlas, if one of the engine's runtimes made it, and otherwise reads the skeleton from its source, atlas text and page textures:

import { ManifestParser } from '@pixijs-userland/spine-layout/pixi4';

PIXI.loader
.add('spines/root.json')
.add('settings/texts.json')
.load((_loader, resources) => {
const manifest = ManifestParser.fromLoaderResources(resources);

layout.createInstancesFromManifest(manifest, 'spines');
});

fromLoaderResources lists every resource under its loader name, and a skeleton pixi-spine parsed with the .atlas and .png aliases the manifest pairing expects. On Pixi 5 and 6 the loader is PIXI.Loader.shared.

Pixi 7 has no loader resources: load into PIXI.Assets. A pixi-spine runtime reads atlas text, so an atlas an Assets loader would parse is loaded with loadParser: 'loadTxt'; an atlas spine-pixi-v7's loader parsed is read by the Esoteric runtime the export picks.

Or from raw data, with no loader:

layout.createInstancesFromDataArray([
{ name: 'root', skeleton: json, atlasText, textures: { 'page.png': texture } },
]);

textures are the engine's Textures by atlas page name. A project without a loader behind it (the editor) can fill the assets port directly with engine.assets.set(alias, value).

Spine version​

A Spine export is read by the runtime of its own editor major.minor. spine-ts does not check the version, so another minor's export throws deep in the reader or loads with data missing. skeletonVersion tells you which a file is:

import { skeletonVersion } from '@pixijs-userland/spine-layout/core';

skeletonVersion(json); // "3.8.99"
skeletonVersion(skelBytes); // "4.3.23"
skeletonVersion(unknown); // undefined

It takes a parsed .json or the bytes of a .skel (ArrayBuffer or Uint8Array), and returns undefined when the file does not say. minorOf gives the major.minor that picks the runtime: 4.1-from-4.2.43 is a 4.1 export.

RUNTIME_MATRIX holds the runtimes each Pixi has, and runtimesOf(pixi) lists them. What reads each export minor, with no pin:

ExportPixi 4Pixi 5Pixi 6Pixi 7Pixi 8DEFAULT_PIXI
3.7pixi-spine 1.5.23pixi-spine 2.0.5runtime-3.7runtime-3.7—4
3.8pixi-spine 1.6.2pixi-spine 2.1.14runtime-3.8runtime-3.8—4
4.0——runtime-4.0runtime-4.1 after 4.0->4.14.2 after 4.0->4.1 and 4.1->4.27
4.1——runtime-4.1runtime-4.14.2 after 4.1->4.27
4.2———spine-pixi-v7 4.2spine-pixi-v8 4.28
4.3———spine-pixi-v7 4.3spine-pixi-v8 4.38

runtime-* is @pixi-spine/runtime-*, 3.1.2 on Pixi 6 and 4.0.6 on Pixi 7. Pixi 8 reads 4.0 and 4.1 only with its 4.2 runtime registered. A pre-pass rewrites JSON, so where one is named a .skel of that minor has no reader. 3.7 is JSON only on every Pixi (JSON_ONLY_MINORS), and Pixi 4 and 5 load one pixi-spine per page (ONE_RUNTIME_PER_PAGE). pixiMajorsFor(minor) lists the Pixi majors that read a minor.

One Pixi major is live per page. The layout's prototype chain is linked to the container class of the engine built last, and the Pixi 4–7 scripts all write window.PIXI, so a page cannot run layouts on two Pixi majors at once. Several runtimes on one Pixi are fine.

One Spine version per layout​

The exports of one layout must all be of one Spine major.minor. Patch versions of that minor mix freely, and so do exports that name no version. A layout given skeletons of two minors builds nothing: createInstancesFromManifest, createInstancesFromDataArray and their load* forms throw MixedSpineVersionsError, whose filesByMinor lists each minor's files, as its message does. choosePixi throws the same error for a folder that mixes them, so a tool can show it before it builds anything. Re-export the odd skeletons from the Spine editor of the layout's version.

oneSpineVersion(files) is the check on its own: it returns the one minor, or undefined when no file names a version.

Different layouts on one page may each be of a different version, where the engine has both runtimes (Pixi 8 with 4.2 and 4.3, Pixi 7 with 3.8 and 4.3): each skeleton is still read by its own runtime.

Choosing a runtime per skeleton​

Every engine reads a skeleton with the runtime pickRuntime gives its export version, in this order:

StepRuntimeReported
PinThe registered runtime pin names for the export's minor, reading the export as it isA warning naming what the cross-read loses
ExactThe runtime of the export's own major.minorNothing
Pre-passThe nearest newer runtime the JSON pre-passes reach, after those pre-passesA warning naming the pre-passes
SkipNone: UnsupportedSpineVersionErrorThe layout logs [SpineLayout] Skipped spine "<id>": … and builds the rest

The pre-passes, PRE_PASSES, chain: a 4.0 export reaches a 4.2 runtime through both. They rewrite the parsed JSON (applyPrePasses), never a .skel.

Pre-passRewrite
4.0->4.1Animation deform → attachments
4.1->4.2Bone transform → inherit

Pins​

A pin, RuntimePin, maps an export minor to the runtime minor the game reads it with, as a game that ships one runtime does:

createPixi8Engine({ runtimes, pin: { '4.2': '4.3', '*': '4.3' } });

The key "*" (UNVERSIONED) is for exports that name no Spine version: they have no minor to route by, so without it they are skipped. A pin to the export's own minor, or to a runtime the engine does not have, is passed over. choosePixi takes the same pin as spine, beside the Pixi a project ships with (ProjectPin).

A cross-read reads the export with no pre-pass. What the warning names it losing:

Cross-readLost
4.2 → 4.3The top-level ik/transform/path/physics constraint lists, which 4.3 does not read; it may throw. Given the JSON, the warning names the lists this export holds, or says it holds none
4.1 → 4.3Bone transform and every constraint; some exports throw
4.1 → 4.2Bone transform: every bone inherits its full transform
4.0 → 4.1Mesh deform keys; on @pixi-spine/runtime-4.1, nothing measured: it converts a 4.0 export itself
Any otherThe runtime reads its own minor only: expect missing data, wrong poses or a throw

Warnings and skips​

Every read by a runtime other than the export's own goes to the engine's warn, naming the skeleton's file where it is known. The default Pixi 8 engine is the exception: it pins every minor to 4.3, "*" included, and reports nothing, as before.

An export no registered runtime reads throws UnsupportedSpineVersionError, whose file, version, registered (the engine's runtime minors) and pixiMajors (the Pixi majors that could render it) are also in its message; for a .skel it names the JSON-only runtimes. The layout catches it per skeleton, logs it and builds the rest.

Where a runtime is lazy (Pixi 6, 7 and 8), engine.spine.prepare(minors) loads the ones the exports need, and the layout awaits it before it reads anything. A runtime that fails to load fails only the skeletons it would read, each logged and skipped; the next prepare that needs it tries again.

Choosing the Pixi​

A game picks its Pixi. choosePixi(exportVersions, pin?) is the choice for a folder of exports, as the editor makes it. It throws MixedSpineVersionsError where the exports are of more than one minor; else it takes the pinned Pixi; else DEFAULT_PIXI of the exports' minor, if that Pixi reads every export; else the newest Pixi that does (a .skel of a JSON-only minor); else that default, the exports it cannot read skipped. It returns the pixi, the reason, a one-line message, the runtimes the exports need and the runtime of each file (perFile).

Several runtimes on one engine​

An engine's spine port is a composite over one SpineRuntimeAdapter per runtime: the runtime's SpineRuntimeInfo, its own port, owns(object) and readsBinary.

  • readSkeleton picks the runtime, runs the pick's pre-passes and reads with that runtime.
  • Every other call goes to the runtime that owns its argument, by instanceof against that runtime's own classes (SkeletonData, Spine, Skeleton, Bone, Slot, Animation, attachments).
  • engine.spine.runtimes lists the runtimes; engine.spine.runtimeOf(spineOrData) names the one that read a skeleton or built a spine.
  • Two runtimes of one minor throw.

readSkeleton, from source, atlas text and page textures, is the read that works over several runtimes, and createInstancesFromDataArray reads that way: on Pixi 7, pixi-spine's and spine-pixi-v7's Assets loaders claim each other's files. The Pixi 6, 7 and 8 engines still serve readSkeletonFromAssets across their runtimes, by the runtime the export picks.

/runtimes turns what a game imported or loaded into runtimes:

FunctionDoes
esotericRuntime(module, version?)Wraps a spine-pixi-v7 or spine-pixi-v8 module; the minor is read off the module
pixiSpineLegacy(namespace, { packageVersion })Wraps pixi-spine 1.x or 2.x, PIXI.spine
pixiSpineModular(base, runtimes, { packageVersion })Wraps @pixi-spine/base and its runtimes, keyed by minor
discoverGlobalRuntimes(scope?)The runtimes a page's scripts left on scope (globalThis): PIXI.spine, PIXI.spine37 … spine41, Esoteric's spine, and app-magic-editor's SpineRuntime_* bundles; not pixi-spine's all-in-one bundles
esotericAdapter(runtime, pixi, options?)Binds an Esoteric runtime to its Pixi; on v8 this registers its render pipe
pixiSpineAdapters(runtimes, host)Binds pixi-spine runtimes to their Pixi and the engine's asset cache, over one port
runtimeAdapters(runtimes, host)Binds any mix of the two
lazyRuntime(info, load)An adapter loaded on demand: info names the runtime, so the engine routes to it before it loads

The engines bind the runtimes they are given. Pixi 7 and 8 also take adapters bound already; Pixi 4–6 take runtimes only.

Differences between the engines​

AreaPixi 8Pixi 4–7
EventsFederated: eventMode, and pointer moves from globalpointermovePixi 4–6: the interaction manager, interactive; pointer moves from renderer's interaction manager when it is given, else from pointermove on an empty interactive child. Pixi 7: federated, as on Pixi 8
Hit-testingeventMode and hitArea; shieldFromHitTesting overrides containsPoint, since every child of an interactive ancestor answers. A spine-pixi-v8 Spine answers as one shapeinteractive (4–6) or eventMode (7), and hitArea; shieldFromHitTesting overrides containsPoint likewise. pixi-spine and spine-pixi-v7 spines answer through their sprites and meshes, each shielded, those made later included; a BitmapText's glyphs get interactiveChildren = false
Slot objectsspine-pixi-v8 attaches them to the slotpixi-spine has none: the engine hangs the object off a holder that cancels the slot container's transform and alpha, so it follows the bone as on Pixi 8 (TransformBase on 4, setFromMatrix on 5–7). spine-pixi-v7 attaches them itself
Text justifySupportedText: not supported, align: "justify" becomes left. BitmapText: from Pixi 6 (text.capabilities.byNode.bitmapText.justify). text.capabilities.justify, what both can, is false
Text maxHeightSupportedSupported
Bitmap font .fntText or XMLText or XML. On 4 to 5.2 the engine parses it and registers it with BitmapText.registerFont; from 5.3 BitmapFont.install reads it
lineHeightA style keyA Text style key. No legacy BitmapText has one: kept per node and applied to the font while that node lays out
Physics, sequencesBoth, in 4.2 and 4.3As the skeleton's runtime reads them: neither in 3.7 or 3.8; sequences from @pixi-spine/runtime-4.1; both in spine-pixi-v7 4.2 and 4.3
Draw order to setup4.3 has drawOrder.setupPose(); on 4.2 the 'drawOrder' scope assigns it by handspine-ts up to 4.2 resets draw order only inside setSlotsToSetupPose, so the 'drawOrder' scope assigns it by hand. spine-pixi-v7 4.3 as on Pixi 8
ParticlesNo port: one warning, every call a no-oppixi-particles 4.3 on Pixi 4–6, @pixi/particle-emitter 5 on Pixi 6 and 7, from PIXI.particles or the particles option (6, 7). pixi-particles on Pixi 7 is refused with a warning

Examples​

The repo's examples/ folder has one scene per Pixi major, each built with that major's engine:

SceneEngine and runtimesLayout
pixi8createPixi8Engine: spine-pixi-v8 4.3, and 4.2 as a lazy runtimeA 4.3 popup and a 4.2 button, one scene
pixi7createPixi7Engine: @pixi-spine/runtime-3.8 4.0.6 and spine-pixi-v7 4.3The 3.8 layout and the 4.3 popup, side by side
pixi6createPixi6Engine: @pixi-spine/runtime-3.8 3.1.2The 3.8 layout
pixi5createPixi5Engine: pixi-spine 2.1.14The 3.8 layout
pixi4createPixi4Engine: pixi-spine 1.6.2, or 1.5.23 with ?spine=3.7The 3.8 layout, or a 3.7 export

Pixi 4 to 7 are loaded the way a legacy game loads them, as <script> globals; the runtimes that ship no browser script of their own are bundled into examples/vendor/ as the server starts. Each page says which runtime read which skeleton. From the package, after pnpm install:

pnpm examples --port <port> --strictPort --no-open

Then open http://localhost:<port>/examples/.

Testing​

tests/engine-contract/contract.ts is the shared contract suite: it is written against Engine alone and runs per engine and per runtime, against that runtime's real package. Each runtime reads the contract fixture of its export minor (CONTRACT_FIXTURES): contract-4.3.* for 4.3 and 4.2, contract-3.8.* for 3.8 and, under their own version, 4.1 and 4.0, and contract-3.7.json for 3.7. twoRuntimesContract runs one engine over two runtimes, their objects interleaved. A new engine passes it or it is not an engine.

The legacy stacks load from devDependencies aliased to avoid the v8 install: pixi.js-v4 to pixi.js-v7, pixi-spine-v4 (1.6.2), pixi-spine-v4-37 (1.5.23), pixi-spine-v5-37 (2.0.5), pixi-spine-v5-38 (2.1.14), @pixi-spine-v6/* (3.1.2), @pixi-spine-v7/* (4.0.6), spine-pixi-v7-42, spine-pixi-v7-43, pixi-particles (4.3.0) and @pixi/particle-emitter-v5, under happy-dom with a stubbed canvas 2D context. spine-pixi-v8-42 is the second Pixi 8 runtime.

Writing another engine​

An Engine is five ports. A port that is undefined is a feature the engine does not have, and the core degrades instead of throwing.

PortJob
spineReads skeletons, creates and updates the Spine, and exposes bones, slots, setup poses, track claims and slot objects; names its runtimes, and prepares lazy ones
displayThe container class the layout is an instance of, plus sprites, textures, hit areas, pointer events and hit-test shielding
textText and BitmapText nodes, texts.json entry to style translation, bitmap fonts and measuring, and its capabilities
assetsget(alias) and has(alias), the engine's asset cache
particlesOptional. createEmitter(parent, textures, config) returning an emitter with emit, update, playOnce and destroy

An engine over several runtimes builds spine with createCompositeSpinePort (src/engines/shared/compositeSpinePort.ts) from one adapter per runtime.

Boundaries the repo enforces:

  • ESLint: src/core/**, src/engine/** and src/core.ts may not import a rendering stack (pixi.js, @pixi/*, pixi-spine, @pixi-spine/*, @esotericsoftware/*, pixi-particles and their npm aliases) or anything under engines/ or runtimes/. Every engine but pixi8 — src/engines/{pixi4,pixi5,pixi6,pixi7,legacy,pixiSpine,esoteric,shared}/**, src/runtimes/** and src/pixi4.ts to src/pixi8.ts — may import a rendering stack's types only; it is handed its PIXI and its runtimes. src/engines/pixi8/** and src/index.ts may not use the PIXI global.
  • TypeScript: tsconfig.core.json (types: []) compiles the core, Engine and engines/shared with no rendering types in scope. tsconfig.legacy.json, tsconfig.pixiSpine.json, tsconfig.esoteric.json, tsconfig.runtimes.json, tsconfig.pixi4.json and tsconfig.entries.json build on it with types: [] too; tsconfig.pixi8.json builds pixi8 with pixi.js's types. pnpm build builds them all; pnpm check-types runs tsconfig.core.json and tsconfig.check.json.