Skip to content

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:

  1. Create you projectile, either implementing CombatProjectileInterface or extending UNinjaCombatProjectileActor.
  2. Create an Attack Ability extending from UCombatAbility_Attack.
  3. Configure the ability tags as needed. Make sure to set bIsRangedAttack to true. Provide an Animation Montage, via the Animation Provider property.
  4. 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.

Implementing the Ranged Interface

// ----------
// .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.

Ranged Animation Notify

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:

  1. First you can use any implementation of ICombatProjectileInterface. It may be a subclass of UNinjaCombatProjectileActor, or any other class.
  2. You can also extend the base UNinjaCombatProjectileActor modifying certain aspects and even exposing its protected values to external objects if needed.
  3. The UNinjaCombatProjectileRequest has a ModifyProjectile that can be overriden. It's invoked even before the projectile finishes spawning.
  4. You can subclass the AnimNotify_LaunchProjectile and modify the GetProjectileClass function to potentially select other Projectile classes, based on certain conditions.