Rez v1.7.0 — open source tool for creating non-linear games/IF

Another little component example:

I’m kind of experimenting with this style which I’ve seen in other games.

The <.dialog> component uses a couple of attributes dialog_tag and dialog_color, added to the actors, as well as their existing name attribute so it can style & attribute the dialog without a lot of noise:

<.dialog char={player}>Can I help you?</.dialog>
<.dialog char={gangster}>Where's the cash?</.dialog>

Looks pretty neat to my eye.

@styles {
  .dialog-box {
    margin-bottom: 1.5rem;
  }

  .speaker-name {
    display: inline-block;
    background: #485fc7;
    color: white;
    padding: 0.5rem 1rem;
    border-radius: 4px 4px 0 0;
    margin-bottom: 0;
  }

  .dialog-content {
    background: #f5f5f5;
    border: 1px solid #dbdbdb;
    border-radius: 0 4px 4px 4px;
    padding: 1.25rem;
  }
}

@component dialog (bindings, assigns, content) => {
  const char = assigns["char"];
  const tag = char.dialog_tag ? ` (${char.dialog_tag})` : "";
  const color = char.dialog_color || "#485fc7";
  return `<div class="dialog-box"><p class="speaker-name is-size-5" style="background: ${color}">${char.name}${tag}</p><div class="dialog-content">${content}</div></div>`;
}

@card c_player_speaks {
  bindings: [
    player: #player
    gangster: #mob_boss
  ]
  content: ```
  <.dialog char={player}>Can I help you?</.dialog>
  <.dialog char={gangster}>Where's the cash?</.dialog>
  ```
}

@player {
  dialog_color: "#485fc7"
  dialog_tag: "You"
}

@actor mob_boss {
  dialog_color: "#c3195d"
  dialog_tag: "Mob Boss"
}

As an aside this use of components makes use of the attr={expr} syntax for passing dynamic expressions to components that I totally pinched from Phoenix LiveView.

1 Like

I’ve just released v1.6.10.

It removes the Heredoc and Tracery grammar attribute types. In practice they were never used. It also removes the bundled tracery.js.

But the main change is that user components can now accept arbitrary expressions for attributes. For example:

<.buy cost={2+2} />

Would pass an assigns map of {cost: 4} to the buy component. All in-scope bindings are also available. So

@card c_test {
  bindings: [
    player: #player
  ]
  content: ```
  <.say actor={player}>I win!</.say>
  ```
}

Works as expected.

I worry for your cormorant …

1 Like

I decided I wanted to use some of the Heroicons in my game. I wrote a component that references an icon and makes it a clickable event generator:

@component icon (bindings, assigns, content) => {
  const icon_name = assigns["icon"];
  const event = assigns["event"];
  const svg = $heroicons.getAttributeValue(icon_name);

  if(event == undefined) {
    return `<span class="heroicon">${svg}</span>`;
  } else {
    // When there is an event, process all other assigns as data attributes
    let dataAttributes = '';

    // Iterate through assigns and convert them to data attributes
    // Skip the 'icon' and 'event' attributes as they're handled specially
    for (const [key, value] of Object.entries(assigns)) {
      if (key !== 'icon' && key !== 'event') {
        dataAttributes += ` data-${key}="${value}"`;
      }
    }

    // Create the anchor element with the event and all other data attributes
    return `<span class="heroicon"><a data-event="${event}"${dataAttributes}>${svg}</a></span>`;
  }
}

So now I can write the following:

<.icon name="arrow-path" event="randomize" />

and I get a nice clickable icon!

Screenshot 2025-03-12 at 17.47.21

Works really well but the component depends on this object that contains the SVG for the icons I want to use:

@object heroicons {
  $global: true

  arrow_path: ```
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
    <path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
  </svg>
  ```
}

I mean, it’s not terrible but I don’t love it. The Heroicons download has SVG files for each icon and I have the @asset tag. Can I do better?

Well I made a small change to the Rez compiler introducing a new system attribute $inline for @asset tags. When $inline is true:

@asset heroicon_arrow_path {
  $inline: true
  file_name: "arrow_path.svg"
}

Instead of copying the asset file into the dist/assets folder as normal the Rez compiler reads the asset content directly into a content string attribute so you can access it via:

$("heroicon_arrow_path").content

I put arrow-path.svg into the assets/icons folder of my game and the <.icon> component now looks for the related @asset with its inlined SVG content.

I still have to manually create an @asset element for each icons that I want but it’s not too onerous and maybe I can find a better way in the future.

1 Like

Actually the “something better” turned out to be super simple. I wrote a Ruby script, compile_assets that I use as follows:

./compile_assets assets/icons --prefix heroicons --inline > src/heroicon_assets.rez

The script automatically generates the @asset definitions for each file in the assets/icons folder, I just redirect the output to src/heroicons_assets.rez:

@asset heroicons_arrow_path_rounded_square {
  $inline: true
  file_name: "arrow-path-rounded-square.svg"
}

@asset heroicons_arrow_path {
  $inline: true
  file_name: "arrow-path.svg"
}

Now my heroicons.rez library is:

%(heroicon_assets.rez)

@styles {
  .heroicon {
    display: inline-block;
    vertical-align: middle;
    width: 1.5rem;
    height: 1.5rem;
  }
}

@component icon (bindings, assigns, content) => {
  const icon_name = assigns["icon"];
  const icon = $(`heroicon_${icon_name}`);
  const event = assigns["event"];

  if(event == undefined) {
    return `<span class="heroicon">${icon.content}</span>`;
  } else {
    // When there is an event, process all other assigns as data attributes
    let dataAttributes = '';

    // Iterate through assigns and convert them to data attributes
    // Skip the 'icon' and 'event' attributes as they're handled specially
    for (const [key, value] of Object.entries(assigns)) {
      if (key !== 'icon' && key !== 'event') {
        dataAttributes += ` data-${key}="${value}"`;
      }
    }

    // Create the anchor element with the event and all other data attributes
    return `<span class="heroicon"><a data-event="${event}"${dataAttributes}>${icon.content}</a></span>`;
  }
}

If I want to use another icon I copy its SVG file into the assets/icons folder and re-run the compile_assets script. Simples!

Here’s my script:
compile_assets.txt (2.1 KB)

2 Likes

My adventures with Twine taught me that Rez needed to have 1st class asset management baked in. It’s not like you can’t use assets in Twine or even that it’s that difficult — but it felt like kind of an afterthought.

That’s why, from the beginning, Rez has had the @asset element. Simply writing:

@asset heroicon_arrow_path {
  file_name: "arrow_path.svg"
}

Does 3 things:

  • It locates the asset where it is under the project assets folder and reports an error if it can’t be found. You can structure project assets any way you like and it will find it. The corollary is that assets have to have a unique file name.
  • When the game is compiled all referenced non-inlined assets are copied into the dist/assets folder ready to be distributed with the game. They’re never missing or out of date. (Inlined assets have their contents read into a content attribute).
  • Ensures that, while you can always refer to an asset file as /assets/file_name.ext (since you know its name is unique, and all assets are packed into the assets folder), you can also use the $dist_path attribute and not concern yourself with hand-coding paths at all.

For example:

@asset canaletto_regata_1 {
  file_name: 'canaletto_venice_a_regatta_on_the_grand_canal.jpg'
  width: 5888
  height: 3760
}

@component img (bindings, assigns, content) => {
  const asset = $(assigns["name"]);
  const path = asset.$dist_path;
  const w = asset.width;
  const h = asset.height;
  return `<img src="${path}" width="${w}" height="${h}" />`;
}

@card c_card {
  content: ```
    While in Venice…
    <.img name="canaletto_regata_1" />
  ```
}

I should probably put that component into the stdlib as it’s so handy.

At the moment adding width & height for visual assets is manual but I’m thinking about integrating ExImageInfo and letting the compiler embed the metadata.

2 Likes

I’m a little rusty with the way things work now, but at one time I found it handy to have higher resolution images displaying at smaller fixed dimensions. It allowed scaling of the interface/content while displaying the highest quality possible (as opposed to stretching and blurring the image). With high ppi displays, I think this is more useful now than ever.

I suppose content could have a CSS style that overrides the inherent pixel dimensions. Anyway, I think what you are doing to handle external media is very much needed. This is a great addition! :slight_smile:

1 Like

Even if I did implement such a feature it wouldn’t preclude you still setting the width and height attributes to whatever you want.

Another approach would be the same as I used for the Heroicons, a script to generate the @asset definitions. It could start with built-in info and then you just customise it. I guess one wrinkle with that approach might be that your custom definitions could get overwritten when you re-run the script. Probably it would need to be sophisticated enough to do the right thing under those circumstances.

1 Like

One small change I have made is to introduce a little bit of sugar so that instead of writing:

@asset blah_blah {
  file_name: "blah_blah.png"
}

you can write:

@auto_asset {
  file_name: "blah_blah.png"
}

and it will automatically assign an id of asset_blah_blah.

That is it automatically assigns an id which is base name of the asset file with an asset_ prefix.

This will break down in a case where you have asset files that only differ in their extension. At the moment this seems unlikely. Of course I will regret this statement.

1 Like

I’d be up for giving this a try. What does this offer that questJS doesn’t is my big question.

I’m quite strong with my own CSS, so inbuilt CSS is not really a draw.

1 Like

Like it. It’s how to do assets. I also copy into a deployment area. So you get the exact files needed. Also meta. The tool creates a json file of all images metadata. Things like w and h.

The same tool can also create image families. Different resolutions for each image. data for families goes into the meta file. Web clients fetch the meta file at start, then they know what sizes are available. etc.

1 Like

Unfortunately I’m not familiar enough with QuestJS to answer your question. At a glance it appears to be a kind of hybrid parser/choice system. Rez does not use a parser, although it provides the tools to build & use a sophisticated world model typical of parser games.

In terms of CSS, Bulma doesn’t create a lot of constraints so I guess you could probably ignore it. On the other hand it wouldn’t be super hard to suppress it. If it bothers you in practice I will add an option to let you remove Bulma entirely.

3 Likes

I have just published Rez v1.6.14, this release contains 3 main changes:

  • Adds the $inline attribute for @asset tags. When $inline: true the asset file is not copied into dist/ but rather the content is read into the content attribute of the asset. This makes most sense for text assets like SVG files.
  • Adds the <.img> component to the stdlib to generate <img> tags from image @asset declarations. So <.img name="foo"/> generates an appropriate image tag for the @asset with id #foo.
  • Moves the Alpine.JS and Bulma CSS @asset declarations from the stdlib into the new game template. To remove either dependency delete the corresponding @asset declaration from your game file.

Warning to anyone already using Rez, you will need to add the following back into your game source file:

@asset _ALPINE_JS {
  $built_in: true
  file_name: "alpinejs.min.js"
  $js_defer: true
  $pre_runtime: true
}

@asset _BULMA_CSS {
  $built_in: true
  file_name: "bulma.min.css"
}

Or they won’t load.

If you do decide to try Rez, the newly released v1.6.14 lets you opt out of either of the Alpine.JS or Bulma CSS dependencies.

Also I love the visual presentation of your games — amazing work!

2 Likes

So this is more of an twine alternative? Sorry, just trying to figure out what types of games one would make with your engine! Going to give it a try, just want to know what to go in trying to build.

Yes, that’s fair. Twine & Rez are both designed around creating choice-based HTML games.

The documentation is still a work in progress but should help you get a feel for what’s possible.

Within the bounds of the time I have available I am also happy to help you.

I have just released v1.7.0 which introduces undo.

The undo boundary is an incoming browser event including clicks, key presses (where they are bound to an event handler), form submissions, and timers.

Using it is as simple as $game.undo().

I think this is the last feature that you would expect a modern IF system to have which Rez hadn’t yet implemented.

At this point Rez is composed of:

  • 10,418 lines of Elixir (compiler)
  • 2,981 lines of Javascript (runtime)
3 Likes

I’ve released v1.7.1 that has the following changes:

  • no automatic undo context created for timer events
  • by default only keep the last 16 undo contexts
  • better support for manual undo

I didn’t think too clearly about events. For example I made a demo using a timer event to animate physics which would generate 40 undo contexts per second and make undo implausible.

I’m not sure how Undo can work properly in the context of timer events so have delegated this to the author. It’s easy enough if it makes sense:

$game.undoManager.startChange()
1 Like

A question was raised about how much programming knowledge is required to use Rez and it’s a good question.

Setting aside that it’s for making parser games, Inform, appears to come at programming from an “authorial” rather than “developer” perspective. To some extent that seems to make it more approachable and easy to use.

As a professional programmer of 30+ years and more than a dozen languages I find Inform code obtuse. I say this not to denigrate it, but to show my biases. I may not be the best person to answer this question. However, right now, I am probably the only person who can.

There are five classes of things I would call “programming” when working with Rez (and, to some extent, Twine).

  1. Game Design
  2. Template Expressions
  3. Event Handlers
  4. Procedural Generation
  5. Behaviour Trees

The good news is that all but the first is entirely optional and you can learn as your ambitions grow.

Game design is planning out your game in terms of the scenes and cards (a card is analogous to a Twine passage, Twine has no equivalent to a Rez scene) and how they link together. Rez eschews markdown style link syntax for plain <a> tags.

@game {
  initial_scene_id: #scene_one
}

@scene scene_one {
  initial_card_id: #first_card
}

@card first_card {
  content: ```
  <h1>Scene 1 — One</h1>
  <a card="second_card">Next</a>
  ```
}

@card second_card {
  content: ```
  <h2>Scene 1 — Two</h2>
  <a scene="scene_two">Next</a>
  ```
}

@scene scene_two {
  initial_card_id: #third_card
}

@card third_card {
  content: ```
  <h1>Scene 2 — One</h1>
  <a scene="first_scene">Again</a>
  ```
}

The <a card="second_card"> syntax is a short-hand for <a data-event="card" data-target="second_card">. Similarly for <a scene>.

Scenes allow you to create more structure and use a different layout. You can also interlude to a scene and back (I do that for things like character panels, inventory, and dialog).

Template Expressions are how you make dynamic templates that vary content based on game state.

@actor player {
  $global: true   %% makes the id 'player' into a global variable $player
  money: 100
}

@card first_card {
  bindings: [player: #player]
  price: 10
  content: ```
  <p>Money: ${player.money} gp</p>
  <p>Price: ${card.price} gp</p>
  ```
}

Rez supports a number of useful template expressions such as ${}, $if, and $foreach.

It also allows you to build your own HTML components which involve writing a Javascript function to output content. For example a component that changes the colour of a price if it’s more money than the player has:

@component price (bindings, params, content) => {
  const {player} = bindings;
  const {price} = params; 
  if(price > player.money) {
    return `<p class="red">${price} gp</p>`;
  } else {
    return `<p>${price} gp</p>`;
  }
}

@card first_card {
  bindings: [player: #player]
  price: 10
  content: ```
  Price: <.price price={card.amount} />
   ```
}

You don’t have to learn any of this, unless you want to start using it.

Event Handlers let you respond to events. Some Rez generates as it does its work (e.g. there is an event when you change card, or scene, etc…) and some you can specify yourself.

@card first_card {
  price: 10
  content: ```
  <p>Price: ${card.price} <a data-event="buy">Buy</a></p>
  ```
  on_buy: (card, evt) => {
    $player.money -= card.price;
  }
}

I’ll leave procedural generation and behaviour trees for another time as they are certainly more advanced topics.

In general the answer is “not much to do simple things” and a little more as you become more ambitious. The main challenge is probably the state of the documentation. I usually just go straight into the source but that’s probably an ask for others.

The language reference has a lot of information but needs an update too often mixes in advanced topics that make things seem more complicated than they need to be.

The authors guide was an attempt to create something more introductory and gradual and focused on making a game. I’m still mulling chapter 2 and would welcome recommendations for where to put my attention.

4 Likes

I have added a second chapter to the authors guide that explains scenes and layout.

5 Likes