Character Overview: Shadow

Following on from implementing the crit system I talked about in the last post, this week I begun prototyping the 4th playable character in Aria: Shadow. Fittingly Shadow is all about stealth, traps and huge crits. Today I want to give you an overview of Shadow’s current design.

But first, a heads up: I want to point out that Aria is still in the pre-alpha phase of development and none of the artwork is necessarily representative of the final version of the game. This is especially true of the character models, which are all currently placeholders! So that means that Shadow looks like a man at the moment. Yeah, you’re just going to have to work with me on that. Onwards!

Stats

Okay so let me quickly explain how Aria’s stats work. There are three stats in the game:

  • Might grants extra direct damage and health
  • Cunning grants extra crit damage and stealth detection
  • Wisdom grants extra damage over time and cooldown reduction

Stats range from 0 to 10, with most characters averaging around 3 per stat. So lets look at how Shadow weighs in:

shot_0

The muscles represent might, the eye cunning and the brain wisdom.

A quick glance shows that Shadow has the highest cunning out of all the characters. So what’s that mean? Shadow is good at finding hidden things (like traps) and does fantastic crit damage. These two things will sound more terrifying once we go over her abilities…

Cloak

This one is pretty straight forward: when activated Cloak allows Shadow to become invisible to the enemy and gain increased movement speed for a period of time.

shadow_cloak

Now, if you happened to catch my explanation of Aria’s crit system in the last post, you might have realised something: since stealth is a kind of advantage, all of Shadows attacks will be crits while she is cloaked. Very handy considering Shadow’s cunning!

Fan

Shadow likes knives. When she’s not shanking an unsuspecting monster in the back with one, she’s hurling a handful of them across a room. The Fan ability involves the latter activity. Shadow will let loose multiple knives in a cone towards her current target. The further away the target is, the tighter the cone will be.

shadow_fan

This makes Fan a surprisingly versatile ability: when used at close quarters it’s a wide-sweeping AoE, when used at long range it becomes a high damage snipe.

Loot

Shadow likes to take things. She also likes to break things. Loot is all about both of these inclinations. When standing next to a trap, Shadow can dismantle it and gain a useful item as a result. The type of item she gets depends on the nature of the trap that was looted but it will most often be some type of grenade.

shadow_loot

For example, looting an explosive barrel will give her an explosive bomb but looting a spider web will give her a web net. This variety of throwables kept in Shadow’s back pocket means she’s always got an answer for when things go sour.

Putting it All Together

So with everything explained, what is the picture painted for Shadow? She takes a little more planning and care than some of the other characters, since most of her damage requires advantage and her arsenal requires looting rooms. However, the payoff is a wickedly versatile character that handily clears rooms of traps, controls enemies with grenades and delivers deadly waves of critical hits.

And remember, team mates are expendable:

shadow_ff

That’s it for this week, see you next time!

 

 

Critting with Query Dispatchers

Crits. Critical hits. Periodic huge damage. They’re a staple of game design and have always been one of my favourite things to chase when playing any kind of game that lets me optimise them.

tenor
This is my jam

So when it came to designing how damage works in Aria, it was a no-brainer that I would try putting crits into the system. Aria’s take on crits works like this: certain situations will allow a character to gain advantage over another. Maybe the attacker is striking from stealth, maybe the target is stunned. Maybe the target is trapped in a gigantic vortex of water.

aria_vortex_a05
This is just asking for a blunderbuss crit

Whatever the cause is, it can occur that one character has advantage over another when they mean to attack. When this happens, the attack is a critical attack and they do bonus critical damage.

Why did I decide to try this style of crit vs a more classic proc system? Well, it’s a design that favours coordination and communication between players. It’s a setup and payoff system, i.e. “I’ll stun him, you wallop him!” and so fits into Aria’s overall design goal of taking every opportunity to enrich the local coop experience.

So that’s all well and good. I have my crit design. Now onto the simple matter of implement…ing…it. Huh.

Here’s the thing about the design I just described: whenever we want to calculate damage for any attack from any source, we need to know a bunch of things:

  • Is the attacker in a state of advantage (e.g. are they cloaked)?
  • Is the target granting advantage (e.g. are they stunned)?
  • How much crit bonus damage should we add (e.g. are there any bonuses active)?

You might think, well that’s easy enough. We’ll just add an attacker.HasAdvantage() check, and a target.IsGrantingAdvantage() check and we’ll add an attacker.critDamage property. You mean like this?

Looks nice and succinct, right? Good readability. Seems like a good solution!

Well, not so fast there cowboy. It’s true that this is how I would have dealt with this in the past, but Aria presents a more wrinkled version of the problem that made such an approach feel dangerous.

Think about it – each one of these functions needs to collate together information from any number of sources elsewhere in the code. We need to find any bonuses we might have active, find a way to ask all of the bonuses if any of them grant advantage. There might be actions being executed such as being stunned, or conditions that are active such as being cloaked.

Not to mention Aria is built around players tacking on all manner of weird and wonderful perks to their characters. So do we add a perk.GivesAdvantage() function to our Perk class? What else will we need to add that to? Are we going to end up with a whole bunch of these random question-answering functions sprinkled through our codebase just to appease our crit system?

Let’s take a closer look at our attacker.HasAdvantage() function:

This goes along with having to make a whole bunch of interfaces like IGivesAdvantageEvaluator. Urgh.

Specifically this method leads to the following problems:

  • It violates the Open-Closed principle and
  • We create deep-running dependency chains all throughout the codebase

Such a system could quickly devolve into a spider web of dependencies. And not one of those nice symmetrical spider webs with the morning dew hanging off of it, I mean a nasty mangled attic spider web with 8 dead bugs in it.

9777f3c8fa5e16782e859405c983ffb0
Ah, I see you’ve found my crit code

So I didn’t want to do that. I didn’t want to do any of that stuff, really. All I wanted to do was have my damage system ask a question like “Does this character have advantage?” and get an answer “Yes” or “No”, without having to know where the answer came from.

Enter the query dispatcher. The query dispatcher is much like an event dispatcher: sections of code can subscribe to events and other sections of code can trigger that event, thereby activating whatever code was subscribed. And no section of code has to know any of the other sections of code exist.

Where the query dispatcher differs is that we aren’t just firing off an event to go and run whoever’s-listening code. We are throwing a question out to our code base and receiving an answer to that question. So we have answers subscribing to questions and askers dispatching those questions to get answers.

QueryDispatcher (1)
Our asker and answerers never directly talk to eachother

The query dispatcher can do the following things:

  • Answer Yes or No questions (e.g. Does Character X have advantage?)
  • Tally numbers (e.g. How much Crit Bonus Damage should Character X receive?)
  • Call Votes (e.g. What colour should Character X’s fireball be?)

All without any dependencies. Sounds pretty neat, right? So what does the code for our crit system end up looking like? Let’s return to our question-asking code:

I’ll admit it is ever-so-slightly less readable than our attic-web-with-dead-bugs-in-it version, but let’s see what our answering code looks like. We’ll use the StunnedAction as an example:

What this does is grant StunnedAction the ability to answer yes (or specifically true) to any question asking if its actor grants advantage by subscribing to all QUERY_ADVANTAGE_GRANTED queries. Note that the Begin() and End() functions already exist to begin and end our stunned state. As such our subscription code is slotting naturally into how the StunnedAction class already works.

We can add similar subscription code anywhere we need to in our codebase to implement our advantage mechanic. Our answering method has become a come-as-you-need setup. With a little more answering code in other modifiers and states, we have our crit mechanic up and running!

aria_blast_crit_a05
Flash and BOOM!

The system is still in its infancy, I will refine it over the next few milestones after which I’ll make my query dispatcher implementation available for use, should you wish to give it a try. Stay tuned!