Skip to content

Intro To The Action System

DesirePathGames edited this page Jun 12, 2026 · 4 revisions

What Are Actions And Why Do We Use Them?

Actions are reusable parameterized scripts that act as the bread and butter of the framework, performing behavior against the player or their cards, enemies, or even aspects of the game itself such as playing music. By providing a common interface through which everything in the game happens, it creates an easy way of defining mechanics in an atomic, consistent, and extensible fashion. It also emphasizes a data-first driven design philosophy, focusing on configuration of behavior rather than hardcoding behavior, which is good for you the developer, as well as anyone looking to mod your game.

Okay But What is An Action?

The anatomy of an action is simple, just a JSON object with a single String file path to a script that inherits from the BaseAction abstract class (usually a hard coded constant stored in Scripts autoload) and a dictionary containing that action's parameters. It'll look like this:

{"path_to_action_script": {"parameter_1": value, ...}}

For instance, here's the card_basic_block's action, simply giving the player some block when the card is played:

actions basic layout

Do note that it's one action per JSON object. DO NOT try to do this:

{"path_to_action_script_1": {"parameter_1": value, ...},"path_to_action_script_2": {"parameter_1": value, ...}}

This is because dictionaries do not guarantee order of key-values, and in fact godot alpha sorts keys when saving to json. You actions will run in alphabetical order as a result.

Instead, these are often stored in arrays of JSON objects, usually referred to as an action payload. These action payloads are assigned to lots of different parts of the code, and most action payloads will be automatically handled by the framework when their behavior is needed. Ex: If you play a card, the card_play_actions payload is invoked. These tend to be pretty self explanatory.

If you want to see what all the various action payload types are, a really fast way to see them all is to CTRL+ SHIFT + F project search "_actions: Array[Dictionary] ="

actions in framework

Tada! As you can see most of it is card related, but there's quite a few other places where actions are used such as status effects, artifacts, event dialogue, and consumables.

Also very important to remember (more on that in ActionHandler section below), action payloads are stored in a LIFO: Last In First Out. This means that if you have a card that you want to attack and then block, you define it as [block action, attack action] so the attack happens first.

The ActionHandler

This singleton manages all actions being performed at once, ensuring they are executed in order with no possibility of multiple actions happening simultaneously. Whenever actions are added to an empty handler, it will "lock" and automatically begin processing the actions, with subsequent action pushes adding on to the current processing rather than interrupting or happening in parallel.

The Action Stack and Current Queue

The complete structure of actions is stored as a stack of queues of actions. There is also a current queue, which contains a queue of actions that are being processed. Once this queue finishes, the next queue is popped from the stack. This continues until the entire stack is empty.

Action Stack And Queue

When the entire stack and current queue are emptied, it will emit the actions_finished signal, allowing for other parts of the framework, primarily the UI, to asynchronously await on them.

Remember LIFO

Do note that the default behavior of populating action payloads onto the stack is to populate each action as its own separate queue. This means that if you have a list of actions [A, B, C, D] they will be populated onto the stack as [Queue(A), Queue([B]), Queue([C]), Queue([D])], which will be executed as D, C, B, A because stacks are Last-In-First-Out.

The ActionGenerator

In order to make generating actions much less of a headache, a factory singleton, ActionGenerator was created. This contains a generic method for turning JSON based action payloads into a list of BaseAction objects you can push onto the ActionHandler. It also contains a lot of factory methods used for generating actions used by the UI or general game mechanics and automatically pushing them to the stack. One example is resetting your energy at the start of your turn.

Clone this wiki locally