Rez is a language and compiler for creating “hypermedia games”.
At its simplest, this means games implemented using HTML, Javascript, CSS, images, audio, and movies, where the primary way the player interacts with the game is by clicking on links.
There is a substantial overlap with ‘interactive fiction‘, a broad genre encompassing visual novels and games. However, sometimes the games will be played by typing instructions rather than clicking links. Zork is an early example of a hugely popular IF game. These were often called “adventure games”. Twine and Inform are popular tools for creating interactive fiction.
Rez is designed to sit between Twine and Inform in terms of complexity. It makes it easy to do things that would be a lot of work in Twine, but its bounded nature and event callbacks (written in Javascript) likely make it more accessible than Inform.
You write Rez in source code form. Here’s an illustrative example to give you a flavour of what a Rez game could look like:
@game begin
name: "The Maltese Parrot"
author: "Matt Mower"
IFID: "D2050DE2-97A2-1ED1-4CCA-AF9D3B0DD883"
created: "2022-08-31 22:13:43.830755Z"
version: 1
initial_scene: #meet_sam_spade
@actor player begin
name: "Miss ..."
intuition: 12
deceit: 15
playfulness: 17
has_parrot: false
container: #player_carrying
end
@actor sam_spade begin
height: 180
eyes: "piercing, yellow grey"
intuition: 17
gunplay: 12
callousness: 14
has_parrot: false
container: #sams_stuff
end
@derive :small_item :item
@derive :gun :small_item
@derive :gun :weapon
@derive :big_item :item
@derive :hat :big_item
@derive :hat :clothing
@derive :parrot :big_item
@derive :parrot :treasure
@slot on_head begin
name: "Head"
accepts: :hat
capacity: 1
end
@slot in_valise begin
name: "Valise"
accepts: :big_item
capacity: 2
end
@slot inside_pocket begin
accepts: :small_item
end
@slot in_wallet begin
accepts: :bank_note
end
@inventory sams_stuff begin
slots: [#on_head #inside_pocket #in_wallet #in_valise]
initial_on_head: #brown_fedora
end
@item sams_valise begin
type: :big_item
description: "A valise, obviously well used judging by how battered and frayed it is."
container: #valise_contents
end
@item maltese_parrot begin
type: :parrot
eponymous: true
description: (game, item) => {
if(item.flag("enamelled")) {
return "A parrot, covered in black enamel. Quite who would want to do that to a parrot is beyond you.";
} else {
return "A plain plaster parrot of no obvious value unless you are an ornithophile. You regard it with some disdain.";
}
}
enamelled: true
}
@asset brown_hat_pic begin
file: "brown_hat.jpg"
end
@item brown_fedora begin
type: :hat
description: "brown fedora"
wearable: true
asset: #brown_hat_pic
description: "A Messer wool fedora hat. Classy."
end
@item suspicious_about_player begin
type: :concept
flavour_text: "Sam raises an eyebrow just a little"
end
@item heard_of_parrot begin
type: :concept
flavour_text: "Ah yes, **that** parrot. Very valuable so they say."
end
@slot known_concept begin
name: "Concept"
accepts: :concept
capacity: 999
end
@inventory sam_knows begin
description: "Things that Sam knows about"
slots: [#known_concept]
end
@scene meet_sam_spade begin
layout_mode: :continuous
initial_card: #sam_at_his_desk
location: #sams_office
blocks: [#sidebar]
layout: """
<div class="sidebar">{{sidebar}}</div>
<div class="main">{{content}}</div>
"""
on_enter: (game, event) => {
const sam = $("sam_spade");
const intuition = sam.getAttribute("intuition");
if(roll("d20") < intuition) {
const sam_knows = $("sam_knows");
const idea = $("suspicous_about_player");
sam_knows.addForSlot("concepts", idea);
}
}
end
@card sam_at_his_desk begin
bindings: [#player]
player_named: false
content: """
You enter the room to see a man, probably 6', with pale brown, almost blonde hair. He looks up with yellowish grey eyes that don't miss a beat.
"Come in miss...", he says to you.
[[* say_name]] [[Enter the Office]] [[>Turn Around|this_didnt_happen_in_the_movie]]
"""
say_name: (choice) => {
if(choice.card.flag("named")) {
choice.hide();
} else {
const player = $("player");
const playfullness = player.getAttribute("playfullness");
if(roll("d20") < playfullness) {
choice.allow("Make up a name", lie_to_sam);
} else {
choice.allow("I am Miss...", tell_sam_name);
}
}
}
end
@card tell_sam_name begin
content: """
You tell Sam your name
<form rez-live name="player_name">
<input rez-live type="text" name="edit_name" />
</form>
"""
on_edit_name: (game, event) => {
$("player").setAttribute("name", "Miss " + event.target.value);
$("player").setAttribute("named", true);
}
on_player_name: (game, event) => {
game.currentScene().playCard("sam_at_his_desk");
}
end
@list made_up_names begin
values: [
"Cairo",
"Gutman",
"Wilmer"
]
end
@card lie_to_sam begin
bindings: [#player]
suspicion_text: ""
on_render: (game, card) => {
const list = $("made_up_names");
const name = list.randomElement();
$("player").setAttribute("name", "Miss " + name);
$("player").setAttribute("named", true);
const sam_knows = $("sam_knows");
if(sam_knows.itemInSlot("suspicious_of_player", "known_concept")) {
card.setAttribute("suspicion_text", "Does he raise an eyebrow just a fraction when you say that?");
}
}
content: """
You're not quite ready to share that yet so you make up a name. I'm {{$ player "name"}} you tell him. {{$ this "suspicion_text"}}
[[Enter the Office]] [[>Turn Around|this_didnt_happen_in_the_movie]]
"""
end
@card enter_the_office begin
...
end
@scene this_didnt_happen_in_the_movie begin
...
end
...
end
A game is described in terms of elements like actor
, item
, inventory
, card
, scene
, faction
, effect
, location
, and so on.
Most of these are described with attributes. For example, the content
attribute of a card specifies (using Markdown + Handlebars) the HTML that will be rendered when the card is displayed.
Dynamic behaviours are implemented using Javascript callbacks. In this example, when the player enters the scene, #meet_sam_spade
we check Sam’s intuition to see if he is immediately suspicious of us. We also use a dynamic link to decide whether the player will share their real name or use an alias. Many event hooks are available for implementing the dynamic parts of the game.
Think of scenes like scenes in a movie where a specific type of action takes place. Some scenes could be pretty generic, some very specific. Every scene has a layout, and playing cards add content that may replace or append to what content has already been displayed.
To play a game, you compile it. This creates a dist
folder containing the index.html
file and all the Javascript, CSS, and asset files (e.g. images) necessary to play the game. You can then distribute the dist
folder.