Rez v1.3.1 — open source engine for choice-based HTML games

Rez is an open-source game engine for making choice-based HTML games.

My target audience is me: someone frustrated building with Twine and comfortable in Javascript. I had been using Twine+Snowman but… Twine wasn’t really doing very much for me.

Rez uses a mostly declarative syntax. Here is a player @actor:

@actor player {
    name: "The adventurer"
    description: "A bold adventurer"
    class: :m

    container_id: #player_inventory

    fought_boss: false

    initiative: 2
    strength: 8
    toughness: 6
    fighting: 8
    dodge: 0
    wounds: 0
    mana: 0

    gold: 0

    kills: 0
    xp: 0

    spells: []

    class_name: ^v{this.class == "m" ? "Mage" : this.class == "r" ? "Rogue" : "Fighter"}

    weapon: ^p{
      const weapon_id = $player.container.getItemForSlot("weapon_slot");
      return $(weapon_id);
    }

    on_start: (player) => {
      player.container.setItemForSlot("weapon_slot", "dagger");
    }
  }

And here is a @card (roughly equivalent to a passage in Twine):

@card monster_dies {
    bindings: {monster: `scene.monster}
    content: ```
    <div class="box">
     ${monster.name} is dead! <a data-event="card" data-target="loot_the_room">Loot the room</a>.
    </div>
    ```

    on_start: (card) => {
      $player.xp += card.scene.monster.xp;
      $("location").monster_id = "";
    }
  }

Inline Javascript functions are used for dynamic behaviours (e.g. the on_start event) and the built-in template expression language for dynamic content (e.g. ${monster.name}).

Rez has a scene/card layout system that is simple but exceptionally powerful, with a template expression system including dynamic content. It uses Bulma CSS for styling.

Rez has support for inventories & items, factions & relationships, NPC actors, plots, customised event handling, and more.

I’m not much of a one for self-promotion but I’m proud of Rez and what it can do at this point. It’s come along way in the 2 years that I’ve been working on it on-and-off. It’s gotten to the point I can make progress on my own games.

So I’m calling out v1.1 because, while there are still rough edges, it’s ready enough and good enough that I think other authors could make use of it. I’d love to see what people can create with it.

Here are the major changes since the last public release (v0.9) in Nov 2022:

  • Switched from begin/end to {} for block scopes
  • Throwing out Handlebars.js in favour of the built-in Template Expression system.
  • Throwing out Markdown in favour of writing plain HTML
  • Bi-directional data binding from HTML form elements to object attributes
  • Automatic creation of JS object properties from game element attributes
  • Expressive dynamic attribute system
  • Extensive improvements to documentation
  • Events all driven from <a data-event="..."></a> so simple but powerful!
  • @systems now take part in processing events
  • Fixed stack layout for running dialogue/events
  • Embeds Tracery for grammars
  • Added probability tables for procgen
  • Working inventory system

Here’s the Hello World of Rez (source).

My test game is a dungeon crawler (source). It’s just a sliver of a game at the moment, a few rooms strung together, but I’ve been using it to incrementally test Rez’s basic functionality.

I would be really pleased to find even a couple of authors who would try Rez. Even reading the documentation and giving me some feedback would be helpful.

10 Likes

I haven’t looked though the documentation yet, so I can’t comment on the language. I did just play through the demo a couple of times and think I’ve encountered a bug (in the demo, not the language). It looks like the mage character is given a limited number of spells, which I’m assuming the number in parentheses denotes. When my character uses a spell, however, that number does not change and it seems as though I have an unlimited amount. EDIT: Oops, not a bug. See below.

It’s not clear but that’s the number of mana points to cast the spell. It’s not much of a game really, more of a test-bed as I figure out how to make things work. But it has a lot of aspects of a real game.

1 Like

I just realized that when I looked through the source and can confirm that the behavior is as expected. Sorry about that. :sweat_smile:

1 Like

No worries, thanks for taking the time to look and post.

2 Likes

Congrats on your Rez update.

2 Likes

I have updated the demo game to include a simple topic-based conversation system.

Screenshot 2024-01-03 at 19.15.46

Topics are defined as a kind of item and held in a dedicated slot in an actors inventory. I use a card with a $foreach() template expression to render the topics and style them according to whether they have been read or not. Ease to add new available topics to an actor by adding more topic items to the inventory.

Both conversation and combat use a ‘scene interlude’ which allows the author to interrupt one scene (dungeon exploration) with another and then resume the previous scene. This is all pretty much automatic.

<a data-event="interlude" data-target="conversation_scene">Talk to the Imp</a>

Suspends the current scene and starts the scene with id #conversation_scene.

<a data-event="resume">Finish the conversation</a>

Ends the conversation scene and restores the previous scene and its UI.

If you need to send data back to the original scene it’s easy enough to specify other params:

<a data-event="resume" data-turn="1">Finish the conversation</a>

In this case the extra parameter is picked up by the clock system and uses it to advance the turn counter before resuming.

2 Likes

I’ve uploaded a 6m52 introduction video which shows the very basics of Rez, presenting & linking between content.

2 Likes

I have released Rez v1.1.4 which includes a new @timer element. For example:

@timer actor_timer {
  autostart: true
  repeat: true
  interval: 60000
  event: :move_actors
}

You might write an event handler like:

@game {
  on_move_actors: (game, params) => {
    game.forAll("actor").forEach((actor) => {
      actor.moveToNextLocation()
    });

    return {
      render: true
    };
  }
}

Every minute this will send the move_actors event to the game. This could, for example, move actors along a path, and then cause the view to update. So actors would potentially appear, or disappear, from player view as the game runs.

Because it uses the standard Rez event handling the handler will be looked up in the current card, then scene, then finally in the game. So different scenes could potentially handle timers in a different way. You can have as many timers as you like.

I’ve also fixed a number of bugs involving inventories, items, & effects. When you add an item to an inventory with an owner, effects carried by the item get applied, automatically, to the owner (and removed again when the item leaves the inventory). For example:

@effect eff_plus_one_fighting {
  on_apply: (effect, params) => {
    const actor = $t(params["owner_id"], "actor");
    actor.fighting += 1;
  }

  on_remove: (effect, params) => {
    const actor = $t(params["owner_id"], "actor");
    actor.fighting -= 1;
  }
}

@item moon_sword {
  type: :edged_weapon
  magic: false

  effects: [#eff_plus_one_fighting]

  name: "moon silver longsword"
  damage: 1d8+1
}

In this case the moon sword carries an effect which applies +1 to the fighting skill of whichever actor wields it. Different inventory slots can have different rules about whether effects are applied or not. So if the sword is placed in a “main hand” slot it gets applied but if in “backpack” slot it does not.

3 Likes

Because I needed it, Rez 1.1.6 now includes the ability to bind keys to trigger events using a new @keybinding directive.

So, for example, to trigger showing the character sheet when the player presses the c key (my use case):

@keybinding c :show_char_sheet

We can add arbitrary modifiers like ctrl or shift to the key we bind to, e.g.

@keybinding shift+ctrl+C :show_char_sheet

In this case show_char_sheet is a standard event that gets sent to the Rez event dispatcher. For this we’d probably implement it on the @game element, e.g.:

@game {
  @keybinding c :show_char_sheet

  on_show_char_sheet: () => {
    const scene = $("character_sheet");
    return scene.$running ? {resume: true} : {interlude: scene.id};
  }
}

This handler looks to see if the scene character_sheet is already running. If it is not, it instructs Rez to do a ‘scene interlude’ and make the character sheet the current scene (i.e. interrupting scene A, to run scene B, before resuming scene A where it left off), otherwise to resume the previous scene.

KeyBindingExample

4 Likes

One of the drivers behind making Rez was that, working with Twine, I found myself implementing a number of core game concepts in Javascript (I ended up using the Snowman story format because the others just got in my way) but it was difficult to make things hang together consistently. There was no ‘system’.

So, when I set out to build Rez it was to create a “game system” rather than a “story system”. Thus Rez includes out of the box support for:

  • Actors: there’s actually little practical support yet although systems & behaviour trees provide a solid basis for “complex” NPC support.
  • Inventory, Items, & Effects: an incredibly flexible system for creating anything you can conceive of as an “inventory” I’ve used it for memories, dialog topics, spells, as well as, … items. Effects allow the possession of items to change the actor who has them (based on different conditions).
  • Assets: images, sounds, movies or whatever. Rez will manage them, how you refer to them in your game templates, and copying them into the distribution automatically,
  • Factions & Relationships: basic but functional
  • Plots: essentially the plot clock concept from forged in the dark as used in Blades in the Dark and Star Forged
  • Scenes & cards: composable elements of game play & of content. Scenes group cards with a configurable ux
  • Events & Systems: everything is driven by events that game objects respond to in order to modify the “world” the player inhabits.

Originally Rez also included maps & locations. Over time I have removed them. There is a tension between a map & location system and using scenes & cards for content. In the context of a players experience a card becomes ‘their location’ since it is the content they can see.

Now I use a custom object for ‘current location’ and a location attribute in objects that need them so you can figure out what is what. I’m opening to bringing back locations if they can be seen to be pulling their weight.

Something else I have striven for in creating Rez is a logical and consistent model & syntax. The format is quite predictable, most things are just elements with attributes and you can go a fair way with just this. From here the complexity is, I hope, incremental. That is certainly my aspiration.

5 Likes

Fantastic! I’m comparing some of the things i do in Strand to Rez. What’s interesting is both these systems are new-generation. Most people here are using systems designed many years ago. But now it’s the 21st century and time for what I call “New IF”.

New IF is the throwing away design-wise of all the dross and baggage that was necessary before due to system constraints. And have a fresh sheet of paper to design with.

Obligatory picture:

For example;

I also discovered “locations” are bogus. Strand is a little different from Rez because it also supports a world model for the purposes of scope. This is because it still has a parser for people that like it. Although I’ve discovered, once sensible choices are on offer, most of those people don’t want to type anymore. But hey.

So locations;

my model is generalised in terms of simple containership. A thing can be in a thing (or actually more than one!). So the “current location” is the thing the player is in. the “inventory” is the things that are in the player. and so on.

Containership gives rise to a simple scope model; all things recursively in the thing the player is in.

The runtime has no special knowledge of locations as a distinct type.

3 Likes

This is very interesting, thanks for sharing. I agree with your characterisation in terms of what new technology enables and that the limits are no longer how much memory or how fast the processor.

I guess I see your long-form/short-form rather as low-interactivity/high-interactivity. And what I mean by interactivity is the degree to which choices can have impact upon the game state. In a visual novel interactivity is about reading pace. To that extent I wouldn’t lump it in with CYOA which has high-interactivity from that perspective. I’m working on things at the simulation end of the spectrum where small changes in state can have big repercussions later on.

Where I can read more about Strand and your design goals? And thank you, this is exactly the kind of conversation I was hoping to have!

2 Likes

Your point about “pace” is what i call game “cadence”. Agreed that most CYOA are high-cadence but some are still long-form.

Generally i think people want more interactivity per text-output. Ie the opposite to walls of text. I put visual novels as high-cadence because they often tend to be like comics with short form dialog between inputs.

Strand. yes will dig up.

1 Like

Here is an overview of the Design of Strand. Goals are a bit different from Rez;

The Design of Strand

Intro

The Strand IF system was invented to fill an emerging gap between classic IF and New IF. New IF is a modern and increasingly innovative form of flow based narrative, where the story takes the lead and is supported collectively by text input, art and choices and clicks - both on text and pictures.

Casting this into the nomenclature of classic IF, we can say that Strand supports parser-input, choice-input as well as point-and-click, but yet it is not limited to either.

Design Goal

It was never a requirement that Strand was to be “better” or “superior” to existing IF systems. Indeed, Strand’s original mission was to achieve the, so called, 90% rule:

Do 90% of what games want really well and leave the remaining 10% to game design.

The motivation for this goal was the supposition that 9/10 features could be made simply and easy to author whilst the annoying and complicated 10% could be isolated from poisoning the main 90%.

In other words, the central goal of Strand was ease of authoring and therefore its corollary, rapid development.

Strategy

Ease of authoring was to be achieved by inventing a non-technical domain specific language (DSL). It was important that this language did not resemble programming with its inevitable collection of weird symbols and annoying syntax.

Because, whether you’re a programmer or not, this is still a development impediment.

How nice it would be to “bosh-out” whole swathes of gameplay without programming; without variables, loops, if-then-else statements and all the other rubbish programming languages force on you.

But how?

Following the 9/10 idea, it was envisaged that the DSL would handle the 90% without programming and the remaining 10% would be relegated to a companion, traditional programming language.

Because, for example, if someone wanted to have a chess game coded inside their game, it would clearly require a general programming system to be implemented. Such a thing would be deemed to fall into the 10% and thus be implemented outside of the DSL.

The strand IF system would therefore consist of a complementary blend of two languages; a DSL, called Strand and a system language called KL.

Models

However, it transpires that the DSL is, in fact, quite capable of implementing well over the 90% originally envisaged. In practice, more like 99%.

The difference between a flow-based paradigm and a rules-based one is that combining flow blocks gives rise to models.

A model is group of flow blocks working together to implement a specific behaviour. Rules can do the same thing, but a model is more cohesive and can also have context.

Models are implemented in the DSL and factored into a “core” package. It is then very easy to build standard IF behaviour just as if these models were actually part of the runtime system.

Standard IF concepts such as doors, locations, gettables and so on are not part of the system runtime at all, but are in fact just DSL that can be further modified and extended by authors.

Really?

Indeed. Here is the implementation of get-ability. No special core logic is needed.

GETTABLE@ THING
* be it
IT
* get it
DOGET
* drop it
DODROP

DOGET > put it in PLAYER
* ok
You get IT.
*
You can't get IT.

DODROP > put it in here
* ok
You drop IT.
*
You can't do that.

How about inventory. Nope this is just a bit of DSL. Also handling clothing.

INV
INV1 INV2

YOURSTUFF > what is in PLAYER
*
> be LAST

YOURCLOTHES > what is on me
*
> be LAST

INV1 YOURSTUFF
* them
You are carrying, LAST.
*
You are empty handed.

INV2 YOURCLOTHES
* them
You are wearing LAST.

The same is true of location, clothing, doors, containers and so on. There is no need for any of these traditional IF concepts to be implemented by the runtime system.

4 Likes

I confess I don’t quite understand how your system works but it’s interesting to see someone take a very different approach to the problem.

I freely confess that Rez is in the “collection of weird symbols and annoying syntax” camp. I guess I’ve been a programmer for over 40 years and Rez is designed to suit me, first and foremost, so it was probably inevitable.

That said Rez is largely declarative with a fairly regular syntax. I’ve tried to make it as clean as possible. Dynamic behaviours are implemented with Javascript so there’s probably no way of avoiding that. But, even here, I’ve made the use of pluggable functions the norm. But there’s no getting away from it.

My target audience is people like me. Someone with at least some programming experience, with an ambitious choice-based game project, and finding that Twine’s constraints aren’t liberating.

I’ll be interested to see a game made with Strand. Maybe one day there will be one made with Rez! :smiley:

2 Likes

I’ve published a new website for Rez.

The previous site was Wordpress. I hated it and, so, did nothing with it. The new site is built using the static site generator Hugo and I’m slowly getting to grips with it.

At the very least it now hosts & links to my own documentation & the generated API documentation, although the latter doesn’t work properly yet.

1 Like

Looks good.

What? Your dad had a PDP?

1 Like

Indeed, from memory, a PDP-11. Playing games is about the only thing I remember about it :slight_smile:

1 Like

Picton Files: The Peter & Paul Case
Picton Files: Murder at the Manor
Solaris
The Tin Mug

2 Likes