跳过正文

CoopGame06-AI基础

··5086 字·11 分钟
目录
coopgame - 这篇文章属于一个选集。
§ 6: 本文

CoopGame06-AI基础
#

AI导航
#

创建AI球
#

  1. 资源准备:
    1. 导入最新工程的TrackerBot文件夹。工程地址:GitHub - zmingu/CoopGame: 教学项目CoopGame
  2. 创建C++类,继承Pawn类的AI角色球STrackerBot的,放在AI文件夹下。
    STrackerBot.h声明静态网格体组件, 并在STrackerBot.cpp的构造函数中初始化并设置为不影响导航
    1
    2
    3
    4
    5
    6
    7
    
    UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category="Components")
    class UStaticMeshComponent* StaticMeshComp;
    
    //构造函数中初始化静态网格体,并设置自己不影响导航
    StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComp"));
    RootComponent = StaticMeshComp;
    StaticMeshComp->SetCanEverAffectNavigation(false);//组件自身不影响导航网格
    
  3. Blueprints文件夹下创建继承STrackerBotC++类的BP_TrackerBot蓝图类。
    1. 给蓝图类设置静态网格体:复制默认的球体网格体Sphere到新建文件夹TrackerBot中,重命名为SM_TrackerBot,打开静态网格体设置其编译设置,构建比例为0.2。给蓝图设置上该静态网格体

      image.png
      image.png

    2. 创建材质M_TrackerBot,先给个白色基础颜色,并设置网格体SM_TrackerBot的材质为这个。

      image.png

    3. 放大场地,摆放一些简单的阻挡物,做一个简单的场景,拖入BP_TrackerBot.

    4. 在放置Actor窗口,选择体积,选择导航网格体边界体积拖入场景内,并调节大小覆盖场景,按p可显示导航面积。

      image.png

    5. Build.cs文件中添加导航系统NavigationSystem

      1
      
      PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore","PhysicsCore","NavigationSystem" });
      

AI球找路径点
#

STrackerBot.h声明寻点函数, 并在STrackerBot.cpp中利用FindPathToActorSynchronously函数实现功能
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
//声明寻路径点函数
FVector GetNextPathPoint();


//实现寻路径点函数
#include "NavigationPath.h"  
#include "NavigationSystem.h"  
#include "Kismet/GameplayStatics.h"
FVector ASTrackerBot::GetNextPathPoint()
{
	//拿到0号玩家对象
	APawn* PlayerPawn = UGameplayStatics::GetPlayerPawn(this, 0);
	//立即找到下一个路径(上下文,路径开始点,目标Actor)
	UNavigationPath* NavPath = UNavigationSystemV1::FindPathToActorSynchronously(this,GetActorLocation(),PlayerPawn);
	//如果路径点数量大于1返回下一个位置点
	if (NavPath->PathPoints.Num()>1)
	{
		return NavPath->PathPoints[1];
	}
	//否则返回初始位置
	return GetActorLocation();
}

朝路径点推动AI球
#

STrackerBot.h声明变量力的大小,是否改变速度到达判定距离下一个点的向量
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//添加力的大小
UPROPERTY(EditDefaultsOnly,Category="TrackerBot")
float MovementForce;
//是否使用改变速度
UPROPERTY(EditDefaultsOnly,Category="TrackerBot")
bool bUseVelocityChange;
//距离目标多少时判定为到达
UPROPERTY(EditDefaultsOnly,Category="TrackerBot")
float RequiredDistanceToTarget;
//下一个点的变量
UPROPERTY()
FVector NextPathPoint;
STrackerBot.cpp中初始化上面的变量,在Tick函数中编写推动AI球的逻辑
 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
#include "Kismet/GameplayStatics.h"

ASTrackerBot::ASTrackerBot()
{
//...
	//设置AI球网格体启用物理模拟
	StaticMeshComp->SetSimulatePhysics(true);
	//启用力改变速度
	bUseVelocityChange = true;
	//作用力大小
	MovementForce = 200;
	//判定到达目标的距离
	RequiredDistanceToTarget = 100;
//...
}

void ASTrackerBot::BeginPlay()
{
	Super::BeginPlay();
	//获取到下一个导航点并赋值
	NextPathPoint = GetNextPathPoint();
}

void ASTrackerBot::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	//获得两点向量差的大小,既是距离下一个点的距离
	float DistanceToTarget = (GetActorLocation() - NextPathPoint).Size();

	//距离下一个点的距离小于阈值100则继续获取下一个点,如果还没到则推进。
	if (DistanceToTarget <= RequiredDistanceToTarget)
	{
		NextPathPoint = GetNextPathPoint();
		DrawDebugSphere(GetWorld(), NextPathPoint, 20, 12, FColor::Yellow, 0.0f, 0, 1.0f);
	}
	else
	{
		//获得从AI球指向下一个点的向量。
		FVector ForceDirection = NextPathPoint - GetActorLocation();
		//获取方向,不受大小的影响。
		ForceDirection.Normalize();
		//方向向量 * 力 = 有方向的力,用来推动小球。
		ForceDirection *= MovementForce;
		//添加推力使小球朝目标滚动。同时改变小球的速度。
		StaticMeshComp->AddForce(ForceDirection, NAME_None, bUseVelocityChange);
        //画出AI球的运动方向
		DrawDebugDirectionalArrow(GetWorld(), GetActorLocation(), GetActorLocation() + ForceDirection, 32, FColor::Red,
								  false, 0.0f,
								  0, 1.0f);
	}
    ////画出下一个目标点位置
    DrawDebugSphere(GetWorld(), NextPathPoint, 20, 12, FColor::Yellow, false, 0.0f, 1.0f);
}

AI球自爆造成伤害
#

添加造成伤害支持
#

  1. 先确保角色的碰撞为Pawn,忽略Visiblity和Weapon,AI球的碰撞为Pawn重叠。
    STrackerBot.h中声明生命组件(看来生命组件是通用的),受伤回调函数
    1
    2
    3
    4
    5
    6
    
    UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category="Conmponents")
    class USHealthComponent* HealthComp;
    
    //受伤回调函数
    UFUNCTION()
    void HandleTakeDamage(class USHealthComponent* OwningHealthComp,float Health,float HealthDelta,const class UDamageType* DamageType, class AController*InstigatedBy, AActor* DamageCauser);
    
    STrackerBot.cpp中初始化生命组件,绑定受伤回调函数,
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    #include "Component/SHealthComponent.h"
    ASTrackerBot::ASTrackerBot()
    {
    //...
    	HealthComp = CreateDefaultSubobject<USHealthComponent>(TEXT("HealthComp"));
    	HealthComp->OnHealthChanged.AddDynamic(this,&ASTrackerBot::HandleTakeDamage);
    //...
    }
    
    void ASTrackerBot::HandleTakeDamage(USHealthComponent* OwningHealthComp, float Health, float HealthDelta,
    	const UDamageType* DamageType, AController* InstigatedBy, AActor* DamageCauser)
    {
    	UE_LOG(LogTemp,Log,TEXT("Health %s of %s"),*FString::SanitizeFloat(Health),*GetName);
    }
    

受伤闪烁,死亡爆炸
#

  1. 修改M_TrackerBot材质,配合动态修改材质参数实现受伤闪烁

    Untitled.webp
    STrackerBot.h中声明材质实例,并在STrackerBot.cppHandleTakeDamage()函数中通过修改材质中的参数来达到受伤闪烁的效果
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    //声明材质实例  
    UMaterialInstanceDynamic* MatInstance;
    
    // 闪烁逻辑
    void ASTrackerBot::HandleTakeDamage(USHealthComponent* OwningHealthComp, float Health, float HealthDelta,
    	const UDamageType* DamageType, AController* InstigatedBy, AActor* DamageCauser)
    {
    	if (MatInstance ==nullptr)
    	{
                    //拿到材质实例
    		MatInstance = StaticMeshComp->CreateAndSetMaterialInstanceDynamicFromMaterial(0,StaticMeshComp->GetMaterial(0));
    	}
    	if (MatInstance)
    	{
                    //受伤时,通过改变材质中的参数,这时这个参数几乎等于输入Timer,所以会亮一下。
    		MatInstance->SetScalarParameterValue("LastTimeDamageTaken",GetWorld()->TimeSeconds);
    	}
    
    	UE_LOG(LogTemp,Log,TEXT("Health %s of %s"),*FString::SanitizeFloat(Health),*GetName());
    }
    

  2. 添加自毁函数,添加爆炸特效等,记得给BP_TrackerBot蓝图类的类默认值指定爆炸特效。

    STrackerBot.h中声明变量是否爆炸爆炸范围爆炸伤害爆炸特效自毁函数
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    void SelfDestruct();//自毁函数,需要生成爆炸特效,造成范围伤害。
    
    UPROPERTY(EditDefaultsOnly,Category="TrackerBot")
    class UParticleSystem* ExplosionEffect;//爆炸特效
    
    bool bExploded;//是否爆炸
    
    UPROPERTY(EditDefaultsOnly,Category="TrackerBot")
    float ExplosionRadius;//爆炸范围
    
    UPROPERTY(EditDefaultsOnly,Category="TrackerBot")
    float ExplosionDamage;//爆炸伤害
    
    STrackerBot.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
    
    ASTrackerBot::ASTrackerBot()
    {
    //...
    	ExplosionRadius = 200;
    	ExplosionDamage = 100;
    //...
    }
    
    void ASTrackerBot::HandleTakeDamage(USHealthComponent* OwningHealthComp, float Health, float HealthDelta,
    	const UDamageType* DamageType, AController* InstigatedBy, AActor* DamageCauser)
    {
    //...
    	if (Health <= 0)
    	{
    		SelfDestruct();
    	}
    //...
    }
    
    void ASTrackerBot::SelfDestruct()
    {
    	//如果爆炸过直接返回
    	if (bExploded) return;
    	//设置爆炸
    	bExploded = true;
    	//播放爆炸特效
    	UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ExplosionEffect, GetActorLocation());
    	//添加忽略的Actor,自身
    	TArray<AActor*> IgnoredActors;
    	IgnoredActors.Add(this);
    	//造成范围伤害ApplyRadialDamage(上下文,原点的基础伤害,原点位置,伤害半径,伤害类型类,要忽略的Actor列表,造成伤害的人,负责造成伤害的控制器,伤害是否根据原点缩放)
    	UGameplayStatics::ApplyRadialDamage(this, ExplosionDamage, GetActorLocation(), ExplosionRadius, nullptr,
    	                                    IgnoredActors, this, GetInstigatorController(), true);
    	DrawDebugSphere(GetWorld(), GetActorLocation(), ExplosionRadius, 12, FColor::Green, false, 2.0f, 0, 1.0f);
    	//设置这个Actor的寿命。当寿命结束就会销毁。
    	//SetLifeSpan(1.0f);
    	
    	//销毁Actor  
    	Destroy();
    }
    

倒计时自爆,以及音效
#

STrackerBot.h声明是否开始自残自残时间句柄,自残函数, 球形组件用于检测重叠,以及重叠事件。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//是否开始伤害自己  
bool bStartSelfDestruction;  
//时间句柄  
FTimerHandle TimerHandle_SelfDamage;  
//伤害自己的函数  
void DamageSelf();  
//球形组件,用来判断和玩家重叠  
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components")  
class USphereComponent* SphereComp;  
//重叠事件  
virtual void NotifyActorBeginOverlap(AActor* OtherActor) override;
STrackerBot.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
#include "Components/SphereComponent.h"
ASTrackerBot::ASTrackerBot()
{
//...
	SphereComp = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComp"));
	//设置半径
	SphereComp->SetSphereRadius(200);
	//设置碰撞类型为只查询
	SphereComp->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
	//设置所有碰撞通道为忽略
	SphereComp->SetCollisionResponseToChannels(ECR_Ignore);
	//只开启和Pawn类型的重叠
	SphereComp->SetCollisionResponseToChannel(ECC_Pawn,ECR_Overlap);
	SphereComp->SetupAttachment(StaticMeshComp);
}

void ASTrackerBot::DamageSelf()
{
    //对自己造成20点伤害,ApplyDamage(被伤害的Actor,基础伤害,造成伤害的控制器,造成伤害的Actor,描述造成伤害的类)
	UGameplayStatics::ApplyDamage(this,20,GetInstigatorController(),this,nullptr);
}

#include "SCharacter.h"
void ASTrackerBot::NotifyActorBeginOverlap(AActor* OtherActor)
{
	Super::NotifyActorBeginOverlap(OtherActor);
	if (!bStartSelfDestruction)
	{
		ASCharacter* MyCharacter = Cast<ASCharacter>(OtherActor);
		//如果碰到的是玩家,就用定时器每0.5秒伤害自己一次,一次20滴血
		if (MyCharacter)
		{
			//SetTimer(时间句柄,调用者,调用的方法,调用间隔,是否循环,离第一次调用的延迟)
			GetWorldTimerManager().SetTimer(TimerHandle_SelfDamage,this,&ASTrackerBot::DamageSelf,0.5f,true,0.0f);
			//设置为开始自爆
			bStartSelfDestruction = true;
		}
	}
}

  • AI球的音效。(滚动,警告,爆炸),记得在蓝图类选择音效。
    STrackerBot.h声明倒计时自爆音效爆炸音效。并在STrackerBot.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
    
    //倒计时自爆音效
    UPROPERTY(EditDefaultsOnly,Category="TrackerBot")
    class USoundBase *SelfDestructSound;
    //爆炸音效
    UPROPERTY(EditDefaultsOnly,Category="TrackerBot")
    class USoundBase* ExplodeSound;
    
    
    //STrackerBot.cpp
    void ASTrackerBot::NotifyActorBeginOverlap(AActor* OtherActor)
    {
    	Super::NotifyActorBeginOverlap(OtherActor);
    	if (!bStartSelfDestruction)
    	{
    	//...
    		if (MyCharacter)
    		{
    		//...
    	    //播放倒计时自毁音效  
    		UGameplayStatics::SpawnSoundAtLocation(this, SelfDestructSound, GetActorLocation());
    		}
    
    	}
    }
    
    void ASTrackerBot::SelfDestruct()
    {
    //...
    	//SetLifeSpan(0.1f);
    	UGameplayStatics::SpawnSoundAtLocation(this,ExplodeSound,GetActorLocation());//播放爆炸音效
    	Destroy();
    }
    

设置声音衰减
#

  1. 两种设置方法
    1. 直接在声音Cue上设置,如图:

      Untitled.webp

    2. 创建声音衰减文件,如图:

      Untitled 1.webp

滚动的声音随速度变化
#

  1. 给蓝图类BP_TrackerBot添加音频组件AudioComp,为其设置滚动音效ball_roll_03_loop_Cue
  2. Tick事件中根据速度设置音量乘数
    Untitled 2.webp

AI球支持网络联机
#

仅服务端执行寻路逻辑
#

TrackerBot.cpp中给寻路逻辑套上网络权限判断
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
void ASTrackerBot::BeginPlay()
{
	Super::BeginPlay();
    //只在服务端执行寻路逻辑
	if (GetLocalRole() == ROLE_Authority)
	{
	    NextPathPoint = GetNextPathPoint(); //获取到下一个导航点
	}

}

void ASTrackerBot::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
    //只在服务端执行寻路逻辑,且AI球没爆炸
	if (GetLocalRole() == ROLE_Authority && !bExploded )
	{
	    //...
	}
}

修改生命值网络复制方式
#

  1. 之前我们使用的是直接给变量添加Replicated宏,只是将该变量复制给客户端,如果需要更多自定义,需要使用ReplicatedUsing宏,这个宏我们之前也用过,每当该变量改变时,回去调用一个函数。
    SHealthComponent.h中生命值变量修改宏为ReplicatedUsing,并声明调用的函数OnRep_Health,并让该函数去广播改变后的生命值
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    UPROPERTY(ReplicatedUsing=OnRep_Health,BlueprintReadOnly,Category="HealthComponent")
    float Health;
    
    UFUNCTION()
    void OnRep_Health(float OldHealth);
    thComponent.cpp
    void USHealthComponent::OnRep_Health(float OldHealth)  
    {  
        //执行多播,让绑定了这个事件的地方进行回调
        float Damage = Health - OldHealth;  
        OnCompHealthChanged.Broadcast(this,Health,Damage,nullptr,nullptr,nullptr);  
    }
    

仅服务端自爆和造成伤害
#

STrackerBot.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
void ASTrackerBot::SelfDestruct()
{
	//...
    //静态网格体设置可见性和关闭碰撞
	StaticMeshComp->SetVisibility(false, true);
	StaticMeshComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	//AI球造成范围伤害也在服务端上做
	if (GetLocalRole() == ROLE_Authority)
	{
	//添加忽略的Actor,自身
		TArray<AActor*> IgnoredActors;
		IgnoredActors.Add(this);
		//造成范围伤害ApplyRadialDamage(上下文,原点的基础伤害,原点位置,伤害半径,伤害类型类,要忽略的Actor列表,造成伤害的人,负责造成伤害的控制器,伤害是否根据原点缩放)
		UGameplayStatics::ApplyRadialDamage(this, ExplosionDamage, GetActorLocation(), ExplosionRadius, nullptr,
		                                    IgnoredActors, this, GetInstigatorController(), true);
		DrawDebugSphere(GetWorld(), GetActorLocation(), ExplosionRadius, 11, FColor::Green, false, 2.0f, 0, 1.0f);
		//设置这个Actor的寿命。当寿命结束就会销毁。
		SetLifeSpan(1.0f);
	}
	//播放特效音效
}

void ASTrackerBot::NotifyActorBeginOverlap(AActor* OtherActor)
{
	Super::NotifyActorBeginOverlap(OtherActor);
	//如果没有开始倒计时自爆,且没有爆炸
	if (!bStartSelfDestruction && !bExploded)
	{
        //...
		//如果碰到的是玩家,就用定时器每0.5秒伤害自己一次,一次20滴血
		if (MyCharacter)
		{
            //设置自爆的定时器也要在服务器上
			if (GetLocalRole() == ROLE_Authority)
			{
				//SetTimer(时间句柄,调用者,调用的方法,调用间隔,是否循环,离第一次调用的延迟)
				GetWorldTimerManager().SetTimer(TimerHandle_SelfDamage, this, &ASTrackerBot::DamageSelf, 0.5f, true, 0.0f);
			}
            //...
		}
	}
}

3.运行结果:两端的角色都能被AI球炸死,特效音效正常。
#

挑战:AI群体Buff
#

1.AI球附近有多个同类时,闪烁并增加伤害。
#

STrackerBot.h声明伤害等级, 检测同类函数
1
2
3
4
5
//检测附近同类的函数
UFUNCTION()
void OnCheckNearbyBots();
//伤害等级
int32 PowerLevel;
STrackerBot.cpp中添加群体buff逻辑
 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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
void ASTrackerBot::BeginPlay()
{
	Super::BeginPlay();
	//只有在服务端上的AI球才寻路
	if (GetLocalRole() == ROLE_Authority)
	{
		//获取到下一个导航点并赋值
		NextPathPoint = GetNextPathPoint();
		//设置每秒调用检测附近AI球同类的OnCheckNearbyBots函数
		FTimerHandle TimerHandle;
		GetWorldTimerManager().SetTimer(TimerHandle, this, &ASTrackerBot::OnCheckNearbyBots, 1.0f, true);
	}
}

void ASTrackerBot::SelfDestruct()
{
//...
	if (GetLocalRole() == ROLE_Authority)
	{
	//...
		//照成的伤害翻倍取决于附近的同类数量
		float Damage = ExplosionDamage + (ExplosionDamage * PowerLevel);
		//造成范围伤害ApplyRadialDamage(上下文,原点的基础伤害,原点位置,伤害半径,伤害类型类,要忽略的Actor列表,造成伤害的人,负责造成伤害的控制器,伤害是否根据原点缩放)
		UGameplayStatics::ApplyRadialDamage(this, Damage, GetActorLocation(), ExplosionRadius, nullptr,IgnoredActors, this, GetInstigatorController(), true);
		DrawDebugSphere(GetWorld(), GetActorLocation(), ExplosionRadius, 11, FColor::Green, false, 2.0f, 0, 1.0f);
    //...
	}
//...
}

FVector ASTrackerBot::GetNextPathPoint()
{
	//拿到0号玩家对象
	APawn* PlayerPawn = UGameplayStatics::GetPlayerPawn(this, 0);
	//拿到0号玩家才继续寻路
	if (PlayerPawn)
	{
		//立即找到下一个路径(上下文,路径开始点,目标Actor)
		UNavigationPath* NavPath = UNavigationSystemV1::FindPathToActorSynchronously(this, GetActorLocation(), PlayerPawn);
		//如果路径点数量大于1返回下一个位置点,且路径存在
		if (NavPath && NavPath->PathPoints.Num() > 1)
		{
			return NavPath->PathPoints[1];
		}
	}
//否则返回初始位置
		return GetActorLocation();
}

void ASTrackerBot::OnCheckNearbyBots()
{
	//声明碰撞球,用于检测球体内有多少AI同类。
	FCollisionShape CollisionShape;
	//设置碰撞球半径。
	CollisionShape.SetSphere(600);
	//重叠结果的数组,用来存放所有重叠到的东西
	TArray<FOverlapResult> OverlapResults;
	//碰撞对象查询参数
	FCollisionObjectQueryParams QueryParams;
	//添加需要查询的两种碰撞通道类型
	QueryParams.AddObjectTypesToQuery(ECC_PhysicsBody);
	QueryParams.AddObjectTypesToQuery(ECC_Pawn);
	//按对象类型的重叠检测,将检测到的东西放进OverlapResults数组中
	GetWorld()->OverlapMultiByObjectType(OverlapResults, GetActorLocation(), FQuat::Identity, QueryParams,
	                                     CollisionShape);
	//画出用于检测的碰撞球
	DrawDebugSphere(GetWorld(), GetActorLocation(), CollisionShape.GetSphereRadius(), 12, FColor::Blue, false, 1.0f);
	//声明其他同类AI球的数量
	int32 NrOfBots = 0;
	//遍历所有重叠检测到的东西,如果是同类就把同类数量+1
	for (FOverlapResult Result : OverlapResults)
	{
		ASTrackerBot* Bot = Cast<ASTrackerBot>(Result.GetActor());
		//重叠检测到的是AI球,且不是自己,就计数
		if (Bot && Bot != this)
		{
			NrOfBots++;
		}
	}
	//定义常量最大伤害等级
	const int32 MaxPowerLevel = 4;
	//限制伤害等级范围0-4
	PowerLevel = FMath::Clamp(NrOfBots, 0, MaxPowerLevel);
	if (MatInst == nullptr)
	{
		//拿到AI球的材质实例
		MatInst = StaticMeshComp->
			CreateAndSetMaterialInstanceDynamicFromMaterial(0, StaticMeshComp->GetMaterial(0));
	}
	if (MatInst)
	{
		//通过设置参数来改变材质闪烁,更详细的材质介绍请看视频
		float Alpha = PowerLevel / (float)MaxPowerLevel;
		MatInst->SetScalarParameterValue("PowerLevelAlpha", Alpha);
	}
	//打印伤害等级
	DrawDebugString(GetWorld(), FVector(), FString::FromInt(PowerLevel), this, FColor::White, 1.0f, true);
}

材质修改,参数PowerLevelAlpha控制材质闪烁
#

蓝图如图:

Untitled 3.webp

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