[Lyra][UE5.1] Spawn different AI Pawns / SetPawnData()?

Hi there,
i am currently trying to understand how to Spawn different AI Actors in the Lyra Project.

I tried the “standard” way without Lyra (Spawn AIFrom class) and although it did indeed spawn my actor, somehow Lyra/The AbilitySystem was not setup correctly for that Pawn and it seemed like some LyraAttributeSets were not correctly set (I could not reduce the Health from that PAwn and the MaxHealth was 0)

So what is the intended Method to spawn new and different AIs with differnet Pawns?

After following the C++LYra Code for quite some time i noticed the function “SetPawnData” which takes a DataAsset (which makes Sense, since this models the Abilities fo example) but i cant figure out if or when i should call it.
My first try just logged an Error, like “PawnData has already been set” and it seems the default Pawn was already set, but currently i dont understand how this is happening.

Some of my tries:

C++:
UAlxSpawnComponent::UAlxSpawnComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}

void UAlxSpawnComponent::SpawnAi(int32 TeamIndex, TSubclassOf<AAIController> BotControllerClass, const ULyraPawnData* InPawnData, const FTransform& SpawnTransform)
{
	FActorSpawnParameters SpawnInfo;
	SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
	SpawnInfo.OverrideLevel = GetComponentLevel();
	SpawnInfo.ObjectFlags |= RF_Transient; // Transient weil es ein AIController ist (und kein Pawn)
	AAIController* NewController = GetWorld()->SpawnActor<AAIController>(BotControllerClass, SpawnTransform, SpawnInfo);

	if (NewController != nullptr)
	{
		if (NewController->PlayerState != nullptr)
		{
			if (ALyraPlayerState* LyraPS = Cast<ALyraPlayerState>(NewController->PlayerState))
			{
				if (LyraPS->IsOnlyASpectator())
				{
					LyraPS->SetGenericTeamId(FGenericTeamId::NoTeam);
				}
				else
				{
					LyraPS->SetPawnData(InPawnData);
					const FGenericTeamId TeamID = (TeamIndex == INDEX_NONE) ? FGenericTeamId::NoTeam : FGenericTeamId((uint8)TeamIndex);
					LyraPS->SetGenericTeamId(TeamID);
				}
			}
		}

		if (NewController->GetPawn() != nullptr)
		{
			if (ULyraPawnExtensionComponent* PawnExtComponent = NewController->GetPawn()->FindComponentByClass<ULyraPawnExtensionComponent>())
			{
				//PawnExtComponent->SetPawnData(InPawnData);
			}

			NewController->GetPawn()->FinishSpawning(SpawnTransform);
		}
	}
}

/*
 Spawns the specified Pawn with Team and Transform.
 The pawn should specify AI Controller Class and Auto Possess inside its Settings.
*/
void UAlxSpawnComponent::SpawnPawnWithDefaultAi(int32 TeamIndex, TSubclassOf<APawn> PawnClass, const FTransform& SpawnTransform)
{
	FActorSpawnParameters SpawnInfo;
	SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
	SpawnInfo.OverrideLevel = GetComponentLevel();
	APawn* NewPawn = GetWorld()->SpawnActor<APawn>(PawnClass, SpawnTransform, SpawnInfo);
	AController* NewController = NewPawn->GetController();

	if (NewController != nullptr)
	{
		if (NewController->PlayerState != nullptr)
		{

			if (ALyraPlayerState* LyraPS = Cast<ALyraPlayerState>(NewController->PlayerState))
			{
				if (LyraPS->IsOnlyASpectator())
				{
					LyraPS->SetGenericTeamId(FGenericTeamId::NoTeam);
				}
				else
				{
					const FGenericTeamId TeamID = (TeamIndex == INDEX_NONE) ? FGenericTeamId::NoTeam : FGenericTeamId((uint8)TeamIndex);
					LyraPS->SetGenericTeamId(TeamID);
				}
			}
		}

		if (NewPawn != nullptr)
		{
			if (ULyraPawnExtensionComponent* PawnExtComponent = NewController->GetPawn()->FindComponentByClass<ULyraPawnExtensionComponent>())
			{
				PawnExtComponent->CheckDefaultInitialization();
			}

			NewPawn->FinishSpawning(SpawnTransform);
		}
	}
}
3 Likes

I’m also interested in figuring this out. It seems that Lyra was built on the assumption that all characters in the game are using the same pawn at any given time.

A placed AI character’s “init state” will not progress beyond “spawned,” which seems to prevent the GAS component from being properly initialized. However, if you spawn the same AI through the Lyra bot spawner component, it will work fine.

The problem there seems to be that 1) it expects a player state, which you may not want for your NPCs and 2) you may need to them have an entirely different base class for their pawn. However, the Lyra Game State assumes you want the pawn defined in your pawn data and will spawn that for your AI controller to posses.

1 Like

We need to keep this topic open. My web searches are not producing any substance on Lyra specific AI usage nor modifications. I really would prefer to stick with the workflow that is the Lyra-way. Any other way will produce the snowball effect the designers warned us about.

What is missing from the lyra project is a good diagram that answers questions like “What does it take to create a Weapon?” or “Everything necessary to create an AI”. Because each one is programming a different project, it is even a little difficult to understand the questions in the Forum.

I also desperately need to spawn the players as one character type and the AI as another. Im at a critical part and this is blocking me:(

@BaseReality
So first thing is AI needs the PlayerState, removing the need for a PlayerState in AI seems to be a bit more involved, since the main issue is that the ability system component gets added to the PlayerState.
If you look at how AI gets spawned in LyraBotCreationComponent, all it’s doing is creating a controller based on the class set in BotControllerClass (this should have “NeedsPlayerState” bool on).
The player state then is listening for a OnExperienceLoaded event, in which it asks the GameMode for a PawnData, the GameMode then checks if the PlayerState has one assinged on its own PawnData Variable, and if not it returns the default for the Experience.
So, we need to intercept this function and give the AI player state another PawnData.
In my case I’ve been trying not to modify the Lyra Code, and instead making child classes of a lot of things to add and modify for my needs.

But here is a quick way if you don’t mind changing Lyra classes, all you have to do is go to LyraExperienceDefinition.h and add a variable

UPROPERTY(EditDefaultsOnly, Category=Gameplay)
TObjectPtr<const ULyraPawnData> DefaultAIPawnData;

Then in LyraGameMode.cpp find the function called GetPawnDataForController and replace this part

if (ExperienceComponent->IsExperienceLoaded())
{
	const ULyraExperienceDefinition* Experience = ExperienceComponent->GetCurrentExperienceChecked();
	if (Experience->DefaultPawnData != nullptr)
	{
		return Experience->DefaultPawnData;
	}

	// Experience is loaded and theres still no pawn data, fall back to the default for now
	return ULyraAssetManager::Get().GetDefaultPawnData();
}

with this

EDIT

if (ExperienceComponent->IsExperienceLoaded())
{
	const ULyraExperienceDefinition* Experience = ExperienceComponent->GetCurrentExperienceChecked();
    if (InController != nullptr && InController->IsA<AAIController>() && Experience->DefaultAIPawnData != nullptr)
	{
		return Experience->DefaultAIPawnData;
	}
	else if (Experience->DefaultPawnData != nullptr)
	{
		return Experience->DefaultPawnData;
	}
	
	// Experience is loaded and theres still no pawn data, fall back to the default for now
	return ULyraAssetManager::Get().GetDefaultPawnData();
}

Now you can add the AI PawnData class in the ExperienceDefinition

I’ll Try to remember to come back here once I find a more elegant way without changing Lyra Base Classes, pretty sure its fine if you make your child gameMode and PlayerState and add it there, I already have a custom child player state with custom attribute sets etc

Seems this is either an overlook on Epic or a future to add thing because they even call the PawnData “Hero”

2 Likes

Of course they made the GetPawnDataForController a non virtual const :roll_eyes:

1 Like

BIG THANKS ! for this as I’m not A C++ Guy, but BP’s gave me a good understanding of Code, and Im willing to try c++ edits.
This is the kind of thing I was looking for. Not a go do a thing without showing me visually what I need to change so top marks for the mini tutorial and explanation.

This is super helpful as I also want to add an extra actor value to the weapon definition so the weapon spawner gives the default weapons to BOT’s but a custom version to the players. I already have that working but as a value in the BP itself.

But being able add a value for, “hey spawn this class for AI bots” and “spawn this class for players” is all I ever wanted and opens up a ton of options in lyra even for different game types. If you discover anything else please keep me informed.

Like many I was super excited watching the Lyra presentation thinking it was the best thing ever, Only to find they gimped it with weird restricted complexity of c++ and not having important values exposed that could make it more flexible for people that are mainly Blueprint coders. :frowning:
even being able to select a team is not exposed :unamused:

Im feeling a bit dumb here!
I made the changes to the lyra definition. h
and the gamemode.cpp
but I was not seeing an extra value in the definition in the project

so I created a solution file and selected Build.
still nothing. Did I do this right?

So I think you may have made a typo here In the first bit of code, Did you not mean to say AIpawnData instead of this?
image
Because when I changed it to AIpawndata I now see it added prob because defaultpawnData is the already existing value, Please correct me if I’m wrong as Im just winging it here and guessing

I figured this out by cross-referencing this with this other Post on the same topic. Different pawns for bots and players in Lyra - #14 by Ikerdo

That is correct, my bad, I hadn’t event made the changes in my project when I posted haha, but yeah It works fine.
Just make sure you use that same variable name in the game mode in the “If Is AI” return.
return Experience->DefaultAIPawnData;
will still look for a solution that does not modify any Lyra code, want to be able to just grab their latest.

Hey, another oopsie, didn’t correct the null check, this is correct

I had already implemented the correct method and I just want to say thanks. as this was the main blocker for me. Now Look where Im at.

3 Likes

Good job! Keep going.

Ahh hell yeah! bust those blockers up!