@drincs/pixi-vn-spine/ink adds ink hashtag commands that control the Spine canvas component from
.ink files. They use the same command system as @drincs/pixi-vn-ink's built-in commands
(# show image, # play sound, ...), and each one calls the TypeScript API described in
pixi-vn-spine.
Official docs: pixi-vn.com/start/canvas-spine2d (open the
"ink" tabs). For ink in Pixi'VN in general, use the pixi-vn-ink skill set
(npx skills@latest add DRincs-Productions/pixi-vn-ink@latest --skill '*' -g -y).
Setup
- Install and register the package as described in
pixi-vn-spine. That includes theimport "@drincs/pixi-vn-spine";side-effect import inmain.ts. - Make sure
@drincs/pixi-vn-inkandzodare installed. They are optional peer dependencies and are only needed for this entry point. - Call
createSpineHandler()once, next toaddBaseHashtagCommands, before any ink content is parsed:
import { addBaseHashtagCommands } from "@drincs/pixi-vn-ink";
import { createSpineHandler } from "@drincs/pixi-vn-spine/ink";
addBaseHashtagCommands({ bundleIds, assetAliasIds });
createSpineHandler();
Calling it more than once registers the commands twice. The spine commands do not intercept the base
commands (# show image, # play sound, ...). If those commands stop resolving, check the
assetAliasIds/bundleIds allow-lists passed to addBaseHashtagCommands. Every alias used in
ink, including the skeleton and atlas aliases, must be listed there when an allow-list is provided.
Command reference
In the commands below, <alias> is the canvas key of the element, as in canvas.add(alias, ...).
It is not an asset alias. Free-form properties are written as <key> <value> pairs and parsed into
an object. Numbers and booleans are converted to their types (xAlign 0.5, loop true).
Show
# show spine <alias> skeleton <skeletonAlias> atlas <atlasAlias> [<key> <value> …] [with <transition> [<key> <value> …]]
skeletonandatlas(asset aliases) are required. Without them, the command logs an error and nothing is shown.- The command loads both assets itself, so a separate
# loadstep is not needed. - Every other property is passed to
SpineOptions:xAlign,yAlign,align,anchor,percentageX,x,y,alpha,zIndex,scale,skin,animation, ...animationstarts looping on track 0. - Entrance transitions:
dissolve,fade,movein,zoomin,pushin. The words after the transition name are its props (duration 1,direction left, ...).
=== start ===
# show spine boy skeleton spineboySkeleton atlas spineboyAtlas xAlign 0.5 yAlign 1 animation idle
# show spine goblin skeleton goblinsSkeleton atlas goblinsAtlas skin goblin xAlign 0.2 yAlign 1 animation walk with dissolve duration 1
-> DONE
Use xAlign/yAlign (0..1) for screen-relative placement. x/y are pixels, so x 0 y 1
puts the model in the top-left corner, not on the bottom edge.
Edit
# edit spine <alias> [<key> <value> …]
Assigns the properties directly on the existing Spine (alpha, tint, x, y, xAlign,
yAlign, visible, zIndex, angle, ...). Use the dedicated commands below for skins and
animations. If no element has that alias, the command logs an error.
# edit spine boy xAlign 0.8 alpha 0.5
Remove
# remove spine <alias> [with <transition> [<key> <value> …]]
Exit transitions: dissolve, fade, moveout, zoomout, pushout. Entrance-only names such as
movein are ignored here, and the element is removed immediately with no transition.
# remove spine boy with fade duration 0.5
Change skin
# change skin <skinName> on spine <alias>
Calls setSkin. The skin name is case sensitive and must exist in the Spine project.
Play an animation
# play <animationName> on spine <alias> [loop <bool>] [trackIndex <n>] [delay <seconds>] [completeOnContinue <bool>]
Calls addAnimation, so the animation is queued after whatever is playing on that track.
Pass trackIndex to queue on an existing track. Without it, the animation goes on a new track
and is layered on top of the current one. For example, after
# show spine boy ... animation idle (track 0):
# play walk on spine boy trackIndex 0 loop true // walk follows idle on track 0
# play wave on spine boy trackIndex 1 // wave layered over the body, on track 1
completeOnContinue true clears that track on the next narration step, which is useful for one-shot
animations that should not keep running into the next line.
Ink has no command for setAnimation (replace immediately) or playSequence. For those, use
# clear tracks followed by # play, or write a custom hashtag command in TypeScript (see the
pixi-vn-ink-hashtag-commands skill) that calls the Spine API.
Clear tracks
# clear tracks on spine <alias>
Calls clearTracks(). This stops every track and running sequence and leaves the skeleton in its
current pose.
Full example
=== start ===
# show spine boy skeleton spineboySkeleton atlas spineboyAtlas xAlign 0.5 yAlign 1 animation idle with fade duration 0.5
Boy: Hi!
# play walk on spine boy trackIndex 0 loop true
# edit spine boy xAlign 0.8
Boy: Let's go.
# clear tracks on spine boy
# change skin alt on spine boy
# pause
# remove spine boy with dissolve
-> DONE
Exported helpers
createSpineHandler(): voidregisters all the commands above withHashtagCommands.addfrom@drincs/pixi-vn-ink. Each command has a zodvalidationandkeySchemas, so the@drincs/pixi-vn-inkVite dev server lists the commands and lints their properties.parseShowSpineTail(tail: string[])splits the tokens after<alias>in a# show spineline into{ propsList, transitionType?, transitionPropsList }. It is used internally and exported so custom commands can parse the same syntax.
Gotchas
createSpineHandlerwas not called:# show spine ...is reported as an unknown hashtag command.- The
skeletonoratlaskey is missing in# show spine: an error is logged and nothing appears. # playwithouttrackIndexlayers a new track instead of queueing (see above).- An asset allow-list in
addBaseHashtagCommandsthat omits the Spine aliases makes the dev-server linter flag them.