News
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.
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
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 itch.io 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:
- Have node.js installed (16 or above)
- Open a terminal and run
npm create narrat
- 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.
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)
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.
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
2.1.1
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.
2.1.0
⚠️ 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';
After:
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 player.name
is equivalent to set data.player.name
.
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.
Example:
{
"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
Strings
- Concat:
concat "a" "b"
-> returns “ab” (Syntax:concat [string1] [string2] [string3]...
) - Join:
join ", " "a" "b"
-> returns “a, b” (Syntax:join [separator] [item1] [item2] [item3] ...
)
Skills
- 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”
Utility
- 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
2.0.12
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
2.0.11
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.
Example:
{
"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"
}
]
}
}
}
2.0.10
fix: quest_completed?
now returns the correct value
2.0.9
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.
2.0.8
- 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
2.0.7
Fixed an error where the roll
function always returned true even if the skill check failed
2.0.6
Fixed a reggression bug introduced in 2.x where player choices weren’t printed anymore in the dialogue history after making a choice.
2.0.4
Fixed more audio edge cases around race conditions
2.0.1
Fixed audio bug around loading
New feature: Text Fields
New text fields feature to let players type answers to questions.
Usage: text_field [prompt]
Example:
main:
set player.name (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
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 %{data.player.name}"
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.
Example:
set data.age 25
if (> $data.age 18):
"The player is an adult"
else:
"The player is not an adult"
More details on syntax and expressions
More complex example:
main:
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 questquestId
is completedobjective_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
main:
run variables_test
"The variable 'test' is now undefined because we left the scope it was created in: %{test}"
variables_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.
Example:
main:
var meal (run_label takeout_menu Cake)
"The player chose to eat %{meal}"
takeout_menu third_option:
var meal ""
choice:
talk helper idle "Which meal do you want?"
"Pizza":
set meal pizza
"Burger":
set meal burger
"%{third_option}":
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.