Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save x157/d59d729f6bb62d816175048b446376ee to your computer and use it in GitHub Desktop.
Save x157/d59d729f6bb62d816175048b446376ee to your computer and use it in GitHub Desktop.
How to derive from ALyraPawn to implement Gameplay Ability System, Gameplay Cues and Gameplay Tags.
ALyraPawn as shipped in UE 5.0.1 does not implement Gameplay Ability System, Gameplay Cues or Gameplay Tags.
This gist documents how I was able to do so. Hopefully Epic updates Lyra to do this themselves in the future, but for now, here you go.
diff --git a/Source/LyraGame/Character/LyraHeroComponent.h b/Source/LyraGame/Character/LyraHeroComponent.h
index 312623d..921feb3 100644
--- a/Source/LyraGame/Character/LyraHeroComponent.h
+++ b/Source/LyraGame/Character/LyraHeroComponent.h
@@ -22,7 +22,7 @@ class ULyraInputConfig;
* A component used to create a player controlled pawns (characters, vehicles, etc..).
*/
UCLASS(Blueprintable, Meta=(BlueprintSpawnableComponent))
-class ULyraHeroComponent : public ULyraPawnComponent
+class LYRAGAME_API ULyraHeroComponent : public ULyraPawnComponent
{
GENERATED_BODY()
diff --git a/Source/LyraGame/Character/LyraPawn.h b/Source/LyraGame/Character/LyraPawn.h
index d2741b0..4220d90 100644
--- a/Source/LyraGame/Character/LyraPawn.h
+++ b/Source/LyraGame/Character/LyraPawn.h
@@ -12,7 +12,7 @@
* ALyraPawn
*/
UCLASS()
-class ALyraPawn : public AModularPawn, public ILyraTeamAgentInterface
+class LYRAGAME_API ALyraPawn : public AModularPawn, public ILyraTeamAgentInterface
{
GENERATED_BODY()
@@ -48,10 +48,12 @@ private:
UFUNCTION()
void OnControllerChangedTeam(UObject* TeamAgent, int32 OldTeam, int32 NewTeam);
-private:
+protected:
+ // must be protected since ALyraPawn doesn't handle NotifyControllerChanged and child classes must do it
UPROPERTY(ReplicatedUsing = OnRep_MyTeamID)
FGenericTeamId MyTeamID;
+private:
UPROPERTY()
FOnLyraTeamIndexChangedDelegate OnTeamChangedDelegate;
diff --git a/Source/LyraGame/Character/LyraPawnExtensionComponent.h b/Source/LyraGame/Character/LyraPawnExtensionComponent.h
index 255984e..758c0a6 100644
--- a/Source/LyraGame/Character/LyraPawnExtensionComponent.h
+++ b/Source/LyraGame/Character/LyraPawnExtensionComponent.h
@@ -21,7 +21,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE(FLyraDynamicMulticastDelegate);
* Component used to add functionality to all Pawn classes.
*/
UCLASS()
-class ULyraPawnExtensionComponent : public ULyraPawnComponent
+class LYRAGAME_API ULyraPawnExtensionComponent : public ULyraPawnComponent
{
GENERATED_BODY()
diff --git a/Source/LyraGame/LyraGameplayTags.h b/Source/LyraGame/LyraGameplayTags.h
index f44e0c3..13c08e7 100644
--- a/Source/LyraGame/LyraGameplayTags.h
+++ b/Source/LyraGame/LyraGameplayTags.h
@@ -12,7 +12,7 @@ class UGameplayTagsManager;
*
* Singleton containing native gameplay tags.
*/
-struct FLyraGameplayTags
+struct LYRAGAME_API FLyraGameplayTags
{
public:
diff --git a/Source/LyraGame/Teams/LyraTeamAgentInterface.h b/Source/LyraGame/Teams/LyraTeamAgentInterface.h
index e685a3a..388519b 100644
--- a/Source/LyraGame/Teams/LyraTeamAgentInterface.h
+++ b/Source/LyraGame/Teams/LyraTeamAgentInterface.h
@@ -23,7 +23,7 @@ inline FGenericTeamId IntegerToGenericTeamId(int32 ID)
/** Interface for actors which can be associated with teams */
UINTERFACE(meta=(CannotImplementInterfaceInBlueprint))
-class ULyraTeamAgentInterface : public UGenericTeamAgentInterface
+class LYRAGAME_API ULyraTeamAgentInterface : public UGenericTeamAgentInterface
{
GENERATED_UINTERFACE_BODY()
};
#include "Character/XCL_Pawn.h"
#include "LyraGameplayTags.h"
#include "AbilitySystem/LyraAbilitySystemComponent.h"
#include "Character/LyraPawnExtensionComponent.h"
#include "GameFramework/PawnMovementComponent.h"
// Sets default values
AXCL_Pawn::AXCL_Pawn()
{
PawnExtComponent = CreateDefaultSubobject<ULyraPawnExtensionComponent>(TEXT("PawnExtensionComponent"));
PawnExtComponent->OnAbilitySystemInitialized_RegisterAndCall(FSimpleMulticastDelegate::FDelegate::CreateUObject(this, &ThisClass::OnAbilitySystemInitialized));
PawnExtComponent->OnAbilitySystemUninitialized_Register(FSimpleMulticastDelegate::FDelegate::CreateUObject(this, &ThisClass::OnAbilitySystemUninitialized));
}
// add support for controller changing teams
// (Epic implemented this in ALyraCharacter but not in ALyraPawn)
void AXCL_Pawn::NotifyControllerChanged()
{
const FGenericTeamId OldTeamId = GetGenericTeamId();
Super::NotifyControllerChanged();
// Update our team ID based on the controller
if (HasAuthority() && (Controller != nullptr))
{
if (ILyraTeamAgentInterface* ControllerWithTeam = Cast<ILyraTeamAgentInterface>(Controller))
{
MyTeamID = ControllerWithTeam->GetGenericTeamId();
ConditionalBroadcastTeamChanged(this, OldTeamId, MyTeamID);
}
}
}
void AXCL_Pawn::OnRep_Controller()
{
Super::OnRep_Controller();
PawnExtComponent->HandleControllerChanged();
}
void AXCL_Pawn::OnRep_PlayerState()
{
Super::OnRep_PlayerState();
PawnExtComponent->HandlePlayerStateReplicated();
}
void AXCL_Pawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PawnExtComponent->SetupPlayerInputComponent();
}
void AXCL_Pawn::OnAbilitySystemInitialized()
{
// [xist] decided not to force a HealthComponent on every pawn. Some pawns are invincible!
// override this yourself in your own pawn if you want a health component.
//
//ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent();
//check(LyraASC);
//HealthComponent->InitializeWithAbilitySystem(LyraASC);
InitializeGameplayTags();
}
void AXCL_Pawn::OnAbilitySystemUninitialized()
{
//HealthComponent->UninitializeFromAbilitySystem();
}
void AXCL_Pawn::InitializeGameplayTags()
{
// Clear tags that may be lingering on the ability system from the previous pawn.
if (ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent())
{
const FLyraGameplayTags& GameplayTags = FLyraGameplayTags::Get();
for (const TPair<uint8, FGameplayTag>& TagMapping : GameplayTags.MovementModeTagMap)
{
if (TagMapping.Value.IsValid())
{
LyraASC->SetLooseGameplayTagCount(TagMapping.Value, 0);
}
}
for (const TPair<uint8, FGameplayTag>& TagMapping : GameplayTags.CustomMovementModeTagMap)
{
if (TagMapping.Value.IsValid())
{
LyraASC->SetLooseGameplayTagCount(TagMapping.Value, 0);
}
}
// [xist] removed explicit ALyraCharacter movement tag logic, made this the
// responsibility of derived classes - override this method to add your
// movement-based gameplay tag.
//
// e.g. SetMovementModeTag(MOVE_Walking, 0, true);
}
}
void AXCL_Pawn::SetMovementModeTag(EMovementMode MovementMode, uint8 CustomMovementMode, bool bTagEnabled)
{
if (ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent())
{
const FLyraGameplayTags& GameplayTags = FLyraGameplayTags::Get();
const FGameplayTag* MovementModeTag = nullptr;
if (MovementMode == MOVE_Custom)
{
MovementModeTag = GameplayTags.CustomMovementModeTagMap.Find(CustomMovementMode);
}
else
{
MovementModeTag = GameplayTags.MovementModeTagMap.Find(MovementMode);
}
if (MovementModeTag && MovementModeTag->IsValid())
{
LyraASC->SetLooseGameplayTagCount(*MovementModeTag, (bTagEnabled ? 1 : 0));
}
}
}
ULyraAbilitySystemComponent* AXCL_Pawn::GetLyraAbilitySystemComponent() const
{
return PawnExtComponent->GetLyraAbilitySystemComponent();
}
UAbilitySystemComponent* AXCL_Pawn::GetAbilitySystemComponent() const
{
return PawnExtComponent->GetLyraAbilitySystemComponent();
}
void AXCL_Pawn::GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const
{
if (const ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent())
{
LyraASC->GetOwnedGameplayTags(TagContainer);
}
}
bool AXCL_Pawn::HasMatchingGameplayTag(FGameplayTag TagToCheck) const
{
if (const ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent())
{
return LyraASC->HasMatchingGameplayTag(TagToCheck);
}
return false;
}
bool AXCL_Pawn::HasAllMatchingGameplayTags(const FGameplayTagContainer& TagContainer) const
{
if (const ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent())
{
return LyraASC->HasAllMatchingGameplayTags(TagContainer);
}
return false;
}
bool AXCL_Pawn::HasAnyMatchingGameplayTags(const FGameplayTagContainer& TagContainer) const
{
if (const ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent())
{
return LyraASC->HasAnyMatchingGameplayTags(TagContainer);
}
return false;
}
#pragma once
#include "CoreMinimal.h"
#include "AbilitySystemInterface.h"
#include "GameplayCueInterface.h"
#include "GameplayTagAssetInterface.h"
#include "Character/LyraPawn.h"
#include "XCL_Pawn.generated.h"
/**
* Base Pawn Class
*
* This stuff should really be implemented in ALyraPawn, but Epic put all of their ability system,
* gameplay cues and gameplay tags into ALyraCharacter, so we need to recreate the wheel.
*
* For the most part I just copied what they put in ALyraCharacter.
*
* If they ever move this stuff into ALyraPawn we can get rid of this class. It literally does
* nothing at all except what I would have expected Epic to bundle with ALyraPawn rather than
* with ALyraCharacter.
*/
UCLASS()
class XISTCORELYRARUNTIME_API AXCL_Pawn : public ALyraPawn, public IAbilitySystemInterface, public IGameplayCueInterface, public IGameplayTagAssetInterface
{
GENERATED_BODY()
private:
UPROPERTY(EditDefaultsOnly, Category="Lyra|Pawn", Meta = (AllowPrivateAccess = "true"))
class ULyraPawnExtensionComponent* PawnExtComponent;
public:
// Sets default values for this pawn's properties
AXCL_Pawn();
//~APawn interface
virtual void NotifyControllerChanged() override;
//~End of APawn interface
//~Support for UPawnExtensionComponent
virtual void OnRep_Controller() override;
virtual void OnRep_PlayerState() override;
virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
//~End Support for UPawnExtensionComponent
//~ALyraCharacter-like ability system / tags implementation
virtual void OnAbilitySystemInitialized();
virtual void OnAbilitySystemUninitialized();
virtual void InitializeGameplayTags(); // [xist] made this virtual
virtual void SetMovementModeTag(EMovementMode MovementMode, uint8 CustomMovementMode, bool bTagEnabled);
UFUNCTION(BlueprintCallable, Category = "Lyra|Pawn")
class ULyraAbilitySystemComponent* GetLyraAbilitySystemComponent() const;
//~End of ALyraCharacter-like ability system implementation
//~IAbilitySystemInterface
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
//~End of IAbilitySystemInterface
//~IGameplayCueInterface
//~End of IGameplayCueInterface
//~IGameplayTagAssetInterface
virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override;
virtual bool HasMatchingGameplayTag(FGameplayTag TagToCheck) const override;
virtual bool HasAllMatchingGameplayTags(const FGameplayTagContainer& TagContainer) const override;
virtual bool HasAnyMatchingGameplayTags(const FGameplayTagContainer& TagContainer) const override;
//~End of IGameplayTagAssetInterface
};
#include "ThirdPersonStrategy/XCL_TPSPlayerPawn.h"
#include "Character/LyraHeroComponent.h"
#include "Components/SphereComponent.h"
#include "GameFramework/FloatingPawnMovement.h"
// Sets default values
AXCL_TPSPlayerPawn::AXCL_TPSPlayerPawn()
{
// Set this pawn to call Tick() every frame.
PrimaryActorTick.bCanEverTick = true;
SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("PrimitiveComponent"));
SphereComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
SphereComponent->SetCanEverAffectNavigation(false);
RootComponent = SphereComponent;
MovementComponent = CreateDefaultSubobject<UFloatingPawnMovement>(TEXT("MovementComponent"));
MovementComponent->SetUpdatedComponent(SphereComponent);
LyraHeroComponent = CreateDefaultSubobject<ULyraHeroComponent>(TEXT("LyraHeroComponent"));
}
// Called by base class
void AXCL_TPSPlayerPawn::InitializeGameplayTags()
{
Super::InitializeGameplayTags();
// tell the ability system that this pawn is flying
SetMovementModeTag(MOVE_Flying, 0, true);
}
UPawnMovementComponent* AXCL_TPSPlayerPawn::GetMovementComponent() const
{
return MovementComponent;
}
#pragma once
#include "CoreMinimal.h"
#include "Character/XCL_Pawn.h"
#include "XCL_TPSPlayerPawn.generated.h"
UCLASS()
class XISTCORELYRARUNTIME_API AXCL_TPSPlayerPawn : public AXCL_Pawn
{
GENERATED_BODY()
private:
UPROPERTY(EditDefaultsOnly, Category="Xist|Pawn", Meta = (AllowPrivateAccess = "true"))
class USphereComponent* SphereComponent;
UPROPERTY(EditDefaultsOnly, Category="Xist|Pawn", Meta = (AllowPrivateAccess = "true"))
class UFloatingPawnMovement* MovementComponent;
UPROPERTY(EditDefaultsOnly, Category="Xist|Pawn", Meta = (AllowPrivateAccess = "true"))
class ULyraHeroComponent* LyraHeroComponent;
public:
// Sets default values for this pawn's properties
AXCL_TPSPlayerPawn();
// Called by base class
virtual void InitializeGameplayTags() override;
//~APawn interface
virtual UPawnMovementComponent* GetMovementComponent() const override;
//~End of APawn interface
};
{
"FileVersion": 3,
"Version": 1,
"VersionName": "0.1",
"FriendlyName": "XistCoreLyra",
"Description": "",
"Category": "Game Features",
"CreatedBy": "Xist",
"CreatedByURL": "http://xist.gg",
"DocsURL": "",
"MarketplaceURL": "",
"SupportURL": "",
"EnabledByDefault": false,
"CanContainContent": true,
"IsBetaVersion": true,
"IsExperimentalVersion": true,
"Installed": false,
"Modules": [
{
"Name": "XistCoreLyraRuntime",
"Type": "Runtime",
"LoadingPhase": "Default"
}
],
"Plugins": [
{
"Name": "CommonGame",
"Enabled": true
},
{
"Name": "CommonUI",
"Enabled": true
},
{
"Name": "EnhancedInput",
"Enabled": true
},
{
"Name": "GameplayAbilities",
"Enabled": true
},
{
"Name": "GameplayMessageRouter",
"Enabled": true
},
{
"Name": "ModularGameplayActors",
"Enabled": true
}
],
"ExplicitlyLoaded": true,
"BuiltInInitialFeatureState": "Loaded"
}
using UnrealBuildTool;
public class XistCoreLyraRuntime : ModuleRules
{
public XistCoreLyraRuntime(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
// ... add other public dependencies that you statically link with here ...
"LyraGame",
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
// ... add private dependencies that you statically link with here ...
// Stuff needed for XCL_Pawn:
"AIModule",
"CommonInput",
"EnhancedInput",
"GameplayAbilities",
"GameplayMessageRuntime",
"GameplayTags",
"ModularGameplayActors",
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}
@ZacharyDK
Copy link

I had compilation issues and solved them exposing the 2 interface classes not just one as in the GIT above... so adding LYRAGAME_API to the second class...

Yep. Get private static class / linker issues in all functions using. ILyraTeamAgentInterface::ConditionalBroadcastTeamChanged

Your fix seemed to solve those linker issues for those of that want to ILyraTeamAgentInterface on our own characters.

You saved my bacon. No way I would have figured out that was the issue, as I included the AIModule correctly in the build.cs for my custom game feature. I took X_API as a black box macro for granted.

Gotta submit a report to Epic, so they fix this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment