While a card’s stats such as cost, attack, and hit points can add a lot of strategy to how you build your deck, its set of abilities may impact your strategy even more. In this lesson we will add a new ability to our minion cards called Taunt, so that whenever it appears on the battlefield, the enemy must attack it before it can attack non-taunt minions.

Go ahead and open the project from where we left off, or download the completed project from last week here. I have also prepared an online repository here for those of you familiar with version control.

Card

Taunt is considered a type of “mechanic” or “ability” that can appear on minion cards. There are a variety of these including: Battlecry, Charge, and Divine Shield. You can read more about them all here. The goal for this lesson is to add the concept of these sorts of abilities to our cards in a very flexible way.

There are tons of different ways to implement your data models. In my first prototype, the card models all had additional List fields for things like abilities, effects, and mechanics. Part of the decision of how you structure your data will probably be determined by how you want to persist the data. Some structures might be better suited to working with JSON for example. For this second protoype, I am deferring the decision about how to persist the data until later and decided to architect this feature based on what would be most convenient to work with.

I am a fan of Entity-Component architecture, and want to use it again here. We have already built a custom Container-Aspect library which we have been using to hold our game systems, and it would also work well for the abilities of our cards. For example, the Card could itself be a subclass of Container, and the abilities, like Taunt, could be implemented as Aspects of the Card.

We could actually push the idea a bit further and make the Card an Aspect that appears on a generic Container instance. However, in practice, I would rather have a Deck of Cards than a Deck of Containers for which I needed to “Get The Card Aspect” repeatedly. Although this alternate approach would be more flexible, I felt that it would come at the cost of more verbose code and that I wouldn’t actually utilize the extra flexibility.

So… with my final decision made, all we have to do is add the using statement for our AspectContainer code and to make the Card class inherit from the Container class like so:

using TheLiquidFire.AspectContainer; public class Card : Container { // ... pre-existing code here }

Taunt

Now we can define the class that will be attached as an aspect to our card. The mere presence of the aspect will cause different behaviour in an upcoming system – no fields needed.

public class Taunt : Aspect { }

Taunt System

We will create a new system to handle the concept of taunting and how it can effect our gameplay. It will be an Aspect so that we can add it as a game system alongside our other systems. It will also implement the IObserve interface because it will respond to a notification sent by our AttackSystem so that it can filter the allowed attack targets based on the presence of a Taunt minion on the battlefield.

public class TauntSystem : Aspect, IObserve { public void Awake () { this.AddObserver (OnFilterAttackTargets, AttackSystem.FilterTargetsNotification, container); } public void Destroy () { this.RemoveObserver (OnFilterAttackTargets, AttackSystem.FilterTargetsNotification, container); } void OnFilterAttackTargets (object sender, object args) { var candidates = args as List<Card>; if (TargetsContainTaunt (candidates) == false) return; for (int i = candidates.Count - 1; i >= 0; --i) { if (candidates [i].GetAspect<Taunt> () == null) candidates.RemoveAt (i); } } bool TargetsContainTaunt (List<Card> cards) { foreach (Card card in cards) { if (card.GetAspect<Taunt> () != null) return true; } return false; } }

As you might have expected, I use the Awake and Destroy methods (required by IObserve) to add and remove the observer of the notification for filtering attack targets. The handler of the notification is the “OnFilterAttackTargets” method. I implemented another private method called “TargetsContainTaunt” to quickly determine whether or not the list of targets included a Taunt minion at all. If there are no Taunt minions in the list then no filtering will be necessary. If there is at least one taunt minion, then I need to loop through the list of candidates and remove any that don’t also have a Taunt aspect.

Game Factory

We added a new system, so we need to make sure it gets included in our game. Add the following to the GameFactory’s “Create” method:

game.AddAspect<TauntSystem> ();

Minion View

We will also need a way to make sure a user can differentiate Taunt minions from non-Taunt minions that appear on the battlefield. I have already created sprites specific to Taunt which look like there is a shield around thier border. Inside of the “Refresh” method, change the following line:

avatar.sprite = isActive ? active : inactive;

to this:

if (minion.GetAspect<Taunt> () == null) { avatar.sprite = isActive ? active : inactive; } else { avatar.sprite = isActive ? activeTaunt : inactiveTaunt; }

Game View System

Finally, we simply need to make sure that some of the cards in our deck have this new feature applied to them. Inside of the “Temp_SetupSinglePlayer” method and inside of the innermost “for” loop where we are creating and configuring cards, add the following:

if (i % 3 == 0) { card.AddAspect (new Taunt ()); card.text = "Taunt"; }

This will cause about a third of our cards to be given the new “Taunt” feature. Note that I also set card text to indicate whether or not a card has the feature in a way that can be viewed while the card is still in your hand.

Demo

The game is now ready to be played with a new feature! Depending on the luck of the order of cards that you draw and which cards get Taunt, you may notice that the game is now slightly harder. Regardless, try to experiement – wait for your opponent to get a couple of minions on the battlefield with at least one taunt minion. Try to initate an attack to a non-taunt minion or the opponent hero, and see that the action will fail. You must kill taunt minions before you can attack anything else.

Summary

In this lesson we added a flexible foundation for our cards to have a variety of abilities. We also fully implemented our first ability – Taunt. This feature will filter what is considered a valid attack target based on the presence of a taunt minion – it must be targeted before non-taunt minions.

You can grab the project with this lesson fully implemented right here. If you are familiar with version control, you can also grab a copy of the project from my repository.

If you find value in my blog, you can support its continued development by becoming my patron. Visit my Patreon page here. Thanks!