Rez is a simple, declarative, language for creating choice based games & interactive fiction. The syntax is easy to learn being largely composed of elements and attributes.
Rez uses Javascript functions to implement dynamic behaviours. JS functions appear as attribute values for handling events and can also be used for implementing custom components.
It’s relatively simple but designed to make large/complex games manageable.
It includes asset management, a renderer with support for different scene styles, a template language with user components, support for actors & behaviours, items & inventories, and more.
Here is a short example that nevertheless covers a number of features including event handling, forms, dynamic templates, element references, scenes & card layout, and more.
%% Include the standard library
%(stdlib.rez)
@game {
name: "my_game"
title: "My Game"
author: "Matt Mower"
author_email: "self@mattmower.com"
game_homepage: "https://rez-lang.com/"
IFID: "B37043A4-6362-11F0-989B-CA41670CF0BC"
archive_format: 1
version: "1.0"
created: "2025-07-17 23:06:41.694789Z"
initial_scene_id: #scene_one
layout: ```
<!-- Main Content -->
<section class="main-content is-fullheight">
<div class="container">${content}</div>
</section>
<!-- End -->
```
}
@scene scene_one {
initial_card_id: #whats_your_name
}
@actor player {
$global: true
name: _
}
@component error (bindings, assigns, content) => {
if(bindings.card.error !== "") {
return `<div>${bindings.card.error}</div>`;
} else {
return "";
}
}
@component live_input (bindings, assigns, content) => {
let {type, name, bind} = assigns;
return `<input type="${type}" name="${name}" rez-bind="${bind}" rez-live class="input" />`;
}
@card whats_your_name {
error: ""
content: ```
<p>This game is called ${game.title}.</p>
<form name="player_form" rez-live>
<p>What is your name: <.live_input type="text" bind="player.name" /></p>
<.error />
</form>
```
on_player_form: (card) => {
if($player.name.length > 0) {
card.error = "";
return RezEvent.playCard("your_name_is");
} else {
card.error = "You must enter a name!";
return RezEvent.render();
}
}
}
@card your_name_is {
liked: false
on_render: (card) => {
card.liked = Boolean.random();
}
bindings: [
player: #player
]
content: ```
<p>Your name is ${player.name}. What a $if(card.liked) -> {% nice %} (true) -> {% horrible %} name!</p>
<a card="whats_your_name">Try a different name</a>
```
}
Major changes in v1.8
Replaced built-in node validation with the @schema directive (see stdlib for examples of schema directives for built-in elements)
@item is no longer a base element but an alias of @card which demonstrates that aliases work well.
RezInventory allows for adding anything with a type: attribute
Removed the table attribute data type
Pass params to game scene_start events
Pass params to scene start events
Started documenting the stdlib
Updated tests
Game start_events is now handled as a bindings list
Updated to Ergo 1.0.4 and improved attribute parsing error messages
The enter key when pressed in the input of a rez-live form now triggers the ‘submit’ event to its card
I started my previous thread in Jan 2024 and it’s grown to be several hundred replies & over 30-min reading time, so I don’t feel too bad about starting a new one.
There’s only one code change: I finally fixed the names of the stdlib functions for converting strings between different cases. They were all over the place and now all the same, e.g. toCamelCase().
What I have done is a lot of updates to the documentation. I completed the stdlib documentation and fixed up the tables of contents which weren’t rendering (or rendering properly) across all the documentation.
Adds the ^c:#id copy initializer. This completes the basic picture on “id based programming” for procedural content. Here’s the simplest possible example:
By marking the skill object as a template the initialisation of the level: attribute doesn’t happen when the object is created, rather it gets deferred until the object is copied. The ^i{} initialiser says “initialise the value of this attribute to the result of the given Javascript expression”.
The new ^c: prefix says “this attribute value will be initialised with the id of a copy of the named object”. When the player is initialised it will copy sk_shooting, creating a new object (with id sk_shooting_1) whose level: will then be initialised with a value from 1 to 10 (biased towards the middle of that range).
So now you can connect together copies of objects into a whole assembly. There are still some rough edges and syntax that could be improved, but I’m making use of it.
You might be wondering, why not something simpler?
@actor player {
name: "Steve"
shooting: ^i{return Math.cl_rand_int_between(1, 10);}
}
Wouldn’t that be much simpler? And you’d be right. For simple examples it is simper and more direct to work directly with the attribute value.
But as I have started working with procedural generation of NPCs I have found that a composition strategy helps tame the complexity. For example in Fleet Commander different kinds of NPCs have different “skill packages” each representing an officer class and composed of a mixture of skills where there is a lot of overlap.
A number of fixes and small changes were also made that are listed in the release notes.
I’m working on what will be Rez v1.8.4 which introduces two changes, one invisible and one visible:
In working with the behaviour trees I found that they could be simplified. Rez’s behaviour trees were a port of my prior Clojure behaviour tree library. The insight is that Rez game elements already form a blackboard for preserving/sharing state, but i wasn’t using them as such. v1.8.4 does and simplifies how the whole system works.
As an aside I’ve also settled on implementing “behaviour-tree-per-element” rather than trying to “blend” different trees into a single unified tree. It seems there is ample prior art here so I feel on safe ground. The one remaining question is the ordering of trees. This matters in cases where agents are coordinating because “who goes first” can have an effect on the outcomes/narrative. I don’t see an obvious winner here. In FC I am tending towards randomising the officer list on each run although that has its own downsides.
The visible change is the introduction of the @const directive that creates both a compile time & runtime constant.
@const ultimate_answer = 42
Will define a constant that can be referred to in attribute values:
console.log(`The ultimate answer is ${$ultimate_answer}`);
At the moment it only supports bool, number, and string values although there is a case for letting it support any legal value.
It’s a small change but I was starting to accumulate “magic numbers” in my source and this lets me lift them out, making things more readable, as well as making it easier to refer to the same values within JS event handlers.
I have now released v1.8.4 which, among a number of things, adds the @const directive and, as I prophesied, you can now make any legal value (in particular lists) a const. There may be some types that won’t translate into very useful JS values but then I can’t see any good reason you’d make one a const.
At runtime will give me a RezList with id cadet_pool that contains 100 fully initialized copies of the T_officer template (in my example they will all have names).
It updates Rez to the latest Elixir and OTP28 as well as bringing a number of dependencies up to date. It also fixes a few bugs. But the main change is the addition of the @pragma compiler directive.
This represents something I have wanted for a long time. There are a things hard-coded in the compiler that shouldn’t be and that offer no simple way to configure. So @pragma allow you to run an author written Lua script as part of the compilation process.
That is to say, a pragma has access to the AST at various points in the compilation process. It can examine & modify it, including adding new nodes (for example adding synthesised assets).
Here’s the first application:
By adding:
@pragma(after_process_ast) source_explorer
in my game file, it runs a Lua script (installed in pragmas/source_explorer.lua) after the compiler has processed the AST nodes but before it outputs anything.
The script generates a source explorer mini-site from the compiled game sources and adds it to the dist folder for distribution with the rest of the game.
This uses the Lua & Luerl libraries. A remarkable technical achievement that provide a complete implementation of Lua in Erlang.
Another use I have for this is that Rez uses Javascript for writing event handlers and actions. Ideally there would be a way to lint that JS code and look for problems.
But this is something I was in two minds about building into the compiler itself. For example is it something you would want to run every time? Is there only one tool for linting?
Now I can write a lint-pragma that can be turned on or off and use different lint tools/configurations without re-releasing Rez itself.
I’ve released v1.8.9 which improves the @pragma directive.
I’ve improved the PluginAPI that Lua @pragma scripts use to interact with the AST and file-system. One thing this will allow me to do is replace the clunky Ruby script I was using for processing Heroicon assets with a @pragma that will be run as part of the asset pipeline.
I’ve also rethought how assets get packaged. In the past all assets were flattened into the output dist folder. Now paths are preserved. It was a relatively easy change to make and avoids the obvious lurking problem of two identically named asset files.
@const values can now appear in ${…} template expressions.
More nerdy stuff. That clunky Ruby script that I had to remember to run, has turned into this Lua script (in pragmas/icons.lua):
-- receives 'compilation' and 'values'
-- values[1] is the path where the Heroicon .svg files should be
-- the plugin will create an inlined asset for each file
-- arrow-path.svg becomes icon_arrow_path
-- whose content attribute is the SVG source
function printTable(t, indent)
indent = indent or 0
for k, v in pairs(t) do
local prefix = string.rep(" ", indent) .. tostring(k) .. ": "
if type(v) == "table" then
print(prefix)
printTable(v, indent + 1)
else
print(prefix .. tostring(v))
end
end
end
function clean_filename(filename)
filename = filename:gsub("-", "_")
filename = filename:gsub("%.svg$", "")
return filename
end
function make_icon_asset(id, path)
local asset = rez.asset.make(id, path)
asset = rez.node.set_attr_value(asset, "$inline", "boolean", true)
return asset;
end
do
local icons_path = values[1]
local files, err = rez.plugin.ls(icons_path)
if files then
for i, icon_file in ipairs(files) do
local icon_name = clean_filename(icon_file)
local asset_id = "icon_" .. icon_name
local icon_path = icons_path .. "/" .. icon_file
local icon_asset = make_icon_asset(asset_id, icon_path)
compilation = rez.compilation.add_content(compilation, icon_asset)
end
end
return compilation
end
Then, by adding the following to my .rez source file:
the compiler will automatically convert every icon file in assets/img/icons into an asset with the file content inlined into a content attribute. For example assets/img/icons/arrow-path.svg is turned into an asset with id icon_arrow_path.
No need to manually remember to run scripts or create and manage @asset definitions manually. Make me happy.