Ranged Combat
The Ranged Combat is implemented by two main pieces: A Ranged Combatant, and a Projectile that can be launched.
The most common workflow happens when a ranged combatant starts the Attack Ability, configured as a Ranged Attack, plays an Animation Montage and receives a Gameplay Event from it, containing instructions about a Projectile to be launched.
Both participants, the Ranged Combatant and the Projectile are defined by specific interfaces in the system.
In a Nutshell
These are the steps necessary to implement a Ranged Attack:
- Create you projectile, either implementing
CombatProjectileInterface
or extendingUNinjaCombatProjectileActor
. - Create an Attack Ability extending from
UCombatAbility_Attack
. - Configure the ability tags as needed. Make sure to set
bIsRangedAttack
totrue
. Provide an Animation Montage, via the Animation Provider property. - In the Animation Montage, add a
Launch Projectile
Notify, configuring its properties accordingly. The most important properties here are the source, socket and the projectile class, the one you created on first step.
If you want to know more about each part of this process, in case you need to extend any part of it, please read on to check each participant in more details.
Participants for Ranged Combat
Multiple participants are involved to launch a projectile. Here's a diagram to show you each step. Participants are listed and the table after, but fully described along this page.
sequenceDiagram
AbilitySystemComponent->>AttackAbility: activate
activate AttackAbility
AttackAbility->>AttackAbility: listen for events
AttackAbility->>AnimationMontage: play
activate AnimationMontage
AnimationMontage->>LaunchProjectileNotify: begin
activate LaunchProjectileNotify
LaunchProjectileNotify->>ProjectileRequestInstance: initialize
activate ProjectileRequestInstance
ProjectileRequestInstance-->>LaunchProjectileNotify: instance
deactivate ProjectileRequestInstance
LaunchProjectileNotify->>AttackAbility: gameplay event
AnimationMontage->>LaunchProjectileNotify: end
deactivate AnimationMontage
deactivate LaunchProjectileNotify
AttackAbility->>SpawnProjectileTask: initialize
activate SpawnProjectileTask
SpawnProjectileTask-->>AttackAbility: instance
deactivate SpawnProjectileTask
AttackAbility->>SpawnProjectileTask: activate
activate SpawnProjectileTask
SpawnProjectileTask->>ProjectileRequestInstance: spawn
activate ProjectileRequestInstance
ProjectileRequestInstance->>Projectile: spawn
ProjectileRequestInstance-->>SpawnProjectileTask: Projectile
deactivate ProjectileRequestInstance
SpawnProjectileTask-->>AttackAbility: Projectile
deactivate SpawnProjectileTask
AttackAbility->>Projectile: set GE Spec
AttackAbility->>Projectile: launch
deactivate AttackAbility
Participant | Description |
---|---|
Attack Ability | Gameplay Ability handling the ranged attack. Plays animation and waits for targets. |
Animation Montage | The animation representing the attack. It triggers the projectile on a proper frame. |
Launch Projectile Notify | Notify that starts the Launch Projectile Request. |
Projectile Request Instance | Represents a Projectile Request, containing all information and spawn logic. |
Spawn Projectile Task | Ability tash spawning projectiles. One task can handle many requests. |
Projectile | The actual projectile spawned. |
Ranged Interface
The CombatRangedInterface
interface defines an API for actors participating in ranged combat.
This separation allows both weapon actors and characters to be equally used by the combat system, without assumptions. Therefore, designers can freely define the source of projectiles for each specific ability, so they'll either launch from a socket in a weapon's mesh or a socket in the actual character, or maybe even both in the same animation.
So first, implement this interface on any actor that will eventually launch projectiles, like a weapon or your character. This can be done either in C++ or Blueprints, following the usual approaches.
This interface has one optional, yet important function: GetProjectileSourceMesh
. By default, it will look for any
Mesh Components tagged as Combat.Component.ProjectileSource
, but this is a component search. If you want to provide
the mesh directly to avoid iterating over the components, or if you have a more complex selection logic for your mesh
source, then please override this method as needed.
// ----------
// .h
UCLASS()
class NINJATEMPLATE_API ANinjacharacter : public ACharacter, public CombatRangedInterface
{
GENERATED_BODY()
// Fast access to this actor's mesh (the interface's default implementation is slower).
virtual UMeshComponent* GetProjectileSourceMesh_Implementation(FName SocketName) const override { return Mesh; }
};
Socket-based Mesh Selection
If your weapon actor has multiple meshes, a way to select the appropriate one is to use the socket name in the function do termine which mesh should be used for that particular launch request.
Mesh Selection via Component Tags
For the Mesh selection, you can say that implementing this interface is optional, as the system will, whenever possible, try to obtain the mesh by finding a Mesh Component by Tags. This is not optional, and won't allow any custom logic. However, it's still supported as you may want to do some fast prototyping before commiting to a class structure for your project.
Projectile Interface
The CombatProjectileInterface
interface defines an API for the actual projectile launched by a Ranged Source.
This interface is very important as it provides the appropriate effect to apply on actors hit by the projectile, which is obtained by the Attack Ability which in turn uses it to create an Effect Spec ready to be applied.
All actors representing projectiles in the game must implement this interface. The Ninja Combat plugin provides a base
implementation, UNinjaCombatProjectileActor
, which uses the UProjectileMovementComponent
to operate.
Projectile Request
The process of launching a projectile begins with a Projectile Request.
Usually this request reaches the default Attack Ability, but you can create your own abilities to react to this request as it's broadcast using the usual Gameplay Event system, from the Gameplay Ability Framework.
Launch Projectile Notify
Similar to the Melee Combat counterpart, the Ranged Combat module is very animation-driven. This means that once
the Attack Ability starts playing its animation, it waits for the Launch Projectile event to be triggered, which is
done by the AnimNotify_LaunchProjectile
notifier class.
In your Animation Montage, find the correct spot where a projectile should be launched and add this modifier, as shown in the image below. Once this notify is reached, the event will be broadcast back to the Gameplay Ability.
The following parameters are available in this notify:
Parameter | Description |
---|---|
Source | Determines if the source mesh is retrieved from the owner or from a weapon. |
Weapon Query | If the source is a weapon, then this query will be used to retrieve it. |
Origin Socket Name | The socket from where the projectile will spawn. |
Projectile Class | Class representing the Projectile Actor. Must implement ICombatProjectileInterface . |
Launch Projectile Tag | Gameplay Event Tag used for the event broadcast. |
Projectile Request Class | The class representing the Request. You can create your subclasses in case you need to have any custom logic. |
Be careful changing the Event Tags
Unless there's a very good reason to change the Event Tags, you shouldn't change them. But if you must, then make sure to also change the tags in the backend Ability that will be listening to these events!
Projectile Provider Interface
The ProjectileProviderInterface
allows weapons or a pawn/character to provide specific projetiles that will be prioritized
by the Launch Notify. This is useful if you are using the same animations but want different projectiles to trigger based
on some specific criteria.
The order of priorities are: 1. Projectile class from the weapon, if the Launch Animation Notify is configured to originate from the weapon and the weapon implements this interface and actually provides a projectile. 2. Projectile class from the owner, if the owner implements this interface and actually provides a projectile. 3. Projectile class defined in the Animation Notify. 4. Projectile class defined in the Attack Ability.
Default Combat Weapon
The default combat weapon, ANinjaCombatWeaponActor
implements this interface and has a property for the Projectile
class. This is an optional property but you can choose to use it if your projectiles are weapon-oriented.
Projectile Request Instance
The Gameplay Event payload allows developers to send two optional objects. In this scenario, the first object
is an object of type UNinjaCombatProjectileRequest
, containing all details for the Projectile Request, as they
were defined in the Animation Notify.
Creating an instance of UNinjaCombatProjectileRequest
requires the following arguments:
Argument | Description |
---|---|
Owner | The owner of the instance. Usually a pawn or character playing the animation. |
Instigator | Pawn used as the instigator for the new projectile. |
Source Mesh | Mesh from a Weapon or the Owner, from which the projectile will spawn. |
Socket Name | Socket in the Mesh used to determine the initial spawn location and rotation. |
Projectile Class | Projectile Class to be spawned. |
Request Class | An optional subclass for the Projectile Request. |
Custom Logic for Projectile Requests
The Projectile Request class is made in a way that can be extended in case you need to have specific logic for the positioning or spawning of projectiles. You which case, you can implement in a subclass, instead of having to modify the Gameplay Ability or the Projectile Task.
Modifying projectiles
This class provides a ModifyProjectile
function, invoked before the projectile is actually spawned. You can override
this function with any logic necessary and then assign your custom Projectile Request to a specific Animation Notify or
globally, in the Combat Settings page.
Spawn Projectile Task
This Ability Task is responsible for actually spawning the projectile, from a request.
It is used by the Attack Ability, whenever a Launch Projectile event is received. Multiple requests can join the same Projectile Task, meaning that if one animation triggers multiple requests in a short interval, they will most likely be handled by the same Ability Task, instead of spawning multiple different ones.
You shouldn't need to worry about this task as all spawning logic is actually implemented in the Projectile Request itself, which is a class designed for subclassing/custom logic.
Modifying the Projectile Instance
If you need to modify the projectile instance, there are a few ways to do so:
- First you can use any implementation of
ICombatProjectileInterface
. It may be a subclass ofUNinjaCombatProjectileActor
, or any other class. - You can also extend the base
UNinjaCombatProjectileActor
modifying certain aspects and even exposing its protected values to external objects if needed. - The
UNinjaCombatProjectileRequest
has aModifyProjectile
that can be overriden. It's invoked even before the projectile finishes spawning. - You can subclass the
AnimNotify_LaunchProjectile
and modify theGetProjectileClass
function to potentially select other Projectile classes, based on certain conditions.