跳过正文

CoopGame09-进阶AI

··2932 字·6 分钟
coopgame - 这篇文章属于一个选集。
§ 9: 本文

CoopGame09-进阶AI
#

跟随B站up主“技术宅阿棍儿”的教程制作的笔记。教程链接 ###0.内容

  • 人形敌人,远程武器
  • AI行为树
  • 使用EQS系统帮助AI掩护或寻找攻击点
  • AI的视觉
  • 挑战:AI低血量自动逃跑和回复

###1.综合使用黑板,行为树,环境查询

1.创建AdvancedAI文件夹放高级AI相关文件,创建BP_AdavncedAI蓝图类,继承SCharacter C++类。

2.设置BP_AdvancedAI蓝图类

设置自身

Untitled.webp

设置网格体

Untitled 1.webp

3.创建黑板BB_AdvancedAI,新建向量类型变量TragetDestination,Object类型变量TargetActor,其基类为Actor。

4.创建行为树BT_AdvancedAI,点击根,绑定黑板BB_AdvancedAI,并编写

Untitled 2.webp

5.编写BP_AdavncedAI蓝图类,实现AI朝一个点前进。

Untitled 3.webp

6.使用EQS决定AI攻击的位置

新建环境查询EQS_FindMoveTo,根下添加Point:Donut(环),设置如图,字面意思,找点模式为根据场景上的导航体。

Untitled 4.webp

测试1距离测试:离AI自己越近的分数越高,权重为2

Untitled 5.webp

测试2追踪测试:

Untitled 6.webp

测试3追踪测试:看不到玩家的点都是无效的

Untitled 7.webp

7.新建装饰器Decorator_DistanceTo,继承BTDecorator_BlueprintBase BT装饰器蓝图基础。 重载PerformConditionCheckAI 执行条件检查AI函数,新建黑板键选择器结构变量BlackboardKey和浮点变量Distance,两个变量都设置为可编辑实例,编写蓝图。

Untitled 8.webp

8.装饰器的作用是,当AI的环境查询设置了看不到玩家的点时无效点时,不能依靠环境查询来靠近玩家,需要另一个分支来使AI靠近玩家

9.修改之前蓝图类BP_AdvancedAI逻辑,改为向目标Actor移动

Untitled 9.webp

10.新建蓝图类EnvQueryContext_TargetActor,继承EvnQueryContext_Blueprintbase,重载ProvideSingleActor 提供单个Actor。

Untitled 10.webp

10.修改行为树BT_AdvancedAI

Untitled 11.webp

Untitled 12.webp

Untitled 13.webp

11.修改环境查询

Untitled 14.webp

###2.AI感知和行为树服务

1.修改蓝图类BP_AdvancedAI,将之前获取玩家Pawn然后设置黑板的TargetActor的步骤删除,改为下面在服务里用视觉组件,看到谁就找谁,而不是自己指定Actor。

添加AI感知组件(AIPerception),并设置添加感官配置AI视力配置

Untitled 15.webp

创建服务蓝图Service_SelectTargetActor ,继承BTService_BlueprintBase类,编写蓝图。注意新建的黑板键选择器变量设置为可编辑实例。

Untitled 16.webp

修改行为树BT_AdvancedAI,服务下面的selector是添加Blackboard装饰器,判断TargetActor已设置

Untitled 17.webp

###3.新环境查询和装饰器的终止机制

实现多人游戏情况下,AI找的是离自己最近的玩家 1.新建环境查询EQS_FindNearesPlayer,根拉出Actor of class,选择我们的玩家蓝图类,取消勾选仅生成半径中的Actor

Untitled 18.webp

Untitled 19.webp

2.修改行为树,设置黑板条件中的观察期终止,终止低优先级。意思Blackboard Based Condition条件为假,既没有看到玩家的时候,是走右边的查询最近的玩家并移动过去,但在移动的过程中看到玩家了,此时条件为真,会终止比他优先级低的节点,既是Blackboard Based Condition右边的会终止。

Untitled 20.webp

Untitled 21.webp

###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任务蓝图基础,并编写

Untitled 22.webp

####3.修改行为树BT_AdvancedAI

Untitled 23.webp

####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蓝图类。

Untitled 24.webp

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

Untitled 25.webp

####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行为树的修改,一个装饰器用来检查血量,一个查询用来查找逃离地点,一个任务用来回血

Untitled 26.webp

1.新建检查血量的装饰器Decorator_CheckBelowHealth,继承装饰器基类,重载PerformConditionCheckAI执行条件检查AI函数,新建血量阈值LowHealthTreshold设为公开。

Untitled 27.webp

2.新建查询逃离点的环境查询EQS_FindCover1.生成环

Untitled 28.webp

2.测试1,找建筑物高于100的掩体,来躲开玩家。

Untitled 29.webp

3.测试2,找距离玩家至少300的距离最远的点。

Untitled 30.webp

4.测试3,找直线距离最近的点,不要绕道的。

Untitled 31.webp

3.新建回血的任务Task_HealSelf,记得变量公开

Untitled 32.webp

coopgame - 这篇文章属于一个选集。
§ 9: 本文