Devlog: Implementing a Status Effects system - Ivan Shyika
Hi, Ivan is here again. I am the Team Leader, Project Manager, and Senior Developer on our game Return to Divinity.
One of the key features of our dungeon crawler game is items that grant passive status effects to the player's squad of heroes. Those items serve as a way for the player to "level up" as they play; otherwise, the player's squad would be no more powerful toward the end of the run than they were in the beginning - in fact, likely weakened and exhausted after all the battles.
Items and the inventory system have already been implemented (I wrote a devlog about it). It was time now to handle status effects.
# Problem
Before we get to solving the problem, let's first assess it. What do we know about the feature of status effects, and what are some specifications we have to follow?
- Individual items can have multiple associated status effects of varying types and intensities. So several effects can be caused by the same item.
- The player may possess duplicates of the same item. Due to how items are implemented (they are Data Assets), in simple terms, there is no way to tell duplicates of the same item apart, they are the same object programming-wise. So we should allow the possibility of applying several identical status effects with effectively the same cause.
- My teammate Keegen, our AI & Gameplay Programmer, proposed a design of a support enemy that would buff its nearby allies with some special effect. So now we should consider if we can make a system where any unit (both the player's Heroes and enemies) can have status effects applied to them.
- To take it further, why not consider temporary status effects?
# Design
You may remember I enjoy drawing diagrams to design and visualize the technical architecture of systems. This time was no exception.
Here is a diagram of the structures that will implement the status effect system:
And here are a couple of flowcharts to describe its operation:
# Solution
## Setting up the architecture
- Status Effect Data is a structure that describes a status effect: its type, potency, and whether it is temporary.
- Some effects do not make good candidates for having a percentage-based modifier (like crit chance), others for an absolute-value-based one (like how much gold enemies drop), so IMO it makes sense for the nature of the modifier to be determined by the status effect type itself rather than a special value like `bIsPercentageBased`.
- Some effects do not make good candidates for having a percentage-based modifier (like crit chance), others for an absolute-value-based one (like how much gold enemies drop), so IMO it makes sense for the nature of the modifier to be determined by the status effect type itself rather than a special value like `bIsPercentageBased`.
- The Status Effect object is a concrete instance of an applied status effect: apart from containing the effect data, it also contains its causer object and the remaining effect duration (for temporary effects).
- Status Effect Actor Component is a manager of all status effects currently applied to its owning actor (unit). It provides a simple interface for adding and removing status effects, removes temporary actors when their time is due, and broadcasts relevant messages so anyone interested can track those changes. Note that it does not care about the types of its status effects; it is merely their disinterested manager.
- Note the optimization feature of storing the combined value of all status effects of a specific type in TMap<EStatusEffectType, float> TotalEffectValue. Imagine you are maximizing your dealt damage and have several status effects that modify it: likely, you care more about the combined benefit of all damage-increasing status effects rather than its individual contributors. Besides, that combined effect changes only on effect application or removal - so why not remember it for quicker lookup?
- We can leverage that optimization and broadcast changes in those total effect values. This way, if someone cares about a change in the combined potency of particular status effect types (like with the aforementioned example of the total damage modifier) with no particular regard for whether it was increased or decreased, they can react only to one signal. This is what the OnTotalEffectValueChanged(EStatusEffectType EffectType, float NewTotalValue) delegate is for.
With this, we are ready to attach the component to our units! After that, we will only need to implement the consequences of status effect types.
## Reacting to status effects
- Units listen to their Status Effect Manager and react to changes in their applied status effects.
- Note that in some cases, we may want to query the total status effect value as needed for extra precision - like when we are dealing damage:
- Note that in some cases, we may want to query the total status effect value as needed for extra precision - like when we are dealing damage:
Voila! Now, we can buff up our units to deal 999 damage, have 999999 HP, and be 10000% faster (only until we start balancing the game, sadly).
# Conclusion
We now have a robust, scalable status effect system that can be applied to any unit in the game - and, in truth, any actor.
Applying or removing a status effect from a unit can be done in one line of code:
Creating a new status effect type merely requires adding a line into the list of status effect types (EStatusEffectTypes) and handling it in the target actor (like in UBaseSquad::HandleOnStatusEffectTotalValueChanged()).
And a remarkable convenience: the entire backbone of the status effect system is contained only in 4 files (StatusEffect.h/.cpp, StatusEffectComponent.h/.cpp)! It makes it strikingly mobile, as any project needing status effects would only need to copy those four files and they would be good to go.
## Bonus: The value of timely considerations
Initially, I considered a rather rigid system controlled entirely by a custom GameMode with a predefined, limited set of status effects, only applicable to the player's squad.
But when my teammate Keegen proposed the idea of a support enemy that casts status effects onto its allies, it made me think: why limit status effects only to the player's units?
And I realized that all I need is a well-constructed Actor Component that reliably manages active status effects.
This is a bright example of the value of considering possible applications of your systems: while you may not have to implement them, designing an architecture with modularity and scalability in mind can lead you to robust, flexible systems.
## Gratitude
I hope you found this reading insightful.
Thank you for your attention,
Ivan Shyika
Team Leader, Project Manager, and Senior Developer of Good Faith Team
Get Return to Divinity
Return to Divinity
Strategic combat, tactical formations, and perilous dungeon runs—can you survive the depths?
Status | Released |
Authors | Good Faith Company, VioletJoy, UnmakerOfThoughts, John Ivess, Senia Icethief |
Genre | Adventure, Action, Role Playing |
Tags | Dungeon Crawler, Fantasy, Isometric, Medieval, Perma Death, Real time strategy, Roguelike, Singleplayer |
More posts
- Devlog: Refactoring Items and TraderActor - Ivan Shyika52 days ago
- Devlog: Technical design of the Inventory System - Ivan Shyika60 days ago
- Devlog: Overhauling Squad Movement and Formations - Keegen Love67 days ago
- Devlog: Hero Classes and Special Abilities - Ivan Shyika68 days ago
- DevLog: Implementing Death (Animations) - Violet Weathersby68 days ago
- Devlog: Preserving squad data across levels - Ivan Shyika73 days ago
- DevLog: Squad Selection Crashes - Violet Weathersby73 days ago
- Devlog: Fixing Squad AI Freezing in Combat: A Deep Dive into Task Instancing and...74 days ago
- DevLog: Camera Issues - Adam Denomme74 days ago
Leave a comment
Log in with itch.io to leave a comment.