https://github.com/technix/atrament-web-ui
Atrament UI, an engine for creating feature-rich text games or choice-based interactive fiction for both web and desktop
https://github.com/technix/atrament-web-ui
choice-based-game game-development ink interactive-fiction javascript
Last synced: about 2 months ago
JSON representation
Atrament UI, an engine for creating feature-rich text games or choice-based interactive fiction for both web and desktop
- Host: GitHub
- URL: https://github.com/technix/atrament-web-ui
- Owner: technix
- License: mit
- Created: 2018-07-10T13:13:35.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2026-01-30T17:20:33.000Z (about 2 months ago)
- Last Synced: 2026-01-31T01:42:11.152Z (about 2 months ago)
- Topics: choice-based-game, game-development, ink, interactive-fiction, javascript
- Language: JavaScript
- Homepage: https://atrament.ink
- Size: 91.3 MB
- Stars: 25
- Watchers: 5
- Forks: 4
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
# Atrament

Atrament is a modular tool to create text games or interactive fiction stories for the web and desktop.
It uses [inkjs](https://github.com/y-lohse/inkjs) to interpret Ink scripts, [@atrament/core](https://github.com/technix/atrament-core) as a game engine, and [Preact](https://preactjs.com/) as a Web UI framework.
[Documentation](https://atrament.ink) | [Live Demo](https://technix.github.io/atrament-web-ui/)
## Getting started
Before proceeding, install [Node.js](https://nodejs.org/) for your platform, version 22.12+ or higher.
### Create a new Atrament project
The easiest way to create a new Atrament project is to use `atrament-wizard` tool:
```
npx atrament-wizard create
```
By default, Atrament comes with a minimal Ink story template. If you want to see a demonstration of various Atrament features, copy content of `examples/showcase` into `root/game` folder after creating a project.
#### Expert mode
If you're an experienced JS developer or want to use development version of Atrament Web UI, you can do all the steps manually:
```
git clone https://github.com/technix/atrament-web-ui.git --depth 1
cd atrament-web-ui
npm install
npm run install-inklecate
```
After performing these steps, you can customize the game configuration:
1. Remove all files from `root/game` and put your game files there (ink script, images, music etc).
2. Edit `atrament.config.json`, change the `source` parameter in `game` section to name of your main Ink file. File path for this file is relative to `root/game` folder.
3. You may change other configuration options in `atrament.config.json`:
* "`name`": your application name
* "`short_name`": your application short name, or codename
* "`description`": your application description
* "`theme`": default app theme, may be "`light`", "`sepia`", or "`dark`"
* "`font`": default UI and game font, may be "`System`", "`Sans Serif`", "`Serif`", "`Monospaced`", "`Fira Sans`", "`Lora`", "`Merriweather`", or "`OpenDyslexic`"
4. (optionally) replace `root/logo.png` with your project logo. This image is used to generate favicon and application icon.
### Run application locally in dev mode
```
npm start
```
The application is available at http://localhost:8900. If any source file (Ink or Javascript) is edited, the application automatically restarts with these changes.
### Build application for publishing
Atrament game can be published as a web application, as a single HTML file, or as a desktop application. See [Publishing](#publishing) section of this document for more details.
## Ink tags handled by Atrament Web UI
### Global tags
| Tag | Description |
| :-------- | :------------------------- |
| `# title: A Story Written In Ink` | Game title |
| `# author: John Doe` | Author |
| `# cover: some/image.jpg` | Cover image to display at the main menu screen. You can change its dimensions by adding width value after the image path: `# cover: some/image.jpg 30%` |
| `# title_screen_layout: cover, title, author` | Order of the components on the title screen. You can remove items from this list or rearrange them. |
| `# theme: light` | Game color theme: `light`, `sepia`, or `dark` |
| `# font: System` | Game font: `System`, `Sans Serif`, `Serif`, `Monospaced`, `Fira Sans`, `Lora`, `Merriweather`, or `OpenDyslexic` |
| `# observe: varName` | Register variable observer for `varName` Ink variable. Variable value is available in `vars` section of the Atrament state. |
| `# persist: varName` | Save `varName` Ink variable value to the persistent storage, restore before game starts. |
| `# sessions: 3` | Amount of game sessions. Each session has its own set of saves. |
| `# autosave: false` | Disables autosaves. |
| `# saves: 5` | Amount of available slots for saves. |
| `# load_from_checkpoints` | Show checkpoints in the list of games to load. |
| `# continue_maximally: false` | Pause the story after each line. |
| `# single_scene` | Store only the last scene in the Atrament state. |
| `# scenes_align: center` | Scene alignment on the screen. Can be set to `top`, `center`, or `bottom`. |
| `# choices: grouped numbered` | Changes the choices appearance. Can be set to any combination of: `grouped` (displayed as button group); `numbered` (displays numbers of choices); `left` or `right` (aligns choice text to the left or right); `row` (show choices in a single row instead of a column); `borderless` (show choice buttons without borders) |
| `# hypertext` | Use links instead of choices. See "[Hypertext mode](#hypertext-mode)". |
| `# toolbar: toolbar_function` | Use output of this function as a toolbar content. |
| `# about: about_function` | Use output of this function as an "About" screen content. |
| `# background: some/picture.jpg` | Set background image for the game backdrop. |
| `# allow_external_function_fallbacks` | If the function defined with EXTERNAL is not found, run Ink function with the same name instead. |
| `# debug` | Enable Ink story debugger. |
### Knot tags
| Tag | Description |
| :-------- | :------------------------- |
| `# IMAGE: some/picture.jpg` | Show image before paragraph text. |
| `# BACKGROUND: some/picture.jpg` | Set background image for the game text. Use `# BACKGROUND: false` to unset it. |
| `# PAGE_BACKGROUND: some/picture.jpg` | Set background image for the game backdrop. Use `# PAGE_BACKGROUND: false` to unset it. |
| `# CLEAR` | Clear scenes list before saving current scene to Atrament state. |
| `# AUDIO: sound.mp3` | Play sound (once). |
| `# AUDIOLOOP: music.mp3` | Play background music (looped). There can be only one background music track. |
| `# AUDIOLOOP: false` | Stop playing music. |
| `# PLAY_SOUND: sound.mp3` | Play sound (once). |
| `# STOP_SOUND: sound.mp3` | Stop playing specific sound. |
| `# STOP_SOUND` | Stop playing all sounds. |
| `# PLAY_MUSIC: music.mp3` | Play background music (looped). There can be multiple background music tracks, played simultaneously. |
| `# STOP_MUSIC: music.mp3` | Stop playing specific background music. |
| `# STOP_MUSIC` | Stop playing all background music. |
| `# CHECKPOINT` | Save game to the 'default' checkpoint. |
| `# CHECKPOINT: checkpointName` | Save game to checkpoint `checkpointName`. |
| `# SAVEGAME: saveslot` | Save game to `saveslot`. |
| `# RESTART` | Start game from beginning. |
| `# RESTART_FROM_CHECKPOINT` | Restart game from latest checkpoint. |
| `# RESTART_FROM_CHECKPOINT: checkpointName` | Restart game from named checkpoint. |
| `# CLASS: classname` | Apply CSS class to the paragraph `
` element. |
| `# SHUFFLE_CHOICES` | Shuffle choice order in this knot. |
| `# PROMPT: What would you like to do?` | Display prompt text before the choices. |
| `# HYPERTEXT` | Use links instead of choices for this scene. See "[Hypertext mode](#hypertext-mode)". |
| `# CHOICES: grouped numbered` | Changes the choices appearance for this scene only. Uses the same syntax as global `#choices` tag. |
| `# AUTO_CHOICE` | Make a choice automatically. See "[Automatic choice](#automatic-choice)". |
Note: For sound effects, please use either AUDIO/AUDIOLOOP or PLAY_SOUND/PLAY_MUSIC/STOP_SOUND/STOP_MUSIC tags. Combining them may lead to unexpected side effects.
### Choice tags
| Tag | Description |
| :-------- | :------------------------- |
| `# UNCLICKABLE` | The choice can't be selected. Alternative names: `#DISABLED`, `#INACTIVE` |
| `# HIDDEN` | The choice is not shown to the player, but can be selected with links or buttons. |
| `# CLASS: classname` | Apply CSS class to the choice `` element. |
## Features
### Save management
Atrament Web UI supports the following save types:
1. **Autosave**. By default, game saves its progress after each choice. If autosave is present for the game, player can continue playing by clicking "Continue" in the main menu. When global tag `autosave: false` is present, autosaving is disabled. If autosaving is disabled and there are no saved checkpoints, "Continue" button will not be available.
2. **Checkpoints**. They are controlled by knot tags `#CHECKPOINT` and `#RESTART_FROM_CHECKPOINT`. Authors can use named checkpoints, adding names to these tags. If there is no autosave, players can continue playing from latest saved checkpoint by clicking "Continue" in the main menu.
3. **Saves**. They are disabled by default. Authors can set global tag `#saves` to define amount of available save slots. Players can save and load games using the slots provided. If global tag `#load_from_checkpoints` is set, players can also load game from any saved checkpoint.
In addition to above, Atrament Web UI supports **sessions**, which can be enabled by global tag `#sessions`. If they are enabled, players have to choose game session before starting a game. Each session has its own autosaves, checkpoints, and saves.
### "Click to continue"
A single choice with text '>>>' is treated as "click to continue". Choice list is not shown, and player can continue story by clicking the screen or pressing "Space" or "Enter" key. After 3 seconds of inactivity, animated hint is displayed in the bottom of the screen.
```
This story will proceed when user clicks screen.
+ [>>>] -> next_knot
```
You can also provide a delay in **seconds** for the "click to continue". A timed choice is presented as a slightly different circular button. After the delay, story continues automatically.
```
This story will proceed either after user clicks screen or after 3 seconds.
+ [>>>3] -> next_knot
```
Click-to-continue choice can be configured:
* `clickable` - pause before the choice becomes clickable and player can continue the story. If omitted, player can click and continue story immediately.
* `animation` - pause before displaying animation. If omitted, the animation displays immediately.
* `continue` - pause before story continues automatically. If omitted, the game continues only after click or keypress.
All pauses are set in seconds.
```
+ [>>>(clickable=3 animation=5 continue=10)] -> next_knot
```
### Automatic choice
You can make specific or random choice automatically with `#AUTO_CHOICE` knot tag:
```# AUTO_CHOICE: delay=10 choice="Some choice"```
Options of the tag:
* `delay` - sets a delay before automatic choice and displays a choice timer bar. If omitted, a choice is selected immediately.
* `choice` - text of the choice to be auto-selected. If omitted, a choice option will be selected randomly.
### Hypertext mode
If global tag `hypertext` is set, Atrament UI switches to hypertext mode. In this mode choice options are not displayed. However, author can use `[link=target choice text]link text[/link]` to link specific phrases to the choices.
For better user experience in hypertext mode authors can set global tags `single_scene` and `scenes_align: top`.
Author can enable hypertext for a chosen scene with `HYPERTEXT` scene tag.
```
# hypertext
# single_scene
# scenes_align: top
You are standing in an open field west of a white house,
with a boarded [link=Open door]front door[/link].
There is a [link=Examine mailbox]small mailbox[/link] here.
+ [Examine mailbox] -> examine_mailbox
+ [Open door] -> inside_house
```
### Custom markup
| Markup | Description |
| :-------- | :------------------------- |
| `[picture]path/to/image.jpg[/picture]` | Display image (same as `#IMAGE` knot tag). The image is sized automatically to fit the container. When using images inside of the `[block]` tags, you may want to set picture margins.
Attributes:
`width=50%` sets picture width.
`leftmargin=0.5em` sets left margin.
`rightmargin=0.5em` sets right margin.|
| `[img]path/to/image.jpg[/img]` | Display inline image. |
| `[button=function]Text[/button]`
`[button onclick=function]Text[/button]` | Display button, call a function when clicked. If function outputs a text, it will be displayed as a new overlay content. If not, current overlay content will be updated.
Attributes:
`onclick=function` function to be called when clicked.
`disabled=true` disables the button
`bordered=false` hide button borders
`display=modal` display modal instead of overlay when clicked |
| `[link=choice text]Text[/link]`
`[link to=choice text]Text[/link]`
`[link onclick=inkFunction]Text[/link]`| Creates a link. When clicked, the target choice is activated, and game continues. If `onclick` attribute is set, the link behaves like a button and runs corresponding Ink function. Attribute `display=modal` can be added to display a modal overlay instead of fullscreen.|
| `[progress value={variable}]Inner text[/progress]` | Displays a progress bar.
Attributes:
`value=x` current progressbar value
`min=x` minimal progressbar value
`max=x` maximal progressbar value
`style=accent` highlight progressbar with accent theme color |
| `[input var=variable]` | Input element, sets value of given variable. Default value of this field is read from the same variable. Disabled on the inactive scenes.
Attributes:
`var=n` variable name to change
`type=number` input type. Possible values: `text`, `number`.
`placeholder=text` placeholder text |
| `[spoiler]text[/spoiler]` | Hidden text. Text visibility toggles on click. |
| `[info]text[/info]` | Display text as an information block. Since this is a block element, it is recommended to use it on a whole paragraph.
Attributes:
`align=left` text alignment: `left`, `right`, or `center`
`font=system` use system font
`side=n` add color to the left infobox side. Possible values: `highlight`, `accent`. |
| `[banner]text[/banner]` | Display text as an banner block. Since this is a block element, it is recommended to use it on a whole paragraph.
Attributes:
`style=accent` use accent color
`allcaps=true` display text in all capitals |
| `[css]text[/css]` | Applies CSS classed and/or styles to the text.
Attributes:
`class=CSS_class` applies CSS class to the text.
`style="CSS style string"` applies CSS style to the text. |
| `[font=Courier New]text[/font]`
`[font family=Courier New]text[/font]` | Applies font to the text. |
| `[highlight]text[/highlight]`
`[highlight color=yellow bgcolor=black]Text[/highlight]` | Highlights text with accent color.
Optional parameters `bgcolor` and `color` allow to set both background and foreground color for text. |
| `[block]text[/block]` | Defines a text block.
Attributes:
`width=value` block width. Can be defined in percents (recommended) or other CSS units.
`align=left` aligns text horizontally in the block. Possible values: `left`, `center`, `right`
`valign=top` aligns text vertically in the block. Possible values: `top`, `middle`, `bottom` |
| `[video]path/to/video.mp4[/video]` | Display video.
Attributes:
`loop=false` disable video loop.
`muted=true` play video muted|
| `[url=https:\/\/atrament.ink]link text[/url]`
`[url href=https:\/\/atrament.ink]link text[/url]` | Creates a link to an web resource. When clicked, the resource is opened in a new browser tab. *Note: you have to escape "/" symbols in the URL, as shown in the example.* |
| `[---]` | Line separator.
Attributes:
`width=30%` set separator width. |
All markup tags support `class` attribute to set a CSS class for the element.
Note: it is not possible to wrap multiple paragraphs with these tags. Use `
` tag for line breaks if you need multiline text in tags.
#### Tables
You can make tables with the following markup:
```
[table]<>
[header]Header 1[ ]Header 2[ ]Header 3[/header]<>
[row]Cell 1[ ]Cell 2[ ]Cell 3[/row]<>
[row]Cell 4[ ]Cell 5[ ]Cell 6[/row]<>
[/table]
```
Please note `<>` operator at the end of each table line - this is required to render the table properly.
The table consists of header `[header][/header]` (optional) and rows `[row][/row]`. The `[ ]` is a cell separator.
Attributes of the `[table]` tag:
* `border=false` disables table borders.
* `padding=false` disables table cell paddings.
* `columns="20% 20% 60%"` sets column width. You have to set width for each column in the table.
* `fixed=true` forces text wrapping, so columns always have fixed width.
#### Layered images
To create a multi-layer image, use `[layers]` markup tag:
```
[layers]<>
[picture]southern_fjord.png[/picture]<>
[picture x=369 y=2669]quill-ink.png[/picture]<>
[picture x=2261 y=2531]fountain-pen.png[/picture]<>
[/layers]
```
First `[picture]` tag inside of the `[layers]` is background. Background dimensions are determined automatically to fit the screen.
The `x` and `y` attributes of other `[picture]` tags determine image coordinates relatively to the top left corner of the background image. These attribute values are set in **pixels**. If omitted, the layer is aligned to the top left corner.
Attributes of the `[layers]` tag:
* `width=50%` sets layered picture width.
* `leftmargin=0.5em` sets left margin.
* `rightmargin=0.5em` sets right margin.
Attributes of the `[picture]` tag inside of a `[layers]` tag:
* `x=10` set X coordinate relative to top left corner of the background
* `y=22` set Y coordinate relative to top left corner of the background
* `to="Choice text"` clicking on this layer links to the choice with this text
* `onclick=inkFunction` clicking on this layer calls this Ink function
* `display=modal` when Ink function is called, the result will be displayed in the modal window
You can have clickable areas on the image with `[area]` markup tag. Attributes are similar to `[picture]` tag, but you have to define width and height of the clickable area explicitly. You can use either "x, y, width, height" or "x,y,x1,y1" attributes to set the area dimensions.
```
[layers]<>
[picture]southern_fjord.png[/picture]<>
[area x=369 y=2669 width=512 height=512 onclick=inkFunction]<>
[area x=2261 y=2531 with=300 height=100 onclick=otherInkFunction display=modal]<>
[area x=123 y=987 width=110 height=222 to="Choice Text"]<>
[area x=123 y=987 x1=223 y1=1200 to="Choice Text 2"]<>
[/layers]
```
Attributes of the `[area]` tag:
* `x=10` set X coordinate of the top left corner of the area relative to top left corner of the background
* `y=22` set Y coordinate of the top left corner of the area relative to top left corner of the background
* `width=10` set width of the area
* `height=22` set height of the area
* `x1=20` set X coordinate of the bottom right corner of the area
* `y1=32` set Y coordinate of the bottom right corner of the area
* `to="Choice text"` clicking on this layer links to the choice with this text
* `onclick=inkFunction` clicking on this layer calls this Ink function
* `display=modal` when Ink function is called, the result will be displayed in the modal window
### Overlay
Atrament UI can display custom data (inventory, character stats etc.) as an overlay.
To display an overlay, authors need to define a button with the `[button]` tag, which calls an Ink function. If the function returns text content, it will be displayed as an overlay. The overlay content can have buttons too.
If the first line of the function is a `[title]Overlay Title[/title]` tag, this title will be displayed in the toolbar.
Example of the toolbar and overlays:
```
# toolbar: game_toolbar
...
=== function game_toolbar()
[button=inventory]Inventory[/button]
[button=stats]Stats[/button]
=== function inventory()
[title]Inventory[/title]
You are carrying:
...
=== function stats()
[title]Character: {character_name}[/title]
Health: {health}
...
```
### "About" screen
Author can add an "About" screen to the game with the `#about` global tag. When it is set, UI shows "About" button on the main game page. Clicking it will display content from the function in the Ink file - see example:
```
# about: game_about
...
=== function game_about()
Game Title
"About" screen content
...
```
### HTML templates
Atrament can render custom HTML templates with variable substitution. It uses [mustache](https://mustache.github.io/mustache.5.html) as a template parser.
1. Create a template in the `resources/templates` folder. It should be an `.html` file - for example, `card.html`.
```
{{title}}
```
2. Add the template to your Ink script with `[template]` markup tag:
```
VAR CARD_TITLE="Card Title"
[template src=card.html var:title="{CARD_TITLE}"]<>
Your content is [highlight]here[/highlight]<>
[/template]
```
Variables are passed via `var:X` attributes. For example, `var:title="Header"` will be passed as template variable `title`. If you need to pass a game asset path (image, sound, video) into the template, prepend it with `GAME_ASSET:` prefix: `var:cardImage="GAME_ASSET:yourimage.jpg"`
Tips:
- templates can be without content: `[template src=card.html][/template]`
- templates are wrapped into a `
` element. You can add custom class to the wrapper with `class` attribute of the `[template]` tag.
- if you need an inline template, you can change wrapper to a ``: `[template src=x.html wrapper=span][/template]`.
- templates can be nested: `[template src=card.html][template src=title.html var:title="Card Title"][/template][/template]`
## Customization
### Themes
To add a theme to the application, create a JSON file in the `resources/themes` folder with the following structure:
```
{
"name": "custom",
"theme": {
"bg-color": "#FCFCFC",
"fg-color": "#5D576B",
"shade-color": "rgba(0, 0, 0, 0.1)",
"font-color": "#333333",
"accent-bg-color": "#FCFCFC",
"accent-fg-color": "#F7567C",
"accent-inverse-color": "#FCFCFC",
"border-radius": "0.5rem",
"border-radius-inline": "0.25rem"
}
}
```
| Theme parameter | Description |
| :-------- | :------------------------- |
| name | Theme display name. |
| bg-color | App background color. |
| fg-color | Primary color for controls. |
| shade-color | Shadows and highlights. |
| font-color | App and game text color. |
| accent-bg-color | Background for accented elements. |
| accent-fg-color | Foreground for accented elements. |
| accent-inverse-color | Foreground for active accented elements (accent-fg-color is used as a background then). |
| border-radius | Round the corners of choices, modals, and boxes. |
| border-radius-inline | Round the corners of inline buttons. |
*Note: You can use any valid CSS values for the theme.*
### Fonts
To add a font to the application, create a folder in the `resources/fonts` folder with the following files:
* `index.js` with the following content:
```
import('./index.css');
export default {
name: 'Font Name',
fallback: 'sans-serif', // fallback font
};
```
* `index.css`, which includes corresponding `@font-face` directives
* font files, referenced in the `index.css`
To remove font from the application, delete the font folder from `resources/fonts`.
### Custom CSS styles and classes
To add custom CSS classes or modify styles of existing elements, edit `resources/styles/custom.css` file. It contains a list of modifiable element classes for reference.
### External functions
If you add any external functions to your Ink file, the JS function code should be added as a separate `*.js` file to the `resources/externals` folder. See example file `example.js.txt` for details.
## Publishing
### Web application
Default Atrament UI build is a progressive web application for web server deployment.
```
npm run build-web
```
The standalone web application files will be in `build/web` folder. Use `npm run preview` command to test it in browser at http://localhost:4173/.
### Single file build
```
npm run build-singlefile
```
The game can be exported to a standalone web page, which can be opened locally - similar to Inky or Twine web export.
To export game in a single file format, run `npm run build-singlefile` command. The resulting web page files will be in the `build/singlefile` folder.
*Please note: single file build uses only system fonts to reduce file size. If you want to include all fonts from the `resources/fonts` folder, use `npm run build-singlefile -- -- --embed-fonts` command to build the game.*
### Standalone executables build
```
npm run build-standalone
```
To build standalone executables for Windows, Linux, and MacOS, use `npm run build-standalone` command. The folder with executables for all platforms will be in the `build/standalone` folder. The build also creates ZIP archives for each platform.
### Zipped game content
Atrament UI supports zipped game content, when whole game is loaded into browser as a single zip file. The advantage of this mode is instant asset loading at the cost of increased startup time. However, it makes sense only for default web export mode.
To enable this feature, edit `atrament.config.json` and add `zip` option to it with the name of zip file:
```
{
...
"game": {
"path": "game",
"source": "gamefile.ink",
"zip": "yourgame.zip"
}
}
```
*Please note: this option is ignored for development and single file builds.*
### Publishing Atrament games from Inky
You can create a single file Atrament games written with Inky, without setting up an Atrament project. Run this command in the project folder with Ink files to create a single file build:
```
npx atrament-wizard publish
```
## Debugger
When `#debug` global tag is set, debugger can be invoked by pressing debugger button on the screen or double pressing of `~` key.
Atrament Debugger provides the following functionality:
* General information on ink script
* List of global tags
* List of ink variables (view and edit)
* List of visit counts
* Run Ink function with specified parameters and see the output
* Navigation to knot/stich path
## Keyboard shortcuts
| Key | Description |
| :-------- | :------------------------- |
| 1,2,3... | Select corresponding choice option. |
| Space, Enter | Continue story. |
| Esc | Show/hide settings dialog. |
## Atrament repositories
- [atrament-web](https://github.com/technix/atrament-web)
- [atrament-core](https://github.com/technix/atrament-core)
See also [Atrament core documentation](https://github.com/technix/atrament-core/blob/master/README.md) for additional info on Atrament API.
## LICENSE
Atrament is distributed under [MIT license](LICENSE.md).
Copyright (c) 2023 Serhii "techniX" Mozhaiskyi
Made with the support of the [Interactive Fiction Technology Foundation](https://iftechfoundation.org/)