Logo

Braindead.bzh

Game Dev Hobbyist

A blog about my game dev hobby and various stuff.

Menu
Logo

Braindead.bzh

Game Dev Hobbyist

A blog about my game dev hobby and various stuff.

Effects

Overview

AbstractEffect class: res://addons/cardengine/effect/effect.gd (source)
EffectInstance class: res://addons/cardengine/effect/effect_instance.gd (source)
EffectManager class: res://addons/cardengine/effect/effect_manager.gd (source)
AbstractModifier class: res://addons/cardengine/effect/modifier.gd (source)

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.

Creation

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.

Deletion

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.

Usage

Applying effects

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 fx() function.

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 affect(card) function.

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)

Displaying changes

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"))

Cancelling effects

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.

Edition

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 []

Available modifiers

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...

Custom modifiers

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)

Adding modifiers

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.

Example:

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"])

Going deeper

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.