CardEngine offers the possibility to temporarily modify cards' data through the effects system. Effects apply modifiers to the cards of your choosing. Modifiers make change to the data that can be reverted. This is useful when, for example, cards affect other cards, or if you have artifacts which modify cards behavior.
Effects are divided in two layers, the logic layer (AbstractEffect) and the instance layer (EffectInstance). In the logic layer, effects are identified by an unique ID and are what you managed in the editor. As an effect can be applied multiple time, the instance layer helps keeping tracks of what is applied and where.
Effects are not what is directly applied to cards, instead effects add modifiers. The reason is to make it easier for you to create effects that can change multiple data at once.
Effects management is a mix of UI driven operation and scripting. This might change in the future to a more UI driven system.
To create an effect press the "Create new effect" button. Fill in the ID and the name. The ID cannot contain spaces, can only contain a-z A-Z 0-9 characters and cannot start with a number. The name can be any non-empty string of characters.
Change ID and name
The ID cannot be changed.
The name can be changed by double-clicking the effect in the list.
To delete an effect, select it in the list and press the "Delete effect" button. You will have to validate the delete with the confirm dialog. Be careful, deleting an effect is not reversible.
Before you can apply an effect, you have to instantiate one using the
instantiate("<effect ID>") function of
EffectManager. The manager is accessible using the the CardEngine singleton
Once you have an instance you can apply the effect to a store, using the
apply(store) function or to a given card, using the
Here is an example from the demo, which apply the "mana_incr" effect to the cards in hand:
var fx = CardEngine.fx().instantiate("mana_incr") fx.apply(_hand)
To make the visual layer of your cards reflect changes made by effects, you have to connect to the signal
modified() from the instance layer. Example from the demo:
func _on_NormalCard_instance_changed() -> void: instance().connect("modified", self, "_on_instance_modified") _update_data() func _on_instance_modified() -> void: _update_data()
Note: the instance changed signal is emitted when a new instance is linked to the visual layer, while the instance modified signal is emitted when the data layer is changed.
The modified data are accessible using the
instance().data() function. You can access the unmodified data using the
instance().unmodified() function. To learn more about how to display data, check the Visual layer section of this page. Example on how to use both data to change the color of the mana cost text depending on if the modified cost is higher or lower:
var val = instance().data().get_value("mana") var orig = instance().unmodified().get_value("mana") if val > orig: _cost.add_color_override("font_color", Color("ff0000")) elif val < orig: _cost.add_color_override("font_color", Color("00ff00")) else: _cost.add_color_override("font_color", Color("ffffff"))
To cancel an effect you have to call the
cancel() function of
EffectInstance. This will remove all the modifiers linked to this effect instance from the affected cards.
Editing an effect requires to write some code. You can find the effects in the
res://effects folder. Alternatively, you can click the "Edit effect" button to open the corresping script. For each created effect you have a corresponding script following this naming convention
<effect ID>.gd. To start editing, open the script with your usual GDScript editor. Example of an effect script when newly created:
extends AbstractEffect func _init(id: String, name: String).(id, name) -> void: pass # Override this to limit the affected cards or leave null to affect all the cards func get_filter() -> Query: return null # Override this to returns an array of modifiers applied by this effect func get_modifiers() -> Array: return 
CardEngine has built-in modifiers which you can use with any effects:
- ValueChange: add a given amount to a given value
ValueChange.new(mod_id, stackable, value_id, amount)
- ValueMultiplier: multiply a given value by a given multiplier, the result is floored
ValueMultiplier.new(mod_id, stackable, value_id, multiplier)
- ValueSetter: set a given value to the given number
ValueSetter.new(mod_id, stackable, value_id, number)
- More modifiers to come...
If you cannot find a built-in modifier that fits your needs, writing a custom one is straightforward. You have to create a new class extending the
AbstractModifier class, either in a new script, if you need to reuse it in multiple effects, or as an inner class of the effect, the quickest way. Here an example of a custom modifier, created as an inner class of an effect, which modify the name of a card if present.
class CustomModifier extends AbstractModifier: func _init(id: String, stackable: bool).(id, stackable) -> void: pass func modify(card: CardData) -> void: if card.has_text("name"): var name = card.get_text("name") card.set_text("name", "%s (modified)" % name)
To add modifiers to an effect you have to override the
get_modifers() function of the effect. This function returns an array of modifiers instance. Instantiating a modifier is done by calling
new() on the desired modifier class name, example:
ValueChange.new("incr", true, "mana", 1). The first parameter is the modifier's ID, important to keep track which modifier has been applied by other effects. The second parameter is to say if the modifier is stackable, a stackable modifier can be applied multiple time instead of just once for non-stackable. Additional parameters can follow depending on the instantiated modifier. Example:
func get_modifiers() -> Array: var modifiers :=  # Adds the buit-in ValueChange modifier modifiers.append(ValueChange.new("incr", true, "mana", 1)) # Adds the custom modifier CustomModifier modifiers.append(CustomModifier.new("custom", false)) return modifiers
Setting up a filter
If you want your effect to only affect certain cards you can override the
get_filter() function. This function returns a Query, to learn how to write queries go at the bottom of this page.
func get_filter() -> Query: var filter := Query.new() # Effect only affects cards with a mana value above or equal zero return filter.where(["mana >= 0"])
Once familiar with the concepts above, you can explore the source code of the game screen from the demo to get a better understanding on how all this is working together. Reading the source code of the visual layer implementation "normal_card" is also a good idea.