初步整理
有哪些类?
/ShooterCore/Bot/B_AI_Controller_LyraShooter_Passive
/ShooterCore/Bot/B_ShooterBotSpawner.B_ShooterBotSpawner
继承关系
B_AI_Controller_LyraShooter_Passive=>B_ShooterBotSpawner=>ULyraBotCreationComponent
/ShooterCore/Bot/B_AI_Controller_LyraShooter.B_AI_Controller_LyraShooter
/ShooterCore/Bot/B_ShooterBotSpawner_Perf.B_ShooterBotSpawner_Perf
父类是
ALyraPlayerBotController
===
通过体验,在 LyraGameState 上加上了 /ShooterCore/Bot/B_ShooterBotSpawner.B_ShooterBotSpawner
蓝图是配置,创建机器人的逻辑是ULyraBotCreationComponent
规划,我们先搞出 ULyraBotCreationComponent,他配置了一个AIController的类
然后我们关联 ALyraPlayerBotController,比较简单的两个类
UEqZeroBotCreationComponent
核心逻辑是体验加载完成后按数量加载 bot
for (int32 Count = 0; Count < EffectiveBotCount; ++Count)
{
SpawnOneBot();
}
void UEqZeroBotCreationComponent::SpawnOneBot()
{
FActorSpawnParameters SpawnInfo;
SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
SpawnInfo.OverrideLevel = GetComponentLevel();
SpawnInfo.ObjectFlags |= RF_Transient;
AAIController* NewController = GetWorld()->SpawnActor<AAIController>(BotControllerClass, FVector::ZeroVector, FRotator::ZeroRotator, SpawnInfo);
if (NewController != nullptr)
{
AEqZeroGameMode* GameMode = GetGameMode<AEqZeroGameMode>();
check(GameMode);
if (NewController->PlayerState != nullptr) // 通过 bWantsPlayerState 来设置需要PlayerState
{
NewController->PlayerState->SetPlayerName(CreateBotName(NewController->PlayerState->GetPlayerId()));
}
// 让AI也能跑 OnGameModePlayerInitialized 的流程
GameMode->GenericPlayerInitialization(NewController);
// 这里面会找到 FindPlayerStart 然后生成的流程
GameMode->RestartPlayer(NewController);
if (NewController->GetPawn() != nullptr)
{
if (UEqZeroPawnExtensionComponent* PawnExtComponent = NewController->GetPawn()->FindComponentByClass<UEqZeroPawnExtensionComponent>())
{
PawnExtComponent->CheckDefaultInitialization();
}
}
SpawnedBotList.Add(NewController);
}
}
AEqZeroPlayerBotController
补充逻辑
void AEqZeroGameMode::RequestPlayerRestartNextFrame(AController* Controller, bool bForceReset)
{
if (bForceReset && Controller)
{
Controller->Reset();
}
if (APlayerController* PC = Cast<APlayerController>(Controller))
{
GetWorldTimerManager().SetTimerForNextTick(PC, &APlayerController::ServerRestartPlayer_Implementation);
}
else if (AEqZeroPlayerBotController* BotController = Cast<AEqZeroPlayerBotController>(Controller))
{
GetWorldTimerManager().SetTimerForNextTick(BotController, &AEqZeroPlayerBotController::ServerRestartController);
}
}
尝试配置一下
创建
/EqZeroCore/Bot/B_ShooterBotSpawner.B_ShooterBotSpawner
关联
/EqZeroCore/Bot/B_AI_Controller_EqShooter.B_AI_Controller_EqShooter
在体验的 game feature add comp 上
加一个 server only 的 eq game state => B_ShooterBotSpawner
运行一下机器人居然就出来了!!!
为啥呢?
GameMode->RestartPlayer(NewController);
内部会跑到这里
APawn* AEqZeroGameMode::SpawnDefaultPawnAtTransform_Implementation(AController* NewPlayer, const FTransform& SpawnTransform)
{
FActorSpawnParameters SpawnInfo;
SpawnInfo.Instigator = GetInstigator();
SpawnInfo.ObjectFlags |= RF_Transient; // We never want to save default player pawns into a map
SpawnInfo.bDeferConstruction = true;
if (UClass* PawnClass = GetDefaultPawnClassForController(NewPlayer))
{
if (APawn* SpawnedPawn = GetWorld()->SpawnActor<APawn>(PawnClass, SpawnTransform, SpawnInfo))
{
if (UEqZeroPawnExtensionComponent* PawnExtComp = UEqZeroPawnExtensionComponent::FindPawnExtensionComponent(SpawnedPawn))
{
if (const UEqZeroPawnData* PawnData = GetPawnDataForController(NewPlayer))
{
PawnExtComp->SetPawnData(PawnData);
}
}
SpawnedPawn->FinishSpawning(SpawnTransform);
return SpawnedPawn;
}
}
return nullptr;
}
GetDefaultPawnClassForController 中我们重写了
UClass* AEqZeroGameMode::GetDefaultPawnClassForController_Implementation(AController* InController)
{
if (const UEqZeroPawnData* PawnData = GetPawnDataForController(InController))
{
if (PawnData->PawnClass)
{
return PawnData->PawnClass;
}
}
return Super::GetDefaultPawnClassForController_Implementation(InController);
}
拿的是pawn data 的 class
具体调用
AEqZeroGameMode::SpawnDefaultPawnAtTransform_Implementation AGameModeBase::SpawnDefaultPawnFor_Implementation (GameModeBase.cpp:1228) AGameModeBase::RestartPlayerAtPlayerStart (GameModeBase.cpp:1301) AGameModeBase::RestartPlayer (GameModeBase.cpp:1267) UEqZeroBotCreationComponent::SpawnOneBot (EqZeroBotCreationComponent.cpp:109) UEqZeroBotCreationComponent::ServerCreateBots_Implementation (EqZeroBotCreationComponent.cpp:64) UEqZeroBotCreationComponent::OnExperienceLoaded (EqZeroBotCreationComponent.cpp:36)
行为树相关
有弹药找敌人
黑板:
/EqZeroCore/Bot/BT/BB_Eq_Shooter_Bot.BB_Eq_Shooter_Bot
SelfActor | Actor
TargetEnemy | Actor
MoveGoal | Vector
OutOfAmmo | bool
行为树的创建:
/EqZeroCore/Bot/BT/BT_Eq_Shooter_BotBT_Lyra_Shooter_Bot.BT_Eq_Shooter_BotBT_Lyra_Shooter_Bot

如果有子弹就环境查询找敌人
创建一个找敌人的EQS
/EqZeroCore/Bot/EQS/EQS_AIPerceptionEnemy.EQS_AIPerceptionEnemy
ROOT-> perceived actors 再右键 add test distance

寻找对于类型的character
=== 注意
对于角色Character B Hero Default
要加一个 AIPerceptionStimuliSource
右边 AI Perception 要加上 AISense_Signt 和 AISense_Hearing
完善AIController
完成AIController 类

顺便添加一个AI感知

初始化流程

【常见加 Nav bound volume】按p 可以看到绿色的那个
【'】开调试,没小键盘
[/Script/GameplayDebugger.GameplayDebuggerConfig]
CategorySlot0=One
CategorySlot1=Two
CategorySlot2=Three
CategorySlot3=Four
CategorySlot4=Five
CategorySlot5=Six
CategorySlot6=Seven
CategorySlot7=Eight
CategorySlot8=Nine
CategorySlot9=Zero
【Have Ammo 的条件False,其中 Notify Observer 的 OnValue Change 和 Both 会让后面的都失败。所以一直卡root。所以还给继续写】
ps:
Flow Control:
- On Vaslue Change : 仅当 Blackboard 值实际变化时才触发重新评估
- On Result Change : 每次 Tick 都检查条件,条件结果从通过变失败或反过来时才触发
Observer Aborts
None: 不中断任何东西,条件只在首次进入时判断一次
Self: 条件不满足后,中断自己所在分支正在执行的子节点,让 Selector/Sequence 重新评估
Lower Priority: 条件重新满足时,中断右边(优先级更低)分支正在执行的节点,切回自己
Both: Self + Lower Priority 的组合。条件不满足→中断自己;条件重新满足→中断右边抢回来
AI感知不到我

AI Controller 的感知 敌人,中立,朋友
先默认全是敌人
ETeamAttitude::Type AEqZeroPlayerBotController::GetTeamAttitudeTowards(const AActor& Other) const
{
if (const APawn* OtherPawn = Cast<APawn>(&Other))
{
return ETeamAttitude::Hostile; // 默认全是敌人
}
return ETeamAttitude::Neutral;
}

看到了,红球是下面的伤害感知

这里也确实能找得到目标了
添加伤害感知
/EqZeroCore/Game/B_Hero_EqZMannequin.B_Hero_EqZMannequin
在角色里面加
