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 ...
}
);
}
}
@x157
Copy link
Author

x157 commented May 15, 2022

Several steps are necessary to accomplish this:

  1. Modify base Lyra code to expose some classes/interfaces.
    • I also had to change the scope of ALyraPawn.MyTeamID so I could implement NotifyControllerChanged in my derived class.
  2. Add dependencies to .Build.cs
  3. Add modules to .uplugin
  4. Implement AXCL_Pawn h | cpp (XistCoreLyra base Pawn)
    • Derives from ALyraPawn
    • Implement ALyraPawn's missing NotifyControllerChanged
    • Implement interfaces that IMO should be in ALyraPawn itself
      • IAbilitySystemInterface
      • IGameplayCueInterface
      • IGameplayTagAssetInterface
    • Made InitializeGameplayTags virtual, even though it's not in ALyraCharacter
      • This allows child classes to implement their own movement types, see AXCL_TPSPlayerPawn.InitializeGameplayTags
  5. Implement AXCL_TPSPlayerPawn h | cpp (XistCoreLyra third person strategy player pawn)
    • Derived from AXCL_Pawn (base pawn)
    • ThirdPersonStrategy player pawn specific implementation
      • Flying/floating movement
      • Lyra "Hero" style user input component

Note that the final child class AXCL_TPSPlayerPawn doesn't really do much. This is just the bare minimum needed to get the pawn working and moving as a floating sphere connected to user input.

Here's a video of me reviewing this code if you want a quick overview:

https://youtu.be/Y_j3PWhYgk4

@frednaar
Copy link

Great tutorials I really love your deep dive into this, ...

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...

class LYRAGAME_API ULyraTeamAgentInterface : public UGenericTeamAgentInterface
{
GENERATED_UINTERFACE_BODY()
};

class LYRAGAME_API ILyraTeamAgentInterface : public IGenericTeamAgentInterface
{
GENERATED_IINTERFACE_BODY()
....

@x157
Copy link
Author

x157 commented Jun 19, 2022

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...

Thank you for the correction!

I added a note in the original comment that this might be necessary and linked to your comment for an example of how to do it.

@SpartanFA
Copy link

SpartanFA commented May 30, 2023

Hey! I love your work, but I believe this gist is outdated. @x157


In XCL_Pawn.h

Old

UPROPERTY(EditDefaultsOnly, Category="Lyra|Pawn", Meta = (AllowPrivateAccess = "true"))
class ULyraPawnExtensionComponent* PawnExtComponent;

This UPROPERTY crashes the editor when opening a BP derived class, I copied what was in the ALyraCharacter class and it stopped crashing:

New

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Lyra|Pawn", Meta = (AllowPrivateAccess = "true"))
class ULyraPawnExtensionComponent* PawnExtComponent;

 
 
Additionally, I think there was a change since this gist was made and in XCL_Pawn.cpp InitializeGameplayTags() and SetMovementModeTag needs to be changed, here is mine (but I basically copied/pasted the code from ALyraCharacter):

In XCL_Pawn.cpp

void AXCL_Pawn::InitializeGameplayTags()
{
	// Clear tags that may be lingering on the ability system from the previous pawn.
	if (ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent())
	{

		for (const TPair<uint8, FGameplayTag>& TagMapping : LyraGameplayTags::MovementModeTagMap)
		{
			if (TagMapping.Value.IsValid())
			{
				LyraASC->SetLooseGameplayTagCount(TagMapping.Value, 0);
			}
		}

		for (const TPair<uint8, FGameplayTag>& TagMapping : LyraGameplayTags::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 FGameplayTag* MovementModeTag = nullptr;
		if (MovementMode == MOVE_Custom)
		{
			MovementModeTag = LyraGameplayTags::CustomMovementModeTagMap.Find(CustomMovementMode);
		}
		else
		{
			MovementModeTag = LyraGameplayTags::MovementModeTagMap.Find(MovementMode);
		}

		if (MovementModeTag && MovementModeTag->IsValid())
		{
			LyraASC->SetLooseGameplayTagCount(*MovementModeTag, (bTagEnabled ? 1 : 0));
		}
	}
}

Thanks for your amazing resources again, Epic should pay you! Cheers!

@SpartanFA
Copy link

SpartanFA commented Jun 17, 2023

Additionally I have found another issue. In the base LyraPawn.cpp class the function PossessedBy is:

void ALyraPawn::PossessedBy(AController* NewController)
{
	const FGenericTeamId OldTeamID = MyTeamID;

	Super::PossessedBy(NewController);

	// Grab the current team ID and listen for future changes
	if (ILyraTeamAgentInterface* ControllerAsTeamProvider = Cast<ILyraTeamAgentInterface>(NewController))
	{
		MyTeamID = ControllerAsTeamProvider->GetGenericTeamId();
		ControllerAsTeamProvider->GetTeamChangedDelegateChecked().AddDynamic(this, &ThisClass::OnControllerChangedTeam);
	}
	ConditionalBroadcastTeamChanged(this, OldTeamID, MyTeamID);
}

However in LyraCharacter.cpp

That function is

void ALyraCharacter::PossessedBy(AController* NewController)
{
	const FGenericTeamId OldTeamID = MyTeamID;

	Super::PossessedBy(NewController);

	PawnExtComponent->HandleControllerChanged();

	// Grab the current team ID and listen for future changes
	if (ILyraTeamAgentInterface* ControllerAsTeamProvider = Cast<ILyraTeamAgentInterface>(NewController))
	{
		MyTeamID = ControllerAsTeamProvider->GetGenericTeamId();
		ControllerAsTeamProvider->GetTeamChangedDelegateChecked().AddDynamic(this, &ThisClass::OnControllerChangedTeam);
	}
	ConditionalBroadcastTeamChanged(this, OldTeamID, MyTeamID);
}

The line PawnExtComponent->HandleControllerChanged(); is important because it continues the initialization chain for the interface IGameFrameworkInitStateInterface that LyraPawnExtensionComponent uses.

To be honest I do not like the way the LyraCharacter is initialized for many reasons. However for my project XCL_Pawn not having PawnExtComponent->HandleControllerChanged(); caused me some issues.

Just throwing this out there just in case someone else get initialization issues. Thanks XCL for all of your work!

@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