Gameplay Ability System + Turn Based

Hi all,

I’ve been looking into the GAS and it looks amazing. The game I’m working on is a traditional CRPG with turn-based combat so there are a lot of skills and status effects etc that would benefit from a system like GAS. However, it seems to be more directed at games with realtime combat. Would it be a lot of effort to integrate it into a turn-based combat system?

Has anyone used GAS for a game without real-time combat? Do you have any advice?

Bump. I too would like to know the answer to this. I get the impression it’ll generally work out as I’m also looking to integrate into a turn base game, but I think the real challenges will be around the over time stuff for effects and perhaps the cool-down aspect. Heal or damage over time, buffs, and cool downs really need to be per turn, not per time, which doesn’t seem to be supported. It would be great if the Duration Policy had a Has Turn option, even if that means me telling the AbilitySystem when turn has finished or started. Otherwise all those things need to be considered as Instant duration and have a way to re-apply them every turn? I’m definitely open to better ideas but so far it seems worth it but we’ll see.

Edit: Came across this that looks promising.

I haven’t used it outside of realtime combat, however if you have a lot of skills and effects, GAS is set up to handle a lot of the management for you. If I were to design my own turn-based game, I’d definitely use GAS if it was a larger project.

I’ve found that it’s worth learning about, and you don’t have to use every aspect of it if you do your side properly. I pretty much just use it for abilities, tasks, effects, and the tag system.
The Unreal Slackers discord also has an entire room dedicated to it. You can get an answer there within minutes if you’re lucky.

Yes I have used the Ability system in a turn based project. It is not designed to be used in this way. I haven’t touched this in a while but there are significant problems with managing active effects. There are ways to manage this and it is still a great system. You can use for it turn based project though perhaps not so effectively as what it was designed for.

That is re-assuring, but could you speak to high-level interaction of using GAS in turn based games? Or rather, how does one hack it? How might one implement a 1-turn cooldown on an ability? Or a turn-based damage over time (DOT)? For example, are DOTs considered instant or permanent and refreshed manually somehow? I think a direction for thinking about this would be super helpful.

In our project, we write a custom AbilityTimerManager to replace the default TimerManager. Change some GameEffect coed, an example is below.

			/*
			FTimerManager& TimerManager = Owner->GetWorld()->GetTimerManager();
			FTimerDelegate Delegate = FTimerDelegate::CreateUObject(Owner, &UAbilitySystemComponent::CheckDurationExpired, AppliedActiveGE->Handle);
			TimerManager.SetTimer(AppliedActiveGE->DurationHandle, Delegate, FinalDuration, false);
			if (!ensureMsgf(AppliedActiveGE->DurationHandle.IsValid(), TEXT("Invalid Duration Handle after attempting to set duration for GE %s @ %.2f"), 
				*AppliedActiveGE->GetDebugString(), FinalDuration))
			{
				// Force this off next frame
				TimerManager.SetTimerForNextTick(Delegate);
			}
			*/
			auto& TimerManager = *UAbilitySystemGlobals::Get().GetTimerManager();
			FTimerDelegate Delegate = FTimerDelegate::CreateUObject(Owner, &UAbilitySystemComponent::CheckDurationExpired, AppliedActiveGE->Handle);
			TimerManager.SetAbilityTimer(Owner, AppliedActiveGE->DurationHandle, Delegate, FinalDuration, false);
			if (!ensureMsgf(AppliedActiveGE->DurationHandle.IsValid(), TEXT("Invalid Duration Handle after attempting to set duration for GE %s @ %.2f"), 
				*AppliedActiveGE->GetDebugString(), FinalDuration))
			{
				// Force this off next frame
				TimerManager.SetAbilityTimerForNextTick(Delegate);
			}

Then I can control the timer manually.

void UGameBlueprintFunctionLibrary::TickAbilityTurn(UAbilitySystemComponent* source, int delta)
{
	auto timerManager = GetTimerManager();
	if(timerManager == nullptr)
	{
		return;
	}
	timerManager->TickTurn(source, delta);
}

Hello, i have the same problem and i would like use your approach, but question, how did you override that method in the gameplay effect if it is not virtual? To do this you need make lot of copies of other classess and it is a huge pain, how did you do that?

I will leave this info here, since it is never old to necropost something regading UE solutions.

TLDR. Any numeric data related to effects can be managed with SetByCaller magnitudes. You can store cooldown turns remaining directly inside SBC Data of said effect, and decrease/increase it, and then remove said effect with your own logic , i.e at turn end event.

You will need to do few things:

  • Tags or Names to store data on effect (avoid overlap with any attribute calculation related data)
  • Functions to Read Write SetByCaller Data from ActiveEffect
  • Way to handle turn start / end , any other means to tick duration and remove effect if said effect SetByCaller value dropped below 0.

I will give a brief steps creating a cooldown effect and using it with ability.

Create a BlueprintLibrary in C++:

Active Effect SetByCaller Accessors

I found easiest way to create Static Library . Note ASC has function to set SetByCallerData on active effect (which is stored in spec anyaway), read can be done directly from Spec.

float UPJCAbilitySystemBlueprintLibrary::GetActiveEffectSetByCallerMagnitude(FActiveGameplayEffectHandle SpecHandle, FGameplayTag Tag)
{

	float Result = -1;
	UAbilitySystemComponent* ASC = SpecHandle.GetOwningAbilitySystemComponent();
	if (ASC)
	{
		auto Effect = ASC->GetActiveGameplayEffect(SpecHandle);
		if(Effect)
		{
			Result = Effect->Spec.GetSetByCallerMagnitude(Tag,false,-1);
		}
	}
	return Result;
}

void UPJCAbilitySystemBlueprintLibrary::SetActiveEffectSetByCallerMagnitude(FActiveGameplayEffectHandle SpecHandle, FGameplayTag Tag, float Magnitude)
{
	float Result = -1;
	UAbilitySystemComponent* ASC = SpecHandle.GetOwningAbilitySystemComponent();
	if (ASC)
	{
		ASC->UpdateActiveGameplayEffectSetByCallerMagnitude(SpecHandle,Tag,Magnitude);
	}
}
Cooldown Effect

Setup Effect Components with :

  • Asset Tags to filter it later in manager ability.
  • Granted Tag for Ability to know it is on cooldown.

Ability With Cooldown

Add properties to control cooldown, and duration on your TurnBasedAbilityBase class ,as well as i use base class cooldown effect class property field.


	UPROPERTY(EditDefaultsOnly, Category = Cooldowns)
	int32 CooldownInTurns = 1;

	UPROPERTY(EditDefaultsOnly, Category = Cooldowns)
	FGameplayTag SetByCallerTurnBasedCooldownTag;

// Override ApplyCooldown function to create spec,inject SetByCaller data andapply to self.

void UTurnBasedAbilityBase::ApplyCooldown(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) const
{
	UGameplayEffect* CooldownGE = GetCooldownGameplayEffect();
	if (CooldownGE)
	{
		FGameplayEffectSpecHandle SpecHandle = MakeOutgoingGameplayEffectSpec(Handle, ActorInfo, ActivationInfo, CooldownGE->GetClass(), 1);
		if(FGameplayEffectSpec* Spec = SpecHandle.Data.Get())
		{
			Spec->SetSetByCallerMagnitude(SetByCallerTurnBasedCooldownTag, CooldownInTurns);
			ApplyGameplayEffectSpecToOwner(Handle,ActorInfo,ActivationInfo,SpecHandle);
		}
	}
}
Manager Ability
  • Create regular ability.
  • Add a Trigger Data , i use events to control all combat events.
  • Override Activate From Event Function and do :
  • Grant this ability to your ASC owner Actor on BeginPlay
  • Send Turn Related evens from your Combat Manager to all relevant Actors.

Additionaly:
You might want to subscribe to FAbilitySpecDirtied AbilitySpecDirtiedCallbacks delegate inside ASC to watch over any Spec changes, it should include our SetByCaller magnitudes too, i.e for UI display.

You might want to pay attention to client/server things in case you networking this.

You might want to use different route for ability to indentify it is on cooldown, different from effect granted tags, then look into ASC functions like : GetCooldownTags(), CheckCooldown() and there is also FName SetByCaller version, probably for times when you want to generate data dynamicaly.