# ink language integration (/ink)





Pixi’VN lets you write interactive narratives using &#x2A;**ink***, a scripting language designed for branching stories.

**What is *ink*?*&#x2A; &#x2A;**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/).

The ***ink* + Pixi’VN integration** uses [inkjs](https://github.com/inkle/inkjs) and [PixiVNJson](/jsdoc/pixi-vn-json&#x29; to parse ***ink* code*&#x2A; and generate JSON that Pixi’VN can interpret. This means JavaScript/TypeScript and &#x2A;**ink**&#x2A; share the same storage and canvas, and you can launch &#x2A;**ink*** `knots` (or `labels&#x60;) from JavaScript/TypeScript and vice versa. You get the best of both worlds: write narration in &#x2A;**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
```

<InkExample />

Why? [#why]

Writing narrative directly in **JavaScript/TypeScript*&#x2A; can be slow and complex, especially for beginners. &#x2A;**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.

Installation [#installation]

<Callout title="Templates" type="info">
  Starting a new Pixi’VN project? Use a <DynamicLink href="/start#project-initialization">template</DynamicLink> that already includes *ink*.
</Callout>

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="yarn">
      yarn
    </CodeBlockTabsTrigger>

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

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

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

  <CodeBlockTab value="npm">
    ```sh
    npm install @drincs/pixi-vn @drincs/pixi-vn-ink
    ```
  </CodeBlockTab>

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

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

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

  <CodeBlockTab value="deno">
    ```sh
    deno install npm:@drincs/pixi-vn npm:@drincs/pixi-vn-ink
    ```
  </CodeBlockTab>
</CodeBlockTabs>

Initialize [#initialize]

After installing, use `importInkText()&#x60; 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 &#x2A;**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_ink_files" id="import-ink-files">
    <Callout type="info">
      This guide uses [Vite](https://vitejs.dev/), but the same logic applies elsewhere.
    </Callout>

    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="import_ink_folder" id="import-ink-folder">
    <Callout type="info">
      This guide uses [Vite](https://vitejs.dev/), but the same logic applies elsewhere.
    </Callout>

    To import multiple `.ink` files, use `import.meta.glob`:

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

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

      <CodeBlockTab value="utils/ink-utility.ts">
        ```ts
        import { convertInkText, importInkText } from "@drincs/pixi-vn-ink";

        async function getInkText() {
            const files = import.meta.glob<string>("../ink/*.ink", {
                query: "?raw",
                import: "default",
            });
            return await Promise.all(
                Object.values(files).map(async (importFile) => {
                    return await importFile();
                }),
            );
        }

        export async function importAllInkLabels() {
            let fileEntries = await getInkText();
            await importInkText(fileEntries);
        }

        export async function convertInkToJson() {
            let fileEntries = await getInkText();
            return await Promise.all(fileEntries.map((data) => convertInkText(data)));
        }
        ```
      </CodeBlockTab>

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

  <Accordion title="Vite.js plugin" id="vite-plugin">
    For a better developer experience, use the Vite plugin:

    <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>
</Accordions>

Upcoming features [#upcoming-features]

These features are in development.

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

* `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&#x2A; 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.

INCLUDE [#include]

`INCLUDE` is an *ink* statement to include another *ink* file. In Pixi’VN, you should use `importInkText()` to load multiple *ink* files instead, so `INCLUDE` is ignored.

Function definitions [#function-definitions]

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

Narration outside the `knots` [#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
```

Other features [#other-features]

<Accordions>
  <Accordion title="ink_differences" id="ink-differences">
    * **LIST entry access*&#x2A;: In native &#x2A;**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 &#x2A;*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 &#x2A;*native *ink***, you will only be able to choose choice `3`. The choice `2` is hidden because being "one time" &#x2A;*native *ink*** will know that you have already made this decision with `-> shove`.
      * if you use &#x2A;*Pixi’VN *ink***, you will be able to choose choice `2` or `3`. The choice `2` is not hidden because &#x2A;*Pixi’VN *ink*** doesn't know that `shove` is paired with a choice.

      To get the same logic as `start` both in &#x2A;*native *ink*** and &#x2A;*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>
