Picking Fights in Unity
As a fun summer project, I’m working on a Rogue-like video game. While our overall goal is to keep ourselves entertained while we learn something new, we’re keeping a careful approach to writing our software. Over the last week, we’ve decided it was time to implement combat.
Rogue-like Redux
If you play video games and know what a Rogue-like is, skip to the next section. For those of you who don’t know, a Rogue-like is a top down role-playing game where your character is inexplicably exploring an awful dungeon. This genre is named after the classic game Rogue. It’s become a popular genre again, especially now that we have graphics!
Throwing Punches
Okay, so maybe it’s not punches. Our game features a seal who is fighting penguins for some reason. It doesn’t make sense, but that’s OK. You’d think that combat would be straightforward - combatants get near each other and then a fight breaks out. Hopefully the player wins and then play progresses. Even if the player loses, failure is just another opportunity to have more fun. Unfortunately, video game combat isn’t quite that easy. The first step is figuring out if there’s a collision going on. Luckily, Unity takes care of that for you. Basically, we check if the critter (player or enemy) can move. If they can, awesome. If not, OnCantMove
is called and things get interesting.
Can’t Move Enough to Fight
Let’s assume that our intrepid little seal is surrounded. Now what? This is where things got interesting. After tossing around a few ideas, we settled on a relatively simple system. No, it’s not “the player always wins”. In our system, everything that can get in a fight can become some kind of CombatData
object. We use this so that there’s no risk of accidentally changing something in a player or enemy until we’re absolutely sure we want to make a change. This system is also general purpose enough to let us extend it until we’re sick of extending things. First off, both attacks and defenses have effects and tags. These let us say that an attack is Unblockable (an effect) or Fire (a tag). These effects and tags change the way combat occurs, but they’re temporary - your unblockable effect may go away. These attacks are applied to the CombatData
only during combat to create a temporary set of data points used just for that combat. The seal hitting the penguin is one combat and then a penguin hitting the seal is a different combat.
Resolving Conflict
Once we’ve computed everyone’s combat efficacy, it’s time to figure out what happened. Many games allow defensive effects to cause damage to the attacker. Think of armor covered in spikes or a skunk. Clearly our combat couldn’t be one sided. Keeping that in mind, we decided to compute damage based on both the attacker’s stats and the defender’s stats. But, the result of that combat is a pair of damages - one set of damage for the attacker and one for the defender. Everything that can be attacked in our game is some kind of IAttackable
and they can TakeDamage(Damage damage)
.
Keeping It Simple
Earlier combat attempts were complicated - the systems were fiddly, unpredictably slow, and complicated to explain (much less code). By focusing on simplicity, we were able to quickly build and refine a combat system that lets us change game play with a few sliders rather than overly complex mechanics.