机器人的生成
通过体验的 GameFeatureAction 往 GameState 加了一个 UGameStateComponent
他在体验加载完成后会加载固定数量的机器人
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);
}
}
核心加载逻辑,SpawnActor
注意他有PlayerState是因为 AAIController 的构造函数中 bWantsPlayerState = true
RestartPlayer 让 AIController 走了 GameMode的流程
GetDefaultPawnClassForController 根据体验 PawnData 中的Class 配置,然后生成Pawn
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;
}

AIController的蓝图中,首先是 Run 行为树
死亡的时候清除敌人,停止行为树逻辑等。
AI感知
AIController 加上 AIPerception 组件。配置一下视觉感知和伤害感知
- 伤害感知需要在角色HP变化的时候调用接口report一下。
- 角色类需要添加 AIPerceptionStimuliSource 组件,才能被感知,Lyra配置了视觉和听觉

这里判断了伤害不是自己造成的情况下,AIController->Report Damage Event
其他感知
- Sight / Touch / Team → 配好参数就行,全自动
- Hearing → 需要在射击、爆炸、脚步等位置调用
MakeNoise()或ReportNoiseEvent() - Damage → 需要在受伤时手动调用
ReportDamageEvent()(如你的蓝图所做)

AI感知的EQS是这样的,他会找到一个Actor复制给行为树的 TargetEnemy
所有感知类型都会过来,如果需要过滤就配置sense to use
或者自定义。Generator和 Test
EQS概述
Generator → Tests(筛选+打分)→ 最佳 Item
EQS 的核心逻辑就三步:
- Generator 生成一堆候选点(Items)
- Tests 逐个淘汰或打分
- 引擎选出得分最高的 Item
例如调用一个 Grid 的 Generator 配置一下,在周围生成了一堆的 Item
基于一系列Test进行打分过滤,最后有一个排序。引擎选择出一个得分最高的点。
例如这个EQS
/EqZeroCore/Bot/EQS/EQS_MoveAgainstEnnemy.EQS_MoveAgainstEnnemy

在角色四周生成 n * n 个点,生成的规则基于正方形的边长和间距。这么多的Item,
下面加了很多个test,可以配置为过滤或者打分
第一个:PathExist 如果寻路不到路径就过滤掉。
第二个:从候选点向敌人做 Camera 通道的 Line Trace。能看到敌人 = 高分,被遮挡 = 低分
我们自己写了一个 EQS_Contex_TargetEnemy 获得了 TargetEnemy 的位置
除此之外,引擎提供的Contex 有 Queries 是查询者 就是AIController,_Item 指哪来测试的对象,例如Grid的那么多点
第三个: 基于距离,远的高分,并且过远的点还会被过滤掉。
这个组成了AI的行为,如果AI看得到你,就会走到一个尽量远的位置。
有点抽象。
想说的就是:
- Generator 生成一堆候选点(Items)
- Tests 逐个淘汰或打分
- 引擎选出得分最高的 Item
其中 Tests计算中 Context 可以自定义的,上面的 EQS_Contex_TargetEnemy
Generator 也可以自定义,你可以用 grid 生成 n * n 的 Item
/EqZeroCore/Bot/EQS/Generator/EQS_GetAllEnemy.EQS_GetAllEnemy
也可以继承 Env Query Generator Blueprint Base 来重写 Do Item Generation
函数里面需要用 Add Generated Actor 来添加
行为树总览

先分有子弹 和 没子弹
有子弹:
-> AI 感知能否找到敌人
-> 找到了 【攻击】【Set Focus】【BOT寻找可看到玩家的最远的点,走过去】
-> 没有敌人 【Clear Focus】【换弹】
-> 【找武器,走过去】
-> 【用get all actor of class 找敌人,这铁定找得到啊,然后走过去】
-> 休息一下
没子弹:
-> 【clear focus】【更新子弹,如果捡枪了,这里会更新是否有子弹的bool】【找枪走过去】
大致的逻辑是这样。
这Shoot和CheckAmmo的 BTService里面有更新是否有子弹的bool的逻辑
Set Focus 是 AI Controller 的接口,是必须的,会影响 Controller的朝向 从而影响弹道。
总结
总结一下Lyra的AI系统
创建带player state 的 AIController,跑和玩家一样的角色逻辑和组件的 机器人。
行为树逻辑:
有子弹时,基于AI 视觉和伤害感知敌人。成功找到敌人时,射击并向 EQS查询的看得到玩家的远处跑。
如果看不到玩家的时候,会清除目标,换子弹,去找枪补子弹。同时用 GetAllActorOfClass 找敌人然后走过去。来重写看到玩家。
看到后又开始基于感知维护目标了。
没子弹简单,直接去找枪。