# Live2D (/start/canvas-live2d)



<CalloutContainer type="warning">
  <CalloutDescription>
    The Live2D integration is currently in a testing phase and its API may change in future releases.
  </CalloutDescription>
</CalloutContainer>

<Accordions>
  <Accordion title="What is Live2D?" id="what-is-live2d">
    Live2D is a technology that allows 2D illustrations to move naturally, creating fluid and expressive animations without the need to redraw every frame. Instead of a bone skeleton (as in Spine 2D), a Live2D model is a single illustration whose parts are deformed and blended by **parameters** (angle, eye openness, mouth shape, ...), driven by **motions** (predefined animation clips) and **expressions** (predefined parameter presets, e.g. facial expressions).

    It is widely used in visual novels, games, and interactive applications (especially VTubers) to bring characters to life.

    You can learn more about Live2D on the [official Live2D website](https://www.live2d.com/).
  </Accordion>
</Accordions>

Within your **Pixi’VN** project, you can use the Live2D integration to display and animate Live2D models for your characters.
This integration is a wrapper around [untitled-pixi-live2d-engine](https://github.com/Untitled-Story/untitled-pixi-live2d-engine) — note that this is **not an official PixiJS/Live2D plugin**, but a community-maintained engine that bridges Live2D with PixiJS — allowing you to use its features directly inside your Pixi’VN project.

## Installation [#installation]

To install the Live2D 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-live2d
    ```
  </CodeBlockTab>

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

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

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

<CalloutContainer type="error">
  <CalloutDescription>
    `extensions.add(Live2DPlugin)` must be called at startup, in your main file, before `Game.init` is called.
  </CalloutDescription>
</CalloutContainer>

```ts title="main.ts"
import { extensions } from "pixi.js";
import { Live2DPlugin } from "@drincs/pixi-vn-live2d/core";
extensions.add(Live2DPlugin);

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

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

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

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

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

## Usage [#usage]

You can use the [`Live2D`](/jsdoc/pixi-vn-live2d/index/classes/Live2D) component just like any other Pixi’VN component. Unlike Spine, a Live2D model doesn't need to be pre-loaded through `Assets.load` — registering the alias with `Assets.add` is enough, since `Live2D` resolves and fetches the model itself.

Because that fetch is asynchronous, the model isn't safe to use until its [`ready`](/jsdoc/pixi-vn-live2d/index/classes/Live2D#ready) promise resolves — `await` it right after construction, before reading anything derived from the loaded model (e.g. its size).

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 { canvas } from "@drincs/pixi-vn";
    import { Live2D } from "@drincs/pixi-vn-live2d";

    const live2d = new Live2D({
        source: "shizuku",
        xAlign: 0.5,
        yAlign: 1,
        scale: 0.5,
    });
    await live2d.ready;
    canvas.add("shizuku", live2d);
    ```
  </CodeBlockTab>

  <CodeBlockTab value="ink">
    ```ink  title="ink/start.ink" groupId="narrative_language"
    === start ===
    # show live2d shizuku xAlign 0.5 yAlign 1 scale 0.5
    -> 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: "shizuku",
                        src: "https://cdn.jsdelivr.net/gh/guansss/pixi-live2d-display/test/assets/shizuku/shizuku.model.json",
                    },
                ],
            },
        ],
    };
    ```
  </CodeBlockTab>
</CodeBlockTabs>

<PixiVnExample path="live2d/show" />

<Accordions>
  <Accordion title="Motion" id="motion">
    A Live2D model's animations are grouped into **motion groups** (e.g. `idle`, `tap_body`), each containing one or more motion clips. You can start one with the [`motion`](/jsdoc/pixi-vn-live2d/index/classes/Live2D#motion) function; omitting the clip index plays a random clip from the group.

    By default, the model automatically loops its `idle` motion group (Cubism 2) or `Idle` group (Cubism 4) on its own — configurable via the `idleMotionGroup` option — so you only need to call `motion` for a deliberate, one-off animation such as reacting to a tap or a click.

    <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 { canvas, newLabel } from "@drincs/pixi-vn";
        import { Live2D } from "@drincs/pixi-vn-live2d";

        export const startLabel = newLabel("start", [
            async () => {
                const live2d = new Live2D({
                    source: "shizuku",
                    xAlign: 0.5,
                    yAlign: 1,
                    scale: 0.5,
                });
                await live2d.ready;
                canvas.add("shizuku", live2d);
            },
            () => {
                canvas.find<Live2D>("shizuku")?.motion("tap_body");
            },
        ]);
        ```
      </CodeBlockTab>

      <CodeBlockTab value="ink">
        ```ink  title="ink/start.ink" groupId="narrative_language"
        === start ===
        # show live2d shizuku xAlign 0.5 yAlign 1 scale 0.5
        # pause
        # play motion tap_body on live2d shizuku
        # pause
        -> DONE
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    <PixiVnExample path="live2d/motion" />
  </Accordion>

  <Accordion title="Expression" id="expression">
    A Live2D model can define multiple **expressions** — presets of parameter values, typically used for facial expressions — which you can switch between at runtime with the [`expression`](/jsdoc/pixi-vn-live2d/index/classes/Live2D#expression) function. As with `motion`, omitting the id picks a random expression.

    <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 { canvas, newLabel } from "@drincs/pixi-vn";
        import { Live2D } from "@drincs/pixi-vn-live2d";

        export const startLabel = newLabel("start", [
            async () => {
                const live2d = new Live2D({
                    source: "shizuku",
                    xAlign: 0.5,
                    yAlign: 1,
                    scale: 0.5,
                });
                await live2d.ready;
                canvas.add("shizuku", live2d);
            },
            () => {
                canvas.find<Live2D>("shizuku")?.expression("f01");
            },
        ]);
        ```
      </CodeBlockTab>

      <CodeBlockTab value="ink">
        ```ink  title="ink/start.ink" groupId="narrative_language"
        === start ===
        # show live2d shizuku xAlign 0.5 yAlign 1 scale 0.5
        # pause
        # change expression f01 on live2d shizuku
        # pause
        -> DONE
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    <PixiVnExample path="live2d/expression" />
  </Accordion>

  <Accordion title="Live2D + motion.js animations" id="motion-js">
    You can combine Live2D motions 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 Live2D 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 { canvas, newLabel } from "@drincs/pixi-vn";
    import { Live2D } from "@drincs/pixi-vn-live2d";

    export const startLabel = newLabel("start", [
        async () => {
            const live2d = new Live2D({
                source: "shizuku",
                xAlign: 0.3,
                yAlign: 1,
                scale: 0.5,
            });
            await live2d.ready;
            canvas.add("shizuku", live2d);
            canvas.animate(
                live2d,
                [
                    [{ x: canvas.width * 0.7 }, { duration: 2, ease: "linear" }],
                    [{ x: canvas.width * 0.3 }, { duration: 2, ease: "linear" }],
                ],
                { repeat: Infinity },
            );
        },
    ]);
    ```

    <PixiVnExample path="live2d/motion-js" />
  </Accordion>

  <Accordion title="Stop motions" id="stop-motions">
    You can stop every motion currently playing on a model — as well as any lipsync audio — with the [`stopMotions`](/jsdoc/pixi-vn-live2d/index/classes/Live2D#stopmotions) 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 { canvas, newLabel } from "@drincs/pixi-vn";
        import { Live2D } from "@drincs/pixi-vn-live2d";

        export const startLabel = newLabel("start", [
            async () => {
                const live2d = new Live2D({
                    source: "shizuku",
                    xAlign: 0.5,
                    yAlign: 1,
                    scale: 0.5,
                });
                await live2d.ready;
                canvas.add("shizuku", live2d);
                live2d.motion("tap_body");
            },
            () => {
                canvas.find<Live2D>("shizuku")?.stopMotions();
            },
        ]);
        ```
      </CodeBlockTab>

      <CodeBlockTab value="ink">
        ```ink  title="ink/start.ink" groupId="narrative_language"
        === start ===
        # show live2d shizuku xAlign 0.5 yAlign 1 scale 0.5
        # play motion tap_body on live2d shizuku
        # pause
        # stop motions on live2d shizuku
        # pause
        -> DONE
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    <PixiVnExample path="live2d/stop-motions" />
  </Accordion>
</Accordions>
