Cian Halpin

“From the rising of the sun to its setting is but a blink of the eye.”

Untitled Card Game Devlog No.1

I’ve spent the last few months working on the vertical slice for my final year project as part of my degree. Along with 4 others, we have the entire college year to produce a playable and polished game that represents a large part of our final grade. For this project, I was chosen to be the lead programmer. This is a position I take quite frequently in smaller projects but I would be lying if I said I wasn’t given pause when faced with this. Especially since, perhaps foolishly, we decided to undertake the task of developing a card-based RPG in roughly 9 months. Would I have the skills necessary to see this project through? Was this going to prove too much? I don’t think I can definitively answer those questions yet, but what I can do is speak about some of the challenges I’ve faced thus far when programming a particularly systems heavy game and how I have gone about solving some of them.

For this post, I want to talk about how I went about tackling the problem of Card Behaviour. Currently in our game, all cards have the same set of basic behaviour, as in cards can attack, be attacked, can die, etc,. However, much like another card game called Inscryption, some cards may come with what we refer to as “Seals”. These seals act as passive abilities, or augments to the card's regular behaviour. For example, some cards may discard a card from the enemy’s hand when played, or may rotate the board (our game takes place on a rotating turntable, could talk about this more in a different post.) if it is in play at the end of the turn. At first, I found myself rather lost on how to implement this. Bespoke card behaviour responding to certain game events seemed challenging. However, I managed to come up with a solution primarily composed of 2 parts. First, the use of the composition pattern when it came to developing the card behaviour; secondly, the use of a global Event Bus to signal when certain events occur.

The use of composition came as a bit of revelation. It is also fortunate that Godot, the engine we are using, added support for abstract classes and methods just recently which allowed me to implement these behaviours in the way I imagined. I started by defining the key components of card behaviour that all cards must implement. These are the ability to be targeted, to target other cards, to attack other cards, to be attacked and to die. I then made an abstract class that defined another abstract method that should contain the logic for that particular component. Thus, if I wanted a card that had a special effect on death, I must merely make a component script that inherits from the base OnDeath class and implement its corresponding function in whatever way I wish. Then whenever our battle conductor calls for a card to be destroyed when it has been defeated, its unique logic is run and the rest of the game doesn’t have to care about how it's being done.

However, I then came across the problem of not all Seals neatly fitting in to the 5 behaviours listed above. To allow for more bespoke behaviour to occur, I had to rely on the Event Bus system that I had been using throughout development already. Nearly all events in the game our piped through the Event Bus, allowing any other system in the game to listen for whatever Event it wants and implementing behaviour depending on the data contained within the Event, such as a card being played or being drawn from a deck. This allows for an extensible system allowing cards to implement their own unique behaviours simply by hooking into a series of preexisting events. Additionally, all card components are simply scenes that can be dragged and dropped onto individual cards allowing for rapid iteration and easy creation of new cards making things easy for designers to change things around without a programmer's intervention.