Skip to main content

Gameplay Ability System

An item can grant gameplay effects, abilities and attribute sets while it sits somewhere, and take them back when it leaves. Equip a helmet and the armour bonus applies; drop it in a bag and it does not.

What a grant is conditioned on​

Every grant — effect, ability or attribute set — answers the same three questions, and any it leaves empty it stops asking:

FilterAnswers
Container FilterWhich containers. Where the item is kept
Slot FilterWhich slots within them. Which peg it hangs on
State FilterWhich equipment states. What it is doing there

The third is usually the one you want. A container says a sword is in your hotbar; only the state says it is the one in your hand. Without it, a weapon's abilities are yours while it sits in a bag — which is almost never the intent.

StateFilter: Inventory.Equipment.State.Held      // only while actually holding it
StateFilter: (empty) // a passive charm, wherever it is

The state is resolved the same way the visuals resolve it — the item's own tag, else the one its container declares, else Stored — so a grant and a mesh can never disagree about whether a helmet in a paperdoll counts as equipped.

What level it comes out at​

A level typed into the asset means a +5 sword and a +50 sword are two assets. They should be one item with a counter.

Level SourceReads
FixedThe number on the grant. Every copy grants the same thing
Item StateA per-instance counter on the item, named by Level State Tag

Item state replicates, saves, and can be changed by a transaction like anything else, so an enchantment, a charge count or a durability tier can drive the grant with no second asset.

An item with no such counter grants zero, not the typed default

Falling back to the design-time number would make every item that was never rolled come out at whatever the designer happened to leave in the field — which is how an unrolled sword becomes a legendary one.

And Stack Mode decides whether a pile of several grants more than a pile of one: Once for an amulet you only benefit from carrying one of, Per Unit for five torches that burn brighter than one. Per Unit scales the level, so the effect has to be built to read one.

Setup​

GAS is a dependency of the plugin, so there is no module to enable. Your character needs the usual one-time setup:

AMyCharacter::AMyCharacter()
{
AbilitySystem = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystem"));
AbilitySystem->SetIsReplicated(true);
AbilitySystem->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);

Attributes = CreateDefaultSubobject<UMyAttributeSet>(TEXT("Attributes"));
}

void AMyCharacter::BeginPlay()
{
Super::BeginPlay();
AbilitySystem->InitAbilityActorInfo(this, this);
}

Implement IAbilitySystemInterface so the inventory can find the component. Then add UEDGASLinkComponent to the actor — that is what watches containers and reconciles grants.

The Grants fragment​

FEDGrantsFragment holds three lists, each entry carrying its own conditions:

GrantGives
Effect GrantsA gameplay effect applied while the condition holds.
Ability GrantsAn ability granted while the condition holds.
Attribute Set GrantsAn attribute set added while the condition holds.

Every grant shares one FEDGrantCondition:

FieldMeaning
Container FilterWhich container the item must be in.
Slot FilterWhich slot, matched against the slot tag and the entry's dynamic tags.

Empty filters mean "wherever it is carried", which suits a passive charm. A helmet narrows the container filter to equipment.

The same vocabulary as visuals​

Grants and equipment visuals read the same tags: the container's type, the slot's tag, and the entry's DynamicTags. One item cannot be worn and stowed at once, so one answer serves both.

That is why drawing a sword can change its model and its buffs in one step:

FEDSetDynamicTagsTransaction Draw;
Draw.Container = Container;
Draw.Handle = SwordHandle;
Draw.Tags = FGameplayTagContainer(DrawnTag);
Container->RequestTransaction(FInstancedStruct::Make(Draw)); // server-only

A visual state filtered on Drawn and an effect grant filtered on Drawn both react. Nothing wires them together.

Reconciliation, not events​

UEDGASLinkComponent does not apply grants on an "equipped" event. It reconciles: on any change it works out what should be granted now and makes reality match, adding what is missing and removing what no longer qualifies.

That matters because events can be missed, arrive out of order, or fire twice — on a client, on a respawn, on seamless travel. Reconciling from state cannot drift.

It also means teardown is free: the component undoes everything it granted on EndPlay, so a pawn being reused after a respawn does not keep the last life's effects.

What a granted ability can ask about its item​

An ability wants to know which item it is acting for — which weapon is firing, which armour is absorbing. GAS answers that with the spec's source object: GetCurrentSourceObject() on an ability, GetSourceObject() on an effect context. The link component fills it in through one overridable:

UObject* UMyGASLink::ResolveGrantSourceObject_Implementation(const FEDItemEntry& Item) const;
CaseWhat the ability gets
The item has an Instance ClassIts UEDItemInstance. Cast to your subclass and you have the item.
The item is on the struct fast-pathThe link component. There is no per-item object to hand out — that is what makes two hundred arrows cost zero UObjects — so nothing about the item can be recovered from it.
Your game has its own per-item objectWhatever you return. Override the function on a subclass of the link component.

That third row is the one to reach for when abilities were written against an equipment instance of your own, which is the usual shape in Lyra-derived projects:

UObject* UMyGASLink::ResolveGrantSourceObject_Implementation(const FEDItemEntry& Item) const
{
if (UObject* Mine = MyEquipment->FindInstanceForItem(Item.Handle))
{
return Mine;
}
return Super::ResolveGrantSourceObject_Implementation(Item);
}

The same object is used for ability specs and for the effect contexts of that item's effects, so an ability and the effects it applies never disagree about what granted them.

Keeping the inventory across a respawn​

Containers on the PlayerState or the Controller survive a respawn; containers on the Pawn die with it. Which is right is a game decision — a shooter loadout wants to survive, a corpse's pockets do not — and the surviving arrangement has consequences worth meeting on purpose.

A respawn is not an inventory event. Nothing moved, nothing changed, so no OnInventoryChanged fires. Anything waiting for one waits forever.

Nothing is cleared for you. The framework will not empty a dead player's bag, because it cannot know whether that is what dying means in your game. Decide on the server at the moment you decide someone died:

RuleWhat to do
Keep everythingNothing. This is the default.
Drop everythingExtract the entries on death and spawn them in the world.
Keep someQuery by tag or category and extract the rest.
Grant a starting loadout on spawnCheck first. A blind TryAddItem on every spawn is how a player ends up with the rifle they picked up and the starting pistol on top of it.

Anything applied to the pawn has to be re-derived. Meshes, anim layers, an actor in the hand, anything your own UEDContainerObserverComponent subclass sets up: it belonged to the pawn and died with it. The entries still say the item is held; the new pawn's hands are empty. Two steps fix it:

  1. Gate CanObserve on the avatar, so the observer applies nothing — and drops nothing silently — while there is no pawn to act through:

    bool UMyBridge::CanObserve_Implementation() const
    {
    return Super::CanObserve_Implementation() && GetPawnToActThrough() != nullptr;
    }
  2. Call NotifyObservationContextChanged() from OnPossessedPawnChanged, or wherever the new pawn becomes known. The observer re-derives every item it holds against the pawn that exists now. Without the call it still recovers, but only on the next safety-net poll — half a second of empty hands on every respawn.

Visuals stay on the Pawn

UEDEquipmentManagerComponent looks for its avatar mesh on its own actor, so equipment visuals belong on the Pawn even when the containers do not. What an actor OWNS can outlive the body; what is ATTACHED to a body cannot.

Attribute-driven capacity​

A container's Slot Count Mode can be Attribute, reading a GAS attribute for its capacity:

Slot Count Mode:    Attribute
Capacity Source: Capacity From Attribute (GAS) -> MyAttributeSet.CarryingCapacity

A Strength perk then grows the bag with no inventory code at all. Editor validation refuses Attribute mode with no attribute set, since that failure is otherwise silent.

The same applies to FEDWeightLimitRule, which can read a weight cap from an attribute.

Core does not depend on GAS​

The bridge is its own module, EDInventoryGAS. Core does not link GameplayAbilities, so a game that never grants an ability does not pay for the ability system.

What Core keeps is the seam. Capacity from an attribute is a FEDCapacitySource picked in a layout, and FEDCapacityProviderRegistry is what resolves one — Core defines the question, and the GAS module registers the implementation that answers it with an attribute. Add your own source the same way and Core never learns what it reads.

Grants work the same way round: fragments describe them, UEDGASLinkComponent applies them, and an item with a Grants fragment on an actor with no ability system is inert rather than broken.

Moving the bridge out of Core moved its types

FEDGrantsFragment and its friends are now /Script/EDInventoryGAS.*. An asset saved before that finds them through the [CoreRedirects] block the plugin ships in Config/DefaultEngine.ini — copy those lines into your project's Config/DefaultEngine.ini, because core redirects are read before plugin configs merge.

Where next​