CoopGame09-进阶AI
# 跟随B站up主“技术宅阿棍儿”的教程制作的笔记。教程链接
###0.内容
人形敌人,远程武器 AI行为树 使用EQS系统帮助AI掩护或寻找攻击点 AI的视觉 挑战:AI低血量自动逃跑和回复 ###1.综合使用黑板,行为树,环境查询
1.创建AdvancedAI文件夹放高级AI相关文件,创建BP_AdavncedAI蓝图类,继承SCharacter C++类。
2.设置BP_AdvancedAI蓝图类
设置自身
设置网格体
3.创建黑板BB_AdvancedAI,新建向量类型变量TragetDestination,Object类型变量TargetActor,其基类为Actor。
4.创建行为树BT_AdvancedAI,点击根,绑定黑板BB_AdvancedAI,并编写
5.编写BP_AdavncedAI蓝图类,实现AI朝一个点前进。
6.使用EQS决定AI攻击的位置
新建环境查询EQS_FindMoveTo,根下添加Point:Donut(环),设置如图,字面意思,找点模式为根据场景上的导航体。
测试1距离测试:离AI自己越近的分数越高,权重为2
测试2追踪测试:
测试3追踪测试:看不到玩家的点都是无效的
7.新建装饰器Decorator_DistanceTo,继承BTDecorator_BlueprintBase BT装饰器蓝图基础。
重载PerformConditionCheckAI 执行条件检查AI函数,新建黑板键选择器结构变量BlackboardKey和浮点变量Distance,两个变量都设置为可编辑实例,编写蓝图。
8.装饰器的作用是,当AI的环境查询设置了看不到玩家的点时无效点时,不能依靠环境查询来靠近玩家,需要另一个分支来使AI靠近玩家
9.修改之前蓝图类BP_AdvancedAI逻辑,改为向目标Actor移动
10.新建蓝图类EnvQueryContext_TargetActor,继承EvnQueryContext_Blueprintbase,重载ProvideSingleActor 提供单个Actor。
10.修改行为树BT_AdvancedAI
11.修改环境查询
###2.AI感知和行为树服务
1.修改蓝图类BP_AdvancedAI,将之前获取玩家Pawn然后设置黑板的TargetActor的步骤删除,改为下面在服务里用视觉组件,看到谁就找谁,而不是自己指定Actor。
添加AI感知组件(AIPerception),并设置添加感官配置AI视力配置
创建服务蓝图Service_SelectTargetActor ,继承BTService_BlueprintBase类,编写蓝图。注意新建的黑板键选择器变量设置为可编辑实例。
修改行为树BT_AdvancedAI,服务下面的selector是添加Blackboard装饰器,判断TargetActor已设置
###3.新环境查询和装饰器的终止机制
实现多人游戏情况下,AI找的是离自己最近的玩家
1.新建环境查询EQS_FindNearesPlayer,根拉出Actor of class,选择我们的玩家蓝图类,取消勾选仅生成半径中的Actor
2.修改行为树,设置黑板条件中的观察期终止,终止低优先级。意思Blackboard Based Condition条件为假,既没有看到玩家的时候,是走右边的查询最近的玩家并移动过去,但在移动的过程中看到玩家了,此时条件为真,会终止比他优先级低的节点,既是Blackboard Based Condition右边的会终止。
###4.进击的AI
####1.修改角色代码
SCharacter.h
1
2
3
4
5
6
public :
UFUNCTION ( BlueprintCallable , Category = "Player" )
void StartFire ();
UFUNCTION ( BlueprintCallable , Category = "Player" )
void StopFire ();
####2.新建任务蓝图Task_AttackTarget,继承BTTask_Blueprint BaseBT任务蓝图基础,并编写
####3.修改行为树BT_AdvancedAI
####4.修改AI的攻击伤害。
1.SWeapon.h
1
2
3
/* 子弹散射角度 */
UPROPERTY ( EditDefaultsOnly , Category = "Weapon" , meta = ( ClampMin = 0.0f ))
float BulletSpread ;
2.SWeapon.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ASWeapon :: ASWeapon ()
{
//...
BulletSpread = 2.0f ;
}
void ASWeapon :: Fire ()
{
//...
if ( MyOwner )
{
//...
// 子弹散射角,以射线为中心的锥形随机向量
float HalfRad = FMath :: DegreesToRadians ( BulletSpread );
ShotDirection = FMath :: VRandCone ( ShotDirection , HalfRad , HalfRad );
FVector TraceEnd = EyeLocation + ( ShotDirection * 10000 );
}
}
3.把BP_Rifle复制到AdvancedAI目录,更名为BP_Rifle_AI,修改类默认值减小攻击伤害,增大散射角。将BP_AdvancedAI蓝图类的累默认值的武器修改为修改后的枪。
4.随机生成两种AI敌人,修改BP_TestGameMode蓝图类。
###5.别开枪,是友军。
####1.人形AI不打友军。
SHealthComponent.h
1
2
3
4
5
6
7
//队伍编号
UPROPERTY ( EditDefaultsOnly , BlueprintReadOnly , Category = "HealthComponent" )
uint8 TeamNum ;
//判断两个Actor是否为友军
UFUNCTION ( BlueprintCallable , BlueprintPure , Category = "HealthComponent" )
static bool IsFriendly ( AActor * ActorA , AActor * ActorB );
SHealthComponent.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
USHealthComponent :: USHealthComponent ()
{
//默认队伍编号为255
TeamNum = 255 ;
}
void USHealthComponent :: HandleTakeAnyDamage ( AActor * DamagedActor , float Damage , const class UDamageType * DamageType , class AController * InstigatedBy ,
AActor * DamageCauser )
{
if ( Damage <= 0.0f || bIsDead )
{
return ;
}
//在伤害发生时进行一个友军判断
if ( DamageCauser != DamagedActor && IsFriendly ( DamagedActor , DamageCauser ))
{
return ;
}
}
bool USHealthComponent :: IsFriendly ( AActor * ActorA , AActor * ActorB )
{
if ( ActorA == nullptr || ActorB == nullptr )
{
return true ;
}
USHealthComponent * HealthCompA = Cast < USHealthComponent > ( ActorA -> GetComponentByClass ( USHealthComponent :: StaticClass ()));
USHealthComponent * HealthCompB = Cast < USHealthComponent > ( ActorB -> GetComponentByClass ( USHealthComponent :: StaticClass ()));
//没有健康值的物体也算友军,不伤害
if ( HealthCompA == nullptr || HealthCompB == nullptr )
{
return true ;
}
//返回两个Actor的队伍编号是否相等
return HealthCompA -> TeamNum == HealthCompB -> TeamNum ;
}
SWeapon.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void ASWeapon :: Fire ()
{
// Trace the world, from pawn eyes to crosshair location
if ( GetLocalRole () < ROLE_Authority )
{
ServerFire ();
}
AActor * MyOwner = GetOwner ();
if ( MyOwner )
{
//...
if ( GetWorld () -> LineTraceSingleByChannel ( Hit , EyeLocation , TraceEnd , COLLISION_WEAPON , QueryParams ))
{
//...
//这里把伤害者改为武器拥有者,武器是没有生命值组件的
UGameplayStatics :: ApplyPointDamage ( HitActor , ActualDamage , ShotDirection , Hit , MyOwner -> GetInstigatorController (), MyOwner , DamageType );
}
}
}
调整人形AI的相机,修改BP_AdvancedAI蓝图类中的弹簧臂,使相机在人物眼睛位置。
修改玩家BP_SCharacter中的生命值组件的队伍编号为0,这样就和其他人形AI的255分开了。
####2.实时更新选择离自己最近的目标攻击
Service_SelectTargetActor
####3.优化球形AI,并让球形AI不伤害友军。
STracerBot.h
1
2
3
4
5
protected :
FTimerHandle TimerHandle_RefreshPath ;
void RefreshPath ();
STracerBot.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//调试用
static int32 DebugTrackerBotDrawing = 0 ;
FAutoConsoleVariableRef CVARDebugTrackerBotDrawing (
TEXT ( "COOP.DebugTrackerBot" ),
DebugTrackerBotDrawing ,
TEXT ( "Draw Debug Lines for TrackerBot" ),
ECVF_Cheat );
ASTrackerBot :: ASTrackerBot ()
{
//修改伤害和范围
ExplosionDamage = 60 ;
ExplosionRadius = 350 ;
}
FVector ASTrackerBot :: GetNextPathPoint ()
{
AActor * BestTarget = nullptr ;
float NearestTargetDistance = FLT_MAX ;
//使用Pawn迭代器,拿到每一个Pawn的生命值组件,比较他们的队伍编号,找到生命值大于0的非友军,设置离自己最近的敌人的距离。
for ( FConstPawnIterator It = GetWorld () -> GetPawnIterator (); It ; ++ It )
{
APawn * TestPawn = It -> Get ();
if ( TestPawn == nullptr || USHealthComponent :: IsFriendly ( TestPawn , this ))
{
continue ;
}
USHealthComponent * TestPawnHealthComp = Cast < USHealthComponent > ( TestPawn -> GetComponentByClass ( USHealthComponent :: StaticClass ()));
if ( TestPawnHealthComp && TestPawnHealthComp -> GetHealth () > 0.0f )
{
float Distance = ( TestPawn -> GetActorLocation () - GetActorLocation ()). Size ();
if ( Distance < NearestTargetDistance )
{
BestTarget = TestPawn ;
NearestTargetDistance = Distance ;
}
}
}
if ( BestTarget )
{
UNavigationPath * NavPath = UNavigationSystemV1 :: FindPathToActorSynchronously ( this , GetActorLocation (), BestTarget );
GetWorldTimerManager (). ClearTimer ( TimerHandle_RefreshPath );
GetWorldTimerManager (). SetTimer ( TimerHandle_RefreshPath , this , & ASTrackerBot :: RefreshPath , 5.0f , false );
if ( NavPath && NavPath -> PathPoints . Num () > 1 )
{
// 返回下一个点
return NavPath -> PathPoints [ 1 ];
}
}
// 寻路失败
return GetActorLocation ();
}
void ASTrackerBot :: RefreshPath ()
{
NextPathPoint = GetNextPathPoint ();
}
###6.挑战:狡猾AI
####1.实现人形AI血量降低到40时,逃跑并回血
AI行为树的修改,一个装饰器用来检查血量,一个查询用来查找逃离地点,一个任务用来回血
1.新建检查血量的装饰器Decorator_CheckBelowHealth,继承装饰器基类,重载PerformConditionCheckAI执行条件检查AI函数,新建血量阈值LowHealthTreshold设为公开。
2.新建查询逃离点的环境查询EQS_FindCover1.生成环
2.测试1,找建筑物高于100的掩体,来躲开玩家。
Untitled 29.webp
3.测试2,找距离玩家至少300的距离最远的点。
4.测试3,找直线距离最近的点,不要绕道的。
3.新建回血的任务Task_HealSelf,记得变量公开