System Design
The Combat System is designed with three layers, which are well defined and serve a specific purpose. Keep this design in mind while navigating the system or creating new functionalities.
In the following sections, let's break down each layer to understand their purpose and how each layer communicates with the next.
Design philosophy
The Combat System was designed to help you and not to force you into any corners. As long as you are using the related frameworks (such as G.A.S.) and follow the design layers and their communication, the combat system should never be in your way!
It's also important to mention that the Combat System only deals with combat! It won't "spill over" other systems, like "Locomotion", "Inventory", "Animation" and so on. It will however provide many ways for these systems to be integrated together, as Integration is a core pillar of the system design.
Triggers
Triggers are elements in the system that will activate or send events to Gameplay Abilities. They can be multiple things like Enhanced Input Action Handlers, a Behavior or State Tree task to activate a Gameplay Ability, a piece of code or functionality triggering a Gameplay Event, like an Animation Notify.
Player Input
Player Input is most likely the main input for any game. Unreal Engine currently uses the Enhanced Input system as the default way to handle Player Input.
The Combat System won't expect any particular strategy for Ability Activation and you can use whichever you prefer: Class, Tags or even Legacy Input. But your input system must be able to handle both Ability Activations and sending Gameplay Events. Thses events might also expect the original Input Action that triggered the event, specifically, the Combo System.
Ninja Input
The Ninja Combat and Ninja Input were designed to play together really well. If you haven't implemented your input system yet, please consider checking our plugin. It will handle all Ability Activations properly, it will properly create Gameplay Events and their input Payloads and it also contains an Input Buffer that might be useful for your Combat Design.
AI Agents
Another common source of combat input is the AI layer. If your game has some type of opponent or ally, then you must likely must trigger combat logic from them.
The Unreal Engine has currently two AI systems: Behavior and State Trees. You can use both to trigger Gameplay Abilities, provided that you have appropriate Tasks to do so.
Ninja Bot
The Ninja Bot is designed to provide Tasks for both Behavior and State Trees that can be used to trigger Gameplay Abilities. It also provides other useful features such as exposing Gameplay Attributes as Services or Evaluators and even an Action Token Manager.
Animation Notifies
Combat animations such as Attacks and Evades will trigger animations that may need to notify these abilities about events like Melee Scans, Launching Projectiles or Invincibility Frames.
For these specific examples, and others, the system provides Animation Notifies and Notify States, which will send Gameplay Events back to their animations.
Gameplay
This layer is built using the Gameplay Ability System and the Gameplay Targeting System (and State Trees for the Combo System, if you want to be precise). It can be considered a "Logical Boundary" for combat behaviors, where multiple calls to the Combat Core layer are made, on the source and target of an ability, from even just one invocation from the Trigger layer.
Gameplay Ability System
All Abilities, Effects and Attributes mentioned in this page are explained in more detail in the dedicated Gameplay Ability System Integration page.
Gameplay Abilities
Each Ability triggered by the Trigger layer will communicate with and orchestrate multiple elements from the Combat Core layer, in a cohesive execution unit. For example, an attack must play an animation, collect melee targets, apply a damage effect. All these logical tasks are orchestrated by a Gameplay Ability.
Many abilities are provided out-of-the box. Even though they always required some basic configuration, like the Animation Montage to play or Gameplay Tags for activation/blocking/etc, once setup, they are fully functional.
Gameplay Effects
Effects will hold a state like "Invincibility", "In Combo Window" or "Blocking", that will modify some logic in the Combat Core layer, or effects are used to modify Attributes. All Effects are applied by Abilities. Once again, multiple effects are already available for use.
Gameplay Tags
Gameplay Tags are used in many ways:
- Inform a certain state.
- Represent a Gameplay Event.
- Represent Effect Data set by the Caller.
They can be added by Abilities or Effects.
Attributes
The Combat System provides an extensive list of combat-related Attributes. Some of them are somewhat required while others can be zeroed-out if you don't have a need for them. Attributes can only be modified by Gameplay Effects.
Some classes, such as the provided Widgets, will react to attribute changes via their appropriate delegates.
Targeting
Targeting is done by the Gameplay Targeting System, which is a separate module from the Gameplay Ability System, but still a Gameplay module. In most situations where collecting targets is required by the Gameplay Layer, an option to use a Targeting Preset will be available.
Targeting Presets are a great way to obtain targets, since they can be executed asynchronously, handle multiple types of collision checks, and also apply filtering and sorting.
Combat Core
The Combat System uses multiple actor components to implement the combat logic and store the relevant states that must be replicated or observed.
In this section you can find a list of these components, their purposes, implementation strategies and how they are integrated in the system.
Component Design
The system will never access these actual components directly. Instead, it will use their interfaces as listed below. This design choice was made to simplify extension and also integration with other systems.
Each component is seen by the system as an UActorComponent
. Object interaction happens via the appropriate
interface, implemented by each component. The following table provides the Components, their interfaces and
default implementations.
Component | Interface | Default Implementation |
---|---|---|
Combo Manager | ICombatComboManagerInterface |
UNinjaCombatComboManagerComponent |
Damage Manager | ICombatDamageManagerInterface |
UNinjaCombatDamageManagerComponent |
Defense Manager | ICombatDamageManagerInterface |
UNinjaCombatDefenseManagerComponent |
Motion Warping | ICombatMotionWarpingInterface |
UNinjaCombatPhysicalAnimationComponent |
Opportunity Manager | ICombatOpportunityManagerInterface |
UNinjaCombatOpportunityManagerComponent |
Physical Animation | ICombatPhysicalAnimationInterface |
UNinjaCombatMotionWarpingComponent |
Weapon Manager | ICombatWeaponManagerInterface |
UNinjaCombatTargetManagerComponent |
The easiest way to retrieve this components from an actor is by using the UNinjaCombatFunctionLibrary
class,
which contains Getter methods for each component, receiving an AActor
as a parameter.
// ----------
// .cpp
#include "NinjaCombatFunctionLibrary.cpp"
const AActor* Owner = GetOwner();
UActorComponent* ComboManager = NinjaCombatFunctionLibrary::GetComboManagerComponent(Owner);
UActorComponent* DamageManager = NinjaCombatFunctionLibrary::GetDamageManagerComponent(Owner);
UActorComponent* DefenseManager = NinjaCombatFunctionLibrary::GetDefenseManagerComponent(Owner);
UActorComponent* MotionWarping = NinjaCombatFunctionLibrary::GetMotionWarpingComponent(Owner);
UActorComponent* OpportunityManager = NinjaCombatFunctionLibrary::GetOpportunityManagerComponent(Owner);
UActorComponent* PhysicalAnimation = NinjaCombatFunctionLibrary::GetPhysicalAnimationComponent(Owner);
UActorComponent* TargetManager = NinjaCombatFunctionLibrary::GetTargetManagerComponent(Owner);
UActorComponent* WeaponManager = NinjaCombatFunctionLibrary::GetWeaponManagerComponent(Owner);
Reinforcing Type Safety
To avoid odd errors due to the fact that all components are retrieved as UActorComponents
, the library
will perform a check
before returning each component, ensuring they implement the right interface.
Another important thing to mention about the system's components is that they provide multiple Multicast Delegates that will broadcast multiple important events. In blueprints you are still able to bind to these components as usual, but in C++ you must do so via the appropriate interfaces.
// ----------
// .cpp
UActorComponent* TargetComponent = UNinjaCombatFunctionLibrary::GetTargetManagerComponent(PossessedPawn);
if (IsValid(TargetComponent))
{
FCombatTargetChangedDelegate Delegate;
Delegate.BindDynamic(this, &ThisClass::HandleCombatTargetChanged);
ICombatTargetManagerInterface::Execute_BindToCombatTargetChangedDelegate(TargetComponent, Delegate);
CombatTarget = ICombatTargetManagerInterface::Execute_GetCombatTarget(TargetComponent);
}
Keep these delegates in mind for your own implementations
Many components will have delegates and these are exposed via their interfaces. Make sure to consider if you have to implement your own component interface implementations.
Combo Manager
For Action, Adventure and RPG games, it's very common for players to chain abilities in a combo. This functionality is managed by the Combo Manager component, which keeps track of the current attack state and triggers new attacks if the player is currently in a combo window.
In the Combat Plugin, the default implementation is based on the engine's UStateTreeComponent
.
We will continue discussing the Combo functionality in the Combo System section.
Damage Manager
This component is always expected to be present. It handles incoming damage, triggering hit reactions and the death. For more information on the Damage Pipeline, please check the Damage Handling page.
Defense Manager
This component is always expected to be present. It handles defense mechanisms such as blocking and invulnerability. For more information on the Defense Pipeline, please check the Damage and Mitigation page.
Motion Warping
Used mainly by abilities playing Animation Montages that must be positioned properly in relation to their targets.
In the Combat Plugin, the default implementation is based on the engine's UMotionWarpingComponent
.
We will continue discussing the Motion Warping functionality in the Melee Combat section.
Opportunity Manager
Used to track opportunity attacks that can be executed against certain targets.
Physical Animation
Used by the Damage System to perform physical animations from impacts.
In the Combat Plugin, the default implementation is based on the engine's UPhysicalAnimationComponent
.
We will continue discussing the Physical Animation functionality in the Damage and Mitigation page.
Target Manager
This component is always exepcted to be present. It handles the current target locked on by the owner, which is the center part of the Combat Targeting functionality.
Weapon Manager
This component is always expected to be present. It handles available weapons, providing them from Gameplay Tag Queries. For more information on this topic, please check the Weapons.