Making a custom Ability Actor Info
Updated for UE 5.8: fixed the wrong
Supertypedef and the missing declarations in the sample, removed the redundantSetAvatarActoroverride (the base version already callsInitFromActor), and added a note about the player controller not always being known at init time.
When you start getting into the power of the Gameplay Ability System, you will want to access your own game types easily from the Actor Info that every ability receives. Epic made this really easy to override and implement, but there are a few steps. The first step, which is mandatory, is setting up your own game specific UAbilitySystemGlobals. You can read that post here: /blog/creating-and-setting-up-a-custom-uabilitysystemglobals-class/
With that out of the way, let us start.
The first thing you want to do is create an AbilityTypes class to hold the structs/enums you will use in your ability system, if you do not have one. In my example I have a KaosAbilityTypes.cpp and KaosAbilityTypes.h file.
Next we need to create our own actor info derived from FGameplayAbilityActorInfo in our AbilityTypes header file:
/** Ability actor info that also caches the game specific pawn, controller and player state of the ASC's owner/avatar. */
USTRUCT(BlueprintType)
struct FKaosGameplayAbilityActorInfo : public FGameplayAbilityActorInfo
{
GENERATED_USTRUCT_BODY()
virtual ~FKaosGameplayAbilityActorInfo() = default;
/** Our KaosAbilitySystemComponent. Should NEVER be null. */
UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
TWeakObjectPtr<class UKaosAbilitySystemComponent> KaosAbilitySystemComponent;
/** Our KaosPawn. Often nullptr. */
UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
TWeakObjectPtr<class AKaosPawn> KaosPawn;
/** Our Kaos Player Pawn. Often nullptr. */
UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
TWeakObjectPtr<class AKaosPlayerPawn> KaosPlayerPawn;
/** Our Kaos Player Controller. Often nullptr. */
UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
TWeakObjectPtr<class AKaosPlayerController> KaosPlayerController;
/** Our Kaos AI Controller. Often nullptr. */
UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
TWeakObjectPtr<class AKaosAIController> KaosAIController;
/** Our Kaos Player State. Often nullptr. */
UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
TWeakObjectPtr<class AKaosPlayerState> KaosPlayerState;
virtual void InitFromActor(AActor* OwnerActor, AActor* AvatarActor, UAbilitySystemComponent* InAbilitySystemComponent) override;
virtual void ClearActorInfo() override;
/** Gets the Kaos Ability System Component. Should not be nullptr. */
class UKaosAbilitySystemComponent* GetKaosAbilitySystemComponent() const;
/** Gets the Kaos Pawn. This is often nullptr. */
class AKaosPawn* GetKaosPawn() const;
/** Gets the Kaos Player Pawn. This is often nullptr. */
class AKaosPlayerPawn* GetKaosPlayerPawn() const;
/** Gets the Kaos Player Controller. This is often nullptr. */
class AKaosPlayerController* GetKaosPlayerController() const;
/** Gets the Kaos Player State. This is often nullptr. */
class AKaosPlayerState* GetKaosPlayerState() const;
/** Gets the weapon currently held by the Kaos Pawn. This is often nullptr. */
class AKaosWeapon* GetKaosWeapon() const;
/** Gets the ranged weapon currently held by the Kaos Pawn. This is often nullptr. */
class AKaosWeaponRanged* GetKaosWeaponRanged() const;
};
As you can see with the above, I want to easily get my game specific Controllers, Pawn (Character) and PlayerState from the Actor Info. We only have to override InitFromActor and ClearActorInfo to achieve this. There is no need to declare your own Super typedef: the GENERATED_USTRUCT_BODY() macro already gives the struct a Super for its base. The base SetAvatarActor simply calls InitFromActor with the new avatar, so it does not need overriding.
Now in our AbilityTypes.cpp we can start grabbing and setting our properties, as well as defining the functions we declared in our header.
void FKaosGameplayAbilityActorInfo::InitFromActor(AActor* InOwnerActor, AActor* InAvatarActor, UAbilitySystemComponent* InAbilitySystemComponent)
{
Super::InitFromActor(InOwnerActor, InAvatarActor, InAbilitySystemComponent);
KaosAbilitySystemComponent = Cast<UKaosAbilitySystemComponent>(InAbilitySystemComponent);
KaosPawn = Cast<AKaosPawn>(InAvatarActor);
KaosPlayerPawn = Cast<AKaosPlayerPawn>(InAvatarActor);
if (PlayerController.IsValid())
{
KaosPlayerController = Cast<AKaosPlayerController>(PlayerController.Get());
KaosPlayerState = PlayerController->GetPlayerState<AKaosPlayerState>();
}
else if (KaosPawn.IsValid())
{
KaosAIController = KaosPawn->GetController<AKaosAIController>();
}
}
void FKaosGameplayAbilityActorInfo::ClearActorInfo()
{
Super::ClearActorInfo();
KaosPawn = nullptr;
KaosPlayerPawn = nullptr;
KaosPlayerState = nullptr;
KaosPlayerController = nullptr;
KaosAIController = nullptr;
KaosAbilitySystemComponent = nullptr;
}
class UKaosAbilitySystemComponent* FKaosGameplayAbilityActorInfo::GetKaosAbilitySystemComponent() const
{
return KaosAbilitySystemComponent.Get();
}
class AKaosPawn* FKaosGameplayAbilityActorInfo::GetKaosPawn() const
{
return KaosPawn.Get();
}
class AKaosPlayerPawn* FKaosGameplayAbilityActorInfo::GetKaosPlayerPawn() const
{
return KaosPlayerPawn.Get();
}
class AKaosPlayerController* FKaosGameplayAbilityActorInfo::GetKaosPlayerController() const
{
return KaosPlayerController.Get();
}
class AKaosPlayerState* FKaosGameplayAbilityActorInfo::GetKaosPlayerState() const
{
return KaosPlayerState.Get();
}
class AKaosWeapon* FKaosGameplayAbilityActorInfo::GetKaosWeapon() const
{
return KaosPawn.IsValid() ? KaosPawn->GetCurrentWeapon() : nullptr;
}
class AKaosWeaponRanged* FKaosGameplayAbilityActorInfo::GetKaosWeaponRanged() const
{
return KaosPawn.IsValid() ? Cast<AKaosWeaponRanged>(KaosPawn->GetCurrentWeapon()) : nullptr;
}
The main bulk of the work is in InitFromActor, where we grab the extra objects we want, and ClearActorInfo, where we clear everything we set. The base InitFromActor is what fills in the standard PlayerController (found by walking up the owner chain, or from the owning pawn’s controller), which is why we can use it straight after calling Super.
Now we need to tell the Gameplay Ability System we want to use this new Actor Info. Open your game specific AbilitySystemGlobals class header, and override the following function:
virtual FGameplayAbilityActorInfo* AllocAbilityActorInfo() const override;
and in the cpp file of your game specific AbilitySystemGlobals, we need to define this like so:
FGameplayAbilityActorInfo* UKaosAbilitySystemGlobals::AllocAbilityActorInfo() const
{
return new FKaosGameplayAbilityActorInfo();
}
Remember to replace the names with your project’s names, e.g. FMyGameplayAbilityActorInfo, etc.
To access your newly created AbilityActorInfo from within abilities, etc, you need to cast. I have shown an example below:
const FKaosGameplayAbilityActorInfo* UKaosGameplayAbility::GetKaosActorInfo(const FGameplayAbilityActorInfo* InInfo)
{
return static_cast<const FKaosGameplayAbilityActorInfo*>(InInfo);
}
With the above function, which is a little wrapper, I can access the specific stuff I want, like this function:
AKaosPawn* UKaosGameplayAbility::GetKaosPawnFromAvatar(FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo) const
{
const FKaosGameplayAbilityActorInfo* KaosActorInfo = GetKaosActorInfo(ActorInfo);
return KaosActorInfo ? KaosActorInfo->GetKaosPawn() : nullptr;
}
Notes and gotchas:
- The
static_castis only safe if the ASC really allocated your struct, so make sure your Globals class is the one being loaded (and that every ASC gets its info throughInitAbilityActorInfo). - The cached controller and player state are only as fresh as the last
InitFromActorcall. If the pawn is possessed afterInitAbilityActorInforan,PlayerController(and so the Kaos controller/state) can still be null. CallInitAbilityActorInfoagain on possession (or when the avatar changes) to refresh them. - Everything is stored as
TWeakObjectPtr, so always null check the getters.