Dev Log 4: Surprise and Trap MechanicsStill recovering from surgery, I decided to use my first week back at programming to work on something light before diving into multiplayer code. Well, as the rule of software engineering goes, when a programmer believes a task will be easy, they are more often than not "surprised" to find the task is much more involved than expected. How appropriate that I should be so surprised working on the "Surprise and Trap" game mechanics.
What is a Surprise Mechanic?In Summoners Fate, a Surprise is a card played with its target and effects hidden from the opponent until they are triggered by a game condition. This mechanic opens up a whole new catalogue of strategies for the more sneaky inclined players, such as placing hidden traps on the board that spring when the player's character walks over them. Traps were one of the most requested "abilities" from my players in Hero Mages, Summoners Fate's predecessor, so of course this time around, I aimed to deliver the feature.
In this video, an AI controlled Summoner casts a surprise Ice Trap, then, they use their knockback ability to push my gladiator into it, taking advantage of the trap's effect during their turn.
More than traps, Surprises are also a way to incorporate a common CCG mechanic known as an "interrupt" into an asynchronous turn-based game. Generally, interrupts are played by during an opponent's turn when they are attempting to do something (for example cast a spell) and you want to interrupt the action, say with a counterspell to stop that spell from happening. Since live interruption is impossible when you are playing a game asynchronously, one way to solve this is to premeditate the interrupt with a conditional effect. In Summoners Fate, counterspell is achieved by placing a hidden status effect on your caster. It has a condition "When enemy casts a spell, counter it". In this way, the game automatically interrupts your opponent on their turn, whether you are actively connected to their game or not.
Programming SurprisesThe architecture of Summoners Fate supports surprise mechanics with the following structure:
- Cards contain an action that performs any number of action effects. One type of action effect can affix a status effect to an object (unit or space on board)
- Status effects can contain 1 or more attributes (modifiers to basic stats such as attack power or life) as well as any number of triggered abilities
- Triggered abilities are actions that can respond to events and can have any number of conditions that must be true before executing the action's effect
- Events are messages fired by the game when something happens, such as a unit entering a position or a spell being cast.
To program a surprise card, I write an action that affixed a status effect containing a trigger for a desired condition (such as when enemy casts spell) and executes desired effect. I do this using the definitions editor I wrote for the game. Pretty straight forward, right?
To make development easier, I created a tool for programming triggered abilities. It includes a "cheat sheet" that identifies what each event property maps to so I don't have to memorize all the permutations.
Well, that's what I thought until I started to unfold all the possible user-facing considerations about what a Surprise flow actually entailed.
Design ConsiderationsThe first major design question I had was: When a surprise is played, what exactly happens? Is the opponent informed that a surprise has been played or do they not see anything until it the surprise triggers? How this question is answered has major implications on the player experience, including the perceived fun and fairness of the mechanic.
I spent a lot of time discussing this with players. Not showing the player anything at the time a surprise is played has the perception of a more "hardcore" experience, where players must rely on very subtle clues in the player's behavior (ex: not appearing to cast any spells on their turn, moving their units in odd patterns, etc.) to suspect there may be a surprise. On the other hand, showing a warning "Surprise has been cast!" creates immediate excitement and tension at the cost of losing a small element of the surprise. All agreed that only in practice can we really uncover what the experience will feel like, so it made sense to proceed programming the early warning system, with an option to turn this off during playtesting to try both options.
The next question, how are surprises tracked/stored? Well, it makes sense that traps placed on the board would naturally have markers indicating them. But, only the player who created the trap should see it, while the other player shouldn't see anything until revealed. For surprises attached to units, such as when a unit is attacked, do X, it made sense to store as effects on the unit's card, viewable when examining that card (but only visible to the creator of the surprise).
The complexity of how these items can be stored and tracked by the player suggested to me that there should be only 1 surprise allowed per target. It doesn't make sense to place multiple traps on the same tile if I can only effectively show 1 marker per tile. Interestingly, this also creates side mechanic for revealing/disarming traps where a player might try and place a trap on a tile already trapped by the opponent. Doing so would replace the existing surprise with a new one, ensuring to the placer of the new surprise that there isn't an additional surprise waiting on that tile and indicating to the placer of the old surprise that their opponent has played something on that spot.
Surprise User FlowTo help determine the list of tasks necessary to program the feature, I started by writing what the user flow looks like when a surprise is played.
- Player casts a Surprise card. From their perspective, it casts like a regular spell. To the opponent, they see a “Surprise” card pop up, describing that the opponent has cast a surprise and that it’s effect will remain hidden until its conditions are triggered.
- The player who casts the surprise will see a special status marker on the unit or space they cast it on. If it was a trap, they’ll also see the icon of the spell card appear on that tile as a marker.
- The opponent will not see the marker nor the surprise status card when inspecting the board.
- When casting surprise, do not change caster’s facing as this can reveal clues about the target.
- When casting surprise, opponent should not see a feedback animation that reveals surprise target.
- When the surprise is triggered, the card shower will reveal the card along with message and special SFX, “Surprise Triggered!” This will show for both players. The flow will mimic similar to if spell had just been cast, showing the card, then hiding card and playing spell animation, and then removing the status markers.
- When a surprise is triggered, the undo action button should change to a replay action. It wouldn't be fair to use the undo action as a way to avoid triggering traps now, would it?
- Each target can only have 1 surprise. If a new surprise status effect is added, remove the existing status.
As I began implementing, the work expanded, revealing other edge cases. For example, when a counterspell counters an enemy surprise, it should reveal the card that they were trying to cast (otherwise, the counterspell feels pretty lackluster).
I play a Counterspell surprise on my Summoner. When the Orc casts his Ice Trap (also a surprise), the Counterspell triggers, countering his effect as well as revealing what his surprise card was.
Other opportunities also revealed themselves. In the current implementation, counterspell triggers whenever an enemy casts a spell (regardless of where). But, with a tactical board, I can now add considerations, such as
"Counter next enemy spell cast in line of sight of your Summoner." which creates a new dynamic on a widely used mechanic in other CCGs.
Naturally, surprises can be quite powerful and demand new strategies that can counter them. One such card, Revealing Light, reveals all of the surprises on board. Note that Summoners Fate does not distinguish between who a surprise has been revealed to. This means, your own traps are also revealed to the opponent when played.
Using Revealing Light, I can detect my opponent's Ice Trap and navigate around it to avoid being frozen.
Expect the UnexpectedMy takeaway for fellow developers is "Always expect the unexpected" when developing a new feature, even if its one that seems straightforward at the onset. The nature of development is to expand like branches on a tree. It's not a bad thing, as opportunities for creativity present themselves with each new discovery, but, it is important to be mindful of timeframes, particularly when working towards a release date commitment. My initial time estimate for completing this mechanic was a couple days, and it took just over double that to complete the user flow. To avoid further impact to my release date, I'm making a conscious choice to stop further development here, get my multiplayer code working, and then see what new discoveries are made once the game is in the hands of players.