GAS ability with contextual text

Hello,
I’m learning GAS and I’m creating a Toggle Openable ability to open/close things.

Here is what I’ve come up with:

code

// Fill out your copyright notice in the Description page of Project Settings.

include “Abilities/Capabilities/EdenGameplayAbility_ToggleOpenable.h”

include “AbilitySystemComponent.h”
include “AbilitySystemGlobals.h”
include “EdenGameplayTags.h”
include “Effects/State/EdenGameplayEffect_Close.h”
include “Effects/State/EdenGameplayEffect_Open.h”

UEdenGameplayAbility_ToggleOpenable::UEdenGameplayAbility_ToggleOpenable()
{
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
TargetRequiredTags.AddTag(EdenGameplayTags::Eden_Gameplay_Capability_Openable);
TargetBlockedTags.AddTag(EdenGameplayTags::Eden_Gameplay_State_Locked);
}

void UEdenGameplayAbility_ToggleOpenable::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);

if (!CommitAbility(Handle, ActorInfo, ActivationInfo))
{
   EndAbility(Handle, ActorInfo, ActivationInfo, true, true);
   return;
}

// Figure out the target door from event data (passed in when triggering)
const AActor\* TargetActor = nullptr;
if (TriggerEventData && TriggerEventData->Target)
{
   TargetActor = const_cast<AActor\*>(TriggerEventData->Target.Get());
}

if (!TargetActor)
{
   EndAbility(Handle, ActorInfo, ActivationInfo, true, true);
   return;
}

UAbilitySystemComponent\* TargetAsc = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(TargetActor);
if (!TargetAsc)
{
   EndAbility(Handle, ActorInfo, ActivationInfo, true, true);
   return;
}


FGameplayEffectContextHandle Context = TargetAsc->MakeEffectContext();
Context.AddSourceObject(this);

if (TargetAsc->HasMatchingGameplayTag(EdenGameplayTags::Eden_Gameplay_State_Opened))
{
   // Door is open → close it
   if (const FGameplayEffectSpecHandle SpecHandle = TargetAsc->MakeOutgoingSpec(UEdenGameplayEffect_Close::StaticClass(), 1, Context); SpecHandle.IsValid())
   {
      TargetAsc->ApplyGameplayEffectSpecToSelf(\*SpecHandle.Data.Get());
   }
}
else if (TargetAsc->HasMatchingGameplayTag(EdenGameplayTags::Eden_Gameplay_State_Closed))
{
   // Door is closed → open it
   if (const FGameplayEffectSpecHandle SpecHandle = TargetAsc->MakeOutgoingSpec(UEdenGameplayEffect_Open::StaticClass(), 1, Context); SpecHandle.IsValid())
   {
      TargetAsc->ApplyGameplayEffectSpecToSelf(\*SpecHandle.Data.Get());
   }
}

EndAbility(Handle, ActorInfo, ActivationInfo, false, false);

}

Is it a good approach?

I’m now looking on how to provide user an interaction indicator text and I’m wondering how I could somehow return a text from my ability “Open” when it targets an openable actor and “Close” when it targets a closable actor. Any idea?

Hi,

I hope you’re doing well.

I think it’s a good idea to use GAS to control interactions in the world—I use that approach myself.

The way I handle it is: I have an interaction component that manages the interaction (showing UI, updating state, calling the interaction function, etc.), but the ability is only triggered through my Interaction Gameplay Ability.

This approach works well because it separates responsibilities: the interaction component focuses on handling the actual interaction logic, while the Gameplay Ability takes care of activation, validation, and replication. That makes the system cleaner, easier to maintain, and more scalable for different types of interactions.

I think you could go with this approach as well.

Hi, thank you for your response. Yep it seems clean like you do!
Another question:

On my door actor, currently I’m switching between the ‘opened’ / ‘closed’ state using an infinite gameplay effect that applies the state tag. It is again OK? I have the impression I could also just fire some gameplay events to my door with SendGameplayEventToActor rather than applying gameplay effect. But maybe is it better to use gameplay effect because I plan for example to add blocking states like: interaction.state.locked

I suggest using an Interaction Component. The interacted actor can implement an Interaction Interface, allowing you to run any code you want inside that actor. This approach gives you the control you need.