Rez is a language for writing hypermedia games built using HTML/Javascript/CSS and optionally incorporating graphics, audio, and movie files.
Rez shares a little DNA with Twine which describes itself as an “open source tool for telling interactive, nonlinear stories.” Where Twine shines is in making it relatively easy for those with little development experience to get started. However as the complexity of a game increases Twine offers less and less support.
Rez was created to serve a niche of games that are too complex for Twine. In sits somewhere between Twine and Inform/TADS in terms of complexity. The Rez syntax is fairly simple and uses Javascript for event handlers and utility code. Rez has built-in support for things like NPCs, inventories, scene management, and maps. Rez features a simple but powerful layout and event system.
Rez is designed to be flexible enough to support a number of use-cases while offering a useful framework for getting started.
Rez games are built from .rez source files and are composed of elements describing different parts of the game.
At the top level is the @game
element that contains the
game metadata and other elements that make up the game.
The command
rez new <name>
Will create a new folder
The command
rez compile <path/to/source.rez>
Will compile the specific source file into a game in the
dist
folder. It creates an index.html and assorted
javascript files containing the games runtime as well as packaging any
assets included in the game.
Rez uses the Bulma CSS framework.
Rez uses Apline.js for dynamic UI support.
Rez uses Handlebars.js for dynamic templating.
Line comments are supported by placing %%
in column
0.
In future the restriction that comments must start in the first column will be lifted.
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)
Most element is specified using a common syntax:
@element <id> begin
attr1: val1
attr2: val2
...
end
For example,
@game begin
name: "Twisty Maze Adventure"
IFID: "D2050DE2-97A2-1ED1-4CCA-AF9D3B0DD883"
created: "2022-08-31 22:13:43.830755Z"
version: 10
initial_scene: #intro_scene
…
end
Every element has an ID (the @game
element has an
implicit id of game
) which must be unique and follow the
rules for legal Javascript identifiers.
If you need to include custom Javascript code (outside of the in-game
event handlers) or CSS styles in your game you can use the
@script
and @style
elements respectively.
These elements do not have an ID, nor do they have attibutes and are
written as follows:
@script begin
function foo() {
// I need this function to be available to my event handlers
}
end
@style begin
/* https://gist.github.com/JoeyBurzynski/617fb6201335779f8424ad9528b72c41 */
.main {
max-width: 38rem;
padding: 2rem;
margin: auto;
}
end
The content of @script
and @style
tags will
automatically be included into the relevant parts of the generated game
files.
A Rez element is defined in terms of attributes. Some attributes are
expected (and perhaps required) by the game (e.g. the
content
attribute of @card
) while some you
will define yourself and use in your event handlers and glue code.
An attribute has a name and a value. The name follows the rules for legal Javascript identifiers:
This means that any attribute defined on any in-game object can be referenced naturally in user Javascript code.
Note that attributes with a leading underscore are considered to be “internal” to the Rez compiler and will not appear in the generated code.
Attributes are written:
name: value
There should be no space between the end of the attribute name and
the :
and at least one whitespace between the
:
and the value.
Legal
title: #foo
Not-legal
title : #foo
title :#foo
title:#foo
The following types are supported:
The values true
, false
, and
yes
, no
are recognised and can be used
interchangably to represent truthy values.
Positive and negative integer and floating point numbers, e.g.
12
, -2.5
Text delimited by "
useful for short strings such as
descriptions. However, for passages of text it can be easier to use a
Heredoc String
Text delimited by """
. These can span over multiple
lines.
An attribute reference is &
prefix followed by the
name of the attribute to be referenced.
name_gen: () => {...function returning a name...}
name: &name_gen
An element reference consists of a #
prefix followed by
an element ID, e.g.
favourite_monster: #ugly_troll
initial_scene: #lost_scene
A reference is used to refer to a unique element in the game. The compiler will generates an error if the referenced element does not exist.
A dice roll is specified in the form [count]d[sides][+|- modifier]. So that:
strength: d6
strength: 2d6
strength: 3d6-1
strength: d20+1
Are all legal die roll specifiers.
A keyword is a :
prefix followed by a Javascript
compatible id, e.g.
:continuous
:weapon
:item
Keywords are used for constant values, for example the type of an
item (e.g. :sword
) or the layout mode of a scene
(:continuous
).
Keywords can be formed into hierarchies using the
@derive
pseudo-element.
A function is an arrow-style Javascript function, typically used as an event handler
(game, event) => {
if(event.target.value == "Fight") {
game.setCurrentScene("fight");
}
}
Most event handlers take two arguments. A reference to the
game
element and an ‘event object’. See the specific
handler documention for details.
A []
bracketed list of values that can include other
collections such as lists, sets, & tables
[#ugly_troll #friendly_troll]
[1 2 3 4]
[[:a 1] [:b 2] [:c 3]]
Note that lists in Rez are whitespace, not comma, separated.
A #[]
bracketed list of values that are constrained to
be unique (according to the JS notion of equality).
#[:one :two :three]
Note that sets in Rez are whitespace, not comma, separated.
A {}
bracketed list of name/value pairs where the name
follows the attribute naming rules and the value can include other
collections such as lists, sets, and tables.
{
hostile: true
coordinates: [1 2]
attrs: {
strength: 10
hp: 15
}
}
Note that key/value pairs in tables are whitespace, not comma, separated.
Please note that sets, lists, and tables use whitespace as a separator between elements/pairs rather than a comma as is common in many programming languages.
So a list is written:
numbers: [1 2 3 4]
rather than:
numbers: [1, 2, 3, 4]
The latter will cause a syntax error.
Rez includes many elements that you will combine to create your game,
starting with the @game
element that wraps the whole
thing.
All elements in a Rez source file are prefixed by @
including aliases.
A few Rez elements like @game
and @zone
contain other elements but most do not.
@alias
@actor
@asset
@card
@derive
@effect
@faction
@game
@group
@inventory
@item
@list
@location
@plot
@scene
@script
@slot
@style
@system
@zone
The elements are:
Using @alias
you can create a new element name that
aliases one of the existing game elements.
Why would you want to do this? Two reasons:
@sword
than
@item
in your sourceIn our Maltese Falcon game hats are a big deal and a range of hat
items will be needed but we don’t want to repeat ourselves defining each
one using @item so we
can create an alias that specifies that a Hat
is an
Item
and how hats are, generally, configured. Then our
Hat
definition just needs to supply what’s different about
that hat.
@alias hat = item # begin
type: :hat
wearable: true
usable: false
bogie_would_approve: false
end
@hat wool_fedora begin
material: :wool
colour: :brown
description: "A Messer brown wool fedora hat"
bogie_would_approve: true
end
Note that, at present, it is not possible to alias an alias and an error will be generated.
None
None
An @actor
element is used to define a character (either
playable or a non-playable NPC).
Typically you would use Actors when you want to represent them in game with their own attributes, behaviours, and potentially relationships.
The Twine approach would be to write all interactions with NPCs in the form of pre-defined passages. This is possible in Rez but if you want more sophisticated handling of NPC behaviour use an Actor.
@list player_names begin
content: ["Elric" "Yrkoon" "Cymoril" "Moonglum" "Rackhir" "Smiorgan" "Yishana" "Zarozinia"]
end
@actor player begin
name: &name_gen
name_gen: (game, player) => {
const list = game.getGameObject("player_names");
return list.randomElement();
}
stats: {
blood: 10
power: 5
}
on_init: (game, player) => {
// Custom initialization can go here
}
end
None
(#[:tag1 :tag2])
#ugly_troll
An @asset
element refers to a file on disk, typically an
image, audio, or video file, that will be presented in game.
Rez automatically copies Asset files into the game distribution folder when the game is compiled and manages pathing so that assets can be referred to in game without worrying about filenames and paths.
By specifying an Assett
Rez will copy the asset file
into the game dist folder and the asset can be referenced within the
game without needing to know its path.
Asset
s can be collected into a a Group
to
dynamically choose them.
@asset hat_01 begin
file: "hat_01.png"
tags: #[:hat]
end
This defines an asset that will be copied into the game when built and which can be referred to in-game by it’s id.
Rez will ensure that all assets are available during compilation.
Assets are the key to using asset groups that can be used for showing different but randomised media.
("hat_01.jpg")
(#[:tag1 :tag2 …])
None
Cards in Rez are broadly equivalent to passages in Twine although they are defined differently. A card is a block of hypermedia defined using the Markdown format.
Internally a card is transformed into a Javascript Handlerbars template so rendering them is performant.
@card intro_part_1 begin
content: """
You are in a mazy of twisty passages all alike.
[[Go forward|#intro_part_2]]
"""
end
@card intro_part_2 begin
…
end
(#[#sidebar_menu #header])
(#[#bandit #player])
(#[:tag1 :tag2 …])
If specified, the blocks
attribute should contain a list
of ids of cards that are going to be included in the output content of
this card. At render time all blocks are pre-rendered and passed into
the rendering context.
Example @card test_card begin blocks: [#other_card]
content: """
This is what is in #other_card
{{{other_card}}}
"""
end
If specified this should be a map of keys to game object ids. When
the card is rendered all bindings will be resolved and made available to
the rendering context. You can then use the $
macro to
render attributes of the bound objects.
Example @card test_card begin bindings: {player #player}
content: """
The player's name is {{$ player "name}}
"""
end
Although a Rez card definition looks a little different to a Twine passage the simple case looks pretty similar. Internally the `[[Go forward|intro_part_2]]`` syntax is transformed into an event that loads the referenced card.
However it’s possible as an author to hijack this mechanism:
@card intro_part_1 begin
content: """
You are in a mazy of twisty passages all alike.
[[Go forward]]
"""
on_go_forward: (game, evt) => {return Scene.load_card(game, "intro_part_2");}
end
Here we define an event handler which will respond to the link being clicked. By default Rez will automatically convert a link such as “Go forward” into the equivalent “go_forward” by downcasing and replacing whitespace with a single underscore(_).
Rez also has support for more dynamic types of links:
@card intro_part_1 begin
content: """
You are in a mazy of twist passages all alike.
[[Go forward|go_forward]]
"""
go_forward: (game, evt) => {evt.choice.show("Go forward);}
on_go_forward: (game, evt) => {return Scene.load_card(game, "intro_part_2")}
When a card link is written in this format, Rez will look inside the card for an attribute with the same name and a function value. It will call the function which can determine whether the link should be shown or hidden and, if it is shown whether it should be enabled or disabled. whether it is enabled or disabled.
See the COOKBOOK for more.
The @derive
element is used to form keyword hierarchies.
Let’s take an example of where this might be useful: inventories.
We setup a hierarchy as follows:
@derive :weapon :item @derive :sword :weapon @derive :mace :weapon @derive :potion :item
The result is that an item with type: :sword
,
type: :mace
, or type: :potion
can be placed
into a slot that accepts: :item
. It’s not required to list
all the different types of items that are legal in that slot.Equally our
sword can be placed into a slot that accepts: :sword
but an
item type: :mace
cannot, nor can an item
type: :potion
.
An item hierarchy can be as simple of complex as you need. At
run-time all of the item type information is converted into tags. For
example an item with type: :sword
would have tags as if we
had written tags: #[:sword :weapon :item]
.
Note that this element is not yet fully implemented in the stdlib
An Effect
is a non-permanent modifier to some aspect of
the game. For example an Item
that needs to confer some
benefit either when possessed or worn where that effect is only meant to
last for a certain period, number of “uses”, or until the
Item
is no longer worn/carried.
@effect fire_resist begin
name: "Fire Resistance"
on_apply: (game) => {
let fire_resist = game.player.getAttribute("fire_resistance");
game.player.setAttribute("fire_resistance", fire_resist + 10);
}
on_remove: (game) => {
let fire_resist = game.player.getAttribute("fire_resistance");
game.player.setAttribute("fire_resistance", fire_resist - 10);
}
end
#[:tag1 :tag2 …]
)None
Note that this element is not yet implemented in the stdlib
A @faction
element is used to define an in-game faction
to which different @actor
s may be affiliated. A faction has
an attitude towards an actor that represents how it views the actor.
This can be used to determine whether actors will interact and how they will interact.
The @game
element is the top level and contains all
other elements. It has an implicit ID of game
and a number
of required attributes and can also hold game-scoped data.
@game begin
name: "Twisty Maze Adventure"
IFID: "D2050DE2-97A2-1ED1-4CCA-AF9D3B0DD883"
created: "2022-08-31 22:13:43.830755Z"
version: 10
initial_scene: #intro_scene
end
None
Note this element is not included in the current stdlib
The @inventory
element creates a container that can hold
@item
s through the use of @slot
s. Rez
inventories are deliberately flexible to handle a range of use cases for
example working memory (where items are thoughts) or spell books (where
items are spells).
Rez has a fairly flexible inventory system that is based around ‘slots’ that define how items can be held. This allows an inventory to hold different kinds of items: you could have an inventory for items as well as an inventory for spells (spell book).
Inventory slots are matched against items to determine whether it’s possible to put an item in a slot.
Inventories are defined using the @inventory
tag.
Inventories have a category which determines the kind of items that can be added to their slots. For example “spell” could represent a spell book, while “equipment” could represent the players inventory.
@inventory player_inventory begin
slots: #[#hat_slot #jacket_slot #trousers_slot #shoes_slot]
end
None.
The @item
element defines a conceptual item the player
the player (or potentially an NPC) can acquire and add to an inventory.
Items don’t have to represent physical objects but anything a player has
for example a spell could be an item or even a memory.
Items are required to have a type
keyword-attribute that
connects them to compatible slots in inventories. That might include a
shop, a wardobe, and a players backpack inventories.
However the Item/Inventory system is quite flexible so we can also think about spells as Items with the Inventory being a spell-book, or knowledge as Items with an Inventory being memory.
Items may be usable in which case they may have a limit to the number of times they can be used.
Some items can grant effects, either when the item is acquired, put into a specific slot (e.g. equipped), or when it is used.
The can_equip/on_equip scripts are used to decide whether the player can put an item in a given inventory & slot, and to process what happens when doing so.
For example equipping a magic ring might confer an effect on the player. But first it may be necessary to check that the player doesn’t already have a magic ring equipped.
A potion on the other hand confers no effect until it is used and might have only one use after which is presumed to be consumed.
@item black_fedora begin
type: :hat
description: "black fedora"
wearable: true
description: "A Messer wool fedora hat. Classy."
end
Note that this example throws up a design issue to be aware of: tags
and boolean attributes are equivalent. For example
wearable: true
can also be represented by presence or
absence of a tag wearable
. In the case of Item
elements its further possible to use the type system:
@derive :wearable :item
@derive :hat :wearable
In this case an Item
with type: :hat
will
automatically be tagged as :wearable
and
:item
.
@item <id> begin
type: :...
name: "..."
description: "..."
usable: false
can_equip: () => {...}
on_equip: () => {...}
end
Locations in Rez are an optional concept that can be used to create a “stage” for one or more scenes. In cases where different scenes may play out in one virtual location it may make sense to use a Location to represent what is the same about the background. If scenes and cards get the job done, you don’t have to worry about using locations.
Locations are defined using the @location
tag.
Tag: @location
The @list
element is for creating lists for use in-game.
For example a list of names, or places. At run-time the list element
supports selecting randomly from lists including with & without
replacement.
@list tourist_traps begin
content: ["Bree" "Buckland" "Dead Marshes" "Dol Guldur" "Esgaroth" "Mirkwood" "Rivendell" "Weathertop"]
end
list.randomElement()
Return a random element of the list.
list.nextForCycle(key)
Cycles through the list element by element. Each cycle is identified by a key.
list.randomUnique(key)
Returns a random element of the list without repeating elements. Each random walk is idenfied by a key.
(#[:tag1 :tag2 …])
A Game in Rez is authored in terms of @scene
s and
@card
s. Each @card
represents some content
that is presented to the player. By contrast the @scene
represent the structure and intelligence about which @card
s
to present and how to respond to player input.
If you are familiar with Twine then a @card
is roughly
equivalent to a Twine passage. A Twine game is one long stream of
passages woven together. Rez differs from Twine in that it uses the
@scene
to organise how the player interacts with the game
and which/how the content is presented.
For example you might use different scenes for moving around the map, examining items, interacting with NPCs, buying from shops, and so on. You don’t have to, you could implement the game in a single scene, but the different layout and event handling possibilities make it easier.
A @scene
requires an
initial_card: #card_ref
attribute that identifies the card
that will be rendered when the scene begins. Additionally it requires a
layout:
attribute that specifies the surrounding
markup.
Within the layout using the {{{content}}}
macro to
specify where scene content is inserted.
A @scene
requires a layout_mode:
attribute
which must be either :single
or :continuous
.
In the :single
layout mode only a single @card
is ever displayed. While in :continuous
mode each new
@card
is layed out after the previous one.
Lastly a @scene
may optionally have a
blocks: [#card_id_1 #card_id_2 ...]
attribute. Each
referenced @card
will be rendered and it’s content can be
inserted into the layout using {{{card_id_1}}}
,
{{card_id_2}}
, etc.
@scene introduction begin
title: "Introduction"
initial_card: #intro_part_1
blocks: [#sidebar_1 #sidebar_2]
layout_mode: :single
layout: """
<div class="sidebar">
{{{sidebar_1}}}
{{{sidebar_2}}}
</div>
<div>
{{{content}}}
</div>
"""
on_new_card: (game, evt) => { … }
end
#card_id
(:single or :continuous)
A @slot
describes a component of an
@inventory
so that an inventory can hold different types of
things.
For example an inventory representing what a player is wearing might have slots for coats, trousers, and so forth while an inventory representing a spell book might have slots for different levels of spell.
@slot sword_slot begin
name: "Sword"
accepts: :sword
end
When an Item
is placed into a Slot
the
on_insert
event handler will be called.
on_insert: (game, evt) => {return game;}
When an Item
is taken out of a Slot
the
on_remove
event handler will be called.
on_remove: (game, evt) => {return game;}
Zone
s are a way of grouping Location
s
together based. Location
s in the same Zone
are
assumed to be near to each other and reachable (although possibly not
directly) while Location
s in a different Zone
are not and need to be reached via some special action or connecting
location.
For example a “downtown” Zone
might be connected to a
“suburbs” Zone
via cab. A cab rank Location
in
each Zone
could act as the gateway to the other
Zone
and its Locations
.
@zone home begin
@location … begin
end
@location … begin
end
…
end
Zones do not handle any of the default events.
All element tags start with the ‘@’ symbol however the begin and end statements delimiting the definition do not.
Attributes begin with a legal Javascript identifier followed immediately by a ‘:’ and at least one space.
Attributes that start with on_
are expected to be event
handlers and should be of script type taking the arguments
game
and event
. At runtime when the event
occurs the function will be called to handle it.
@game options
One @game
directive is required and specifies top-level
attributes such as the games title and IFID.
name: a string containing the name of the game IFID: a string
containing the game IFID (a UUID) output: a string containing a path
where the game will be written by the compiler initial_scene: a
reference to the @scene
id of the starting scene
title: a longer form of name, if not supplied name will be used on_init: an event handler called before the game starts on_start: an event handler called before the first card is to be rendered
@group group-id {options}
The @group
directive specifies an asset group which
collects together one or more assets.
For example an asset group could collect together a set of similar images and in-game select an image at random, or cycle through the collection one-by-one.
The members of an asset group can be determined either by the author by including the ID’s directly, or through the use of tagging.
assets: list of ID’s of assets to be statically included in the group tags: list of tags to dynamically include assets in the group
The card directive specifies a block of content that will be shown to the player. It’s analgous to a passage in Twine with a few upgrades.
@card <id> [options] """[content]"""
Example: @card intro_1 begin content: ““” Markdown goes here ““” on_enter: (game, event) => {} end
Specifies an event handler that will be called when the card is being entered and before rendering occurs.
Specifies an event handler that will be called when the passge is about to be shown to the player, after rendering has occurred.
Specifies an event handler that will be called just before the game switches to another card.
Any option specified with a ‘$’ at the beginning of its name is considered to be an ‘assign’. What this means is that whenever the card is being rendered the value of all assigns will be updated using either a function or an expression. Assigns are made available to the card as a variable that can be interpolated in a template.
For example:
$player_name: #game.player.name
can then be interpolated into a template as
{{player_name}}
Alternatively:
$player_name: (game, event) => {return game.player.name;}
uses the function sequence. The expression syntax is a syntactic sugar.
Using functions an assign can be arbitrarily complex.
This keeps the logic out of the template itself.
A card can define event handlers that respond to what happens in the game.
There are presently three event handlers:
The on_enter
and on_shown
call backs happen
in sequence. When the active card is switched any on_enter
handler is called. At the ender of the rendering process when the new
card is visible the on_shown
handler is called. Finally
when the user triggers a new card the on_leave
handler has
a chance to run before the on_enter
for the new card is
invoked.
Handlers are passed the game state and details of the event that has been triggered and are expected to return the (possibly modified) game state.
The assigns process happens between after the on_enter
handler is called and before the on_shown
handler runs. The
on_enter
handler is a good opportunity to set values that
assigns will depend upon.
Rez depends upon NPM to deliver dependencies of the game:
We need a safe way of delimiting content blocks.
Using {**
and **}
seemed safe until I
remembered that Markdown uses the double star for bold so
**}**
would prematurely terminate parsing of a content
section.
Alternatives <<< >>> - Javascript unsigned right & left shift operators === === - Javascript operator
““” ““” - it’s nice to have a visual distinction between open & close %%% %%% - ditto
on_start
on_scene_change
The on_start
event is triggered right after the Rez
framework has initialized itself and before the first scene or card gets
rendered. It’s an opportunity to customise game setup.
The on_scene_change
and on_card_change
are
interstitial event handlers. To take scene changes for example, the
current scene is sent an on_end
event when a scene change
is being made and the new scene will receive an on_begin
event after that. The on_scene_change
handler will be
called between these other events. It can even change which scene is
being routed to.
on_start
on_card_change
on_end
The on_start
handler is called when a scene is about to
be started and before the first card is rendered. It can change the card
to be rendered.
The on_card_change
handler is conceptually similar to
the on_scene_change
handler in the Game object and runs
between the on_end
and on_start
events of
cards.
The on_end
handler is called right after a scene is over
and before the transition to a new scene.
on_start
on_action
on_end