top of page
Combat System
About
In this project I created a system that allows adding and removing features(called abilities) in an easy way. The goal of the system to keep objects independent and still able to communicate between each other.
Project Info
-
Role: Gameplay and Animation Programmer,
-
Time: 6 Weeks
-
Team Size: 1
-
Engine: Unreal Engine
-
Engine Experience: 10 months
-
Programming Experience: 2 years
Background
The system implemented in this project can be the most advanced system I've ever made. I researched how data driven systems operate, studied how Unreal Engine's Lyra project is functioning and researched in great details how Gameplay Ability System is functioning in Unreal.
I had one personal question that I managed to answer during this project. "How do you keep animations independent from Game logic and still manage to have functions react at certain frames of the animations."
I think everyone who programmed a game knows how much of a hassle is to remove features without breaking anything else. Sometimes you might even need to add the same features later again after removing it. I managed to address that problem in the system.
In order to explain this system, I'm going to use my Combat system that it was designed for.
WARNING: it is more code heavy page, so it may contain less video\images. Instead, there will be simple explanations on how the system operates
The System
How does it all work
First of all, I would like to provide you with an overall view on how the system works. There are a few key parts to the whole system. I will walk you through all of them.
Key parts
-
Gameplay tags - (Tags\Indefications)
-
CombatAbility - UObject (Directors)
-
CombatTask - GameplayTasks (Workers)
-
CombatSystemComponent - (Main hub)
GameplayTags
The role
The system uses GameplayTags to provide a fast and independent way for different object to work together.
Directors use tags to send messages that other directors might use. In other words Directors don't know anything but the tags about each other.
The communication happends through tags in the MainHub (CombatSystemComponent)
Simplicity and flexibility
Since the features (combat abilities) are using gameplay tags, it is very easy to add and remove tags from the editor.​
Accessing tags from C++ side was a little bit more complicated, therefore I made a static class that not only adds GameplayTags to the editor, but also provides access to them in C++.
Challenge - TagCount
During development, I realised that I need to keep track not only on added\removed gameplay tags, but also how many times it happenned.
Let me explain. Let's say we have two active abilities that block movement. Then one of them gets deactivated and it takes away "BlockMove" message(Tag). Despite having the second ability active the player can now move since "BlockMove" Tag was removed.
To solve that problem I made GameplayTagContainerWithCount struct.
It has a Map\Dictionary that keeps track on how many tags are added and removed.
Combat Ability - Director
The role
Combat abilities (AKA Directors) are the features themselves. They need to be added to the MainHub(CombatSystemComp) to apply functions to the controlled avatar.
The directors' have several responsiblties.
-
know when they can get activated.
-
know about what directors they can\cannot work with by providing \reading GameplayTags stored in the MainHub.
-
Directors can utilize Workers(GameplayTasks) to perform their functions e.g. move objects, play animations.
Compatability
Directors may have restictions. For example, one ability cannot get activated while the other one is active. Another example can be when an ability gets activated it should cancel other abilities.
This is done by using Tags. Each director can supply with information about how it should work with other Tags.
How to trigger it
In the project there are two ways how to trigger abilties: Input and Tags.
The way abilities work with input I'm discussing here.
Another way is to send Tags as messages through a function from a library. This will make Abilities that are interested in listening to those Tags get activated (if possible).
How does it work
I'll provide an example on how the Directors work. There are a few important functions to consider when creating new Directors.​
Important Functions
-
ActivateAbility
-
CanActivateAbility
-
EndAbility
-
OnGiveAbility
Example
I include here Hit ability that is triggered when the avatar received "Hit" Tag. It simply calculates what animation should play based on Current Transform and Location of the Hit and plays it.
CombatTasks - Workers
The role
The role of the worker is simply perform one corresponding task and broadcast messages when something has happened. For instance, play animation montage task broadcasts when the animation is finished, interrupted, blended out or AttackTrace Task broadcasts if it has hit something.
Example
There isn't much to say about the tasks, I'll simply provide you an example of a task in action.
We have a Director that wants to perfrom a dodge. For that it employs two Workers (Tasks). One worker plays a dodge animation, while the other changes the location of the actor.
AnimationWorker - line 21 (UCombatSystem_PlayMontage)
MoverWorker - line 26 (UCombatSystem_ApplyMotion)
CombatSystemComponent - MainHub
The role
This component is the skeleton of the whole system. Directors, tags and workers are functioning in unison thanks to this component. CombatSystemComponent (MainHub) is responsible for
-
Granding and Removing Combat Abilities (Directors)
-
Checking conditions for activating Directors and Activating them.
-
Storing tags
-
Processing input for abilities.
How does it work
I don't feel it's needed to explain in details how what the MainHub does. Instead, I'll explain how to work it with, hopefully you will see the advantages and conveniences of the component.
-
Attach it to the actor as any other component
-
Have the actor implement "ICombatSystem_AbilityInterface" which provides access to the MainHub (not super strict, but some Tasks might not work if the step is skipped).
-
The actor needs to call "InitAbilityActorInfo" on the component.
-
Then, you can add abilities to the component and you are ready to go.
​
Here's an example of the character doing all those steps.
​
Inputs
Challenge
Player input is required to directly trigger Directors (Combat abilities). If you think for example "Attack" action, it is a combat ability, but for the player it is triggered by a press of a button. Remember, Directors know how things work, but other classes don't and don't need to.
As I've been exaplaing, I wanted an easy way to add\remove combat abilities and it applies to the ones provoked by the input as well.
However, the Directors are triggered by Tags or at the very least accessed/found by tags.
Sending tags with input
First of all, I needed my inputs to send tags as an additional parameter.
Solution:
-
Inherit from EnhancedInputComponent
-
Add a templated function for binding actions.
-
Bind actions with a Tag parameter.
-
Have my player controller call the templated function.
Adding inputs
Player controller needed a list of Action inputs. I wanted to have a convenient way to add/remove inputs. In order to do that I made InputConfig class. It is simply a data asset c;ass that contains input actions and corresponding tags.
Triggering Directors
Last step is to trigger Directors (Combat abilities) when input with the corresponding tag is received.
Player controller simply calls functions on MainHub(CombatAbilityComponent) and the MainHub is trying to trigger the abilities.
Animations
Combo Animations
In order to implement Combo attacks I made Directors responsible for that. They are responsible to trigger animations and waiting for Tags to be received.
The montages have AnimNotifies that send different tags to the controlled actor. There are three important AnimNotifies.
-
InputWindow Start/End - between these frames the player can press next button to continue the combo. If an input isn't received the combo will be reset.
-
ReadyForTheNextAttack - the earliest frame where the next animation can start playing.
-
AttackExecution - During these frames the task for checking collisions is active.
Combo + Input
There are two combos implemented. The first one is triggered by LMB, the second by RMB. In this diagram you can see the possible sequences with just these two combos.
​
Combo Example
Combo 2 - Attack 1 --> Combo 1 - Attack 2 --> Combo 2 Attack 3
Takeaway
During this project I've been reading an immense amount of Unreal Engine's source code to see how they program things. It took a lot of time and dedication, but it was worth it as I learnt things that I would never even think about.
I think one of the personal takeaways for me was that I can read complicated code and able to learn from it. In my opinion, Unreal Engine barely has any advanced tutorials available on the internet. However, Unreal Engine is an open source engine which was the best teacher for me. It allowed me to see how professionals do things, learn from it and adapt.
The most important things I learnt here is how to communicate between object with minimal dependencies, how to divide responsibilities between different objects and how to implement features in a more data driven approach. Creating Directors(Combat abilities) allows multiple people work at the same time without interrupting each other and develop features independently.
In addition to that, Workers(Tasks) are programmed in the way that can be reused without much troubles. I think this is how it should be when it comes to developing such systems. It should be reusable in different contexts.
bottom of page

