Catalyst
Make your game react!
Stats, resources and effects that are easy to make and easy to modify!
For when you want to apply complex rules to gameplay values in a very simple way!
WHAT IS CATALYST.
I started work on what became Catalyst nearly six years ago, during early work on Alchementalist. And since then, it’s been a core piece of my game development kit. I’ve extended it many, many times, hardened it and made it extremely flexible to suit any genre and any game. From a humble beginning, it has now grown into Catalyst 2, a powerful manager for the numbers in your game!
Catalyst handles how numbers interact in your game, from statistics to resources to effects. The core premise is this: It’s simple to create a damage variable, or a health variable when you’re just starting your game. But over time, stuff gets more complex and a simple variable doesn’t cut it. You might want to add 20% damage but only against specific enemies, or perhaps the player can stack multipliers on their damage based on a chain of crits, or maybe you want a ton of different modifiers that all have different durations. Coding this kind of thing in a haphazard way leads to awkward code that breaks easily and is painful to extend, which limits how much you want to experiment with the game’s design.
Catalyst is intended to remove that pain and make it super simple to come up with some way you want the numbers in your game to react and then simply add it in with only a few lines of code. It allows you to express your game design concepts quickly and easily, and without fear of breaking a fragile system.
And Catalyst code is quite simple:
// Create hp and mp resources
hp = new CatalystResource(100);
mp = new CatalystResource(100);
// Create a strength stat
strength = new CatalystStatistic(1).SetName("Strength");
// Create a +1 modifier to it that lasts for 60 frames and has 3 stacks
var _add_mod = new CatalystModifier(1, eCatMathOps.ADD, 60)
.SetStack(3);
// Create a +25% modifier that is permanent, and add a tag
var _multi_mod = new CatalystModifier(0.25, eCatMathOps.MULTIPLY)
.AddTag("permanent_buff");
// Add the modifiers to the strength stat
strength.AddModifier(_add_mod).AddModifier(_multi_mod);
// Read the value of strength
strength.GetValue();
Catalyst handles everything else.
- For the first 60 frames,
strength.GetValue()returns5. Calculated from:- Base value of
1 - +
1+1+1for 3 stacks of the timed modifier - x
1.25multiplier for the permanent multi mod - Current value =
(1 + 1 + 1 + 1) * 1.25=5
- Base value of
- After the 60 frames have elapsed, the timed modifier will automatically expire and
strength.GetValue()will return1.25. Calculated from:- Base value of
1 - x
1.25multiplier for the permanent multi mod - Current value =
1 * 1.25=1.25
- Base value of
Catalyst handles all the backend for you and makes this deterministic and bug free, while also giving you a lot of options to adjust exactly how it behaves. Want to have the multiplier applied before the addition? As simple as strength.SetModifierOrder(eCatModifierOrder.MULTIPLY_FIRST). Want to remove the permanent mod? strength.DestroyModifiersByTag("permanent_buff"). And so on. Easy to set up, easy to extend and easy to adjust.
STATISTICS.
You’ve already seen some Statistics in the Designer and code above. Catalyst lets you easily create stats like strength, agility, movement speed, crop yield, factory output, whatever your game needs.
And then you can modify them on the fly: add, subtract, multiply their base values, add stacks, give temporary modifications (Catalyst handles the cleanup for you), layer the modifiers (have gear modifications applied before augment modifications), preview modifiers (what will that sword do to the players stats if the player equips it?) and tons more.
In other words, Catalyst lets you easily manipulate stats in a huge variety of ways, meaning you can come up with an idea (“what if I made it so that for every burning enemy surrounding the player, they got a stacking speed boost?”) and then implement that idea with just a few lines of code. Here’s an example of that exact mechanic.
var _heat_rush = new CatalystModifier(0.25, eCatMathOps.ADD)
.SetStackFunc(function(_stat, _facts) {
return _facts.Get("burning_enemy_count");
});
speed.AddModifier(_heat_rush);
We’ve seen statistics and modifiers already in the code blocks above, and by composing them in smart ways you get complex mechanics implemented in very few lines of code.
Facts are Catalyst’s window into the rest of your game. Change the burning enemy count and the statistic will recalculate automatically. Catalyst has extensive documentation that guides you through all of this.
It’s easy to create a basic stat system, but it’s not easy to build one that lets your creative brain run free without risk of breaking.
RESOURCES.
Catalyst takes that same core conceit and also applies it to resources, such as health, mana, energy, ammo, electricity, again whatever your game needs. These are different to statistics because stats require a modifier in order for its current value to change, whereas resources allow the current value to vary freely between bounds.
Catalyst then also gives you resource flows, which can be applied to resources, like health regen, ammo reloading, stamina recharge and so on, and make the resources change over time.
The real trick is in the way that resources, resource flows and statistics interact.
Each resource can have an upper or lower bound, and these bounds can be statistics, which means you can modify them easily in the same way you can modify any stats. Resource flows have a rate of change, which again, can be a statistic and therefore modified freely and easily.
By composing these three primitive ingredients together, you get highly flexible, easily modifiable results without having to build out complex systems. Let’s say we are building a fishing game, and want to have the line tension ease whenever the player isn’t reeling in:
line_tension = new CatalystResource(100, 0, 0);
line_tension.AddFlow(
new CatalystResourceFlow(-6)
);
Then reeling just needs to run set this: line_tension.Increase(reel_force);. Again, we just compose a simple resource and resource flow, and a couple of lines later, we have the entire mechanic.
EFFECTS.
But that’s not all Catalyst gives you. Catalyst also lets you easily compose gameplay effects. Yet again, all we need to do is compose the effects out of modifiers and resource flows. For instance, a poison effect might drain health, reduce defence, tick once per second, last five seconds and refuse to stack with another poison.
Sounds complicated right? Well the entire effect is done with this simple and compact code (assuming you’ve already setup your hp and defence stats and an effects effect manager):
// Create a poison effect that lasts for 5 seconds
var _poison = new CatalystEffect("poison", 5)
.SetTickInterval(1)
.SetReapplyPolicy(eCatEffectReapplyPolicy.IGNORE)
.AddTag("poison") // Not necessary, but useful
// Drain 5 health every tick
.AddFlow(hp, new CatalystResourceFlow(-5))
// Reduce defence by 2 while poisoned
.AddModifier(defence, new CatalystModifier(-2, eCatMathOps.ADD));
// Apply the poison
effects.AddEffect(_poison);
The effect will run for five seconds, ticking 5 health every second, and while the player is poisoned, their defence will drop by 2. After five seconds, the effect is removed, the defence goes back up to normal and the player stops taking damage.
That’s it. You don’t need to do anything else. A fairly complicated poison effect implemented within six (seven including the not required tag) simple lines of code.
Now, let’s say you wanted a potion that cleared debuffs. Simply add another tag to any debuff effects (like our poison example above) and then you can remove every debuff with a single line of code:
_poison.AddTag("debuff");
// Later on, whenever the player drinks the potion
effects.RemoveEffectsTagged("debuff");
The same effect primitive works for buffs and debuffs, weather, blackouts, festivals, temporary difficulty rules, production failures and any other short-lived or permanent states like that for any genre of game.
GET CATALYST.
Catalyst is also available as part of the Ignition Kit and Full Suite Pass.
Bundle
Ignition Kit
Statement, Catalyst and Pulse together at a bundle price.
Bundle
Full Suite Pass
Every current library plus future releases as they launch. The pass price increases as the suite grows.
Also included: Echo + Echo Chamber.
A purchase of Catalyst also includes Echo, my GameMaker debugging toolkit, and it’s “sub-library” Echo Chamber, for free. Echo gives you level-based logging, tags, filtering, optional stack traces, history, file dumps and in-game debugging tools.
HAVE FUN WITH IT.
Catalyst’s whole goal is to make things simple. Let’s have some fun looking at some other examples of complicated design ideas being expressed easily with Catalyst, to get your creative juices flowing:
Racing game.
Tyres lose grip as they overheat
var _overheat = new CatalystModifier(-0.05, eCatMathOps.MULTIPLY) .SetStackFunc(function(_stat, _facts) { return max(0, floor((_facts.Get("tyre_temperature") - 80) / 5)); }); grip.AddModifier(_overheat);
City builder.
A blackout temporarily screws with several systems at once
var _blackout = new CatalystEffect("blackout", 30) .AddModifier(solar_output, new CatalystModifier(-1.00, eCatMathOps.MULTIPLY)) .AddModifier(water_pump_rate, new CatalystModifier(-0.50, eCatMathOps.MULTIPLY)) .AddFlow(battery_charge, new CatalystResourceFlow(-4)); effects.AddEffect(_blackout);When the blackout effect ends, those temporary changes disappear with it.
Deckbuilder.
A card gets cheaper for every spell you’ve already played this turn
var _combo_discount = new CatalystModifier(-1, eCatMathOps.ADD) .SetStackFunc(function(_stat, _facts) { return _facts.Get("spells_played_this_turn"); }); card_cost.AddModifier(_combo_discount);
Tactics game.
Suppression fades twice as fast while you’re in cover
var _recovery = new CatalystResourceFlow(-2); _recovery.GetRateStatistic().AddModifier( new CatalystModifier(-2, eCatMathOps.ADD) .SetCondition(function(_stat, _facts) { return _facts.Get("in_cover"); }) ); suppression.AddFlow(_recovery);Here the flow’s rate is itself a statistic, so the exact same modifier system can change how quickly suppression recovers.
Cooking game.
An unattended pan keeps getting hotter
pan_heat = new CatalystResource(100, 0, 20); pan_heat.AddFlow( new CatalystResourceFlow(4) );And then taking the next conceptual step, making the quality of the dish drop while the pan is overheated:
var _burning = new CatalystModifier(-0.25, eCatMathOps.MULTIPLY) .SetCondition(function(_stat, _facts) { return _facts.Get("pan_overheated"); }); dish_quality.AddModifier(_burning);
Rhythm game.
Make the timing window tighter as the streak gets longer
var _pressure = new CatalystModifier(-2, eCatMathOps.ADD) .SetStackFunc(function(_stat, _facts) { return floor((_facts.Get("perfect_streak") ?? 0) / 10); }); timing_window.AddModifier(_pressure);
Typing game.
Mistakes temporarily widen the acceptance window
var _assist = new CatalystEffect("mistake_assist", 3) .AddModifier(input_tolerance, new CatalystModifier(0.20, eCatMathOps.MULTIPLY)); effects.AddEffect(_assist);
Required foundations this library uses
These supporting libraries are required by Catalyst.
