Skip to content

Ability System

About the plugin

Github Repository

Author plugin written for Riftborn. It implements the "attributes" and "effects" system.

  • Attributes are entities that store a float variable.
  • Effects are entities that modify attributes.

Attributes

You can create your own attributes by inheriting from UAttribute.

ArmorAttribute.h
UCLASS(Blueprintable, BlueprintType)
class HUM_API UArmorAttribute : public UAttribute
{
    GENERATED_BODY()

public:
    UArmorAttribute(const FObjectInitializer& InObjectInitializer = FObjectInitializer::Get());

public:
    virtual FString GetDebugString_Implementation() const override;
};

Info

Be sure to overwrite the GetDebugString function so that other developers can understand which attribute currently exists on the actor.


There are some events that can be useful for tracking the status of Attribute: - OnMinValueChanged - OnMaxValueChanged - OnValueChanged - OnValueMinThresholdReached - OnValueMaxThresholdReached

Effects

There are only 4 types of effect in the plugin:

  • Effect
  • PeriodicInstantEffect
  • DurationEffect
  • PeriodicEffect
classDiagram
    UAdvancedReplicatedObject <|-- UBasicAsEntity
    UBasicAsEntity <|-- UEffect
    UEffect <|-- UDurationEffect
    UEffect <|-- UPeriodicInstantEffect
    UDurationEffect <|-- UPeriodicEffect

    class UAdvancedReplicatedObject {
    }

    class UBasicAsEntity {
    }

    class UEffect {
        +OnAttributeListUpdated()
        +OnAttributeAdded()
        +OnAttributeRemoving()
        +OnEffectListUpdated()
        +OnEffectAdded()
        +OnEffectRemoving()
        +StartWork()
        +OnWorkEnded()
        +GetOwningComponent() UASComponent
    }

    class UDurationEffect {
        +float Time
    }

    class UPeriodicInstantEffect {
        +float Period
    }

    class UPeriodicEffect {
        +float Period
    }

Effect

UEffect is the base abstract class for all effects.

Effect.h
class ABILITYSYSTEM_API UEffect : public UBasicAsEntity

Note

It automatically receives notifications about changes to other effects and attributes.

When an effect is added, it starts working. When an effect is removed, it finishes the job.

Effect.h
UFUNCTION(BlueprintNativeEvent, Category="Effect|Main")
void StartWork();

UFUNCTION(BlueprintNativeEvent, Category="Effect|Main")
void OnWorkEnded();

Effect life cycle

You can call EndWork() to terminate the effect.

This will call the EffectHasEnded(this) method of the effect owner (GetOwningComponent()), which will properly clear memory and remove the effect from the array by calling OnWorkEnded() and ConditionalBeginDestroy() on it

PeriodicInstantEffect

PeriodicInstantEffect starts a looped timer for an "Tick" action.

PeriodicInstantEffect.h
class ABILITYSYSTEM_API UPeriodicInstantEffect : public UEffect

Note

On StartWork() it calls RunPeriodicTimer(), on EndWork() it calls ClearPeriodicTimer()

PeriodicInstantEffect.cpp
void UPeriodicInstantEffect::RunPeriodicTimer()
{
    FTimerManager& manager = GetWorld()->GetTimerManager();
    if (manager.IsTimerActive(PeriodicTimerHandle))
        manager.ClearTimer(PeriodicTimerHandle);
    manager.SetTimer(PeriodicTimerHandle, FTimerDelegate::CreateUObject(this, &UPeriodicInstantEffect::PeriodTick),
                     FMath::IsNearlyZero(Period) ? 0.1f : Period, true);
}

void UPeriodicInstantEffect::ClearPeriodicTimer()
{
    FTimerManager& manager = GetWorld()->GetTimerManager();
    if (manager.IsTimerActive(PeriodicTimerHandle))
        manager.ClearTimer(PeriodicTimerHandle);
}

You just need to override PeriodTick() in your class for looped actions.

PeriodicInstantEffect.h
/**
* @brief Called periodically
*
* The `PeriodTick` function is executed at regular intervals while the effect is active. It allows the developer
* to define custom logic that should occur periodically, such as applying damage, healing, or other time-based effects.
* This function is intended to be overridden in derived classes for specific behavior.
*/
UFUNCTION(BlueprintNativeEvent)
void PeriodTick();

You can adjust the intervals of the effect in Blueprints by changing the ‘Period’ field.

PeriodicInstantEffect.h
UPROPERTY(BlueprintReadWrite,
    EditDefaultsOnly,
    meta=(UIMin="0.0001",
        ClampMin="0.0001"),
    Category="PeriodicEffect")
float Period;

DurationEffect

This effect activates a timer at the beginning of its work. When the timer expires, the effect automatically ends. Useful for temporary effects such as "damage protection".

DurationEffect.h
class ABILITYSYSTEM_API UDurationEffect : public UEffect

You can adjust the time of the effect in Blueprints by changing the ‘Time’ field.

DurationEffect.h
UPROPERTY(BlueprintReadWrite,
    EditDefaultsOnly,
    meta=(UIMin="0.0001",
        ClampMin="0.0001"),
    Category="DurationEffect")
float Time;

PeriodicEffect

This effect inherits UDurationEffect, but has the same tick logic as PeriodicInstantEffect.

Tip

Can be useful for effects like ‘Health Regeneration’, where the effect restores 25 health every second, but disappears after 5 seconds.

PeriodicEffect.h
class ABILITYSYSTEM_API UPeriodicEffect : public UDurationEffect
PeriodicEffect.h
UFUNCTION(BlueprintNativeEvent)
void PeriodTick();

Ability System Component

This class is the main manager in the effects and attributes system. It contains effects and attributes as an array of objects and operates them.

ASComponent.h
class ABILITYSYSTEM_API UASComponent : public UActorComponent
flowchart LR
    UASComponent -->|TArray| Effects
    UASComponent -->|TArray| Attributes

    subgraph Effects
        Effect1
        Effect2
        ...
    end

    subgraph Attributes
        Attr1
        Attr2
        ...
    end

Warning

Since this is a actor component, there is no limit to the number of component per actor, but 1 manager per 1 actor is recommended. This will make it easier to manipulate objects


There are some events that can be useful for tracking the status of Ability System Component:

  • OnEffectAdded
  • OnEffectStacked
  • OnEffectRemoved
  • OnEffectListUpdated
  • OnAttributeAdded
  • OnAttributeRemoved
  • OnAttributeListUpdated

Tip

You can overwrite any methods in a class for custom managers

Attribute Management

You can use the following methods to handle the attributes of this component:


Add Attribute

This method adds a new attribute on a class basis. You will be able to edit the attribute after adding it.

ASComponent.h
virtual UAttribute* AddAttribute(TSubclassOf<UAttribute> AttributeClass);

Notification system

All attributes and all effects will be notified of the new attribute and that the attribute list has changed.

for (int32 i = 0; i < n; ++i)
{
    Attributes[i]->OnAttributeAdded(temp);
    Attributes[i]->OnAttributeListUpdated();
}

Duplicate exception

You can only add one attribute of a single class. If you try to add another duplicate, the method will ignore the attempt and return nullptr.

if (IsValid(entity) && entity->IsA(AttributeClass)) return nullptr;


Remove Attribute

The methods remove the attribute from the attribute array.

ASComponent.h
virtual void RemoveAttributeByEntity(UAttribute* InAttribute);
virtual void RemoveAttributeByClass(TSubclassOf<UAttribute> AttributeClass);

Notification system

All attributes and all effects will be notified of the selected attribute and that the attribute list has changed.

Memory clearing

The object is cleared from memory after deletion and is no longer available. Make sure you clear memory (timers, allocated memory within the object, linked systems, etc.)


Get Attribute

The method returns the first attribute in the list that matches the class. If there are two attributes with the same class in the array, it will return the first one.

ASComponent.h
virtual UAttribute* GetAttribute(TSubclassOf<UAttribute> AttributeClass);

Object Class

The object class check is performed as follows:

if (IsValid(attribute) && attribute->IsA(AttributeClass))

Nullptr return

Returns nullptr if the attribute could not be found.


Has Attribute

Checks if the attribute is in the list. Uses GetAttribute() to get the attribute.

ASComponent.h
virtual bool HasAttribute(TSubclassOf<UAttribute> AttributeClass) const;


Get Attribute List

Getter for a list of attributes. Returns the current state of attribute list

ASComponent.h
virtual void GetAttributeList(TArray<UAttribute*>& OutAttributes);

Attribute Execution order

1. Attributes.Add()
2. Attribute->StartWork()
3. Attributes:  OnAttributeAdded, OnAttributeListUpdated
4. Effects:     OnAttributeAdded, OnAttributeListUpdated
5. OnAttributeAdded.Broadcast()
6. OnAttributeListUpdated.Broadcast()
1. OnWorkEnded()
2. Attributes:  OnAttributeRemoving()
3. Effects:     OnAttributeRemoving()
4. OnAttributeRemoved.Broadcast()
5. Attribute->ConditionalBeginDestroy()
6. Attributes.RemoveAt(index)
7. Attributes:  OnAttributeListUpdated()
8. Effects:     OnAttributeListUpdated()
9. OnAttributeListUpdated.Broadcast()

Effect Management

You can use the following methods to handle the attributes of this component:

Add Effect

The method adds the effect to the list of effects.

ASComponent.h
virtual UEffect* AddEffect(TSubclassOf<UEffect> EffectClass);

Notification system

All attributes and all effects will be notified of the selected effect and that the effect list has changed.

Stacking

If a duplicate effect by class is found, it will try to call Stack().

a. If Stack is successful, OnEffectStacked() will be called.

b. If Stack is not possible, the temporary object will be deleted and the method will return nullptr.


Remove Effect

The methods remove the effect from the effects array.

ASComponent.h
virtual void RemoveEffectByEntity(const UEffect* InEffect);
virtual void RemoveEffectByClass(TSubclassOf<UEffect> EffectClass);

Notification system

All attributes and all effects will be notified of the selected effect and that the effect list has changed.

Memory clearing

The object is cleared from memory after deletion and is no longer available. Make sure you clear memory (timers, allocated memory within the object, linked systems, etc.)

Effect life cycle

You don't have to remove the effect from its owner component. You just need to call EndWork() on the effect.

This will call the EffectHasEnded(this) method of the effect owner (GetOwningComponent()), which will properly clear memory and remove the effect from the array by calling OnWorkEnded() and ConditionalBeginDestroy() on it


Get Effect

The method returns the first effect in the list that matches the class. If there are two effects with the same class in the array, it will return the first one.

ASComponent.h
virtual UEffect* GetEffect(TSubclassOf<UEffect> EffectClass);

Object Class

The object class check is performed as follows:

if (IsValid(ef) && ef->IsA(EffectClass))

Nullptr return

Returns nullptr if the effect could not be found.


Has Effect

Checks if the effect is in the list. Uses GetEffect() to get the attribute.

ASComponent.h
virtual bool HasEffect(TSubclassOf<UEffect> EffectClass) const;

Get Effect List

Getter for a list of effects. Returns the current state of effect list

ASComponent.h
virtual void GetEffectList(TArray<UEffect*>& OutEffects);

Replication

The plugin fully supports replication and uses the Unreal Push Network Model to optimise network.

To replicate UObjects, the plugin uses ReplicatedObject.

For full functionality, you must enable Push Model in the project and replicate the ASComponent in constructor

AbilitySystemComponent = CreateDefaultSubobject<UASComponent>(TEXT("AS"));
AbilitySystemComponent->SetIsReplicated(true);

DefaultEngine.ini
[SystemSettings]
net.IsPushModelEnabled=1
net.PushModelSkipUndirtiedReplication=1

Examples

You can set specific values to any attribute if you have a need to dynamically configure each attribute for different actors.

AHumAbilityCharacter.cpp
void AHumAbilityCharacter::AddDefaultAttributes_Implementation()
{
    const int32 n = DefaultAttributes.Num();
    for (int32 i = 0; i < n; ++i)
    {
        const FDefaultPlayerAttribute& el = DefaultAttributes[i];
        if (el.AttributeClass)
        {
            if (UAttribute* entity = AbilitySystemComponent->AddAttribute(el.AttributeClass))
            {
                UAttributeSettingsDataAsset* settings = el.Settings.LoadSynchronous();
                if (IsValid(settings))
                {
                    ApplySettingsForAttribute(entity, settings);
                }
            }
        }
    }
}


void AHumAbilityCharacter::ApplySettingsForAttribute_Implementation(
    UAttribute* InAttribute,
    UAttributeSettingsDataAsset* InSettings)
{
    if (IsValid(InAttribute) && IsValid(InSettings))
    {
        InAttribute->SetMinValue(InSettings->MinValue);
        InAttribute->SetMaxValue(InSettings->MaxValue);
        InAttribute->SetValue(InSettings->InitialValue);
    }
}

You can cache frequently used attributes to make it easier to retrieve them in future code.

AHumAbilityCharacter.cpp
TObjectPtr<UAttribute> AHumAbilityCharacter::GetHealth() const
{
    if (!HealthAttribute || !IsValid(HealthAttribute.Get()))
    {
        if (AbilitySystemComponent)
        {
            // Remove const from the current object to modify HealthAttribute
            auto nonConstThis = const_cast<AHumAbilityCharacter*>(this);
            nonConstThis->HealthAttribute = AbilitySystemComponent->Attribute(UHealthAttribute::StaticClass());
        }
    }
    return HealthAttribute;
}

You can bind to attribute events after you have added them to list.

AHumAbilityCharacter.cpp
void AHumAbilityCharacter::BeginPlay()
{
    Super::BeginPlay();
    if (HasAuthority())
    {
        AddDefaultAttributes();
        AddDefaultEffects();
        if (TObjectPtr<UAttribute> hp = GetHealth())
        {
            hp->OnValueMinThresholdReached.AddDynamic(this, &AHumAbilityCharacter::OnHpLost);
        }
    }
}

Authority

Working with attributes and effects is possible only on the server. Check HasAuthority() before calling methods.

Replication

Attributes and Effects are replicated, so it is possible to find out the state of an entity on the client side.


You can change the value of the attributes as you like.

AHumAbilityCharacter.cpp
hp->SetValue(hp->GetCurrentValue() - FMath::Abs(Amount));

Note

An attribute value cannot be greater than MaxValue, and less than MinValue. It will be clamped.