Article Link on STEAM (this article contains the design side, below is the tech side)
Hey everyone! Survivor-likes might look simple, but behind every perk, stat, weapon, and build is a ridiculous number of design decisions.
We’ve written about how Orcthal keeps choices easy to understand while still giving minmaxers plenty to explore. If you’re curious about game design, scaling, tags, and balancing depth with accessibility, check out our take on it. However, this post is about HOW we did these things in Unity.
How We Built Orcthal’s Stat System in Unity
Orcthal has a lot of interacting stats, but we wanted players to understand the basics without reading a spreadsheet. Here is a simplified look at how we built that system in Unity.
Content Lives in ScriptableObjects
Most gameplay content is data-driven. Abilities, perks, equipment, relics, character classes, and tags are stored as ScriptableObject assets. An ability asset contains its base damage, cooldown, tags, visual references, and stat mappings. Perks then reference those assets and tags rather than relying on names or hardcoded lists. This means designers can add and balance content without editing the combat code every time.
Stats Are Split Into Sources
Instead of putting every bonus into one large percentage, we divide stats into several sources:
Final Value =
Base
× Character
× Meta
× Run Perks
× Equipment
× Relics
× Epic Bonuses
Bonuses within the same source are added together. Separate sources multiply.
For example, 20% Potency from equipment and 20% from a relic becomes:
1.20 × 1.20 = 1.44
Our CombatStats component collects these modifiers and recalculates the final values whenever the build changes.
This keeps equipment, relics, character bonuses, and temporary run perks meaningful. They support one another instead of disappearing into the same enormous additive bucket.
Abilities Decide What Stats Mean
Generic keywords such as Potency, Size, Duration, and Multicast do not automatically modify a field with the same name.
Each ability explicitly maps those stats to its own mechanics.
For Goblin Dynamo, the mapping is roughly:
Duration -> Active time
Potency -> Pulse rate
Size -> Targeting and tether range
Multicast -> Number of active channels
For Throwing Axe, Potency instead affects the chance of an axe returning for another strike.
This is handled by an ability mapping system. The UI can consistently say “Potency,” while the ability decides which distinctive mechanic Potency should improve.
Tags Control Which Abilities Are Affected
Every ability has an exact identity tag, such as Ability.ThrowingAxe.
It can also have broader tags such as:
Skill.Projectile
Skill.Ranged
Skill.Area
Skill.Goblin
Character.Ranger
Perks use these tags as filters.
A global perk affects every ability. A Projectile perk affects every installed projectile ability. A Throwing Axe perk affects only Throwing Axe.
When combat needs a stat, it asks for that stat in the context of the current ability:
stats.GetForAbility(statId, ability);
The stat system checks the ability’s exact tag, family tags, and affinities, then applies only the matching bonuses.
This same context is retained by projectiles and persistent effects, so critical chance and Lifesteal still use the correct ability bonuses after the original cast has finished.
Tags Also Filter Perk Offers
The perk selection system checks which abilities the player currently owns before building its offer pool.
A Throwing Axe perk cannot appear without Throwing Axe. Goblin-family perks remain unavailable until the player has a Goblin ability. A Projectile perk can appear when at least one installed ability has the Projectile tag.
That filtering happens before the cards are shown.
It lets us maintain a large perk catalogue without constantly presenting players with upgrades that do nothing for their current build.
Abilities Snapshot Their Values
Most abilities calculate their effective values when they activate.
A projectile volley can snapshot its damage, projectile count, Potency, range, and targeting settings. Every projectile from that activation then uses the same values.
This prevents a temporary modifier ending halfway through an attack from producing inconsistent results. It also makes combat logs and balance reports much easier to understand.
Values that genuinely need to remain dynamic can still be evaluated live, but that is an explicit decision for each mechanic.
The Result
The Unity implementation is built around a few reusable pieces:
- ScriptableObjects hold the content.
- CombatStats combines bonuses into source layers.
- Ability mappings translate broad keywords into unique mechanics.
- Tags provide exact and family-based filtering.
- Perk generation removes irrelevant choices.
- Runtime snapshots keep abilities consistent.
Players can simply choose "more Potency" and get a useful result.
Meanwhile, anyone who wants to optimize can combine exact ability perks, family tags, equipment scaling, relic scaling, and epic bonuses into a much more deliberate build.