Shield to Shield
Download the demo!

An intense fight.
Shield to Shield was one of my first major Unity projects, and I had big ambitions. The original gameplay was based on the controls of League of Legends but in a cooperative dungeon crawler, inspired by the co-op Star Guardian mode that had just ended.
The heart of the game was an extremely robust Ability system. The pie in the sky goal of this system was to be able to build any ability I could imagine without having to write any specialized code. The core of this system was chained ScriptableObject
s using Sirenix’s Odin Inspector package to allow expansive editing. Nearly everything that happened in the game ran through this Ability system: Weapon attacks, class abilities, enemy AI, traps, etc.
I eventually stopped working on this game as I began to see the cracks in the design created by inexperienced self. Though, recent playthroughs have shown me I can still have fun with this game so it may get revived and released at some point.
Genre: Coop Dungeon-crawler
Timeline: Worked on primarily between Early 2020 and Late 2022
Engine: Unity
Platform: Windows
Team size: 2
Responsibilities: Concept, Programming, Design, 3d Modelling, Animation
Itch Page: https://buildsgames.itch.io/shield-to-shield
Source Code of Ability System: https://github.com/WillyBMiles/Shield-to-Shield-Scripts/
Challenges
After about a year of development I ran a play test with an old friend that lived across the country. Every time a fight happened he would be fine for a few seconds and then suddenly everything froze until a few seconds later when every ability that occurred in that time happened all at once. After running a network profiler and doing some research I realized I was sending way too many network messages. My first remedy was to switch from reliable TCP to unreliable UDP to prevent stale messages from being sent. More importantly, I found out I was sending a separate TCP Remote Procedure Call for every single effect. For example, if an ability applied a status effects, set a status bar, did damage, and created a particle effect each of those would be its own RPC. Yikes. I was able to batch simultaneous effects and a few other optimizations like only sending one RPC if an ability was simple and couldn’t be interrupted. This reduced my network traffic by a factor of 10 and finally my friend could actually play the game!
The number one impediment to Shield to Shield’s development was my inability to stick to my own design and make up my mind. Here’s a non-exhaustive list of things I added to the game, many of which I eventually removed:
- A three hit combo system for every weapon that had a unique effect for each combination of light and heavy attacks. Using the Sword as an example, the combo Light-Light-Heavy would produce a rapid 3 hit attack culminating in a spin; Light-Heavy-Light would produce a damaging dash; and Heavy-Light-Light would produce a short range stab that dealt high damage over time.
- A blacksmith and durability system where you could spend money to fix and improve your weapons and armor on a number of different axes like damage and attack speed.
- Two completely independent fully fleshed out armor systems, one that was based on a League of Legends-esque damage type reduction system and the other based on regenerating health buffers
- Three separate class systems, each with multiple unique ability sets. The most fleshed out class system had such unique classes as the Lycanthrope that occasionally turned into a powerful werewolf, the Force Cultist who gained a powerful cascading attack after taking and dealing enough force damage, and the Titan Walker who was accompanied by a massive titan that could crush enemies under foot.
- A skill system that allowed you to earn new abilities with experience and eventually upgrade them.
- A Borderlands style item system with random attributes and the possibility of active abilities.
- A set of fight modifiers that would apply a random crazy effect to every couple of fights. For example, a fight might have a large number of enemies but they fight each other in addition to the player
- A set of diablo-style enemy modifiers, such as a boost to speed/damage or the ability to summon minions to assist them
- A ball that you had to pass around between players on the team. You gained various bonuses while holding the ball, including that you could only regenerate mana while holding it.
As my first major game project I wanted to try every new idea I had. My failure to focus made the game unfocused, bloated, and difficult to maintain.
How to make an ability in this crazy system:
Here is a case study on the creation of a single ability: the Time Bomb. This ability belongs to the Inventor class, who has a special resource called Scrap that they must pick up to fuel their abilities. My first order of business when making a new ability is to make the base Ability ScriptableObject (SO), and fill in the basic details. This inspector screen makes heavy usage of Sirenix’s Odin Inspector for things like buttons, conditional serialization, and serializing many of the fields that Unity cannot.
Note that this tutorial and the images within may not be fully up to date with the current version of the game, though the broad strokes still hold.

Here’s the finished Ability SO. Here’s a quick explanation of each field from top to bottom:
- ID: A unique ID that identifies this Ability. This is primarily used in synchronization, ability IDs are sent between host and client and reference an autofilled dictionary. The I_R at the beginning of the ID refer to the fact that this belongs to the second version of the Inventor class (dubbed Inventor Revamp).
- Beauty Name: A nice looking name that is shown to the player.
- Is Basic Attack: A simple flag that determines how this ability is countered. Basic attacks are countered by the Disarm effect, while other abilities are countered by the Silence effect.
- Cant Move While Casting: Flag that determines if the player’s movement interrupted while casting. Any active, animated ability, usually has this flag checked, where as a quick ability with an instantaneous effect might not have this checked.
- Lock Cursor For All: Convenience flag for more complicated abilities. If an ability has multiple activations, this flag will lock the location targeted by the player when any of the activations are being casted, which allows multiple activations to occur in sequence without the player being able to change the where the activation is being targeted.
- Activations: A list of all activations associated with this Ability. Activations are the basic building blocks of an ability. Each activation can be “cast” independently. Below is a much more in-depth run through of activations, but right now it’s of note that to add activations one must use the buttons at the bottom of the inspector window so that they will be properly set up.
- Values: A list of all values associated with this ability. Values are numbers that can be used throughout the ability, and in the description. These help reduce duplication and help show more specific tooltips to the player.
- Sprite: To the left of the description is a sprite that will represent this ability on the ability bar when it is active.
- Description: The description shown to the player. Anything marked with a dollar sign ($0, $1, $2 etc) refers to values from the above value list. During runtime these will be replaced with their actual numbers, with their derivations being shown if the player holds shift for more detail.
- Buttons: At the bottom are a number of useful buttons for activations, updating IDs to be unique, and updating targets.
As seen from the description, the time bomb will cost 5 scrap and some amount of Theurgy (which is basically just mana), with a 2 second cooldown. The player will throw it to the ground and it will explode for some amount of Energy damage and root them in place for some amount of seconds. Let’s take a look at the values to fill in those missing numbers.
Values

The indexes in the description index into this list. So, matching those up we can see that the bomb will do Energy damage equal to 400 plus 10% per point of Focus the player has. Focus, along with Control and Resolve, are stats the player can get from levelling up or from items they find. Continuing, the length of the root will be 4 seconds plus 10% per point of Control. Finally, the Theurgy cost will be 20, reduced by 10% per point of resolve. The final value is just a single source of truth for how much Scrap the ability will cost, as we’ll need that later in the implementation.

The value system is quite flexible. They can chain together recursively, and have many different stats and numbers they can reference, both globally and locally. For example, the top value here would equal: (The item quality of the item this ability belongs to * the caster’s Max Health * a random number between 0 and 1) divided by the number of alive players. Clearly a very useful number to have.
Activations
Back to the Time Bomb, the next level of abstraction is the Activation. As previously stated, each Activation can be casted, but it doesn’t have to be. An Activation is really a set of Targets, Limitations, and effects. Below are the main fields for an Activation:

That’s a lot of options. Any of those teal fields are Values, as above; they can refer to the Ability’s value list or be their own formula. Most of these categories are either specific Activation level options or common limitations that apply to most abilities. Not every category makes sense for every ability, for example UI related options only matter for player abilities. Here’s a summary of each category:
- Top Level: Just the unique ID, usually matching the ID of the Ability, and whether this ability will be cast automatically. NPC abilities and passives usually cast automatically.
- Winding: Wind Up is time after the control is hit before the ability goes off. Wind down occurs after casting and prevents the caster from casting this ability again, and possibly doing anything else, until the wind down is over. As seen here, the ability has a half-second wind up and no wind down.

- Cursor: Options related to the actual targeting of the ability. These options are helpful when the player needs to target a specific point to cast the ability. In this case, the player chooses a location within 4 units of them to throw the bomb.
- Static Cooldown: Most abilities have cooldowns, so this is a simple way to add one. As seen here the Time Bomb has a 2 second hard coded cooldown. It could be better to add this as a Value to the Ability’s Value list so it can be changed more easily later.

- This is also the first appearance of the
Timing
enum, which we’ll go over in a moment. - Theurgy: Theurgy is like Mana, and gates ability usage. Here the Theurgy cost references the 2nd Ability value. To “Prepare” an ability is to preview it’s casting without committing to casting it, such as to see it’s range. Most abilities can’t be prepared if you can’t afford them.

- Stamina: Similar to Static Cooldown, many abilities require Stamina, which is a fast regenerating resource. This ability, and most abilities, cost 1 Stamina on cast.

- Button Info: The way the ability interacts with the button that’s pressed to use it and other abilities. Only one ability can be the character’s Casting Ability and only one can be the Preparing Ability, so these options help with mutual exclusivity. Additionally, by default, all abilities are buffered if they can’t be cast but then a few moments they become available to cast. The Dont Buffer On Self option refers to whether this ability can buffer when the thing that’s blocking it is that this ability itself is being cast, a surprisingly common situation for multi-activation abilities.

- Cast Conditions: These options are pretty straight-forward. Cast Any Time just means that this Activation skips checks for whether another ability is currently being cast, which is mostly useful for passive abilities. Checking Can Cast if Dead, reveals another option which is Can Only Cast If Dead.


- Display: Just one option. Normally the UI will show that an ability is available if it can be cast, this changes that to show it is available only if the ability can be prepared.

Properties
Here is the Timing
enum. A list of various convenient timings that can be used to trigger various things. For example, as seen above, the timing for when to “charge” the player for their spent Stamina or Theurgy, or when to start the Static Cooldown.
Most of these timings are one off but a lot of them occur every frame as long as they are true. For example, Cast triggers just once each time the caster Casts the ability, while Wind Up occurs every frame that the caster is Winding Up their cast.


There are three types of Properties. Targets determine which locations or characters the ability can affect. Limitations affect whether an ability can be cast. Effects make changes to the targets, such as affecting their damage.
Each type of Property is its own class and each of those has several derived classes with unique options. To add a new property to any of these lists, click the plus button and then pick the proper derived class for the situation.

For example, here’s some of the list of possible effects. Even just in the first section you can see quite the variety, from earning money with Effect_Coins, dashing across the screen with the Effect_Dash, healing with Effect_Heal, or blocking from a direction with Effect_DirectionalDamageReduction. With such a variety of effects, limitations, and targets this system is extremely robust. Furthermore, it’s fairly easy to add a new property by simply deriving a new effect, limitation, or target class and filling in the implementation details and adding any necessary serialized fields.
Targets
TargetIDs are a psuedo-enum in each ability. Every Activation shares a list of targetIDs, in addition to a standard list. When a new Target is added, the Update Targets button must be triggered to refresh this list Ability-wide. In Time Bomb, the one non-standard target is Target_Nearest. Because IDs must be unique, a random number is appended to TargetIDs.
TargetIDs can refer to a location in the world, a particular character, or both depending on the specific target. Most effects only work if they target a character or a location, but not both.


There are a few standard targets that help reduce the number of Target objects needed: CURSOR is the location of the cursor according to the cursor settings on the activation; SELF is the caster. HIT is a convenient way to share targets between multiple effects, which we’ll see later. ORIGIN HIT is similar to HIT, another target that can be shared between multiple effects.
There are two other targets in Time Bomb: Projectile and GroundProjectile. There is a very important Effect that acts very much like a Target: Projectile. This will be discussed in greater depth below.

Here’s the one Target on Time Bomb. Summarizing, this target continuously gets the 999 nearest characters to a point with TargetID GroundProjectile. They must be within 5 units of that origin and must be enemies to the caster. This target will be used later to determine which targets are in the explosion when the bomb is placed. The Timing being Update works, but is somewhat inefficient and could be replaced by Target When Triggered, which only polls new targets when it needed.
Determining where the bomb is thrown and where GroundProjectile is will be done by Projectile Effects.
Limitations
A limitation prevents an ability from being cast. They are the simplest Property and don’t have many standard options. There’s only one limitation on Time Bomb, which checks to make sure the player has enough Scrap to use it. Scrap is a unique resource to the Inventor class, and all of their abilities share the same scrap limitation, which compensates for their shorter cooldowns.

This ability checks whether they have Scrap greater than or equal to the third Ability value, which, as seen above, is 5 Scrap.
The Scrap resource uses a generic system called CheckFloat that can be used to save any number. In this case, the SCRAP Float ID is used by all of the Inventor’s abilities and shown on their resource bar. Most limitations don’t need a Timing because they don’t do anything when it gets triggered, they are simply checked whenever they need to be. Some limitations like cooldowns do need triggers, for example to trigger the start of the cooldown.
Effects
Effects are the meat of an activation and do most of the heavy lifting to make an ability work.

There’s quite a few on Time Bomb but most of them are fairly straight forward.

For example, one of them simply makes a BombToss sound whenever this ability is cast.
From this inspector panel there’s a clear delineation between options that are specific to the Effect_Sound class and those generic to every Effect. Fields below the Callback ID is implementation specific, while fields from Name to Callback ID are generic.

Name and Timing are shared between all properties. The timing usually triggers the effect. In the example of Effect_Sound, the Timing is when the sound starts playing.

Conditions are like limitations but only apply to a single effect. They are usually quite generic, like affecting allies or enemies only, so they are their own ScriptableObject. This is occasionally annoying if bespoke ones need to be created for a specific ability. Here’s the list of all current conditions in the game.

Most effects use the Target ID to determine who or what gets hit by the effect. Some effects also use an Origin ID, such as where a projectile starts before travelling. The Callback ID only matters on certain timings, such as Basic Callback and Projectile Hit. This is used as an ID in case multiple properties trigger a callback on the same Activation. In this case, the effect would only be triggered if the calling property’s Callback ID matched this Callback ID.
Taking a look at the advanced options there are some more specific options.

This includes standard support for additional Callback IDs and TargetIDs, support for delaying an effect, and a few other specific options that can change when an Effect occurs or how it can trigger other effects.
For a more specific example of an effect in use, this is the effect for the visual thrown projectile that travels to the location the player is targeting when the ability is cast. It starts at the casting player (Origin ID) and travels towards the Cursor targeted location (TargetID).

Here’s a look at the Projectile specific fields, which are quite expansive. Projectile is one of the most important an flexible Effects in the game. It’s basically an Effect and a Target rolled into one.

First there’s layer information. This projectile will not actually have collision. It should not be blocked by targets and will stop when it reaches it’s target; hence the Destroy Upon Reaching Target Point option. The remaining options are fairly straightforward. It includes a ton of options that affect when the target can hit or stop on targets which affect various specific situations. As one concrete example, the Can Hit Targets Behind option exists to prevent projectiles that represent lines, like the stab of the spear weapon, from accidentally having a hitbox that extends behind the player.
The Appearance field holds a prefab that will be instantiated under a generic Projectile Prefab. This Appearance prefab should just be a nice looking model or particle effect and not have any gameplay effects.
At the bottom is a motion option, another class that can have any of a number of different derived types. There are relatively few options here, and most projectiles use either the Motion_Seek class to follow a character or point, or the Motion_Stationary class to just stay still.


The Motion_Seek projectile motion for this projectile shows that it takes 0.2 seconds to arrive and spins mid air.
One quick look at the effect for the ground projectile effect shows that it is created when the first projectile is created, stays still on the ground, and then after 1.5 seconds it destroys itself.

The actual damage dealt by Time Bomb is done by the DirectDamage effect. It uses the Target ID from the target created earlier, but triggers when the ground projectile is created.

Using the same targeting conditions, the root status is also applied.

At the same time a large explosion particle effect (also known as a Decoration) is placed at the point where the bomb was.

Summary
Here’s a summary of the backend set up for this ability, and the timeline of how the effects trigger.
- First, the player needs enough Theurgy, Stamina, and Scrap to use the ability and it must be off cooldown.
- When the player presses the key it plays some sounds and animations.
- After 0.5 seconds a projectile is fired from the player to the location where their mouse is, within a certain range.
- When that projectile reaches its target another ground projectile is placed on the ground at that position.
- After a moment, that ground projectile is destroyed.
- Throughout this, the ability has been tracking which enemies are near enough to the ground projectile to be affected by it.
- When the ground projectile is destroyed, a large explosion particle is created and those nearby enemies are damaged and given the “root” status effect.
A lot of implementation details and subtleties have been glossed over here. This system is expansive. There are complex systems involved in storing targets, applying damage with reduction effects, stacking statuses and so much more.

Here’s the final ability!
A playthrough of a few floors and levels of progression with four players.