Andrey Dyakov is CTO at Mundfish, the developer behind the upcoming game Atomic Heart. He has more than 10 years of game development experience working at well-known studios on AAA games that span Unreal Engine 3 and Unreal Engine 4.
Hello, my name is Andrey Dyakov, and I’m the CTO of Mundfish, the developer behind the upcoming game Atomic Heart. This tech blog will cover some of the technologies we are using to develop our title and will primarily focus on how we’re leveraging Unreal’s Gameplay Ability System (GAS) to alter our development process.
This post is not a manual but details our experience. Nevertheless, it still could help developers who may be familiar with GAS and have used it in their projects.
Nowadays, there’s more information on GAS out there, but we wanted to walk you through some of the research we did when there wasn’t a lot of documentation and support for the system early on.
Looking back at 2018, we had our own action filters system for separating players’ actions and defining execution rules for them, and we were generally happy with it. It let us control the ability to start specific player actions during other actions or to prevent them from starting (For example, doing jumps while crouched).
However, it was just one of the many required subsystems we needed. In our case, we also needed to solve other key development issues, such as:
How to handle special damage from acid, electricity, and fire from charged melee weapons and bullets for range weapons
How to stack and handle different damage types applied to a single character
How to implement a player character's special abilities and how to control their execution flow
Finally, before we ultimately decided to make Atomic Heart a single-player game, we asked ourselves how we would handle online replication
There was one system that could potentially solve all of those problems: Unreal’s Gameplay Ability System. I used it once to prepare a small example project for the UE4 development course that I taught at an outsourcing company for 60 people for several weeks. But it’s one thing to try a then-experimental system on a small educational project versus applying it on a big, ambitious commercial one.
The team had many concerns initially, which is quite understandable. At that time, GAS had limited support from Epic. As you may know, this plugin was released by Epic after it closed its Paragon project, and initially, it was a significant part of that game. Seeing this, however, was proof that GAS was nearly production-ready at the time, and I believed that our team could handle the challenges of filling in the gaps. Luckily, I was right.
GAS plugin with an [UNSUPPORTED] tag in the description
At that time, around the launch of Unreal Engine 4.20, there wasn’t a lot of documentation about GAS on the internet aside from a few articles from enthusiasts in the community. Thus, we started our own research. The team inspected every line of the plugin and shared all the pros and cons during brainstorming sessions.
In the end, we determined lots of features that were absolute pros by integrating GAS into our game:
The cons, at the time, were obvious: Complexity coupled with lack of official documentation and support. GAS is a complex system, and usage approaches vary from company to company and project to project. We also ended up spending a bit of time to define our own best practices with the system.
During integration, we checked all base mechanics for listed goals, and everything was done great. At that point, we also understood that GAS could fully replace our action filters system because it had a similar and even better mechanism for controlling ability executions. After we made this transition, we started moving all player actions to separate abilities, which made our AHBaseCharacter and AHPlayerCharacter classes (yes, our classes have AH prefixes) much lighter.
Now, we could create lots of abilities without needing to design the architecture of our own abilities system. It was a real time-saver. But the more abilities we added, the more complex gameplay interconnections/dependencies we got.
It’s fair to say we had some challenges working with GAS early on, but these issues wouldn’t come close to developing and implementing our own system.
One of our brainstorming sessions
Once we found the best formula for abilities and setting up effects, we got everything under control.
Eventually, with this new-found approach, we started to move AI characters’ actions to their separate abilities. It made our Behavior Trees lighter and gave us more agility to set up characters using our data-driven AI pipeline. Now, let’s dive into some of our GAS practices.
For those who are not familiar with the system, in a regular GAS pipeline, there are four main classes: the Ability System Component that attaches to the owner, the Attribute Set that is defined as the owner's property, the Gameplay Effect that could be applied to the owner of the Ability System Component and modifies attributes defined in an Attribute Set, and finally, there is the Ability, the action controlled by Gameplay Tags and Effects. There are also optional classes like Gameplay Task (which are sub objects of Abilities) and Gameplay Cues that help provide visual feedback from Gameplay Effects.
Gameplay tags rule them all
So, first of all, it's worthwhile elaborating on gameplay tags. This subsystem was added to UE4 earlier than GAS. It provided the possibility to define and use hierarchical tags (labels) that could be applied to objects and used for many different purposes, for instance, to distinguish different types of damage.
This image shows our common Gameplay Tag categories
In our case, it was very important to strictly organize the hierarchy of gameplay tags. Each ability has its own ability tag (ability subtree) and its own prohibition tag (prohibition subtree).
Having everything organized helps to keep control and maintain the same approach for working with abilities on the entire project.
Gameplay tags for base character abilities
One of the decisions made at the beginning of the integration that helped us replace action filters was the use of prohibition effects. These effects can be added to characters by abilities or directly from other places. Their only purpose is to grant tags that block ability activations.
One example is the RestoreStamina ability. It’s always an active ability that applies the RestoreStamina gameplay effect. This is an infinite effect. Its main goal is to restore player character’s stamina by adding a constant float value every 500 milliseconds to the stamina attribute defined within the player character’s AttributeSet (a special class that is required for specifying all attributes of the GAS component owner). In the event any other ability grants the prohibition tag “Prohibitions.RestoreStamina,” the effect GE_RestoreStamina will stop because it’s defined in its asset settings (see the screenshot below).
Most of the abilities in our game don’t activate in case there is an applied prohibition tag that prohibits this ability. Things can get very complex in a game like Atomic Heart, where a player has more than 50 abilities.
Before I share our approach to debugging the tag-based ability execution rules/prerequisites, I would like to share one more best practice about cost effects. This kind of effect is needed when you have to spend some resources to execute an ability, and, of course, if such an effect is specified for ability, it will be checked before applying. For example, in our game, an ability for a super melee hit has an instant stamina cost. If a player character runs out of stamina, this ability can’t be executed. But if he has enough stamina, the ability will be executed and the stamina will be reduced by the value specified in the cost effect.
Our own debugging tools
During development using GAS, it’s easy to get many abilities, effects, tags, and gameplay cues. So when we add a new ability, we have to take it all into account. Otherwise, it’s easy to get “stuck in abilities,” which is a term that we use to describe a situation when one currently active ability blocks many others and the player literally can’t do anything. We created a very simple and informative debug message that’s drawn in the viewport to work around that. It shows currently applied tags and effects on the player character. And for the AI, we also draw widgets that are drawn at the AI character’s location and contain the same information, but with the respective AI character’s abilities.
Using this information, you can easily understand what tags/effects should be and shouldn’t be active. So, in most cases, it can be easily resolved as a data fix within the Gameplay Ability assets inside the editor.
At first glance, it looks chaotic, but each widget is collapsable and can be highlighted by mousing over it.
The Gameplay Ability System went through uncertain times before being supported again with an official project example and livestream. These days, more and more developers integrate this system into their projects and are sharing their knowledge with the community.
As a team, we’re very happy having such powerful and cool instruments that help out our daily work, and we hope that our experience described in this post will be helpful or inspiring for other developers.
We’d also like to give JetBrains a special shoutout for granting us tools that helped us along our development process.
If you’re interested in hearing more about how our work is progressing, follow us on Twitter and Facebook and add Atomic Heart to your Steam wishlist! And if you’re interested in becoming a part of our team, please visit our career page!
Get Unreal Engine today!
Get the world’s most open and advanced creation tool.
With every feature and full source code access included, Unreal Engine comes fully loaded out of the box.