Docs/Gameplay Moveset System/Extending the System
// docs/gameplay-moveset-system/extending.md
READ

Extending the System

Custom MovesetComponent subclasses, mesh source resolution, hit gating via interfaces

The plugin exposes three optional interfaces and one subclassing point for project-specific customisation. None of these are required for the basic flow — but each is the right hook for a common production need.

Custom MovesetComponent subclass

Subclass UMovesetComponent for game-specific overrides; pass the subclass via Apply Moveset → OverrideComponentClass. The async action enforces that the BP-added component on the actor is of this class or a subclass — useful as a sanity check that prevents Apply Moveset from running against the wrong component:

UCLASS()
class UMyMovesetComponent : public UMovesetComponent
{
    GENERATED_BODY()

protected:
    virtual bool Internal_ActivateAttack(FGameplayTag AttackTag, UAnimMontage* Montage) override
    {
        // Pre-activation hook (e.g. consume stamina, gate by status)
        if (!Super::Internal_ActivateAttack(AttackTag, Montage)) return false;
        // Post-activation hook (telemetry, ability cost, etc.)
        return true;
    }
};

Then in your Character Blueprint's Add Component → Moveset Component, set the picker to your subclass instead of the base. Apply Moveset's OverrideComponentClass = UMyMovesetComponent enforces the contract at runtime.

Common reasons to subclass:

  • Pre-activation gates — refuse the attack if stamina is insufficient, the character is staggered, etc. Override Internal_ActivateAttack and return false.
  • Custom analytics — log every attack tag + outcome to your telemetry system. Override Internal_ActivateAttack and the hit dispatch path.
  • Override hit dispatch — wrap ReportHit to apply game-wide hit-stop / hit-stun rules before the delegate fires.

Overrides should always call Super:: first (or last, depending on whether you want to intercept input or react to output) — bypassing the base implementation breaks state machine invariants.

IMovesetAnimProvider — custom mesh source

By default, the component finds the character's USkeletalMeshComponent by class. If your actor has multiple skeletal meshes (e.g. a pawn that drives a separate display mesh), implement IMovesetAnimProvider to disambiguate:

USkeletalMeshComponent* AMyCharacter::GetMovesetMesh_Implementation() const
{
    return DisplayMesh;  // not the default `Mesh` component
}

The component prefers the interface result over the default FindComponentByClass. If the interface returns null, the default lookup runs as fallback.

IMovesetMeshSourceProvider — per-slot socket source

When a Moveset: Hitbox AnimNotifyState has a non-empty MeshSourceSlot, the component asks the owner actor (via IMovesetMeshSourceProvider::GetMovesetMeshSource) for the SceneComponent that hosts the hitbox's AttachSocket. This lets you route slot tags to your equipment system's weapon meshes:

USceneComponent* AMyCharacter::GetMovesetMeshSource_Implementation(FGameplayTag MeshSlot) const
{
    if (MeshSlot == MovesetGameplayTags::TAG_Moveset_MeshSlot_Primary)
        return EquippedRightHandMesh;
    if (MeshSlot == MovesetGameplayTags::TAG_Moveset_MeshSlot_Offhand)
        return EquippedLeftHandMesh;
    return nullptr;  // empty slot = body / character mesh fallback
}

If you don't implement the interface (or return null for a slot), slot-tagged hitboxes fall back to the character's SkeletalMeshComponent. Designers using only body sockets never need to think about this.

IMovesetHitTarget — per-target hit gating

Filtering is split between two sides:

  • Hitbox side (FMovesetHitboxData::IgnoreTagsQuery) — designer-authored FGameplayTagQuery on the hitbox's mode data; "candidates whose identity tags match this query are skipped".
  • Target side (IMovesetHitTarget::GetMovesetIdentityTags) — actors return the curated set of tags that describe themselves for hit-filtering purposes (team, faction, invulnerability, downed state, …). The runtime evaluates the hitbox's query against this container.
FGameplayTagContainer AMyEnemy::GetMovesetIdentityTags_Implementation() const
{
    FGameplayTagContainer Tags;
    Tags.AddTag(FGameplayTag::RequestGameplayTag("Team.Enemies"));
    if (bInvulnerable)
        Tags.AddTag(FGameplayTag::RequestGameplayTag("Trait.Invulnerable"));
    if (bDowned)
        Tags.AddTag(FGameplayTag::RequestGameplayTag("State.Downed"));
    return Tags;
}

Then on the hitbox set IgnoreTagsQuery to e.g. "match Team.Allies OR Trait.Invulnerable" and friendly-fire / invulnerable targets are silently dropped.

If a candidate doesn't implement IMovesetHitTarget, an empty container is used (no filtering on this axis — the hitbox can still gate via bIgnoreOwner, ActorClassFilter, and the per-target / per-activation hit caps).

Custom hit detection paths

If you have a custom hit detection path (predictive client-side hits, projectile hits, hitscan weapons), call MovesetComponent->ReportHit(HitResult) directly to feed the Action Module + delegate fan-out:

FMovesetHitResult HitResult;
HitResult.HitActor = HitTarget;
HitResult.HitLocation = ImpactLocation;
HitResult.HitNormal = ImpactNormal;
HitResult.AttackTag = CurrentAttackTag;
HitResult.NativeHit = SourceHit;  // full UE FHitResult

MovesetComponent->ReportHit(HitResult);

ReportHit runs through the same dispatch fan-out as a hitbox sweep — OnHit delegate fires + Moveset.Event.Hit dispatches to Action Modules with the same NetMode rules. This is the recommended hook from any custom hit source you don't want the AnimNotifyState pipeline to drive.

Custom event tags

You can emit your own event tags (e.g. Game.Combat.PerfectParry) into the Action Module routing system. Define your tag in Project Settings, then:

FMovesetEventPayload Payload;
Payload.EventTag = FGameplayTag::RequestGameplayTag("Game.Combat.PerfectParry");
Payload.AttackTag = FGameplayTag::RequestGameplayTag("Moveset.Attack.Parry");
MovesetComponent->SendMovesetEvent(Payload.EventTag, Payload);

…and any module on the active combo node that subscribes to that tag fires. This is the recommended hook from AnimNotifys, ability tasks, or other game systems that want to talk to per-node logic without coupling to specific attack classes.

Debug-drawing your own state

The plugin's hitbox debug-draw lives in UMovesetComponent::DrawActiveHitboxes. To overlay your own per-attack debug info (cancel windows, parry frames, etc.), subclass and add to that path:

void UMyMovesetComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

#if !UE_BUILD_SHIPPING
    if (CVarShowMyDebug.GetValueOnGameThread() > 0)
    {
        DrawMyOverlay();
    }
#endif
}

Always wrap debug-only code in #if !UE_BUILD_SHIPPING so it strips from cooked shipping builds.

When to subclass vs. when to use Action Modules

Need Pattern
Per-attack damage / VFX / sound Action Module on the attack node
Pre-attack stamina check Subclass UMovesetComponent, override Internal_ActivateAttack
Post-hit telemetry Action Module subscribed to Moveset.Event.Hit (Authority NetMode)
Game-wide hit-stop Subclass UMovesetComponent, wrap ReportHit
Per-attack particle decal Action Module
Reroute hitbox attach sockets to equipment meshes IMovesetMeshSourceProvider
Friendly-fire gate IMovesetHitTarget + IgnoreTagsQuery on the hitbox

The general rule: per-attack logic → Action Module. System-wide logic → subclass.