# ink (Inkle) integration (/ink)



Pixi’VN lets you write interactive narratives using **_ink_**, a scripting language designed for branching stories.

<Accordions>
  <Accordion title="What is ink?" id="what-is-ink">
    **_ink_** is a simple scripting language for interactive stories, used in games like 80 Days, Heaven's Vault, and Sorcery! Learn more on the [*ink* website](https://www.inklestudios.com/ink/).
  </Accordion>

  <Accordion title="Why use ink?" id="why-use-ink">
    Writing narrative directly in **JavaScript/TypeScript** can be slow and complex, especially for beginners. **_ink_** is much easier to learn and write.

    New developers can start with an <DynamicLink href="/start#project-initialization">*ink* template</DynamicLink>, then gradually learn JavaScript/TypeScript for advanced features.
  </Accordion>
</Accordions>

The ***ink* + Pixi’VN integration** uses [inkjs](https://github.com/inkle/inkjs) and [PixiVNJson](/jsdoc/pixi-vn-json) to parse ***ink* code** and generate JSON that Pixi’VN can interpret. This means JavaScript/TypeScript and **_ink_** share the same storage and canvas, and you can launch **_ink_** `knots` (or `labels`) from JavaScript/TypeScript and vice versa. You get the best of both worlds: write narration in **_ink_**, and use JavaScript/TypeScript for minigames or advanced animations.

```ink title="ink/start.ink"
=== start ===
We arrived into London at 9.45pm exactly.

* "There is not a moment to lose!"[] I declared.
 -> hurry_outside

* "Monsieur, let us savour this moment!"[] I declared.
 My master clouted me firmly around the head and dragged me out of the door.
 -> dragged_outside

* [We hurried home] -> hurry_outside

=== hurry_outside ===
We hurried home to Savile Row -> as_fast_as_we_could

=== dragged_outside ===
He insisted that we hurried home to Savile Row
-> as_fast_as_we_could

=== as_fast_as_we_could ===
<> as fast as we could.
-> start
```

<PixiVnExample path="ink/getting-started" />

## Installation [#installation]

<CalloutContainer type="info">
  <CalloutTitle>
    Templates
  </CalloutTitle>

  <CalloutDescription>
    Starting a new Pixi’VN project? Use a <DynamicLink href="/start#project-initialization">template</DynamicLink> that already includes *ink*.
  </CalloutDescription>
</CalloutContainer>

To install the *ink* 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 @drincs/pixi-vn-ink
    ```
  </CodeBlockTab>

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

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

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

### Initialize [#initialize]

After installing, use [`importInkText`](/jsdoc/pixi-vn-ink/index/functions/importInkText) to load your ***ink* script**:

```typescript title="main.ts"
import { importInkText } from '@drincs/pixi-vn-ink'

const inkText = `
=== start ===
Hello
-> END
`

importInkText([inkText, ...])
```

Now you can run an ***ink* `knot`** (or `label`) using <DynamicLink href="/start/labels#run-a-label">Pixi’VN functions</DynamicLink>:

```typescript title="main.ts"
import { narration } from "@drincs/pixi-vn";

narration.call(`start`, {});
```

<Accordions>
  <Accordion title="Import text from .ink files" id="import-ink-files">
    <CalloutContainer type="info">
      <CalloutDescription>
        This guide uses [Vite](https://vitejs.dev/), but the same logic applies elsewhere.
      </CalloutDescription>
    </CalloutContainer>

    To import `.ink` files, you need to configure your project to handle them as text files.

    Below are the steps to do this:

    * Declare the `*.ink` module in `ink.d.ts`.
    * Add `.ink` to `assetsInclude` in `vite.config.ts`.
    * Import the file with `?raw` to get its text content.

    <CodeBlockTabs defaultValue="main.ts">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="main.ts">
          main.ts
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="vite.config.ts">
          vite.config.ts
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="ink.d.ts">
          ink.d.ts
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="main.ts">
        ```ts
        import { importInkText } from '@drincs/pixi-vn-ink'
        import startLabel from './ink/start.ink?raw'

        importInkText([startLabel, ...])
        ```
      </CodeBlockTab>

      <CodeBlockTab value="vite.config.ts">
        ```ts
        export default defineConfig({
            // ...
            assetsInclude: ["**/*.ink"],
        });
        ```
      </CodeBlockTab>

      <CodeBlockTab value="ink.d.ts">
        ```ts
        declare module "*.ink" {
            const value: string;
            export default value;
        }
        ```
      </CodeBlockTab>
    </CodeBlockTabs>
  </Accordion>

  <Accordion title="Vite.js plugin" id="vite-plugin">
    For a better developer experience, use the [`vitePluginInk`](https://pixi-vn.com/jsdoc/pixi-vn-ink/vite/functions/vitePluginInk):

    <CodeBlockTabs defaultValue="vite.config.ts">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="vite.config.ts">
          vite.config.ts
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="main.ts">
          main.ts
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="utils/ink-utility.ts">
          utils/ink-utility.ts
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="vite.config.ts">
        ```ts
        import { defineConfig } from "vite";
        import { vitePluginInk } from "@drincs/pixi-vn-ink/vite";
        export default defineConfig({
            // ...
            plugins: [
                // other plugins...
                vitePluginInk(),
            ],
        });
        ```
      </CodeBlockTab>

      <CodeBlockTab value="main.ts">
        ```ts
        import { setupInkHmrListener } from "@drincs/pixi-vn-ink/vite-listener";

        setupInkHmrListener();
        ```
      </CodeBlockTab>

      <CodeBlockTab value="utils/ink-utility.ts">
        ```ts
        async function getInkText() {
            const files = import.meta.glob<string>("../ink/*.ink", {
                eager: true,
                import: "default",
            });
            return await Promise.all(
                Object.values(files).map(async (importFile) => {
                    return importFile;
                }),
            );
        }
        ```
      </CodeBlockTab>
    </CodeBlockTabs>
  </Accordion>

  <Accordion title="Enable base Hashtag Commands" id="base-hashtag-commands">
    Pixi’VN ships a set of built-in <DynamicLink href="/ink/hashtag">Hashtag Commands</DynamicLink> (the default `#` commands used to interact with the canvas, such as `# show`, `# moveIn`, etc.), but they are **not registered automatically**. Call [`addBaseHashtagCommands`](/jsdoc/pixi-vn-ink/index/functions/addBaseHashtagCommands) once during your app's setup to enable them.

    ```ts title="content/ink/hashtag-commands.ts"
    import { addBaseHashtagCommands } from "@drincs/pixi-vn-ink";

    addBaseHashtagCommands();
    ```
  </Accordion>

  <Accordion title="Agent Skills" id="agent-skills">
    Pixi'VN ships a set of [Agent Skills](https://www.skills.sh/) that teach AI coding assistants how to correctly use each part of the engine.

    > In LM Studio Bionic, ask the agent to install skills from `https://github.com/DRincs-Productions/pixi-vn-ink`, or add the skill files in **Settings → Skills**. To reuse skills installed for Codex or Claude Code, enable **Settings → Skills → Use skills found in other apps**.

    **Codex**:

    <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
        npx skills@latest add DRincs-Productions/pixi-vn-ink@latest --skill '*' --agent codex -g -y
        ```
      </CodeBlockTab>

      <CodeBlockTab value="pnpm">
        ```bash
        pnpm dlx skills@latest add DRincs-Productions/pixi-vn-ink@latest --skill '*' --agent codex -g -y
        ```
      </CodeBlockTab>

      <CodeBlockTab value="yarn">
        ```bash
        yarn dlx skills@latest add DRincs-Productions/pixi-vn-ink@latest --skill '*' --agent codex -g -y
        ```
      </CodeBlockTab>

      <CodeBlockTab value="bun">
        ```bash
        bun x skills@latest add DRincs-Productions/pixi-vn-ink@latest --skill '*' --agent codex -g -y
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    **Claude Code**:

    <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
        npx skills@latest add DRincs-Productions/pixi-vn-ink@latest --skill '*' --agent claude-code -g -y
        ```
      </CodeBlockTab>

      <CodeBlockTab value="pnpm">
        ```bash
        pnpm dlx skills@latest add DRincs-Productions/pixi-vn-ink@latest --skill '*' --agent claude-code -g -y
        ```
      </CodeBlockTab>

      <CodeBlockTab value="yarn">
        ```bash
        yarn dlx skills@latest add DRincs-Productions/pixi-vn-ink@latest --skill '*' --agent claude-code -g -y
        ```
      </CodeBlockTab>

      <CodeBlockTab value="bun">
        ```bash
        bun x skills@latest add DRincs-Productions/pixi-vn-ink@latest --skill '*' --agent claude-code -g -y
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    **Other agents**:

    <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
        npx skills@latest add DRincs-Productions/pixi-vn-ink@latest --all -y
        ```
      </CodeBlockTab>

      <CodeBlockTab value="pnpm">
        ```bash
        pnpm dlx skills@latest add DRincs-Productions/pixi-vn-ink@latest --all -y
        ```
      </CodeBlockTab>

      <CodeBlockTab value="yarn">
        ```bash
        yarn dlx skills@latest add DRincs-Productions/pixi-vn-ink@latest --all -y
        ```
      </CodeBlockTab>

      <CodeBlockTab value="bun">
        ```bash
        bun x skills@latest add DRincs-Productions/pixi-vn-ink@latest --all -y
        ```
      </CodeBlockTab>
    </CodeBlockTabs>
  </Accordion>
</Accordions>

## Upcoming features [#upcoming-features]

These features are in development.

<CalloutContainer type="info">
  <CalloutDescription>
    If you are interested, feel free to write in the chat below!
  </CalloutDescription>
</CalloutContainer>

* `CHOICE_COUNT()`
* `TURNS()`
* `TURNS_SINCE()`
* `SEED_RANDOM()`
* `LIST_RANDOM()`
* `LIST_COUNT()`
* `LIST_INVERT()`
* `LIST_ALL()`
* `LIST_RANGE()`
* `LIST_MIN()`
* `LIST_MAX()`
* `LIST_VALUE()`

## Syntax ignored by Pixi’VN [#syntax-ignored-by-pixivn]

Some *ink* syntax is ignored by Pixi’VN. You can use these in your ***ink* script** (e.g., for testing in Inky editor), but Pixi’VN will ignore them.

<Accordions>
  <Accordion title="INCLUDE" id="include">
    `INCLUDE` is an *ink* statement to include another *ink* file. In Pixi’VN, you should use [`importInkText`](/jsdoc/pixi-vn-ink/index/functions/importInkText) to load multiple *ink* files instead, so `INCLUDE` is ignored.
  </Accordion>

  <Accordion title="Function definitions" id="function-definitions">
    Function definitions are ignored by Pixi’VN, you can read more about this in the <DynamicLink href="/ink/functions">Functions</DynamicLink> page.
  </Accordion>

  <Accordion title="Narration outside the knots" id="narration-outside-the-knots">
    Narration outside the `knots` (or `labels`) is ignored, except for variables.

    For example:

    ```ink title="ink"
    VAR my_var = false // ✅ This will be handled (because it is a variable)
    Hello // ❌ This will be ignored  [!code warning]
    -> start // ❌ This will be ignored [!code warning]
    === start === // ✅ This will be handled
    My name is John // ✅ This will be handled
    -> DONE // ✅ This will be handled
    ```
  </Accordion>
</Accordions>

## Other features [#other-features]

<Accordions>
  <Accordion title="Differences between native ink and Pixi’VN ink" id="ink-differences">
    * **LIST entry access**: In native **_ink_** you can reference a list entry either by its short name (e.g. `sword`) or by its qualified name (e.g. `items.sword`) and they are equivalent. In **Pixi’VN *ink*** you must **always** use the qualified form `items.sword`. Writing just `sword` resolves to a plain storage entry whose key is the string `"sword"` — it will **not** point to the list member. This applies to all `LIST` declarations.

    * Since "Threads" are not supported by pixi-vn, the `<-` symbol corresponds to <DynamicLink href="/ink/labels#call">calling a knot</DynamicLink>.

    * in this case:

      ```ink title="ink"
      { shuffle:
        -  2 of Diamonds.
          'You lose this time!' crowed the croupier.
      }
      ```

      **In native *ink***, you will see two different dialogues, the first one will be `2 of Diamonds.` and the second one will be `'You lose this time!' crowed the croupier.`.

      **In Pixi’VN *ink***, you will not see two different dialogues, but the following dialogue: `2 of Diamonds.\n\n'You lose this time!' crowed the croupier.`. In <DynamicLink href="/ink/markup#markdown-on-ink">Markdown</DynamicLink> it will be displayed like this:

      ```txt
      2 of Diamonds.
      'You lose this time!' crowed the croupier.
      ```

    * if a `weave` (in following example `shove`) is attached to a one time choice, and it is opened with `-> shove` it will not invalidate the one time choice. To invalidate it you will have to select the choice as usual.

      Here is an example:

      ```ink title="ink"
      -> start
      === start ===
      * [1] -> shove
      * (shove) [2] 2
      * {shove} [3] -> END
      -  -> start
      -> DONE
      ```

      In case you take choice `1`, the second time it will be opened `start`:

      * if you use **native *ink***, you will only be able to choose choice `3`. The choice `2` is hidden because being "one time" **native *ink*** will know that you have already made this decision with `-> shove`.
      * if you use **Pixi’VN *ink***, you will be able to choose choice `2` or `3`. The choice `2` is not hidden because **Pixi’VN *ink*** doesn't know that `shove` is paired with a choice.

      To get the same logic as `start` both in **native *ink*** and **Pixi’VN *ink*** you will have to write the following code:

      ```ink title="ink"
      -> start
      === start ===
      * [1] -> shove
      * (shove) {!shove} [2] 2
      * {shove} [3] -> END
      -  -> start
      -> DONE
      ```
  </Accordion>
</Accordions>
