Introduction
Rez is a language, compiler, and runtime for writing choice-based interactive fiction/RPG/simulation games based on HTML, Javascript, CSS, and — optionally — graphics, audio, and movie files.
Rez started as a quick alternative to Twine for an author who had become frustrated with Twine.
Twine describes itself as an "open source tool for telling interactive, nonlinear stories." It makes it relatively easy for those with almost no development experience to get started and create a choice-based game.
Rez, by contrast, is designed for making games whose complexity is not well suited to Twine and where a move to a parser based alternative such as Inform or TADS is not desirable. Obvious alternatives to Rez are probably Ink or ChoiceScript.
Rez’s complexity sits somewhere between advanced Twine and Inform/TADS. It has a relatively simple, declarative, syntax but requires the author to be comfortable writing small amounts of Javascript to implement more complex behaviours.
Rez has a standard library including support for creating NPCs, item & inventory management, scenes, and maps and a behaviour tree system to introduce AI behaivour. Rez also features a simple, yet powerful, layout & templating system.
Rez is designed to be flexible enough to create a really complex and ambitious game while offering a simple & usable framework for those just getting started.
Outline of a Rez Game
A Rez game is written in the form of one or more .rez
source files that get compiled into an HTML index page & a Javascript application, plus any associated assets like images, movies, and sounds.
A Rez source file contains elements and directives that describe the various components of a game and how it connects together.
At the top level is the @game
element that contains the game metadata and all
the other elements that make up the game.
Creating a new game
The command:
rez new <game name> --author-name="Name" --author-email="email" --game-title="Title" --game-homepage="URL"
For example specifying <game name>
as "mygame" will create a mygame
folder containing a number of subfolders. In the src
subfolder will be mygame.rez
that will contain some example source. In the lib
folder will be the stdlib.rez
containing Rez standard library. It’s good to familiarize yourself with the contents of stdlib.rez
but do not modify it as the game will overwrite it when you compile.
Compiling your game
The command:
rez compile src/<game_name>.rez
Compiles the sources into a game in the dist
sub-folder. It creates an index.html
as well as copying all of the Javascript & other asset files that constitute the game.
Distributing your game
To distribute your game you distribute the contents of the dist
folder. For example by compressing it into a .zip
file or wrapping it in an Electron app.
Frameworks
Rez includes two default frameworks:
The files for these will automatically be copied into your dist
folder when you compile the game.
Source code format
Rez games are written in plain UTF-8 files with a .rez
extension.
The %
character is special in Rez and indicates a macro of which the most common is %%
for comments.
Comment macro
Rez comments begin with %%
and continue to the end of the line.
%% this line will be ignored
Include macro
Once source file may include another by using the include macro %(…)
, for
example:
%(act_one.rez)
An included file may include other files but beware of creating a cyclic dependency. For example this code will hang the compiler:
file1.rez --------- %(file2.rez) file2.rez --------- %(file1.rez)
The Rez Language
Rez is a declarative language for writing a game in terms of a set of elements representating the game contents.
In Rez elements are things like items, actors, scenes, locations, assets and so forth. During compilation Rez draws these elements together and converts them into Javascript objects that represent the game when running in the browser.
Elements are generally described using a set of named attributes. For example an item
might have a description
attribute that can be displayed to the player when they examine the item.
Rez uses Javascript functions to supply dynamic behaviour. For the most part you can ignore this but, as your game becomes more complex and you want to incorporate dynamic behaviours, you may need to familiarize yourself with writing small Javascript functions.
Here is an example of a Rez element that includes a dynamic attribute:
@item magic_ring { is_a: :ring magic: true material: gold owners: 5 belongs_to: #sauron inscription: "Please return to Mordor", on_wear: (actor) => { if(actor.id == "sauron") { actor.game.sauron_victory = true; } else { actor.makeInvisible(); actor.corruption += 10; } } }
There’s a lot going on here but we’ll unpack it piece by piece.
Introduction to Elements
In the first place is the element itself:
@item magic_ring { ....attributes.... }
There is a common pattern for writing elements:
-
element specifier with
@
prefix, e.g.@item
-
a unique ID of the element, e.g.
magic_ring
-
open brace
{
-
attributes
-
close brace
}
Directives, by contrast, may look a little bit different, e.g. they don’t have a unique id.
Elements are used to describe in-game concepts. The Element Catalog describes each element in detail.
The id
of an element must be unique and follow the rules for Javascript identifiers. In JavaScript, identifiers are case-sensitive and can contain Unicode letters, $
, _
, and digits (0
-9
), but may not start with a digit.
In some situations you may want to use similar ids for different kinds of elements, in this case a helpful protocol is to prefix the id with the type, e.g. instead of #emergency_exit
you might use #s_emergency_exit
for a scene or #c_emergency_exit
for a card.
Introduction to Attributes
The most important thing when you are writing an element is its attributes. These describe the element and how it behaves in the game. In our @item
example there are 7 attributes that demonstrate many of the built-in types:
is_a: :ring magic: true material: gold owners: 5 belongs_to: #sauron inscription: "Please return to Mordor", wear: (actor) => { if(actor.id == "sauron") { actor.game.sauron_victory = true; } else { actor.makeInvisible(); } }
There are seven attributes defined here:
|
a keyword, a symbol often used when there are a few legal values |
|
a boolean |
|
another keyword |
|
a number |
|
a reference to the ID of an element |
|
a string |
|
an event script in Javascript arrow function format |
The pattern for any attribute is <name>: <value>
. The space after the colon is required and note that there is no ,
or ;
at the end as you may be familiar with from other programming languages.
Legal
title: "The Maltese Parrot"
Not-legal
title : "The Maltese Parrot" title :"The Maltese Parrot" title:"The Maltese Parrot"
Attribute names follow the rule for Javascript identifiers:
-
cannot contain spaces
-
are case sensitive
-
must begin with a letter, underscore
_
, or dollar$
-
can only contain letters, numbers, underscores, or dollar signs
Note that attribute names with a leading underscore (_
) are considered to be 'internal' to the Rez compiler. These attributes are not converted into runtime attributes and are, therefore, not available.
Attribute names with a leading dollar ($
) are considered to be 'special' and it is not advised to use them yourself unless you know what you are doing. Rez itself makes use of attributes with the $
prefix for housekeeping and you could, inadvertently, trample these.
Rez defines many attribute types, some simple and some more complicated. The more complicated types are generally related to creating dynamic behaviour and may require additional Javascript knowledge:
Boolean |
a truth value that you can test to create conditional behaviour |
|
Number |
a numeric value that can be positive, negative, integer, or decimal. Rez doesn’t have separate types for these. |
|
String |
a text value suitable for shorter strings. For longer passages a Heredoc may be easier |
|
Keyword |
a symbol, usually used for constant values. Note that keywords can be turned into hierarchies by the @derive directive |
|
Element Reference |
an identifier referring to the unique id of an element |
|
Attribute Alias |
References an attribute in another element |
|
Heredoc String |
a text value that can span across multiple lines |
|
File |
a string value that is imported from a file |
|
Template |
a text value that can span multiple lines and content template expressions that are dynamically interpolated at runtime |
|
List |
a sequence of other values, that can be of any Rez attribute type, inside |
|
Set |
an unordered collection of unique values of any Rez attribute type. Note that rez Sets do not use a |
|
Table |
a collection of name: value pairs where the values can be of any Rez attribute type. Note that Rez tables do not use a |
|
Script (Event) |
a Javascript function for handling an event. Expected to be in arrow format and passed the object receiving the event and the event as parameters. |
|
Script (Action) |
a Javascript function expected to be called, e.g. in an event handler. Expected to be in traditional function style and where |
|
Behaviour Tree |
A behaviour tree is an alternative to Javascript for creating dynamic behaviours. See [behaviours] for more information about using behaviour trees. |
|
Dice |
a dice roll, in Dice Notation that is re-evaluated each time it is referenced |
|
Probability Table |
A list of pairs wrapped in |
|"key_1" freq_1 "key_2" freq_2 "key_3" freq_3| |
Tracery Grammar |
a text value whose contents should be a Tracery grammar |
|
Binding Path |
Used within |
|
Code Block |
A Javascript expression. It is converted into an expression |
|
Dynamic Initializer |
A Javascript expression that is evaluated when the game starts |
|
Dynamic Property |
A Javascript function expression that is converted into an object property |
|
Boolean
A boolean value is either true
or false
(alternatively we can use yes
and no
) and is often used for flags.
The underlying data representation is a Javascript boolean.
Number
A number value can represent either integers or floating point values.
The underlying data representation is a Javascript number.
String
A string value is text enclosed with double-quote ("
) characters used for descriptive properties. Typically single lines, where multiple lines need to be used the suggestion is to use the Heredoc string instead.
The underlying data representation is a Javascript string.
Keyword
A keyword value is a special kind of string primarily used for identifier values. It is prefixed with a colon (:
) and must obey Javascript identifier rules.
The underlying data representation is a Javascript string.
Element Reference
An element reference is used to refer to the id of a game element. It is prefixed with a hash (#
) and must obey Javascript identifier rules. Although it acts like a string part of the value of element references is that the compiler will attempt to verify that they refer to an existing object.
The underlying data representation is a Javascript string.
Attribute Alias (Deprecated)
An attribute alias is used to refer to an attribute of a specific element. It is prefixed with an ampersand (&
) and consists of elem_id.attr_name
where elem_id
is an element id and attr_name
is the name of an attribute of that element.
The underlying representation is a Javascript object {elem_id: <elem_id> attr_name: <attr_name>}
.
Heredoc String
A heredoc string is a multi-line capable string that is whitespace aware.
File String
A file string is a string value whose content is stored and read in from an external file.
Template
A template is a kind of string value that supports dynamic content that is interpolated at run-time. This is controlled by the use of expressions such as ${…}
, $if() {% … %}
, and $foreach(x: xs) {% %}
. See template expressions for more.
List
A list of whitespace separated values that can include any of the other attribute types. It is separate from a @list
element.
Set
A set of whitespace separated values that can include any of the other attribute types.
Table
A series of key:
value
pairs where the key should be a Javascript id and the value can be any of the other attribute types including another table.
However it is worth noting that using deeply nested tables is not advised. It does work, but the entire set of tables is the attribute making working with nested values more complicated.
Event Script
An event script is written as a Javascript arrow function (args) ⇒ {…}
and therefore this
will be null
when it runs. Typically the object the event has been triggered for will be the first argument.
Action Script
An action script is written as a regular Javascript function function (args) {…}
and this
will refer to the object the script has been defined on.
Dice
Probability Table
Esp. useful for procedural generation a probability table is a list of pairs where the first element is the key and the second is the frequency. Let’s take eye color for example, we want characters we generate to have different coloured eyes. In reality brown eyes are most common at about 48% of the population, then blue at 29, green at 14%, and grey at about 9%. How could we generate a realistic distribution of eye colour (very important in games):
eye_color: |:brown 48 :blue 29 :green 14 :grey 9|
A different example might be a loot table, how could we generate one of those:
loot_quality: |:poor 20 :okay 10 :great 5 :amazing 1|
Our frequencies don’t have to % based and add up to 100, in this example we’ve given relative frequencies.
We can also use `#id’s as the key:
meet_on_the_road: |#ranger 15 #wizard 10 #traveller 45 #evil 30|
At the moment, due to a lack of JSON support, it is not possible to use attribute refs or functions as entries. A work around looks like this:
@card card1 { content: ``` ${f} ``` func_table: |#o1 50 #o2 25 #o3 25| choose_f: function() { return $(this.func_table).f(); } } @object o1 { f: function() { return 1; } } @object o2 { f: function() { return 2; } } @object o3 { f: function() { return 3; } }
It’s not elegant but it’s feasible. This will likely get cleaned up in a future version.
Tracery Grammar
-
TODO
Code Block
A code block uses the form ^{…}
and can contain a legal Javascript expression.
The code block is implicitly transformed into a function of 1 argument obj
and that returns the value of the expression.
Dynamic Initializer
A dynamic initializer uses the form ^i{…}
to run an expression once at the time the object is created.
This is useful for setting a generated value (e.g. a random value) after which the attribute behaves normally using getters/setters.
Note that this is not a function, the initializer uses the last expression as the value. In the following example we name an actor using a randomly generated given & family name.
@actor random_npc { name: ^i{ const given_name = $("given_names").randomElement(); const family_name = $("family_names").randomElement(); `${given_name} ${family_name}`; } }
Dynamic Property
A dynamic property is a property generated from an expression in the form ^p{}
for example:
@actor random_npc { class_name: ^p{ return this.class === "g" ? "Gunslinger" : class === "s" ? "Sleuth" : "Crook"; } }
Special Attributes
-
$global
:boolean
Declaring an element as a global, Rez will create a global variable with the same name as the object id.
-
$template
:boolean
Declaring an element a template means it is intended to be used as a template for objects created with Rez copyAssigningId()
and copyWithAutoId()
methods. A template does not the usual init system.
-
$js_ctor
:string
Using the $js_ctor
attribute allows overriding of the JS prototype for a given element.
What’s in a Game?
The simplest possible Rez game would look something like this:
@game { name: "Test Game" IFID: "D3C31250-53B4-11ED-9A26-3AF9D3B0DD88" archive_format: 1 initial_scene_id: #play_game layout_mode: :single layout: ``` ${content} ``` } %(stdlib.rez) @scene play_game { initial_card: #did_you_win layout_mode: :single played: 0 won: 0 win_p: 0 layout: ``` <div class="container"> $if{scene.played > 0} {% <section class="hero is-primary"> <div class="hero-body"> <p class="title">Winning Percentage: ${scene.win_p | round: 0}%</p> <p class="subtitle"> $if{scene.win_p >= 50.0} {% You are a winner! %}, {% You are a loser! %} </p> </div> </section> %} <p>Played: ${scene.played}</p> <p>Won ${scene.won}</p> ${content} </div> ``` win: function() { this.played += 1; this.won += 1; this.win_p = this.won * 100 / this.played; } lose: function() { this.played += 1; this.win_p = this.won * 100 / this.played; } } @card did_you_win { content: ``` Did you win? [[yes|yes_i_won]] | [[no|no_i_lost]] ``` } @card yes_i_won { content: ``` Congratulations! [[Play again|did_you_win]] ``` on_start: (card) => { card.scene.win(); } } @card no_i_lost { content: ``` Better luck next time! [[Play again|did_you_win]] ``` on_start: (card) => { card.scene.lose(); } }
This is a terrible game but it illuminates some of the basic principles of how you create a game using Rez.
It uses 3 types of element: @game, @scene, and @card. The scene has some attributes to keep track of the game state and two actions, the cards use an event handler and some template links.
The @game
is a required top-level element that contains the definintion of the game and holds the master layout into which scene content is inserted, and the reference to the scene that starts the game.
A game must have at least one @scene
. A scene represents a context where specific events or interactions take place. It must also have an initial_card
attribute that defines which card is played into the scene when it starts. You can run your game from a single scene or use multiple scenes where it makes sense to do so.
Lastly the cards, which are "played" into the scene, and which provide the bulk of the content presented to the player.
So we have a structure:
@game/layout @scene/layout @card/content
The card content is rendered into the scene layout, and the scene layout is rendered into the game layout. You might notice the scene has a layout_mode
attribute. In this case we are using the single
layout mode that presents only the current card. There is also a stack
layout mode that presents all of the cards played into the scene.
The scene in this case defines two script attributes win
and lose
that update the score and winning percentage. These are called from the on_start
event handler of the cards yes_i_won
and no_i_lost
. The event handlers are Javascript arrow functions that take their source object (and, optionally, an event object) as a parameter. The scene scripts are regular functions where this
is the object in question (in this case the scene play_game
).
You can use Markup for simple formatting although here we are showing off some of the Bulma CSS classes. We also using template expressions to display variables ${}
and conditionally present content $if{} {% … %}
.
You can go quite a long way using only this subset of Rez’s features.
What’s Going on in the Browser
We should distinguish between two environments: The author_time environment where we’re dealing with .rez
source files containg elements & attributes, and the runtime environment where these have been compiled into JavaScript code that runs in the browser.
runtime.js
All of the functionality of the game is converted into Javascript objects and functions which end up in a file called runtime.js
. You can see this in the dist/assets
folder of your game. It’s worth looking through runtime.js because you can see all of the library classes and functionality. Note that you should never modify runtime.js
as it will be overwritten the next time you compile your game. However, in practice, there should be no reason to modify this file as its contents are produced from your game.
In the runtime environment, your @game
element is translated into a JS object with RezGame
as its prototype, the scenes into JS objects with RezScene
as its prototype, and cards into JS objects having RezCard
as their prototype. For most elements there is a 1:1 correspondence between it and an equivalent JS object defined in runtime.js
.
[Advanced Note]: If you want to use different objects you can use the $js_ctor
attribute to define which constructor function gets called. When replacing built in objects its advisable to have the built-in object as a prototype of your custom object.
The Game starts with a called to the game object start
method which handles initialization and presenting the first scene & card.
The Rendering Process
The HTML that is presented in the browser is generated as follows:
At the top level the @game
element requires a layout:
template attribute. It further requires that this template contains a ${content}
template expression. Internally the game uses a RezSingleLayout
object to render the current scene, which it adds to the layout bindings as content
. So the scene content is inserted into the game layout as ${content}
.
At the next level down the @scene
also requires a layout:
template attribute and, it too, requires a ${content}
template expression to be present. The scene either uses a RezSingleLayout
(layout_mode: :single
) or a RezStackLayout
(layout_mode: :stack
) depending on whether the scene is based on one @card
or many @cards
. The layout renders the card content and places it in the layout binding content
. So the card content is inserted into the scene layout as ${content}
.
At the next level down the @card
provides a content_template:
and, optionally, flipped_template:
attribute. The flipped template is used in the stack layout which we’ll discuss shortly.
So in the simplest case the structure is:
Game Layout Scene Layout Card Template
The actual picture can be a little more complicated because the scene layout and card can also include content from other cards by specifying the id of the cards in their blocks:
attribute. But what is a block?
What is a Block?
Using the blocks:
attribute we can specify the attribute of cards that we want to include beyond the main content card. For example, to include a sidebar that is common across cards in a scene:
@card sidebar { content: ``` sidebar content goes here ``` } @scene explore { blocks: [#sidebar] layout: ``` <div class="sidebar">${sidebar}</div> <div class="main">${content}</div> ``` }
When the explore
scene gets rendered it will render its current card and bind the rendered content to content
and also render the card #sidebar
and bind that content to sidebar
. So using the ${sidebar}
template expression from the layout includes the sidebar content.
Note that when a card is used from a block:
attribute it is automatically given a $parent_block
binding (that points to the card using it as a block) so that it can refer to the attributes of its parent card.
This is useful when you want to create a "parameterized" block. For example, we could dynamically render a list of available exits in a card representing a location, this way:
@card list_exits { bindings: [ location: $block.$parent_block.source ] content: ``` $if(location.exits) {% $foreach(exit: location.exits) {% %% render an exit here %} %} ``` } @card room_with_exits { exits: [#exit_1 #exit_2 #exit_3] blocks: [#list_exits] content: ``` Room ${exits} ``` } @card another_room_with_exits { exits: [#exit_4 #exit_5 #exit_6] blocks: [#list_exits] content: ``` Another Room ${exits} ``` }
In this example #room_with_exits
and #another_room_with_exits
both define an exits:
attribute and render the card #list_exits
as a block.
However, #list_exits
doesn’t have to know which card is rendering it, only that it defines an exits:
attribute.
We use a code-block binding location:
from the #list_exits
card to reach up to its 'parent' card (the one that included it as a block) to find its exits:
attribute and use that for rendering the list of exits.
This means we can use #list_exits
from any card that defines an exits:
attribute.
What are Bindings?
Bindings are how we make data from our game elements available to the code and templates that are rendering our view. You’ve already seen an example of a binding in the section above:
bindings: [ location: $block.$parent_block.source ]
But what does this mean? And why do we need it?
Let’s go back to basics with a very simple content template, e.g.:
@card c_test_1 { content: ``` Hello from the first chapter ``` }
This will present the text "Hello from the first chapter" in the browser.
But how does this happen?
The next section is quite technical and will likely require a good understanding of Javascript, far more than is required to use bindings & expressions. Feel free to skip ahead if you don’t feel comfortable digging this deep.
Rez converts this simple markup into a Javascript function that renders it. For the content above you’d end up with something like
function(bindings) { return [ function(bindings) { return `<div id="card_c_test_1" data-card="c_test_1" class="card">Hello from the first chapter.</div>`; } ].reduce( function(text, f) { return text + f(bindings) }, "" ); }
Now you might be thinking "OMG! Why do we need such a complex function to render a simple line of text?"
If every template was as simple as this, we wouldn’t. But a simpler approach wouldn’t allow us to build more complex, dynamic, templates. Before we get to that, let’s break down this function.
The outer level is a function that accepts an argument bindings
and then returns the result of a reduce()
call on an array. In this case an array containing a single function also taking bindings
as its argument.
This 'inner' function doesn’t use its argument, it just returns the static string that is our output.
The reducer function takes some text (initially an empty string) and a function (taking a bindings argument), and returns of appending the function result to the text.
The result is that we "thread" the outer bindings
variable through the inner function and concatenate the results.
So essentially this boils down to:
"" + `<div id="card_c_test_1" data-card="c_test_1" class="card">Hello from the first chapter.</div>`
And, hence, to our output.
To see why it works this way, let’s look at a dynamic template using a template expression:
@card c_test_2 { chapter: "second" content: ``` Hello from the ${card.chapter} chapter. ``` }
This template breaks down into three chunks:
-
"Hello from the "
-
${card.chapter}
-
" chapter."
The first and last chunk are simple strings, like our previous example. But the middle chunk is a template expression that must be generated, using some Javascript, when the card is being rendered. At that time the value of card.chapter
is "second"
so the template is equivalent to an expression like:
"Hello from the " + "second" + " chapter."
Let’s look at the rendering function generated for this template:
function(bindings) { return [ function(bindings) { return `<div id="card_c_test_2" data-card="c_test_2" class="card">Hello from the `; }, function(bindings) { return (function(bindings) { return bindings.card.chapter; })(bindings); }, function(bindings) { return ` chapter.</div>`; } ].reduce( function(text, f) { return text + f(bindings) }, "" ); },
It’s more complicated but follows the same exact pattern, running reduce()
over an array, that now contains three inner functions. These functions return the following content respectively:
`<div id="card_c_test_2" data-card="c_test_2" class="card">Hello from the ` `second` ` chapter.</div>`
That is concatenated to present the user with "Hello from the second chapter."
But how is "second" getting from the chapter:
attribute of the card into the second inner function. It happens through the bindings
argument that we are threading through the outer rendering function to those inner functions. Let’s look at the second inner function:
function(bindings) { return (function(bindings) { return bindings.card.chapter; })(bindings); }
If we strip away the mechanism here the core part is:
return bindings.card.chapter;
This is what the template expression ${card.chapter}
boils down to.
This inner function is using it’s bindings
argument to look up card.chapter
. card
is an example of a default binding that Rez makes. Whenever a @card
is rendering it binds card
to the RezCard
object representing that card. In this case the object that defines the chapter
property, containing the string "second"
. Rez also automatically binds scene
to the current RezScene
and game
to the RezGame
instance. So you can always use expressions involving card
, scene
, or game
bindings without needing to bind anything yourself.
But, by itself, the rendering system knows nothing about your game world and the elements you have populated it with. For example, you may have an @actor
element with id #player
that has a name:
attribute, but the renderer doesn’t know about that. In order to use an expression like:
${player.name}
We have to teach Rez how to point player
at the right object. That’s where bindings and the bindings:
attribute come in. They bind a variable name that you can use in a template to the Javascript object containing the values you want to refer to. To make the expression above work we’d use:
@card c_player_name { bindings: [player: #player] content: ``` Your name is ${player.name} and a very fine name it is too! ``` }
This is an example of an 'element binding'.
Element Bindings
The simplest form of binding is to bind a variable name to the game object representing an element. In the example above we’re looking for the name:
attribute of some element with the id #player
. We can make this work by binding the player
variable as follows:
bindings: [player: #player]
Here we’re teaching Rez to make a binding to the Javascript reference (player
) representing the given element id (#player
). With this binding in place we can refer to player
in our templates.
Function Bindings
Sometimes we want to be able to refer to something that isn’t an element with a a fixed id. Two common reasons are:
-
we want to refer to a dynamic value
-
we want to refer to a dynamically choosen element (i.e. we don’t know the id at authoring time)
-
we want to refer to something that doesn’t have an id, such as a collection of objects
For these situations we have function bindings. Here we bind a variable name to the return value of a function written inline in the bindings. Here are some examples:
bindings: [ random_number: () => {return Math.rand_int(10)} weapon: () => {return game.get_weapon($player.favourite_weapon_id);} exits: () => {return $player.location.exits(true);} ]
In each case the variable will be bound to the return value of the function.
Note that these bindings are re-created each time the template is rendered so while weapon
and exits
might have the same values, random
is going to have a different value each time.
Attribute Bindings
Attribute bindings are a convenience when you want to refer to a specific element attribute.
bindings: [ name: &player.name ]
Path Bindings
A path binding is used to refer an object by a key-path from the $block object. This is mainly useful when implement cards intended to be used as bound blocks, that want to refer to their parent card context.
bindings: [ exits: `$parent_block.source.exits ]
All path-references implictly begin with the $block
variable (that refers to the card currently being rendered). So $parent_block
refers to the $parent_block
attribute of the current $block
.
Using a path binding we can get to the parent card which may be one of many cards (why we can’t use an element reference) and its attributes.
Sharp Edges
Path bindings are often used to get at the internal mechanics (parent blocks, sources and so on) which are already a little complicated.
Note that unlike previous versions it is now possible to make bindings that refer to previous bindings, so:
bindings: [ player: #player name: player.name ]
Is legal, however you can only refer to previous bindings made in the same bindings block.
Stack Layout, Flipped Cards, and Blocks
By default a @scene
specifies a layout_mode:
of :single
which means that the scene renders a single 'main' @card
as its content. When a new card is played into the scene it replaces the previous card and the view gets re-rendered.
However, there are times when when you might want to render more than one card into a scene. For example a dialogue scene might represent a number of interactions back and forth between characters with the player able to specify a response. In these, and similar examples, you don’t want the "history" of the scene to disappear.
To achieve this a @scene
can specify layout_mode: :stack
to use the RezStackLayout
. When using the stack layout, playing new cards into the scene do not replace the exist card but are appended or pre-pended to the list of previous cards (based on the layout_reverse:
attribute).
When the RezStackLayout
renders, it renders the list of cards played into the scene (separated by any content in the layout_separator:
attribute).
However, in fact, an author probably doesn’t actually want to re-render previous cards. A card that presented a set of dialogue choices doesn’t make sense when the player has already made their choice. It would make more sense to render a version of the card representing the choice the player has made.
This is why cards support a flipped_content:
attribute. When a new card is played into a scene with a stack layout the previous card gets 'flipped' and renders the flipped_content:
template rather than the content:
template.
But what happens if we play the same card multiple times? How does it know which is flipped and which is 'face up'. What happens if an event wants to store data in the card? To answer these questions we need to go a little deeper.
The rendering process doesn’t directly render @card`s, `@scene`s, or `@game’s. Rendering is done via an object whose prototype is `RezBlock
. RezSingleLayout
and RezStackLayout
both have RezBlock
as their prototype. For each @card
that is being rendered there is an instance of RezBlock
.
A RezBlock
handles generating HTML to output to the view by calling executing it’s template with appropriate bindings. Where appropriate a block also has a parent_block
reference that allows walking back up the content tree. (See the example above related to bindings).
So when a RezCard
is added to a RezStackLayout
it’s actually the card wrapped in an instance of RezBlock
. The same card can get added to the layout many times, it’s always the same card, but different block instances.
What this means is that when a card is being flipped it’s actually the block that tracks flipped status and decided whether to render its cards content:
or flipped_content:
template.
Further it means that when an event wants to track how this changes the cards content it can store those changes in the block.
@card next_move { content: ``` <a href="javascript:void(0)" data-event="shoot">Take a shot</a> or <a href="javascript:void(0) data-event="flee">Flee</a>. ``` flipped_content: ``` $if($block.action == "shoot") {% You shoot and ${block.hit | alt: "hit", "miss"}. %}, {% You run for it. %} ``` on_shoot: (card, evt) => { card.current_block.action = "shoot"; card.current_block.hit = $player.hits_with_primary_weapon(); return { card: "next_move" }; } on_flee: (card, evt) => { card.current_block.action = "flee"; return { card: "run_away" }; } }
The first time the next_move
card is added to a scene it displays the options to shoot or flee. There are two event cards which set the choosen route into the block and in the case of shooting what the result was.
When the card is re-rendered the flipped_content:
template is rendered which uses the block properties action
and hit
to decide what should get rendered.
HTML Structure
When the game layout gets rendered its content is embedded inside a built-in template:
<div class="game"> ...game layout... </div>
You can target the whole game content using the game
CSS class.
The game layout
is a good place to put fixed parts of the interface, for example titles, score, current time or location, and so on. The game layout is expected to contain the template expression ${content}
which will include the contents of the current scene.
When the current scene gets rendered its content is embedded into a different template:
<div id="scene_<scene-id>" data-scene="<scene-id>" class="scene"> ...scene content... </div>
In the same was as the game, the scene layout
is expected to contain the template expression ${content}
which will include the contents of the current card or (in stack mode) cards. You can style scenes by targetting the scene
CSS class or customise styles for particular scenes by targetting the DOM id. In our example game that would be scene_play_the_game
.
When a card gets rendered its content
template is embedded within the following template:
<div id="card_<card-id>" data-card="<card-id>" class="card <card-type>"> ...card content... </div>
One thing to note is that the scene_id
may not be what you expect. If the current scene was set to #explore_office
you might expect that the rendered HTML would contain this id. However Rez treats your @scene and @card elements as a template and uses a copy when rendering a scene.
Block Content
Block content comes from cards that are being rendered inside another card. For example you might have a card #sidebar
that we want to use to render sidebar content that should always be visible.
In this case we would add it to (for example) the scenes blocks:
attribute. To include it within the scene layout you would use the template expression ${sidebar}
.
Scene Layout Mode
A @scene
has a required attribute layout_mode:
which can, as of v0.11, have two values:
-
:single
-
:stack
In :single
mode the ${content}
substitution embeds the content of the current card in the scene. When the card changes the content will change to match it. The effect is that the scene will jump from card to card.
In :stack
mode the ${content}
substitution embeds the content of every card that has been played into the scene so far. Rather than jumping from card to card the cards will accumulate.
However, as a new card is played the previous card gets "flipped". What that means is that instead of rendering the content
attribute it renders the flipped_content
attribute.
For example a card might present the player with two options. If the card didn’t get flipped it would continue to present two options even though an option had been selected. But the flipped version can, instead, display the chosen option.
Linking to cards, scenes & events
Playing a card
When we play a card into the current scene we are either replacing (scene layout_mode: :single
) or adding (scene layout_mode: :stack
) to the content in the scene.
<a href="javascript:void(0)" data-event="card" data-target="play_game">Play Again</a>
This will create a link titled "Play Again" that plays the card with id #play_game
.
Switching to another scene
A scene switch is when we end one scene and begin another, automatically playing its initial card.
<a href="javascript:void(0)" data-event="switch" data-target="fight">Draw your gun</a>
This will create a link titled "Draw your gun" that will end the current scene and begin the scene #fight
.
Creating an interlude
An interlude is when we interrupt one scene to play out another, and when that scene ends returning to the original scene.
<a href="javascript:void(0)" data-event="interlude" data-target="store">Shop at the store</a>
This will create a link "Shop at the store" that interrupts the current scene and starts the scene #store
. This should be followed by a resume to return to the original scene.
An example of where this kind of link is useful is for presenting a player inventory. Looking at the inventory steps out of normal gameplay. When the player is done with the inventory they expect to be back where they were before they triggered it.
It is possible to have an interlude within an interlude but may get confusing if taken too far.
Resuming the previous scene
From an interlude we can resume the previous scene using a resume link.
<a data-event="resume">Leave the store</a>
This will end the interluded scene and resume the previous scene where it left off.
There may be situations where you only want links to appear under specific circumstances. You could do this a template expression but Rez has a built-in facility for dynamic links. Using the syntax:
Triggering events
A link can trigger a custom event.
<a data-event="reload">Reload gat</a>
This will create a link titled "Reload gat" that when clicked will run an event on_reload
on the game, scene, or card (in that order).
Once the event handler has done its work it should return a response object.
Passing data
Any of the previous types of link can be amended to pass arbitrary data values. For example we might have a dialogue scene and want to control which actor the player is going to have a dialog with:
<ul> <li><a data-event="switch" data-target="conversation" data-actor_id="gutman">Speak with Gutman</a></li> <li><a data-event="switch" data-target="conversation" data-actor_id="wilmer">Speak with Wilmer</a></li> </ul>
When either link is clicked it will start the new scene #conversation
and that scene will have it’s actor_id
attribute set to either #gutman
or #wilmer
based on which of the links is clicked. This offers a great deal of ability to customise the behaviour of cards and scenes.
Event Reponse Objects
Return an object from an event handler to determine what happens next. Some object types can be combined (e.g. the flash
message combines with most of the other choices)
{scene: "scene_id"}
To start a new scene.
{card: "card_id"}
To play a new card into the current scene.
{flash: "message"}
To set a flash message.
{render: true}
To have the current view re-rendered.
{error: "message"}
To log an error message to the console.
Buttons
An alternative to using a link is to use a <button>
with a data-event
attribute. For example a button to play a new card would look like:
<button data-event="card" data-target="new_card_id" class="button">Load Card</button>
By specifying data-event="card"
we tell the button it’s loading a new card and the data-target
attribute specifies which card to load. We can use a similar approach to load new scene:
<button data-event="switch" data-target="new_scene_id" class="button">Switch Scene</button>
Here data-target
specifies the id of the scene to switch to. Use data-event="interlude"
for an interluded scene, rather than a scene switch.
Where you want to run a custom event handler, on_something_interesting
, use specify the event name directly in the data-event
attribute:
<button data-event="something_interesting" data-custom-value="..." class="button">Something Interesting!</button>
You would pair this with an event handler as follows
on_something_interesting: (card, evt) => { const custom_data = evt.target.dataset.custom_value; // Interesting processing happens here // then... // what should happen next? return { render: true } }
In this example the handler is in a card but you can also put in the scene or game as appropriate.
Dynamic Links
Sometimes you want a link to be disabled based on dynamic criteria (the bar doesn’t open until 8am) or maybe not even to appear at all (the portal entrance isn’t visible if you’re not wearing your x-ray specs).
To make a dynamic link use the dyn_link
template expression filter. Here’s an example:
@card { content: ``` ${card | dyn_link: "rest"} ``` on_rest: function(dyn_link) { if($player.is_fully_rested) { dyn_link.deny("You are already rested"); } else { dyn_link.allow("Rest", "player_rests"); } } }
In this case, if the player is already rested they are shown a disabled option. In some cases it might be preferable to use dyn_link.hide()
so that no choice is offered at all.
The event handler is passed a RezDynamicLink object that it can use to customise link presentation.
Forms
An HTML interface will often use form controls to allow the player to input or interact with data. A simple example would be using an <input> to accept a characters name. Rez offers a number of ways to support using forms.
Binding form elements
For data capture the simplest approach is to bind an HTML form input element to an attribute value using the rez-bind
attribute.
textfields and textareas
To bind an input
with type='text'
or a textarea
:
<input type="text" rez-bind="player.name"> <textarea rez-bind="player.description">
This sets up a two-way binding between the content of the <input>
and the player.name
and player.description
attributes respectively. For example, whatever is entered into the name form input will be set directly on the player.name
attribute. Equally assigning to the attribute $("player").name = "…"
will update the input field.
checkboxes
You can bind a checkbox input to a boolean attributes.
<input type="checkbox" rez-bind="player.isOver18">
radios
You can bind a set of radio buttons to an attribute.
<input type="radio" name="class" value="detective" rez-bind="player.class"> <input type="radio" name="class" value="hood"> <input type="radio" name="class" value="dame">
Note that radios with the same name
attribute will form a group and you only need to bind the first radio in the group.
select drop-downs
You can bind a <select>
to an attribute:
<select rez-bind="player.gender"> <option value="m">Male</option> <option value="f">Female</option> </select>
Adding events to inputs and forms
For more complex interactions use the rez-live
attribute to generate events.
<input name="name" rez-live >
When the user changes the value of the field this will generate an on_input
event on the corresponding RezCard
object, passing the generated event as a parameter.
<form rez-live>...</form>
Will generate an on_submit
event to the form. The handlers in either case should return as any other event handler. In the case of submit it is probably to load a new card or scene.
Assets
Assets are files that you want to include in your game for example images, audio files or movies. Rez handles copying these into your game distribution folder and generating appropriate references.
You declare an asset with an @asset
element:
@asset pistol_image { file: "pistol_image_01.png" width: 60 height: 60 }
Rez handles finding the asset file and making it available in the dist folder. Now if you want to include it you have two options, both using template expressions.
${"pistol_image" | asset_tag}
Because the asset is an image this will generate an <img />
tag that points to the image file relative to the game file.
As of v0.11 only image files are supported but sound & movie support will be included soon.
The second approach is to generate a path and build your own tag:
<img src='${"pistol_image" | asset_path}' />
This will work for audio & movie assets.
Template Expressions
Template expressions are how you include dynamic content in your game user interface. They work in @game
& @scene
layout
attributes and in a @card`s `content
and flipped_content
template attributes.
Template Expressions are loosely based on the Liquid markup system. But it’s worth noting that they are not actually Liquid and you should always refer to this documentation not the Liquid docs.
There are three kinds of template expression.
Subsitution Expressions
A substitution is where we replace a token like ${player.name}
in a template with the value of the expression. For example:
content: ```Your name is ${player.name}. It is a good name.```
If the player
objects name
attribute is "matt" this will return:
Your name is matt. It is a good name.
Note that the an expression is only a lookup. You cannot use arbitrary JS expressions, so:
content: ```Your name is ${player.name + "!"}```
Will not work. If you want to modify the value you must use a filter expression (see below) to do so. In this case it would be:
content: ```Your name is ${player.name | append: "!"}```
Where does this player
reference come from? Good question, this is an example of a binding. You’ve already seen bindings at work with ${content}
and ${sidebar}
. content
is an example of a binding that Rez automatically makes available but you can add your own to refer to any objects you like.
bindings: [ player: #player ] content: ```Your name is ${player.name}```
Here we are binding the Javascript variable player
to an element with id player
(which we might assume is an @actor
element defining the player character). For example:
bindings: [ player: ^{$("player")} ]
is an equivalent way of creating the same binding. If we didn’t know the object we wanted to bind to in advance we can use a dynamic binding with a function.
bindings: [ actor: ^{$("npc_list").randomElement()} ]
But you don’t have to make bindings only to elements, you can bind to any Javascript value:
bindings: [ coins: ^{Math.clrand_int(25)} ] content: ``` You found ${coins} coins on the floor and put them in your pocket. ```
Default Bindings
In the context of a template there are usually default bindings:
-
$block
- the current rendering block, the element it represents is usually in itssource
property -
card
- theRezCard
of the card being rendered -
scene
- theRezScene
of the scene being rendered -
game
- theRezGame
instance
Substitution Filters
If all we could do was return the attribute values of functions then expressions wouldn’t be very useful. Filters, inspired by Liquid, let us manipulate values into the content we want to display.
For example, let’s say we wanted to capitalize the players name:
content: ```Your name is ${player.name | capitalize}. It is a good name.```
Would render as:
Your name is Matt. It is a good name.
When using a filter you put a pipe symbol |
followed by the filter expression which is sometimes just the name of the filter (See the Filter Catalog for a complete list of built-in filters) but can also include parameters.
content: ```The item has the inscription "${item.inscription | trunc: 40}"```
This is an example of a filter that takes parameters. They are separated from the filter name by a colon :
and if there is more than one parameter separate them with a comma.
You can also have multiple filters, separating each with a |
. For example:
content: ```The book belongs to ${actor.name | prepend: actor.title}.```
might render as:
The book belongs to Mr Sam Spade.
Conditional Templates
The third type of template expression is the conditional template. This allows content to be dynamically included based on an expression. The format of a conditional template is:
$if(expression) {% ...true path template content... %}
or
$if(expression) {% ...true path template content... %}, {% ...false path template content... %}
In the game example above we used:
$if(scene.played > 0) {%...%}
To determine whether to show the won/lost percentage template content. You can nest conditional templates inside other conditional templates.
Iteration Templates
The fourth type of template expression is an iterator template. This allows content to be created from a list of values (In Javascript terms, anything that could be an treated as an array). The format of an iterator template is:
$foreach(x: list) {% <div id="${x.id}">${x.title}</div> %}
This will iterate over the binding list
and run the template expression once for each element of list
binding x
to that element.
$foreach(x: list) {% <div id="${x.id}">${x.title}</div> %}, {% <hr /> %}
This alternate form accepts an optional second template expression. This expression will be rendered between each rendering of the content expression.
Note that the list binding should either be an object in the bindings
or a property of an object in bindings
. You cannot use arbitrary expressions. If you need to use an arbitrary expression use a function binding, so instead of:
content: ``` $foreach(x: a.b.map((el) => somefun(el))) {% <div id="${x.id}">${x.title}</div> %} ```
you would write:
bindings: [ list: ^{a.b.map((el) => somefun(el))} ] content: ``` $foreach(x: list) {% <div id="${x.id}">${x.title}</div> %} ```
Partial Templates
A fairly common requirement when building dynamic interfaces is to want to render one card within another. For simple cases you can use the blocks:
attribtue on @game
, @scene
, or @card
to render a named card as a block.
However there are two areas where this approach does not work:
-
you don’t know the name of the card to render at author time
-
you want to render the same card multiple times and get a different output
The latter is the $foreach
case.
Solving these two problems are what the $partial
expression is for.
Do Blocks
To setup attributes for rendering you can run code in an event handler. For example a @card
can have an on_start
hander:
@card test_card { content: ``` $if(card.show_section) {% stuff goes here %} ``` on_start: (card) => { card.show_section = Math.random() < 0.5; } }
However in many cases it might be easier to use a "do block" inline in the template:
@card test_card { content: ``` $do{ $card.show_section = Math.random() < 0.5; } $if(card.show_section) {% stuff goes here %} ``` }
User Components
Sometimes you want to be able to hide away some of the complexity of what you are doing behind a clean syntax. That is what @components
are for.
For example let’s say we are writing a lot of event buttons like this:
<button class="button is-small" data-event="reload">…</button>
The boilerplate to make a button can be a bit tiresome and it obscures the most important thing about this button, that pressing it triggers the reload
event. Let’s make a component:
@component event_button (bindings, assigns, content) => { return `<button class="button is-small" data-event="${assigns["event"]}">${content}</button>`; }
Now we can write:
<.event_button event="reload">…</.event_button>
And things are neater. Note that the component tag has a .
prefix which is what Rez uses to identify event_button
as a user component and look for its @component
definition.
Because <.event_button>
is a container tag it has a content
argument containing all the rendered content that goes inside the tag. For self-closing tags content
is undefined.
Behaviour Trees
In the realm of game development and interactive simulations it is very desirable to be able to create entities that can respond to their environment in realistic and complex ways. Rez enables authors to infuse characters and objects with dynamic behaviors using Javascript. However, as the complexity of these behaviors grows, managing them can become problematic. This is where behaviour trees come into play, offering a structured yet flexible way to design and implement AI behaviors.
Behaviour trees are an artificial intelligence technique that revolutionized NPC behavior in video games, with their roots tracing back to landmark titles like Halo 2. Behaviour trees provide a modular, scalable, and easy-to-understand approach. They excel in managing complex decision-making processes, making them an ideal choice for developers looking to create nuanced AI behaviors without getting lost in a web of code.
At the heart of behaviour trees lies the concept of breaking down behaviors into a tree of decisions, where each node in the tree represents some kind of choice or action. These choices and actions guide the entity’s behavior based on conditions and events in the game world. This hierarchical structure allows for clear and logical organization of behaviors, from simple actions like moving to a location, to complex sequences of decisions such as engaging in combat or solving puzzles.
The beauty of using behaviour trees in Rez lies in their versatility and ease of integration. With Rez’s support for behaviour trees, authors can create rich, adaptive, AI that can handle a wide range of scenarios, reacting to the game world and player actions in realistic ways.
In this section, we’ll dive deep into the fundamentals of behaviour trees, explore their syntax and structure within Rez, and provide practical examples to illustrate how they can be implemented to bring your game’s characters and world to life
Behaviour Tree Syntax
Let’s start with an example of the syntax and main concepts:
@actor sam_spade { behaviours: ^[$select [$sequence [actor_in actor=#sam_spade location=#sams_office] [item_in item=#whisky_bottle location=#sams_office] [actor_drinks actor=#sam_space item=#whisky_bottle]] [$sequence [actor_in actor=#sam_spade location=#gutmans_suite] [actor_in actor=#kasper_gutman location=#gutmans_suite] [actor_wisecracks]]] }
At a high-level this behaviour tree defines some behaviour for our NPC Sam Spade. When Sam’s in his office he will attempt to have a drink. If he finds himself with Kasper Gutman he will attempt a wisecrack. While this is a contrived example it will serve to highlight the main concepts at work.
Behaviour trees are attribute values. In the example above the behaviours:
attribute contains a behaviour tree. They are written in the form of a list but have a special ^
prefix to distinguish them from regular lists.
A behaviour tree is composed of behaviours, which may contain other behaviours as children, and where each behaviour is written as a list in the form:
[behaviour options* children*]
So we have the name of the behaviour, followed by zero or more options, followed by zero or more children. In the example the first behaviour is a $select
with no options ($select
doesn’t take any) and two children:
[$select child1 child2]
The first child is a $sequence
behaviour that again has no options ($sequence
doesn’t take any either) and three children:
[$sequence child1 child2 child3]
and its first child is an actor_in
:
[actor_in actor=#sam_spade location=#sams_office]
This behaviour has two options actor
and location
and no children. The other two children of the $sequence
have a similar structure.
The name of the behaviour corresponds to the id of a @behaviour
element in the game. The $
prefix tells us that $select
and $sequence
are a core behaviours that is @behaviour
elements defined in the Rez stdlib and always available to any game.
Options are written as <option name>=<value>
where the option name follows the Javascript variable naming pattern the same as attribute names, and the value can be any legal Rez value type.
The behaviours actor_in
, item_in
, and actor_drinks
are not core behaviours but examples of author defined behaviours. As an author your job is to create behaviours that are meaningful in your games context.
Now let’s talk about what all of this means.
Although behaviour trees are syntactically written as a list, they form a branching tree structure as each behaviour can nest other behaviours in its child list, to an arbitrary depth. The first behaviour is the root behaviour of the tree.
When we "execute the tree" it means excuting the root behaviour which in turn may execute some or all of its children until execution reaches the leaves of the tree. But, before we can understand all that, there are some other concepts we need to be aware of.
Success and Failure
When we talk about executing a behaviour tree what we are really saying is: when we execute the root behaviour of the tree does it succeed or fail?
At the level of the tree itself, if the root behaviour succeeds it means that some action was successfully taken in response to whatever event caused us to execute the tree in the first place.
In the context of a behaviour itself success or failure has different meaning. For example, a $select
will succeed if any of its children succeed, while a $sequence
will only succeed if all of its children succeed (the observant may notice a relation to boolean logic here).
To put this in context, $select
can be used to choose among a range of behaviours (given as its children). If one child fails, the next is tried, and so on. If the $select
succeeds we know that one of its children succeeded. Conversely the $select
failing tells us that none of its children succeeded. By contrast the $sequence
is a step-by-step procedure where if any step fails, the $sequence
fails.
You will see this pattern of $select
/$sequence
for selecting among options that are described as a procedure (that may themselves contain other such patterns) often.
$sequence
and $select
are examples of composite behaviours. We’ll talk about behavior types in a moment.
The 'leaf' behaviours in a tree don’t have children and their success or failure it more directly tied to the state of the game world. In our example above the behaviour:
[actor_in actor=#sam_spade location=#sams_office]
Will, we supppose, succeed if Sam is in his office and fail otherwise. The actor_drinks
example is a little more subtle.
[actor_drinks actor=#sam_spade item=#whisky_bottle]
In our example we have already tested that the whisky item is in Sam’s location we can assume that actor_drinks
is going to succeed. But this doesn’t always have to be the case. Whether or not a behaviour succeed automatically if its prior conditions succeed is down to you.
Behaviour Types
There are four different types of behaviour:
-
composite
-
decorator
-
condition
-
action
Composite Behaviours
A composite behaviour always has at least one child and can be thought of as a kind of coordinator where its success or failure is based on what happens when it executes some, or all, of its children.
As you’ve seen, $sequence
executes its children in turn until either they have all succeeded (in which case $sequence
succeeds) or until one of them fails (at which point $sequence
fails). So a sequence general consists of a set of conditions and then the actions that should arise if they are met.
The $select
behaviour executes its children in turn until one of them succeeds (at which point $select
succeeds) or they have all failed (at which point $select
fails). This means that $select
is a good way of choosing between a set of alternatives, for example different ways to achieve a goal where the AI can have fall back tactics should any particular behaviour fail or not be available.
You may have observed that $select
implements OR-logic while $sequence
implements AND-logic. Just these two behaviours allow us to create any complexity of decision-making structure we want.
Decorator Behaviours
A decorator behaviour usually has a single child and its purpose is to modify the result of running its child behaviour.
For example the $invert decorator executes its child and flips the result making success into failure (and vice verca).
Condition Behaviours
A condition behaviour tests the state of the game world and succeeds or fails based upon that test. For example we defined two conditions in the example above actor_in
and item_in
which test whether an actor, or item, are in a given location.
We can imagine that actor_in
succeeds when the specified actor is in the specified location and fails otherwise.
As an author you will create condition behaviours that test things that are meaningful to your game and these behaviours become available to your behaviour trees.
Action Behaviours
An action behaviour modifies the state of the game world. Actions are usually expected to succeed (because, typically, they are placed after conditions that gate whether they should happen or not).
Typically an action will succeed (because the conditions for success should already have been met) although this is not a hard and fast rule.
However be careful about making changes to world state and then failing. This could have unintended consequences.
Putting It All Together
Our first example of a simple behaviour tree uses one composite behaviour ($sequence
), two conditions (actor_in
, item_in
), and one action (actor_drinks
).
Let’s make a more complex example:
@actor sam_spade { behaviours: ^[ [$select [$sequence [actor_sense_danger] [$select [$sequence [actor_is_armed] [actor_draws_gun]] [$sequence [actor_sees_item type=:weapon] [actor_equips_item]] [$sequence [actor_sees_exit] [actor_escapes]]]] [$sequence [actor_thirsty] [$select [$sequence [actor_sees_item type=:drink] [actor_equips_item] [actor_drinks]] [$sequence [actor_says msg="Boy I could use a drink!"]]]]]] }
In this example we’re using $select
to choose between 2 high level behaviours (respond to threat, respond to thirst) with each of those behaviours being composed of further $sequence
and $select
based behaviours.
For example if the player senses danger, do they have a gun? Is there a weapon in their environment? Can they escape?
We’ve made up a bunch of condition and action behaviours like actor_is_armed
and actor_says
and hand waved over the detail of their implementation.
As an author most of your work will be creating meaningful conditions and actions that represent the state and actions available in your game world.
You’ll note in this example we have a number of actor_xxx
behaviours but haven’t specified an actor=xxx
option. In this context we’d probably put the actor into working memory.
Working Memory
When a behaviour tree gets executed (using RezBehaviour.executeBehaviour(wmem)
) it is passed an object to represent the "working memory" of the tree. This object is passed from behaviour to behaviour and can be used to communicate information between behaviours.
In our example above we might initialize working memory with something like:
{self: $("sam_spade")}
Now any behaviour that needs it can refer to self
in the working memory to find out who is performing these behaviours. Working memory can be a useful way to pass information around that is either dynamic (and therefore difficult to refer to as an option) or repetitive.
Core Behaviours
The Rez stdlib defines a range of core behaviours whose id have a $
prefix to distinguish them from author written behaviours. The implementation of the core behaviours is in stdlib.rez
.
The core behaviours are intended to provide an overall structure for creating different kinds of behaviour. In particular look at behaviours like $either, $random_choice and $random_each which can introduce variability into behaviour patterns.
Writing Your Own Behaviours
The @behaviour
element allows you to write your own behaviours, typically these will be conditions and actions that query & modify, respectively, your game world. Let’s take a look at a query first:
@behaviour actor_is_armed { execute: function(owner, behaviour, wmem) { return { success: ($player.main_inventory.weapon_contents.length > 0), wmem: wmem } } }
First note that the only thing we are required to define is the execute:
attribute, which must be a regular function (an arrow function will not do). All execute:
handler functions are required to return an object with two keys: success
and wmem
. The value for success
should a boolean. In this case we return true
if the players weapon slot has something in it, false if not. The value for wmem
should be the working memory we were passed in.
In this example we’ve assumed the actor is the #player. But we could make it more flexible using an option.
@behaviour actor_is_armed { options: ["actor"] execute: function(owner, behaviour, wmem) { const actor = $(actor); return { success: (actor.main_inventory.weapon_contents.length > 0), wmem: wmem } } }
Now we’d specify the behaviour as
^[actor_is_armed actor=#sam_spade]
An as long as Sam’s @actor
definition contained a main_inventory
all would be well.
Now let’s examine how we would define an action:
@behaviour actor_drinks { execute: function(owner, behaviour, wmem) { $player.drunk += 1; return { success: true, wmem: wmem }; } }
We can see that this is even simpler. Most actions will return a successful result since you have, likely, already queried whether they be executed or not. But if an action can be executed and fail you can return a different results as per the query above.
Working memory is assumed to be an object that is passed between the different behaviours in the tree. For example one behaviour could store a value that a later behaviour will use. We can see how actor_sees_item
could return success and store the id of the item in the working memory for actor_equips_item
to put into their inventory.
Lastly the owner is the object that owns the behaviour tree. This allows you to make use of the owner’s attributes within the behaviour. Here’s an example of doing this. Let’s create a generic query
behaviour that lets us test a property of any object and compare it with an attribute of the owning element:
@behaviour query { options: ["if", "obj"] execute: function(owner, behaviour, wmem) { const f = behaviour.option("if").bind(owner); const o = $(behaviour.option("obj")); return { success: f(o), wmem: wmem }; } } @object foo { cost: 2 } @object bar { cash: 10 test: ^[query obj="foo" if=^{obj.cost < this.cash}] }
Might take you a while to see what is going on here but we are making use of bind
to make this
into the owner object within the context of the code block being passed to the query
via its if
option.
Using Behaviour Trees
Having defined a behaviour tree, how do you use it?
Remember that a behaviour tree is defined as an attribute on an element:
@actor sam_spade { behaviours: ^[...] }
At a basic level we can run this behaviour tree using:
const result = $("sam_spade").behaviours.executeBehaviour({});
The executeBehaviour()
method returns an object:
{ success: true|false, wmem: <modified working memory> } if(result.success) { } else { }
The authors are still experimenting but a broad approach suggests itself:
Behaviours are likely to fall into broad categories that form a response to an event. An example might be "new day" or "player enters location".
You could then define, e.g., a new_day_behaviours:
attribute any element that should respond to the :new_day
event. Then use a @system
to reponds to the event, running the behaviours for all relevant elements.
@system new_day_system { after_event: (system, event, result) => { if(event === "new_day") { const wmem = {}; $game.getObjectsWith("new_day_behaviours").forEach((el) => { el.new_day_behaviours.executeBehaviour(wmem); }); } return result; } }
Another approach is to define a behaviour tree to cover all events and use $select
to decide which branch to follow. For example:
@actor sam_spade { behaviours: ^[select [$sequence [event is=:new_day] [...]] [$sequence [event is=<some other event>] ...]] }
Now you could define a system for running all behaviours in response to any event:
@system event_behaviours { after_event: (system, event, result) => { const wmem = {event: event}; $game.getObjectsWith("behaviours").forEach((el) => { el.behaviours.executeBehaviour(wmem); }) } }
The behaviour system is quite flexible and can be made to work in a number of different ways to suit the needs of your game and ease of authorship.
Differences To Other Systems
Much of the writing on behaviour trees comes from a real-time video game perspective where the trees are used to power enemy-AI. This introduces a number of constraints for example that the behaviour AI must run within one frame (i.e. <0.04s). The "running" status can be used to "break up" a behaviour.
In the Rez context this is less relevant so we do not support the "running" status. However it may be implemented later if it proves useful.
From a terminology perspective we use "executing" instead of the more common "ticking" language. We think it’s more natural to say you are executing a behaviour than ticking one.
We use the term "behaviour" to refer to our behaviour types while you may see them referred to as "nodes" elsewhere. Node is a more mathematical term, we think behaviour is more natural.
Also where you see fallback
as a type of behaviour we call it $select
. The behaviour is ultimately the same.
Custom Scripts & Styles
Rez supports the addition of custom Javascript & CSS in a number of different ways.
Script & Stylesheet Directives
The @script
and @style
directives allow embedding arbitrary Javascript or CSS classes into your game.
@script { function identifyParrot(p) { if(p === "parrot") { return "Sqwauk"; } else { return "Pfffft"; } } }
@stylesheet { /* https://gist.github.com/JoeyBurzynski/617fb6201335779f8424ad9528b72c41 */ .main { max-width: 38rem; padding: 2rem; margin: auto; } }
The contents of these directives is automatically inserted into an appropriate spot in the game files.
Patching Javascript
Another way to include your own Javascript is through the use of the @patch
directive which allows you to add new methods to existing JS classes. Here is an example from the stdlib.
@patch ARRAY_FY_SHUFFLE { %% Fisher-Yates Shuffle impl from: https://sebhastian.com/fisher-yates-shuffle-javascript/ patch: "Array" method: "fy_shuffle" impl: function() { let idx = this.length; while(--idx > 0) { const rand_idx = Math.floor(Math.random() * (idx+1)); [this[rand_idx], this[idx]] = [this[idx], this[rand_idx]]; } return this; } }
This adds a new method fy_shuffle
to Javascript Array
instances. So you can now write:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].fy_shuffle() => [3, 7, 6, 8, 4, 9, 1, 2, 5, 10]
To add a method to instances use the method:
attribute and specify the method name. To add a function to a constructor use the function:
attribute instead.
Write Your Own Filters
A third way to include custom Javascript is by implementing a template expression filter. Here is an example from the stdlib:
@filter STRING_STARTS_WITH_FILTER { %% String -> Bool name: "starts_with" impl: (s, search) => {return s.startsWith(search);} }