New narrat syntax highlighting extension for Visual Studio Code

Published on August 01, 2022

Narrat Syntax highlighting

Until now, narrat used .rpy files as a way to easily get syntax highlighting from another similar language.

Now, narrat has its own syntax highlighting grammar definition used to create the Narrat Language extension for Visual Studio Code.

This means narrat scripts can now use the official Narrat .nar extension

This means that the quality of the syntax highlighting is much better, and in longer term it will be possible to add snippets, code autocompletion and other intellisense features to narrat scripting.

Syntax highlighting

The extension is open source (see the GitHub repository), so it should be easy for anyone to create alternative versions of it for other text editors. The grammar is defined in the standard TextMate format.

Templates and docs updated

All the game templates and docs have been updated to reflect the change with everything defaulting to .nar, and all references of .rpy in the docs deleted to avoid confusion.

Narrat Game Jam

Published on July 23, 2022

Narrat Game Jam

game jam banner There is a narrat game jam coming. This will be an occasion for anyone to try narrat out.

Anyone can join the game jam on the page

The engine is very easy to get into as it has an easy scripting syntax and doesn’t need much to get started.

A new tool to easily and automatically start a new project has been created for the occasion, see the updated Getting Started guide for info on how to start.

TLDR on how to create a narrat game with the new system:

  1. Have node.js installed (16 or above)
  2. Open a terminal and run npm create narrat
  3. The tool will guide you and let you customise your game project. You’re good to go!

Narrat 2.2.0 - Scripting improvements, Steam support, new sound features and save slots

Published on July 21, 2022

Narrat 2.2.0

Narrat 2.2.0 (and a few more versions since) released recently, there are a few new features since the last 2.1.0 update:

Docs rework

Documentation of the various commands has been reworked to be more exhaustive, and split in categories. There are a lot of commands now, and it was getting hard to find them in the docs, so this should be easier to follow.

new docs

Steam Support

Narrat now has Steam support! The narrat template now provides by default everything needed to run on Steam.

See the docs to learn how to publish for Steam

Save slots

The engine now supports multiple save slots out of the box! There is no change required on the game side. The main menu will now show a “New Game” button, and if a game has already been started once, it will also show “Continue” (to continue the last save slots) or “Load Game” (to pick a save file)

Save slots screenshot

New volume mode, multiple audio channels per mode and individual volume sliders

There is a new audio mode: ambiant meant to be used for ambiant sounds. The engine has also been updated to support multiple audio channels per mode, and individual volume sliders for each channel.

What this means is that games can overlay multiple musics or ambiant sounds easily, and control each channel individually.

The menu modal with game options now shows one volume slider per audio mode, and a master volume slider for overall volume.

Volume sliders

See the audio commands section of the docs for more info.

New think command

A new think command has been added to narrat which works the same as the talk command but won’t print quotes around the text.

The talk, think and text command now also use their own CSS class when displaying, so that it’s easy for a game to customise how each version looks.

New math commands

A lot of new math commands have been added to manipulate numbers, including functions to round numbers, make numbers positive/negative, constraining numbers between min and max values, and more.

Find them in the new Math operations docs

Big internal Refactor

This doesn’t really affect usage, but it fixes internal bugs and enables future development + better confidence in the codebase. To summarise, the way the Virtual Machine that runs narrat scripts works has been improved a lot.

More details: The scripting engine has been refactored internally to differentiate between blocks and frames. A frame in the stack is now a function call (running a label or jumping), whereas blocks are for branching. That means any block inside a frame still has access to the same scoped variables (which are stored in the frame), and return can now be called anywhere inside a function and will interrupt the function and return, like in other languages.

And more stuff

See the full changelog of recent updates to find more new features

Narrat 2.1.0

Published on July 15, 2022

Here are all the new features from the changelog since 2.0.0


fix: The text command (default command for printing text without using talk) was adding quotes around text since 2.0.0. This is now fixed.


⚠️ Breaking Change ⚠️

Narrat has been updated to use vite 3. The only breaking change is that the path of the narrat CSS file has changed:

In the game’s index.ts the import of CSS needs to change. Before:

import 'narrat/dist/lib.css';


import "narrat/dist/style.css";

Many new features, TODO: fill changelog.

Data shorthand

It is now no longer necessary to prefix things with data when setting or getting variables.

For example set is equivalent to set

The way the system works when looking up variables is:

  • Return if there’s a base variable in the lookup state matching (for example data, skills etc)
  • Otherwise if a variable exists in the local scope (created with var) use that
  • Otherwise default to assuming we’re editing something inside data

New example RPG game

There is now an example dungeon crawler turn based RPG game made as a test to push the engine and scripting features. It is not meant to be a full game, but can be a useful reference for advanced usage.

Anchor feature for buttons

There is now an optional anchor property for buttons, useful for anchoring a buttom from its center or any other place.


  "buttons": {
    "go_front": {
      "enabled": false,
      "background": "img/ui/front.png",
      "position": {
        "left": 440,
        "top": 120,
        "width": 96,
        "height": 96
      "anchor": {
        "x": 0.5,
        "y": 0.5
      "action": "choose_front"

Base assets path

There is now a way to pass a base assets path to narrat, which will be prepended to the path of any assets that need to be loaded by the engine (this was needed to implement the multiple demos in a single repo).

The option is baseAssetsPath which can be passed in the first options object of startApp.

See the demo.ts file for an example of how to use it.

Game examples now in the repo

The repo has been restructures to allow having multiple example games directly inside it.

The examples/ folder contains a subfolder for each demo game, where the data/asset files for a game can be placed.

To run an example game, the dev script needs to be run with a special environment variable pointing to the path of the game to run. For example, a command has been added to run the rpg game:

npm run rpg -> Runs cross-env VITE_EXAMPLE=examples/rpg npx vite dev

Demo games can also be built from the repo. For example:

npm run build-rpg -> Runs cross-env VITE_DEMO_BUILD=rpg npx vite build && shx cp -r examples/rpg/* built-example/rpg. The built game is then available in the built-example/rpg folder.

New commands

Added a lot of new commands while making the RPG example.

Math operations

  • Negate numbers: neg 1 -> returns -1
  • Absolute function: abs -1 -> returns 1
  • Min - returns lowest passed number: min 1 2 -> returns 1
  • Max - returns highest passed number: max 1 2 -> returns 2
  • Clamp - returns number between min and max: clamp 1 2 3 -> returns 2 (syntax: clamp [min] [max] [value])

Random generation

  • Random number: random 1 10 -> returns an integer random number between 1 and 10 (inclusive)
  • Random float: random_float 1 10 -> returns a float between 1 and 10
  • Random from args: random_from_args "a thing" "another thing" 2 "things can be any value" -> returns a random item from the list of arguments


  • Concat: concat "a" "b" -> returns “ab” (Syntax: concat [string1] [string2] [string3]...)
  • Join: join ", " "a" "b" -> returns “a, b” (Syntax: join [separator] [item1] [item2] [item3] ...)


  • Set level: set_level agility 1 -> sets the level of the skill “agility” to 1
  • Get level: get_level agility -> returns the level of the skill “agility”
  • Get xp: get_xp agility -> returns the xp of the skill “agility”


  • Log: log $someVariable -> logs the value of the variable $someVariable to the console (Syntax: log [value1] [value2] [value3]...). Can be used to log anything for debugging


New layers feature: Multiple screens can be overlaid on top of each other in layers.

Layers are defined by their number, being displayed from 0 to x. By default, the set_screen command sets a screen on the first layer, as it did before. To set a screen on a different layer, pass the layer number as a second parameter.

set_screen my_screen 1
// do stuff, then remove the overlay
empty_layer 1


feature: The left-side viewport now uses DOM instead of canvas so screens and buttons can use animated gifs or webp.

The config has optionally been made easier to edit, with no need to define images in the images part of the config. buttons can also now be optionally defined inside the screen directly. The config is still compatible with the old syntax.


  "screens": {
    "default": {
      "background": "narrat"
    "map": {
      "background": "img/backgrounds/map.png",
      "buttons": [
          "id": "shopButton",
          "enabled": false,
          "background": "img/ui/shop-button.png",
          "position": {
            "left": 38,
            "top": 6,
            "width": 255,
            "height": 226
          "action": "shopButton"
          "id": "parkButton",
          "enabled": false,
          "background": "img/ui/park-button.png",
          "position": {
            "left": 632,
            "top": 86,
            "width": 255,
            "height": 226
          "action": "parkButton"


fix: quest_completed? now returns the correct value


Internal engine changes, shouldn’t impact users.

Refactor to the VM and commands system to handle the way the stack flows better. Commands don’t have to call nextLine on the VM to run the next line, instead the VM properly knows when a command is truly finished and controls the program’s flow.

Also renamed stacks to frames, as the stack is the array that contains the frames and this was confusing.


  • Fix: Auto scrolling when new text is added now works properly
  • Fix: error when using set_stat or add_stat with a value of 0


Fixed an error where the roll function always returned true even if the skill check failed


Fixed a reggression bug introduced in 2.x where player choices weren’t printed anymore in the dialogue history after making a choice.


Fixed more audio edge cases around race conditions


Fixed audio bug around loading

New feature: Text Fields

New text fields feature to let players type answers to questions.

Usage: text_field [prompt]


  set (text_field "Enter your name")
  "Your name is %{playerName}"

Narrat 2.0.0

Published on July 01, 2022

What changed in narrat 2.0

Narrat has a new language syntax in 2.0.0 - The parser has been improved to turn the narrat scripting into a full programming language with support for expressions, variables and functions

The syntax is generally the same so existing scripts would mostly work, except for the use of $if (which used to be a hack by sending your code to JavaScript eval).

Script updating tool

There is a script updating tool which can be used to help automatically update existing scripts from 1.x to work with the new syntax.

{% hint style=“danger” %} The old syntax is largely compatible, but the $if instruction works very differently now. The script will update those $if instructions to match the new system, but might fail at updating long series of conditions in $if {% endhint %}

Installing narrat 2.0

Narrat 2.0 is currently published under a different tag to avoid people on 1.x accidentally installing it.

To install narrat to, run npm install narrat@next. The next tag is where the latest 2.x version is published.


Expressions are now a first party feature. An expression is any operation between parenthesis. Any command in the game can be used as an expression, if it returns a value. For example (+ 2 3) is an expression that would get evaluated to 5.

Syntax for using commands

The syntax for using commands hasn’t changed, but it’s been formalised.

The format for any operation is [operator] [arg1] [arg2] [arg3] .... Operator is the command’s keyword (like talk, set, if etc).

For example:

set data.player.score 3 is the operator set with arguments data.player.score and 3.

set data.player.score (+ $data.player.score 2)

The first command would set 3 in data.player.score.

The second command is also a set on data.player.score, but the second argument (the value) is an expression itself: (+ $data.player.score 2). So the final resulting command is effectively set data.player.score 5.

Using Variables

To use a variable’s value in a command, prefix its name with $ as in the example above. In the case of the set command, we want to pass to set the variable’s name, not its value, so we don’t use $ at the start.

Variables are also available in string interpolation as before, to insert variables in text:

talk player idle "Hello %{}"

New If syntax

The previous $if is gone, now if is a command itself, which takes one argument: the condition. If the condition is true it plays the next branch, and it can have an optional else branch.


set data.age 25
if (> $data.age 18):
  "The player is an adult"
  "The player is not an adult"

More details on syntax and expressions

More complex example:

  set data.winThreshold 10
  set data.player.score 5
  set data.player.scoreBonus 5
  if (== (+ $data.player.score $data.player.scoreBonus) $data.winThreshold):
    "The player won!"

In this example, the script stores a few variables, and then uses them in an if to compare their value. The == operation returns true if all arguments are equal, while the + operation adds values together and returns the result.

Here’s how the code above would get broken down as the expressions get calculated:

if (== (+ $player.score $player.scoreBonus) $data.winThreshold):
if (== 10 $data.winThreshold):
if (== 10 10):
if true

New operators

A lot of new operators have been added to be able to perform basic operations with the new scripting system:

  • +, -, *, / : Will add/substract/etc arguments passed and return the value. Can take infinite arguments
  • || and && : Will or/and all arguments passed and return true or false. Inifinite arguments
  • >, >=, <, <=, ==, != : Compares arguments 1 and 2, returns true or false. Note: equality uses truthy equality, not strict equality
  • !: Negates argument 1
  • ?: Ternary operation. Arg 1 is the condition, 2 is what gets returned on success, 3 what gets returned on failure

New helper functions

New helper functions for easily checking quests and inventory without long lines:

  • quest_completed? [questId]: Returns true if the quest questId is completed
  • objective_completed? [questId] [objectiveId]: Same for an objective
  • Also quest_started and objective_started
  • has_item? [itemId] [amount (optional)]: Returns true if the player has an item (if amount is passed, the player needs to have amount or more of it)
  • item_amount? [itemId] Returns how many of an item the player has.

Local variables

Local variables can now be declared. They exist inside the scope in which they are declared. Example

  run variables_test
  "The variable 'test' is now undefined because we left the scope it was created in: %{test}"

  var test 1
  "Test value is %{test}"

Function with arguments and return values

Labels are now “functions” and can take arguments or return values.


  var meal (run_label takeout_menu Cake)
  "The player chose to eat %{meal}"

takeout_menu third_option:
  var meal ""
    talk helper idle "Which meal do you want?"
      set meal pizza
      set meal burger
      set meal $third_option
  talk helper idle "Chosen %{meal}"
  return $meal

Other new features

Audio triggers

New audio triggers feature to play sounds on certain events in the game.

Simply add the sounds to the config:

  "audioTriggers": {
    "onPlayerAnswered": "click",
    "onPressStart": "game_start",
    "onSkillCheckFailure": "failure",
    "onSkillCheckSuccess": "success"

Keys are event names, and values are the id of an audio you’ve defined in the config. For now all the available events are the ones above. Once defined, the sound will play every time that event is triggered.