In this post, I’ll describe the system I use to make cards with unique abilities for my roguelike deckbuilding auto battler Gods vs Horrors. The game is similar in mechanics to Hearthstone’s Battlegrounds mode (HS BGs), and Super Auto Pets, but the system could easily be adapted to make a game like Slay the Spire or Magic the Gathering.
![](https://oriolcosp.com/wp-content/uploads/2024/07/thor_card.png)
In all these games, cards have attributes (mana cost, attack, …) and unique abilities. Coding each ability as an individual function can be very costly since each game can have more than 100 cards.
I’ll describe a general approach that should work on any engine. My particular implementation is in Unity, but I won’t be sharing any code.
My approach is to break abilities into independent components and parameterize each of them. The main components are:
- Trigger: when does the ability happen? (Eg: when you play a card)
- Effect type: what does the ability do? (Eg: deal damage)
- Effect values: what is the magnitude of the ability’s effect? (Eg: 5 damage)
- Targets: who is affected by the ability? (Eg: target minion)
To define a new ability I just need to add a row with the ability parameters to my abilities Excel sheet and import it into Unity. The game can apply the correct effect from the encoded data.
Let’s dig deeper into each of the components.
Trigger
This parameter controls when the ability happens. Some examples of values for the parameter are:
- When you play a card
- When you play this card
- When this minion dies
- At the end of turn
- After taking damage
For each potential trigger, there is a point in the code where it checks if a minion has that kind of trigger or not. For example, whenever you play a card the game checks if that card has a “When you play this card” ability and if any other card in play has a “When you play a card” ability and triggers those effects.
Some cards have conditional effects like “When you play this card: if X, then Y”. Those are encoded using an additional parameter called trigger condition. If the condition is met then the effect is applied, otherwise nothing happens.
Effect type
This parameter controls what happens when the ability triggers. Some examples of values for this parameter are:
- Deal damage
- Increase stats (attack and defense)
- Choose a card
- Get gold
For each effect, there is a function that applies it. Most effects have targets and values that are encoded on other parameters.
Effect values
This parameter controls the magnitude of the effect. Each effect takes different types of values as inputs. For example:
- Deal damage needs 1 value: damage
- Increase stats needs 2 values: attack increase and defense increase
- Choose a card needs 1 value: what pool of cards do I choose from (I’m aware one could argue this is many values but I just use keywords like the tribe or tier from which the choice is made)
So this parameter can take many values separated by a delimiter.
Additionally, sometimes values can’t be known a priori and have to be calculated during the game. For example:
- This card’s attack
- The number of cards in your hand
- The target’s defense
In those cases, I just encode a string that will tell the game to look for the appropriate value. The strings for each example are: num.origin.atk, num.hand.count, num.target.def. The “num” string just says it’s a number, the next string tells the game where to look and the last bit tells it which number to get.
Targets
This parameter controls who is affected. Some examples of values for the parameter are:
- This card
- All your cards of a certain type
- All enemies
- Adjacent cards
This parameter is broken into 3 components: location (your army, your hand, enemies, the shop …), position (all, adjacent, the origin of the ability, a selected target, …), and filters (only cards of a certain type, cost, …).
Other ideas
There is an additional parameter called “modifiers” where I put any mechanic not included in the other five parameters. So far I’ve only used it to repeat effects and make combat buffs permanent.
Since cards can have multiple abilities, each card has a list of ability ids associated with it.
From time to time, new abilities will need to introduce a new keyword for one of their components, but that’s to be expected.