# Spine 2D (/start/canvas-spine2d)





<Accordions>
  <Accordion title="What is Spine 2D?" id="what-is-spine-2d">
    Spine 2D is a powerful 2D animation software specifically designed for game development. It uses a skeletal animation system, meaning that characters and objects are animated through a hierarchy of **bones** that control the movement of attached parts.

    You can learn more about Spine 2D on the [official Spine 2D website](https://it.esotericsoftware.com/).
  </Accordion>
</Accordions>

Within your **Pixi’VN** project, you can use the Spine 2D integration to create complex and smooth animations for your characters and objects.
This integration is essentially a wrapper around the official [Spine 2D runtime for PixiJS](https://it.esotericsoftware.com/spine-pixi), allowing you to use all Spine 2D features directly inside your Pixi’VN project.

## Installation [#installation]

To install the Spine 2D package in an existing JavaScript project, use one of the following commands:

<CodeBlockTabs defaultValue="npm">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="npm">
      npm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="pnpm">
      pnpm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="yarn">
      yarn
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="bun">
      bun
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="npm">
    ```bash
    npm install @drincs/pixi-vn-spine
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```bash
    pnpm add @drincs/pixi-vn-spine
    ```
  </CodeBlockTab>

  <CodeBlockTab value="yarn">
    ```bash
    yarn add @drincs/pixi-vn-spine
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```bash
    bun add @drincs/pixi-vn-spine
    ```
  </CodeBlockTab>
</CodeBlockTabs>

<CalloutContainer type="error">
  <CalloutDescription>
    The library must be imported when the game is initialized, so that it can be used inside Lazy components.
  </CalloutDescription>
</CalloutContainer>

```ts title="main.ts"
import "@drincs/pixi-vn-spine";

Game.init(body, {
    // ...
});
```

<Accordions>
  <Accordion title="ink" id="ink">
    You can enable Spine 2D hashtag commands in your **ink** scripts by using the [`createSpineHandler`](/jsdoc/pixi-vn-spine/ink/functions/createSpineHandler) function.

    ```ts title="content/ink/hashtag-commands.ts"
    import { addBaseHashtagCommands } from "@drincs/pixi-vn-ink";
    import { createSpineHandler } from "@drincs/pixi-vn-spine/ink"; // [!code focus]

    addBaseHashtagCommands({ bundleIds, assetAliasIds });
    createSpineHandler(); // [!code focus]
    ```

    The `show`, `edit`, and `remove` hashtag commands can also be used with `spine`, just like with any other canvas element.
  </Accordion>
</Accordions>

## Usage [#usage]

You can use the [`Spine`](/jsdoc/pixi-vn-spine/index/classes/Spine) component just like any other Pixi’VN component.
However, you must first load the Spine 2D assets (**skeleton** and **atlas**) before using it.

For example:

<CodeBlockTabs defaultValue="Typescript">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="Typescript">
      Typescript
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="ink">
      ink
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="src/assets/index.ts">
      src/assets/index.ts
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="Typescript">
    ```ts  title="main.ts" groupId="narrative_language"
    import { Assets, canvas } from "@drincs/pixi-vn";
    import { Spine } from "@drincs/pixi-vn-spine";

    await Assets.load(["spineboySkeleton", "spineboyAtlas"]);
    const spine = new Spine({
        atlas: "spineboyAtlas",
        skeleton: "spineboySkeleton",
        xAlign: 0.5,
        yAlign: 1,
        animation: "idle",
    });
    canvas.add("boy", spine);
    ```
  </CodeBlockTab>

  <CodeBlockTab value="ink">
    ```ink  title="ink/start.ink" groupId="narrative_language"
    === start ===
    # show spine boy skeleton spineboySkeleton atlas spineboyAtlas xAlign 0.5 yAlign 1 animation idle
    -> DONE
    ```
  </CodeBlockTab>

  <CodeBlockTab value="src/assets/index.ts">
    ```ts
    import { AssetsManifest } from "@drincs/pixi-vn";

    /**
     * Manifest for the assets used in the game.
     * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets
     */
    export const manifest: AssetsManifest = {
        bundles: [
            {
                name: "start",
                assets: [
                    {
                        alias: "spineboySkeleton",
                        src: "https://raw.githubusercontent.com/EsotericSoftware/spine-runtimes/4.3/examples/spineboy/export/spineboy-pro.skel",
                    },
                    {
                        alias: "spineboyAtlas",
                        src: "https://raw.githubusercontent.com/EsotericSoftware/spine-runtimes/4.3/examples/spineboy/export/spineboy-pma.atlas",
                    },
                ],
            },
        ],
    };
    ```
  </CodeBlockTab>
</CodeBlockTabs>

<SpineExample />

<Accordions>
  <Accordion title="Skin" id="skin">
    A Spine model can have multiple skins, and you can switch between them at runtime by using the [`setSkin`](/jsdoc/pixi-vn-spine/index/classes/Spine#setskin) function.

    <CodeBlockTabs defaultValue="Typescript">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Typescript">
          Typescript
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="ink">
          ink
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Typescript">
        ```ts  title="content/labels/start.label.ts" groupId="narrative_language"
        import { Assets, canvas, newLabel } from "@drincs/pixi-vn";
        import { Spine } from "@drincs/pixi-vn-spine";

        export const startLabel = newLabel("start", [
            async () => {
                await Assets.load(["goblinsSkeleton", "goblinsAtlas"]);
                const spine = new Spine({
                    atlas: "goblinsAtlas",
                    skeleton: "goblinsSkeleton",
                    skin: "goblin",
                    xAlign: 0.5,
                    yAlign: 1,
                    animation: "walk",
                });
                canvas.add("goblin", spine);
            },
            () => {
                canvas.find<Spine>("goblin")?.setSkin("goblingirl");
            },
        ]);
        ```
      </CodeBlockTab>

      <CodeBlockTab value="ink">
        ```ink  title="ink/start.ink" groupId="narrative_language"
        === start ===
        # show spine goblin skeleton goblinsSkeleton atlas goblinsAtlas skin goblin xAlign 0.5 yAlign 1 animation walk
        # pause
        # change skin goblingirl on spine goblin
        # pause
        -> DONE
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    <SkinExample />
  </Accordion>

  <Accordion title="Animation" id="animation">
    There are two functions you can use to play a Spine animation: [`setAnimation`](/jsdoc/pixi-vn-spine/index/classes/Spine#setanimation) and [`addAnimation`](/jsdoc/pixi-vn-spine/index/classes/Spine#addanimation).

    * [`setAnimation`](/jsdoc/pixi-vn-spine/index/classes/Spine#setanimation) immediately replaces whatever animation is currently playing on the track with the new one.
    * [`addAnimation`](/jsdoc/pixi-vn-spine/index/classes/Spine#addanimation) queues the new animation so that it starts only after the current animation on the track finishes, without interrupting it.

    <CodeBlockTabs defaultValue="Typescript">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Typescript">
          Typescript
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="ink">
          ink
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Typescript">
        ```ts  title="content/labels/start.label.ts" groupId="narrative_language"
        import { Assets, canvas, newLabel } from "@drincs/pixi-vn";
        import { Spine } from "@drincs/pixi-vn-spine";

        export const startLabel = newLabel("start", [
            async () => {
                await Assets.load(["spineboySkeleton", "spineboyAtlas"]);
                const spine = new Spine({
                    atlas: "spineboyAtlas",
                    skeleton: "spineboySkeleton",
                    xAlign: 0.5,
                    yAlign: 1,
                    animation: "idle",
                });
                canvas.add("boy", spine);
            },
            () => {
                canvas.find<Spine>("boy")?.addAnimation("walk", { loop: true });
            },
        ]);
        ```
      </CodeBlockTab>

      <CodeBlockTab value="ink">
        ```ink  title="ink/start.ink" groupId="narrative_language"
        === start ===
        # show spine boy skeleton spineboySkeleton atlas spineboyAtlas xAlign 0.5 yAlign 1 animation idle
        # pause
        # play walk on spine boy loop true
        # pause
        -> DONE
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    <AnimationExample />
  </Accordion>

  <Accordion title="Spine + motion.js animations" id="motion">
    You can combine Spine animations with Pixi’VN's classic motion-based animations ([`canvas.animate`](/jsdoc/pixi-vn/index/interfaces/CanvasManagerInterface#animate)), as shown in the example below.

    For more advanced games, you can also drive Spine animations directly from a ticker triggered by events, such as a button press, instead of relying only on predefined sequences.

    ```ts title="content/labels/start.label.ts" groupId="narrative_language"
    import { Assets, canvas, newLabel } from "@drincs/pixi-vn";
    import { Spine } from "@drincs/pixi-vn-spine";

    export const startLabel = newLabel("start", [
        async () => {
            await Assets.load(["spineboySkeleton", "spineboyAtlas"]);
            const spine = new Spine({
                atlas: "spineboyAtlas",
                skeleton: "spineboySkeleton",
                xAlign: 0,
                yAlign: 1,
                animation: "walk",
            });
            canvas.add("boy", spine);
            canvas.animate(
                spine,
                [
                    [{ xAlign: 1 }, { duration: 1, ease: "linear" }],
                    [{ scaleX: -1 }, { duration: 0.2 }],
                    [{ xAlign: 0 }, { duration: 1, ease: "linear" }],
                    [{ scaleX: 1 }, { duration: 0.2 }],
                ],
                { repeat: Infinity },
            );
        },
    ]);
    ```

    <MotionExample />
  </Accordion>

  <Accordion title="Animation sequence" id="animation-sequence">
    It is also possible to create animation sequences by using the [`playSequence`](/jsdoc/pixi-vn-spine/index/classes/Spine#playsequence) function.

    ```ts title="content/labels/start.label.ts" groupId="narrative_language"
    import { Assets, canvas, newLabel } from "@drincs/pixi-vn";
    import { Spine } from "@drincs/pixi-vn-spine";

    export const startLabel = newLabel("start", [
        async () => {
            await Assets.load(["spineboySkeleton", "spineboyAtlas"]);
            const spine = new Spine({
                atlas: "spineboyAtlas",
                skeleton: "spineboySkeleton",
                xAlign: 0.5,
                yAlign: 1,
            });
            spine.playSequence([["idle", { loop: true, duration: 0.5 }], "jump"], {
                repeat: Infinity,
            });
            canvas.add("boy", spine);
        },
    ]);
    ```

    <AnimationSequenceExample />
  </Accordion>

  <Accordion title="Clear tracks" id="clear-tracks">
    You can remove Spine animations using two methods: [`clearTrack`](/jsdoc/pixi-vn-spine/index/classes/Spine#cleartrack), which clears the animation on a single track, and [`clearTracks`](/jsdoc/pixi-vn-spine/index/classes/Spine#cleartracks), which clears the animations on all tracks.

    <CodeBlockTabs defaultValue="Typescript">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Typescript">
          Typescript
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="ink">
          ink
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Typescript">
        ```ts  title="content/labels/start.label.ts" groupId="narrative_language"
        import { Assets, canvas, newLabel } from "@drincs/pixi-vn";
        import { Spine } from "@drincs/pixi-vn-spine";

        export const startLabel = newLabel("start", [
            async () => {
                await Assets.load(["spineboySkeleton", "spineboyAtlas"]);
                const spine = new Spine({
                    atlas: "spineboyAtlas",
                    skeleton: "spineboySkeleton",
                    xAlign: 0,
                    yAlign: 1,
                    animation: "walk",
                });
                canvas.add("boy", spine);
            },
            () => {
                canvas.find<Spine>("boy")?.clearTracks();
            },
        ]);
        ```
      </CodeBlockTab>

      <CodeBlockTab value="ink">
        ```ink  title="ink/start.ink" groupId="narrative_language"
        === start ===
        # show spine boy skeleton spineboySkeleton atlas spineboyAtlas x 0 y 1 animation walk
        # pause
        # clear tracks on spine boy
        # pause
        -> DONE
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    <ClearExample />
  </Accordion>
</Accordions>
