Target Data in Gameplay Abilities

Target Data is designed to be a polymorphic type you can use for a range of target providing information. The name Target Data is a remanent from Paragon where everything was really Target Data. But Target Data can be used for more than just “Targets”.

When to use Target Data

You can use target data to provide information to your abilities, to apply Gameplay Effects to hit data, etc. It differs to Gameplay Effect Context which is a transient class for providing information during an effect execution and is passed along through the entire chain of an execution. Some of the most common uses of target data is is for applying gameplay effects to actors gathered via a trace/overlap or some other mechanic.

How do I use Target Data

Using Target Data is really pretty simple for the most basic of tasks. Here is an example of a overlap which gathers actors in a range, and applies a Damage Gameplay Effect to those actors.

In C++ the same is doable with this:

	FGameplayAbilityTargetDataHandle Targets;
	for (const FHitResult& HitResult : HitResults)
	{
		FGameplayAbilityTargetData_SingleTargetHit* NewTargetData = new FGameplayAbilityTargetData_SingleTargetHit();
		NewTargetData->HitResult = HitResult;
		Targets.Add(NewTargetData);
	}
	ApplyGameplayEffectToTarget(GetCurrentAbilitySpecHandle(), GetCurrentActorInfo(), GetCurrentActivationInfo(), Targets, DamageGEClass, 1.f);

In Blueprint it can be done like so:

Making a custom Target Data

You can make a custom Target Data in C++, which you can use in both C++ and Blueprint. Benefits of having custom Target Data is the ability to provide additional or different information which you may want. The example below adds a CartridgeID which is used for Ranged Weapons and is unique for every shot of a weapon. This can be used for simulating hit results (for example a shotgun that does 8 shots per cartridge).

/** Game-specific additions to SingleTargetHit tracking */
USTRUCT()
struct FAresGameplayAbilityTargetData_SingleTargetHit : public FGameplayAbilityTargetData_SingleTargetHit
{
	GENERATED_BODY()

	FAresGameplayAbilityTargetData_SingleTargetHit()
		: CartridgeID(-1)
	{
	}

	virtual void AddTargetDataToContext(FGameplayEffectContextHandle& Context, bool bIncludeActorArray) const override;

	/** ID to allow the identification of multiple bullets that were part of the same cartridge */
	UPROPERTY()
	int32 CartridgeID;

	bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess);

	virtual UScriptStruct* GetScriptStruct() const override
	{
		return StaticStruct();
	}
};

template <>
struct TStructOpsTypeTraits<FAresGameplayAbilityTargetData_SingleTargetHit> : public TStructOpsTypeTraitsBase2<FAresGameplayAbilityTargetData_SingleTargetHit>
{
	enum
	{
		WithNetSerializer = true // For now this is REQUIRED for FGameplayAbilityTargetDataHandle net serialization to work
	};
};


void FAresGameplayAbilityTargetData_SingleTargetHit::AddTargetDataToContext(FGameplayEffectContextHandle& Context, bool bIncludeActorArray) const
{
	FGameplayAbilityTargetData_SingleTargetHit::AddTargetDataToContext(Context, bIncludeActorArray);

	// This adds the CartridgeID to the Gameplay Effect Context so the CartridgeID can be used
	// across the entire Gameplay Effect Execution (for example Damage GE)
	if (FAresGameplayEffectContext* TypedContext = FAresGameplayEffectContext::GetEffectContext(Context))
	{
		TypedContext->SetCartridgeID(CartridgeID);
	}
}

bool FAresGameplayAbilityTargetData_SingleTargetHit::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess)
{
	FGameplayAbilityTargetData_SingleTargetHit::NetSerialize(Ar, Map, bOutSuccess);

	//So it can be sent over the network
	Ar << CartridgeID;

	return true;
}

How can I use this custom Target Data?

We first need to expose some BP getters so you can retrieve the CartridgeID from the target data and create our special target data from Blueprint. This should be inside a BlueprintFunctionLibrary C++ class.

	/** Creates a target data with a single hit result and cartridge id */
	UFUNCTION(BlueprintPure, Category = "Ability|TargetData")
	static FGameplayAbilityTargetDataHandle	AbilityTargetDataFromHitResultWithCartridgeID(const FHitResult& HitResult, int32 CartridgeId);

	/** Returns the cartridge id from a hit result if its of our type, will return -1 if invalid. */
	UFUNCTION(BlueprintPure, Category = "Ability|TargetData")
	static int32 GetCartridgeIDFromTargetData(const FGameplayAbilityTargetDataHandle& TargetData, int32 Index);

And our implementation for those will be:

FGameplayAbilityTargetDataHandle UAresGameplayStatics::AbilityTargetDataFromHitResultWithCartridgeID(const FHitResult& HitResult, int32 CartridgeID)
{
	// Construct TargetData
	FAresGameplayAbilityTargetData_SingleTargetHit* TargetData = new FAresGameplayAbilityTargetData_SingleTargetHit();

	//Set the hit result
	TargetData->HitResult = HitResult;

	//Set the cartridge id
	TargetData->CartridgeID = CartridgeID;

	// Give it a handle and return
	FGameplayAbilityTargetDataHandle	Handle;
	Handle.Data.Add(TSharedPtr<FGameplayAbilityTargetData>(TargetData));

	return Handle;
}

int32 UAresGameplayStatics::GetCartridgeIDFromTargetData(const FGameplayAbilityTargetDataHandle& TargetData, int32 Index)
{
	if (TargetData.Data.IsValidIndex(Index))
	{
		FAresGameplayAbilityTargetData_SingleTargetHit* Data = static_cast<FAresGameplayAbilityTargetData_SingleTargetHit*>(TargetData.Data[Index].Get());
		if (Data)
		{
			return Data->CartridgeID;
		}
	}
	return -1;
}

With these new functions, you can now use your custom Target Data in Blueprint. Here is an example:

To use your new Target Data in C++, you can do the following:

	FGameplayAbilityTargetDataHandle Targets;
	for (const FHitResult& HitResult : HitResults)
	{
		FAresGameplayAbilityTargetData_SingleTargetHit* NewTargetData = new FAresGameplayAbilityTargetData_SingleTargetHit();
		NewTargetData->HitResult = HitResult;
		NewTargetData->CartridgeID = CartridgeID;
		Targets.Add(NewTargetData);
	}
	ApplyGameplayEffectToTarget(GetCurrentAbilitySpecHandle(), GetCurrentActorInfo(), GetCurrentActivationInfo(), Targets, DamageGEClass, 1.f);

	if (Targets.IsValid(0))
	{
		FAresGameplayAbilityTargetData_SingleTargetHit* MyTargetData = static_cast<FAresGameplayAbilityTargetData_SingleTargetHit*>(Targets.Data[0].Get());
		if (MyTargetData)
		{
			int32 CartridgeID = MyTargetData->CartridgeID;
		}
	}

Other uses for Target Data

Target Data can be passed in for ability activation via Gameplay Events. Example below:

Here we are sending a gameplay event, which has the target data for as hit result, we can use this inside an ability,

There is a lot more to Target Data, but wanted to keep this a little brief.