0
\$\begingroup\$

I am working on a card game in Unity and want to implement a system where I can create card effects that happen when certain events are triggered.

The events could be anything from when a card is played or destroyed, to more complex events like buying and playing 3 cards in the same turn, for example. Similarly, the effects could be any number of things, from draw a card, to deal damage to all enemy units of a specified type.

The strategy I am currently considering is to have an event queue, and whenever anything happens, push an event object containing data about what just happened to the queue. Then, create a data structure to store a pointer to an Event class, which has a method that looks at the event queue and determines if the event has happened, and a pointer to an Effect class, which invokes a method to make something happen in the game. The cards will monitor the event queue and if it detects that the event was triggered, invoke the effect.

My concern is that this requires a lot of subclasses for each event and effect which will really muddy the codebase. Ideally I would be able to create new effects entirely within the inspector using enums for the event trigger and effect, but don't know how this would be implemented.

If anyone with experience of these kinds of systems has any advice regarding if this is the way to go, or on the implementation it would be greatly appreciated.

Thanks.

\$\endgroup\$

2 Answers 2

0
\$\begingroup\$

Consider C# events.

This allows you to have cards in the game which subscribe to events by registering a callback method with the event. The result is that as soon as you .Invoke(arguments) the event in your game loop, all those previously subscribed event handlers will get called.

I assume that you already have some class which manages the general progression between different game phases. I will call this class GameState. This GameStateManager would expose a bunch of events like this:

public event Action<Player> OnBeginTurn;
public event Action<Card> OnCardPlayed;
public event Action<Card> OnCardDestroyed;
public event Action<Unit, int> OnUnitDamaged;

and so on.

The GameStateManager would then invoke those events at appropriate times.

public void SwitchTurn(Player newPlayer) {
    currentPlayer = newPlayer;
    // all the other stuff which needs to happen on turn changes
    OnBeginTurn?.Invoke(currentPlayer);
}

A card could then subscribe to an event like this:

public void EnterGame() {
    gameStateManager.OnCardPlayed += OnCardPlayed
}
public void LeaveGame() {
    gameStateManager.OnCardPlayed -= OnCardPlayed
}

private void OnCardPlayed(Card playedCard) {
    // Deal 3 damage to every unit in the game when another player plays a resource card
   if (playedCard.type == Card.Type.RESOURCE && playedCard.owner != owner) {
       foreach(var otherCard in gameStateManager.allCardsInPlay) {
            if(otherCard.type == Card.Type.UNIT) {
                 otherCard.Damage(3);
            }
       }
   }
}

More complex events like "playing 3 cards in the same turn" might require to keep a journal of all events which happened in the current match and then search through that journal for what happened in the past.


Yes, this will require that you have a hardcoded OnCardPlayed method for every card (although similar cards might share the same method).

I don't think that it would be a good idea to try to get around hardcoding here. If you attempt to implement some "soft logic" which you can edit via a GUI in a custom editor window, you will likely end up investing a ton of work into a system which isn't much easier to use than just hardcoding and still won't be able to cover the more exotic use-cases you have in mind.

\$\endgroup\$
3
  • \$\begingroup\$ If you use delegates instead of Action, you can also name the parameters. \$\endgroup\$ Commented Jul 29, 2021 at 18:00
  • \$\begingroup\$ Hey, thanks for such a detailed answer. Architecturally, would the hardcoded OnCardPlayed method (which is just the effect so can be invoked at any time and not just when the card is played) require a new C# script for every effect in the game, or would it be better to have one script that holds a bunch of methods for all the different effects? \$\endgroup\$ Commented Aug 16, 2021 at 13:24
  • \$\begingroup\$ While thinking about this, realised that each card would need to subscribe to events independently anyway so leaning towards having a script for each card which contains a method for its effect, and a Start method which subscribes to whichever event triggers the effect. Is this reasonable? \$\endgroup\$ Commented Aug 16, 2021 at 13:31
0
\$\begingroup\$

You can use an event bus. It is basically the observer pattern, but many-to-many instead of one-to-many; which means an arbitrary amount of listeners can listen to an event that can be raised by an arbitrary amount of emitters.

I have a library on GitHub that implements a simple event bus for Unity. You can use it, or write your own, it is a fairly simple piece of code: https://github.com/starikcetin/Eflatun.EventBus

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .