Rez 0.9: the Behaviour Tree release

At the end of October, I released v0.8 of Rez, a game authoring system I built for myself to replace my use of Twine. Today I have published v0.9.

Originally meant to be a “quick & dirty” replacement for Twine, over a year, it has become a complete system for building HTML games. It has built-in support for things I wanted, like flexible items & inventories, NPC actors, and a scene & card based rendering system. Rez is declarative, with Javascript callbacks providing dynamic behaviour.

However, for my own use, it was missing a key component: behaviour trees. I hate trying to implement behaviour in a programming language (esp. one like Javascript). I have used behaviour trees before, so I ported a behaviour tree library I wrote for Clojure and here we are.

Rez now natively supports behaviour trees as an attribute type and provides a helpful selection of tasks in the standard library (composites & decorators) and a @task element to define custom condition tasks & action tasks yourself (see stdlib source for how the core tasks are implemented).

The result: you can write something like:

@actor sam_spade begin
   ...
   behaviours: ^[SELECT [
      [SEQUENCE [
        [ACTOR_IN location=sams_office]
        [ACTOR_SEES item=booze]
        [MAYBE p=20 [ACTOR_DRINKS]]]
      [SEQUENCE [
        [ACTOR_SEES actor=wilmer]
        [ACTOR_RELATIONSHIP actor=wilmer rel=antagonist]
        [ACTOR_HAS item=handgun]
        [SELECT [
          [SEQUENCE [
            [ACTOR_HAS tag=drunk]
            [ACTOR_SHOOTS_AND_MISSES]]]
          [MAYBE p=20 [
            [ACTOR_SHOOTS target=wilmer item=handgun]]
      [SEQUENCE [
        [ACTOR_SEES tagged=dame]
        [RANDOM_EACH [
          [ACTOR_SAYS line="You know, that's good, because if you actually were as innocent as you pretend to be, we'd never get anywhere"]
          [ACTOR_SAYS line="We didn't exactly believe your story, Miss. We believed your 200 dollars"]
          [ACTOR_SAYS line="You won't need much of anybody's help. You're good. Chiefly your eyes, I think, and that throb you get in your voice when you say things like "Be generous, Mr. Spade"]]]]]
@end

Okay, very simple and silly example, but hopefully illustrative of what is possible. SEQUENCE, SELECT, MAYBE, and RANDOM_EACH are core tasks from the standard library. The author of this game would implement condition tasks like ACTOR_IN and ACTOR_SEES and action tasks such as ACTOR_DRINKS, ACTOR_SAYS, and so on.

With the addition of behaviour trees, Rez is now conceptually complete, although there are still a lot of holes in my implementation of some of those concepts. I plan to fill those in as I switch my focus from tooling to game development.

I realise there are already a lot of authoring systems out there, but if anyone fancies trying Rez drop me a line. The source is all available, but I guess it might require my help to set it up.

10 Likes

Very nice. I hope to give this a try. Thank you.

I will be trying it on an RPi 4 using RaspiOS. (A derivative of Debian for the Arm Processor.)

1 Like

I’d be interested to know if that would work. I think you can get Erlang working on the Pi, so who knows… please report back either way :slight_smile:

1 Like

Will do. I am going to try tonight or tomorrow.

Thanks.

I game it a shot. I installed all of the dependencies. The Elixir version is only 1.10.13 in the RPi repositories. I changed the dependency in the dependency in the mix.exs file. I had to execute mix dep.git to install the Hex package.

It seemed to compile with several warnings until it got to the Poison section and then failed to compile

I looked at the Elixir website. I may try to manually install the proper version of Elixir tomorrow.

PS. There is an install procedure for Elixir 1.13 on the Elixir page. That may do the trick. More tomorrow.

If that’s Elixir 1.10.13, then I would be surprised if it would work; that’s pretty old.

Although I target 1.14 now, most of the development was done on 1.13, which should work.

M.

2 Likes

1.14 is available. I will try that later today.

PS. Still no luck. Tried to manually install the recent versions of Elixir and Erlang. Multiple dependencies that cannot be overcome with RaspiOS.

Maybe RaspberryPi.org will update their OS soon or I may try to do an install with Ubuntu. ?

Alas, I know nothing about RaspiOS, so I can be of little help to you in getting Erlang or Elixir to work. So far as I am aware, you should have no problems with Ubuntu.

1 Like

I am going to give it a try. Just have to get an SD card to set it up.

I’ve updated some dependencies and tested my own installation instructions in the updated README. The only macOS dependency I make now is on Homebrew to install ASDF. So as long as you can install ASDF on your platform, I think it should work. But I think a lot of things that don’t turn out to be true :slight_smile:

1 Like

As I’ve begun working on my two games, I’ve fleshed out some areas of Rez that either weren’t there or didn’t work. In v0.9.2

1. I’ve added @rel that established relationships between entities, e.g.

@rel begin
  source: #ronin_faction
  target: #player_faction
  affinity: -0.5
end

Affinity scores range from -5.0 (hate) to +5.0 (love), and relationships can be tagged to add further options. In this case, the ‘ronin faction’ starts out mildly disliking the ‘player faction’, but they don’t hate them and won’t take negative actions towards them, but they won’t help them either. When that starts to hit -2.0, things will get worse.

In this case, I’ve set up a relationship between two factions so that I don’t have to set up separate actor->actor relationships. But I could do that if it made sense. Or do both so that a specific relationship could override a faction-based relationship (could be helpful in a game based on Romeo & Juliet, for example).

2. Assets and Groups (dynamic sets of assets) now work. So, for example, I have 100 background images tagged #background_image and a group background_group with include_tags: #{:background_image}:

const group = $("background_group");
const asset_id = group.randomAssetId();
const asset = $(asset_id);
return asset.tag();

This code will give me the <img src='...' /> tag for a background image chosen randomly (without replacement).

3. The Handlebars helpers that were included in the runtime have been turned into an element @helper with the existing helpers moved into the standard library:

For example, the old r_asset helper is now called atag:

@helper ASSET_TAG_HELPER begin
  %% The $atag helper returns a tag that embeds the file (be it image, movie,
  %% or audio) referenced by the asset.

  name: "atag"
  args: ["asset_id"]
  handler: (asset_id) => {
    const asset = $(asset_id);
    if(asset.game_object_type != "asset") {
      throw "Attempt to retrieve asset with id '" + asset_id + "' but found '" + asset.game_object_type + "'!";
    } else {
      return Handlebars.SafeString(asset.tag());
    }
  }
end

The author can now easily create their own helpers, although, at present, it’s not easy to override the built-in helpers. I may switch from Handlebars at some point, but it’s good enough right now.

4. Added the @object element.

The object element is an escape hatch allowing the author to define their own things for which there isn’t a built-in concept. This can be useful along with @alias so you can do the following:

@alias skill = object # begin
  template: true
  description: "Something an actor has learned how to do"
  min: 0
  max: 5
  level: 0
end

@skill fisticuffs begin
  name: "Fisticuffs"
  type: :martial
  description: "Marquis of Queensbury be damned, hit 'em where it hurts."
end

Rez knows nothing about skills, but presumably, your custom code & behaviour tasks will.

I added this late in the day because I hadn’t yet decided where to draw the line. I have actors and items and all sorts that have pre-defined behaviours.

But as I’ve been playing with my own designs, I feel like I have a reasonable balance between built-in but flexible base concepts and now want to be able to add game-specific concepts.

There are many ways to do things like skills, classes, perks, jobs, etc.; how they play out is likely to be very game-design-specific. So now it’s time to add @object and allow that process to start. There may be changes here as I learn what works and doesn’t.

5. Numerous big-fixes.

Of course, now I try to use it in anger, I’m finding all the problems and rough patches!

2 Likes

I’ve merged your topics together because I don’t think we need a new topic for each incremental update. My rule of thumb would be only to start a new topic for really major updates (a 1.0 release, or if it supports a different type of game (like adding parser support), or if it supports a new way of publishing games, new OSes etc), or if it’s been 6+ months since the last update. (Though even then it’s not a problem to update the older topic!) Thank you for understanding.

No worries. I posted in a different place because I wasn’t sure I’d put the first post in the right place (in tech rather than dev systems). Thanks. M

1 Like

should the relations span [-1,1], then you’d scale by 0.5 in your calculation?
Just so it’s a bit easier to author.

I could have used [-1, 1], and in some ways, it would be more natural.

But it didn’t seem likely authors would be applying the kind of function where that domain would matter to an affinity score. By contrast, I felt that [-5, 5] felt somehow more expressive.

If you have particular arguments for [-1, 1] I’d be pleased to hear them. It’s hardly set in stone.

Apologies if I’m not following this too closely, but this is something I can weigh in on.

Mathematically, there are a lot more convenient and easy formulas for scaling, curving, and measuring things when continuous values span between [-1, 1]

For x, where it spans [-1, 1], x*x gives a bias curve toward zero, without causing the maximum value to go beyond the limits of the span.

If z=237 out of a maximum of 255, then you simply need to do x=z/255 to get a gradient between [0, 1].

If you want to distribute x across, say, 7 modes, you simple do modeIndex=x*7.

It’s less cognitively intuitive than [-5,5], but it’s more mathematically simple and useful.

2 Likes

Hi Joey. Thanks, you’re quite right; a range of functions works well on a domain of [-1,1].

What was in my mind was whether the likelihood of anyone using them on an affinity score outweighs what I felt was, as you put it, a more cognitively intuitive range.

If someone did, scaling between [-5,5] and [-1,1] isn’t very difficult. But at the same time, I don’t want to make life unnecessarily more complicated.

1 Like

An insanely heavy work schedule means I’ve had little time to work on Rez or my game(s) but I have been chipping away at both.

I’ve just pushed a new version of Rez which adds the @generator element as an affordance for procedural generation:

@actor street_urchin begin
  $template: true

  name: &{rand_name()}
  pick_pocket: &{Math.rand_int(1,5)}
  sneak: &{Math.rand_int(1,5)}
  fast_talk: &{Math.rand_int(1, 5)}
  strength: &{Math.clrandr_int(1,6)}
  hp: &{Math.rand_int(2,4)}
  hungry: 1
  dirty: 2
end

@generator urchin_gang begin
  source: #street_urchin
  copies: &{Math.clrandr_int(4, 20)}
end

At run-time this will create a number of copies of the #street_urchin object which will get their dynamic attributes assigned when they are copied so each will have unique values. These objects will be put into a RezList so they can be accessed like:

const urchin = $("street_urchins").randomElement();
urchin.getAttribute("name");

Since the 0.9 release I’ve also:

  • moved almost all the stuff that was hardcoded into the app into the stdlib.rez
  • now have a @patch & @helper elements to add to the JS stdlib & Handlebars
  • added js_ctor to allow overriding the JS object used as a prototype
  • added an @object element that allows authors to define custom things
  • separated the view/layout code from the scene/card code
  • added a range of useful JS functions, particulary for generating different types of random values (e.g. central limit randomization)

The dynamic initializers entirely replace &attr attribute references. The old way was to do:

name_gen: () => {return randomName();}
name: &name_gen

which is now:

name: &{randomName()}

The $template attribute is used to suppress dynamic initalizers on objects meant for copying.

Things I want to do before calling it a 1.0 are look again at using immutable data and designing a proper event system a la re-frame.

4 Likes

I’ve also sorted out “inheritance”. You can now specify one or more objects as a template for another, so:

@item ring begin
  $template: true
  is_a: :ring
  wearable: true
  usable: false
  magical: false
  description: “a non descript ring”
end

@item curious_ring<ring> begin
  magical: true
  belongs_to: #the_big_guy
  description: “a plain golden band inscribed ‘property of Mordor’ probably more trouble than it’s worth to keep it.’
end

this replaces and enhances what was possible with aliases. The problem with aliases is that they weren’t composable. It’s also possible to specify more than one ‘parent’ (still pondering the terminology) which allows for behaviour components to be shared between items.

I’ve converted aliases into a short-hand so you can write:

@alias magic_ring = item<ring, magic_item>

@magic_ring the_troublemaker begin
  …
end

It’s a small affordance and I will need to see if it proves useful still or if I drop aliases.

This new way of specifying common attributes has already benefited my item and actor generation so overall I think was worth the hassle.

I may, somewhat to my surprise, be done with the syntax part. At least modulo outside feedback.

The standard library has grown but that’s slowing down now and more and more stuff is going into the extlib I am building alongside the game (mainly game-related behaviours).

The main issue I have with Rez now is that the runtime is a mess of Javascript objects and prototype inheritance which I don’t care for. I’ve tried to make it as clean as I can but it’s objects with mutable state all the way down. Implementing back/undo is going to be a PITA (I could abuse the save system under the hood I suppose).

I’m thinking about adopting immutable data structures (this means the entire game state moving into a big world map). If I was working in ClojureScript that would be fine & natural but I’m concerned it won’t be in Vanilla(ish) JS.

Big pluses would be a natural approach to event handling, less bugs, easy state management, clean separation of behaviour into modules that work with data & more open to extension.

Okay, probably mused we’ll past anyone’s caring already…

3 Likes

That is how I did it for QuestJS.

The problem is deciding what can change and what cannot. Sure there are not many authors will want to modify the location description during play, but inevitably one will. What about changing the behaviour of an exit? So do you have a way for authors to flag it can change? What if they forget?

So… good luck.

3 Likes