• Customer Care
  • +1 (780) 830-8814
Gameplay Ability System

Gameplay Ability System

This article explores common questions that may arise when setting up the Gameplay Ability System (GAS) in your project. Since best practices vary by project, this guide highlights key factors to consider.

Throughout the article, the Lyra Starter Game is frequently referenced as it demonstrates many best practices. Having the Lyra project files on hand can be useful for exploring the code. Additionally, reading through Abilities in Lyra can provide valuable insights into effective implementation.

When working with GAS, it’s important to understand the debugging tools Unreal Engine provides for inspecting GAS values. For an overview, check out this article on GAS Debugging Tools.

When working with GAS it's helpful to know what tools UE provides out of the box to inspect GAS values. Check out this article that presents an overview of GAS Debugging Tools.

1. Ability System Component

Which actors can have an Ability System component?

An Ability System Component (ASC) can be added to any actor that needs modifiable attributes or gameplay tags. This includes controllable entities like characters and vehicles as well as passive objects such as destructible crates or lootable chests.

Which Player-Related Actor Should Have an Ability System Component?

In most cases, the PlayerState is the best place to add the Ability System Component (ASC). However, depending on your game design, you could also assign it to the PlayerController or Pawn.

Persistence Across Respawns

The ideal placement of the ASC depends on whether you want its state to persist across respawns.

  • For persistent attributes, buffs, debuffs, and cooldowns: Attach the ASC to an actor that remains unchanged when the player respawns, such as the PlayerState.
  • For a reset on respawn: If you want all abilities and effects to reset when a player dies and respawns, place the ASC on the Pawn.
  • For a mix of persistent and temporary effects: The best approach is to place the ASC on a persistent actor like PlayerState. It’s easier to remove specific effects manually than to migrate them between ASCs on different actors.

No PlayerState?

In single-player games, you might not have a custom PlayerState class. In this case:

  • Use the PlayerController if you want abilities and effects to persist across respawns.
  • Use the Pawn if you want them to reset upon respawn.

However, in multiplayer games, the PlayerController is not a valid ASC owner since it doesn’t exist on all clients.

For AI-controlled pawns in multiplayer, PlayerStates aren’t always assigned by default. To ensure consistency, consider enabling bWantsPlayerState in the AIController, so each AI bot gets its own PlayerState. Since AIControllers only exist on the server (and are not replicated to clients), they are not suitable for hosting an ASC, whereas PlayerStates are replicated and make managing abilities across players and AI much easier.

OwnerActor vs. AvatarActor in the Ability System Component

The OwnerActor represents the entity that persistently owns the Ability System Component (ASC), while the AvatarActor is the physical representation of that entity in the game world. Your game code should set both by calling InitAbilityActorInfo, which can be done multiple times during the ASC’s lifetime. The ASC stores these references, making them easily accessible when needed.

Using Owner and Avatar in Abilities

When working with an ability blueprint, you can retrieve both the OwnerActor and AvatarActor—whether for the entity executing the ability or its target. Some abilities require an AvatarActor, such as a dodge move that applies momentum to the character. Others, like placing a unit in an RTS game, may not need one at all.

Determining the OwnerActor

The OwnerActor is typically one of the following:

  • Player-related entities – PlayerController (PC), PlayerState (PS), or Pawn
  • AI entities – AIController or directly owned AI actors
  • Standalone objects – Objects like a lootable chest, which can serve as both its own owner and avatar

For player-controlled characters, the OwnerActor should be a Pawn, PlayerController, or PlayerState. However, any actor that is ultimately owned (directly or indirectly) by one of these is also valid. Calling GetOwner recursively on the OwnerActor should always lead back to the player’s PC, PS, or Pawn. This ensures that FGameplayAbilityActorInfo::InitFromActor correctly resolves and caches the PlayerController, which is necessary for activating locally predicted abilities.

Determining the AvatarActor

The AvatarActor is usually a Character or Pawn—something with a physical presence in the world. Since some abilities require an AvatarActor, it’s important to handle cases where it might be null. For example, when a player isn’t controlling a Pawn, abilities must account for this scenario to prevent unexpected behavior.

jlknkljhlikh

When Should You Call InitAbilityActorInfo for a Player?

InitAbilityActorInfo must be called separately on both the server and client whenever the OwnerActor or AvatarActor is created or changes during gameplay.

When to Call InitAbilityActorInfo

  • On Actor Creation: Call AbilitySystemComponent->InitAbilityActorInfo(OwnerActor, AvatarActor) as soon as either the OwnerActor or AvatarActor is available.
  • On Replication: On the client, use OnRep functions on actors referencing the Owner or Avatar to detect when they have been replicated.
  • On Initialization: The BeginPlay or PostInitializeComponents functions of the Owner/Avatar are also good places to call it.
  • On Avatar Changes: If the player switches to a new pawn (e.g., respawning or possessing a different character), you can call it again to update the AvatarActor.

Handling the PlayerController Dependency in Multiplayer

For multiplayer games, the local player’s PlayerController must be replicated before the ASC can be fully initialized.

  • InitAbilityActorInfo calls FGameplayAbilityActorInfo::InitFromActor(), which caches the PlayerController.
  • This caching step is essential for activating locally predicted abilities in multiplayer.
  • However, there’s no guarantee that the PlayerController will be replicated when the ASC first initializes client-side, as actor replication order is not deterministic.

To ensure the PlayerController is available before calling InitAbilityActorInfo, you can:

  • Use the PlayerController’s OnRep function for its owning actor.
  • For example, if the PlayerState owns the ASC, you can call:
    MyPlayerState->GetAbilitySystemComponent()->InitAbilityActorInfo();

Below is example from Lyra OnRep_PlayerState

void ALyraPlayerController::OnRep_PlayerState()
{
	Super::OnRep_PlayerState();
	BroadcastOnPlayerStateChanged();

	// When we're a client connected to a remote server, the player controller may replicate later than the PlayerState and AbilitySystemComponent.
	if (GetWorld()->IsNetMode(NM_Client))
	{
		if (ALyraPlayerState* LyraPS = GetPlayerState<ALyraPlayerState>())
		{
			if (ULyraAbilitySystemComponent* LyraASC = LyraPS->GetLyraAbilitySystemComponent())
			{
				// Calls InitAbilityActorInfo
				LyraASC->RefreshAbilityActorInfo();
				LyraASC->TryActivateAbilitiesOnSpawn();
			}
		}
	}
}

Choosing the Right Replication Mode

The Gameplay Ability System (GAS) offers three replication modes: full, mixed, and minimal. These determine how much detail about active Gameplay Effects (GE) is replicated to clients.

Replication Modes Overview

  • Full – All clients receive complete details about active Gameplay Effects, including durations and gameplay tag counts.
  • Mixed – Only the owning client receives full details, while other clients receive minimal information (only the gameplay tag set, without counts).
  • Minimal – Even the owning client receives only minimal details, which is rarely useful since most games require the player’s own client to track active effects.

Which Mode Should You Use?

A good rule of thumb is to use mixed replication mode, unless all clients need access to full effect details.

  • Use full replication if players need to see details like remaining effect durations for other players and AI-controlled characters.
  • Use mixed replication for a balance between performance and functionality—only the owning client gets full details, reducing unnecessary network traffic.

Regardless of the replication mode, attribute values are still replicated through the Attribute Set if they are marked as replicated.

Gameplay Tag Counts and Game Code

Gameplay tag counts track the number of sources (such as Gameplay Abilities or Gameplay Effects) contributing to a tag. These counts are for internal tracking only and should not be used directly in game logic.

  • Game code should only check if a tag is present, not rely on its count.
  • Unless the ASC’s replication mode is Full, tag counts will not be available on simulated proxy actors (i.e., clients that don’t own the ASC).

2. Attributes and Attribute Sets

Base Value vs. Current Value


The Base Value represents an attribute’s raw, unmodified value before any Gameplay Effects (GEs) are applied.
The Current Value is the final, modified value after all active Gameplay Effects have been factored in.
You can find a detailed breakdown of how attributes are calculated here.

Should attributes be in one or multiple attribute sets?

Whether to use a single attribute set or multiple depends on how attributes are distributed across different actor types in your project.

Start by listing the actors that will have an Ability System Component (ASC) and attribute sets.
Identify which attributes each actor needs.Group attributes logically common attributes like Health and Max Health can be in one set, while specialized attributes like Weapon Damage might be in another.
For example, in Lyra, attributes are split into multiple sets to keep things modular and efficient.

 

What is the ATTRIBUTE_ACCESSORS macro for?

#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)

If you use the macro on attributes defined in the set, like in the snippet below, it generates convenient getters and setters for the attribute Base value and the FGameplayAttribute definition.

UCLASS()
class ABILITIESLAB_API ULabHealthAttributeSet : public UAttributeSet
{
	GENERATED_BODY()

public:
	// Current health
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, ReplicatedUsing=OnRep_Health)
	FGameplayAttributeData Health;
	// Upper limit for health value
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated)
	FGameplayAttributeData MaxHealth;

	// Current shield
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated)
	FGameplayAttributeData Shield;
	// Upper limit for shield value
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated)
	FGameplayAttributeData MaxShield;

	// Damage value calculated during a GE. Meta attribute.
	UPROPERTY(VisibleAnywhere)
	FGameplayAttributeData Damage;

	ATTRIBUTE_ACCESSORS(ULabHealthAttributeSet, Health);
	ATTRIBUTE_ACCESSORS(ULabHealthAttributeSet, MaxHealth);
	ATTRIBUTE_ACCESSORS(ULabHealthAttributeSet, Shield);
	ATTRIBUTE_ACCESSORS(ULabHealthAttributeSet, MaxShield);
	ATTRIBUTE_ACCESSORS(ULabHealthAttributeSet, Damage);
}

The value setters are useful for attributes whose changes tend to be permanent, like health. The attribute definition getter is useful for testing during attribute set events, which is the affected attribute like below. The attribute definition getters are static functions so can also be retrieved from elsewhere, like in GameplayEffectExecutionCalculations.

void ULabHealthAttributeSet::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue)
{
	Super::PostAttributeChange(Attribute, OldValue, NewValue);

	// GetHealthAttribute() : FGameplayAttribute is generated by ATTRIBUTE_ACCESSORS
	// It provides an identifier for the 'Health' attribute.
	if (Attribute == ULabHealthAttributeSet::GetHealthAttribute())
	{
		…
	}
}

How to Add Attribute Sets to an Actor

There are four main ways to attach Attribute Sets to an actor:

  1. Default Subobject in C++ Constructor (Code only)
  2. PostInitializeComponents / BeginPlay (Code only)
  3. Adding at Runtime (Code only)
  4. Using DefaultStartingData in Blueprint (Editor)

Option 1: Default Subobject (DSO) – Best for Known Attribute Sets

If you already know which Attribute Sets a C++ actor class requires, the best approach is to create them as Default Subobjects in the constructor using CreateDefaultSubobject().

Why Use DSOs?
  • No need for replication – DSOs exist on both server and client by default.
  • Immediate access – Clients can use them right away, such as binding to delegates, without waiting for replication.
  • Better performance – Since DSOs aren’t runtime-replicated UObjects, they reduce network overhead.

This method is the most efficient and recommended when the Attribute Set is known at compile time.

AAbilitiesLabCharacter::AAbilitiesLabCharacter()
{
	LabAbilitySystemComp = CreateDefaultSubobject<ULabAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
	HealthSet = CreateDefaultSubobject<ULabHealthAttributeSet>(TEXT("HealthSet"));
	CombatSet = CreateDefaultSubobject<ULabCombatAttributeSet>(TEXT("CombatSet"));
}

ffsfsafasf

Advantages of Using Default Subobject (DSO) Attribute Sets

  • Immediate Access – Clients can access DSO attribute sets right away, without waiting for replication.
  • Useful for Event Binding – This is especially helpful when binding to delegates early in gameplay.

Important Considerations

1. Prevent Garbage Collection

  • Always store a UPROPERTY() reference to the DSO attribute set.
  • If not referenced, it may be garbage collected, especially in persistent level map actors.
  • This is important in Play-In-Editor (PIE) since Unreal performs a garbage collection pass after duplicating the world.

2. Blueprint Defaults Are Not Available in the Constructor

  • At construction time, Blueprint default and instance values aren't loaded yet.
  • This means you cannot conditionally add Attribute Sets based on Blueprint settings at this stage.

3. Binding Delegates to the Correct Instance

  • The DSO attribute set created in the constructor is an archetype, not the one used at runtime.
  • To ensure you bind to the correct instance, perform delegate bindings inside PostInitializeComponents() or BeginPlay().

4. Automatic Detection

  • Default Subobject Attribute Sets are automatically detected by AbilitySystemComponent::InitializeComponent().

Option 2: Adding Attribute Sets in PostInitializeComponents() or BeginPlay()

Another approach is to add Attribute Sets dynamically when the actor starts play, using AddSet<T>().

  • PostInitializeComponents() and BeginPlay() are good places for this.
  • Unlike the DSO approach, at this point Blueprint defaults have been loaded, allowing you to conditionally add Attribute Sets based on Blueprint settings.
void AAbilitiesLabCharacter::PostInitializeComponents()
{
	Super::PostInitializeComponents();
	LabAbilitySystemComp->AddSet<ULabHealthAttributeSet>();
	LabAbilitySystemComp->AddSet<ULabCombatAttributeSet>();
}

sfsfsfd

Contact Us

Address:

Po Box 99900 QZ 905 832, Stn Main,
Leduc,
AB, T9E 1A1,
Canada

Phone: + 1 (780) 830 8814

Email: support@dazzlesoftware.org