CoopGame06-AI基础
type: Post
status: Published
date: 2022/10/10
slug: CoopGame06
summary: UE4 C++多人游戏入门
category: Unreal
跟随B站up主“技术宅阿棍儿”的教程制作的笔记。教程链接
AI导航
资源准备:
导入最新工程的TrackerBot文件夹。工程地址
1.创建继承Pawn
类的AI角色球STracerBot
的C++类,。
STracerBot.h
UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category="Components")
class UStaticMeshComponent* StaticMeshComp;
STracerBot.cpp
ASTracerBot::ASTracerBot()
{
//...
StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComp"));
RootComponent = StaticMeshComp;
StaticMeshComp->SetCanEverAffectNavigation(false);//自身不影响导航
//...
}
2.创建继承STracerBot
C++类的BP_TracerBot
蓝图类。
3.给蓝图类设置静态网格体:复制默认的球体网格体
到新建文件夹Meshes
中,重命名为SM_TracerBot
,打开静态网格体设置其编译设置,构建比例为0.2
。
4.创建材质M_TracerBot
,先给个白色基础颜色,并设置网格体SM_TracerBot
的材质为这个。
5.放大场地,摆放一些简单的阻挡物,做一个简单的场景,拖入BP_TracerBot
.
6.在放置Actor窗口,选择体积,选择导航网格体边界体积
拖入场景内,并调节大小覆盖场景,按p
可显示导航面积。
7.Build.cs文件中添加导航系统
项目名.Build.cs
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" ,"NavigationSystem"});
8.获取导航路径的下一个点
STracerBot.h
FVector GetNextPathPoint();
STracerBot.cpp
FVector ASTracerBot::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();
}
9.用作用力移动AI角色球
STracerBot.h
//添加力的大小
UPROPERTY(EditDefaultsOnly,Category="TracerBot")
float MovementForce;
//是否使用改变速度
UPROPERTY(EditDefaultsOnly,Category="TracerBot")
bool bUseVelocityChange;
//距离目标多少时判定为到达
UPROPERTY(EditDefaultsOnly,Category="TracerBot")
float RequiredDistanceToTarget;
//下一个点的变量
UPROPERTY()
FVector NextPathPoint;
STracerBot.cpp
ASTracerBot::ASTracerBot()
{
//...
//设置AI球网格体启用物理模拟
StaticMeshComp->SetSimulatePhysics(true);
//启用力改变速度
bUseVelocityChange = true;
//作用力大小
MovementForce = 200;
//判定到达目标的距离
RequiredDistanceToTarget = 100;
//...
}
void ASTracerBot::BeginPlay()
{
Super::BeginPlay();
//获取到下一个导航点并赋值
NextPathPoint = GetNextPathPoint();
}
void ASTracerBot::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);
}
与玩家互动
1.添加造成伤害支持
STracerBot.h
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);
STracerBot.cpp
ASTracerBot::ASTracerBot()
{
//...
HealthComp = CreateDefaultSubobject<USHealthComponent>(TEXT("HealthComp"));
HealthComp->OnHealthChanged.AddDynamic(this,&ASTracerBot::HandleTakeDamage);
//...
}
void ASTracerBot::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);
}
2.受伤闪烁,通过控制材质参数来改变材质。
M_TracerBot材质文件修改。
STracerBot.h
class UMaterialInstanceDynamic* MatInstance;
STracerBot.cpp
void ASTracerBot::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());
}
3.AI球生命值为0时解体爆炸,记得给BP_TracerBot
蓝图类的类默认值指定爆炸特效。
STracerBot.h
void SelfDestruct();//自毁函数,需要生成爆炸特效,造成范围伤害。
UPROPERTY(EditDefaultsOnly,Category="TracerBot")
class UParticleSystem* ExplosionEffect;//爆炸特效
bool bExploded;//是否爆炸
UPROPERTY(EditDefaultsOnly,Category="TracerBot")
float ExplosionRadius;//爆炸范围
UPROPERTY(EditDefaultsOnly,Category="TracerBot")
float ExplosionDamage;//爆炸伤害
STracerBot.cpp
ASTracerBot::ASTracerBot()
{
//...
ExplosionRadius = 200;
ExplosionDamage = 100;
//...
}
void ASTracerBot::HandleTakeDamage(USHealthComponent* OwningHealthComp, float Health, float HealthDelta,
const UDamageType* DamageType, AController* InstigatedBy, AActor* DamageCauser)
{
//...
if (Health <= 0)
{
SelfDestruct();
}
//...
}
void ASTracerBot::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(2.0f);
}
4.靠近玩家时倒计时自爆。
STracerBot.h
//球形组件,用来判断和玩家重叠
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components")
class USphereComponent* SphereComp;
//时间句柄
FTimerHandle TimerHandle_SelfDamage;
//伤害自己的函数
void DamageSelf();
//是否开始伤害自己
bool bStartSelfDestruction;
//重叠事件
virtual void NotifyActorBeginOverlap(AActor* OtherActor) override;
STracerBot.cpp
ASTracerBot::ASTracerBot()
{
//...
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 ASTracerBot::DamageSelf()
{
//对自己造成20点伤害,ApplyDamage(被伤害的Actor,基础伤害,造成伤害的控制器,造成伤害的Actor,描述造成伤害的类)
UGameplayStatics::ApplyDamage(this,20,GetInstigatorController(),this,nullptr);
}
void ASTracerBot::NotifyActorBeginOverlap(AActor* OtherActor)
{
Super::NotifyActorBeginOverlap(OtherActor);
if (!bStartSelfDestruction)
{
ASCharacter* MyCharacter = Cast<ASCharacter>(OtherActor);
//如果碰到的是玩家,就用定时器每0.5秒伤害自己一次,一次20滴血
if (MyCharacter)
{
//SetTimer(时间句柄,调用者,调用的方法,调用间隔,是否循环,离第一次调用的延迟)
GetWorldTimerManager().SetTimer(TimerHandle_SelfDamage,this,&ASTracerBot::DamageSelf,0.5f,true,0.0f);
//设置为开始自爆
bStartSelfDestruction = true;
}
}
}
5.AI球的音效。(滚动,警告,爆炸),记得在蓝图类选择音效。
STracerBot.h
//倒计时自爆音效
UPROPERTY(EditDefaultsOnly,Category="TracerBot")
class USoundBase *SelfDestructSound;
//爆炸音效
UPROPERTY(EditDefaultsOnly,Category="TracerBot")
class USoundBase* ExplodeSound;
STracerBot.cpp
void ASTracerBot::NotifyActorBeginOverlap(AActor* OtherActor)
{
Super::NotifyActorBeginOverlap(OtherActor);
if (!bStartSelfDestruction)
{
//...
if (MyCharacter)
{
//...
//播放爆炸特效
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ExplosionEffect, GetActorLocation());
//播放爆炸音效
UGameplayStatics::SpawnSoundAtLocation(this, ExplodeSound, GetActorLocation());
}
}
}
void ASTracerBot::SelfDestruct()
{
//...
UGameplayStatics::SpawnSoundAtLocation(this,ExplodeSound,GetActorLocation());//播放爆炸音效
Destroy();
}
6.两种设置声音衰减的方法。
1.直接在声音Cue上设置,如图:
2.创建声音衰减文件,如图:
7.设置滚动的声音随速度变化。
1.蓝图BP_TracerBot添加音频组件,选择滚动音效。
2.写蓝图,如图:
联机AI
1.只在服务端执行寻路逻辑。
TracerBot.cpp
void ASTracerBot::BeginPlay()
{
Super::BeginPlay();
//只在服务端执行寻路逻辑
if (GetLocalRole() == ROLE_Authority)
{
NextPathPoint = GetNextPathPoint(); //获取到下一个导航点
}
}
void ASTracerBot::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
//只在服务端执行寻路逻辑,且AI球没爆炸
if (GetLocalRole() == ROLE_Authority && !bExploded )
{
//...
}
}
2.修改生命值组件和AI球联机逻辑。
SHealthComponent.h
UPROPERTY(ReplicatedUsing=OnRep_Health,BlueprintReadOnly,Category="HealthComponent")
float Health;
UFUNCTION()
void OnRep_Health(float OldHealth);
SHealthComponent.cpp
void USHealthComponent::OnRep_Health(float OldHealth)
{
OnHealthChanged.Broadcast(this,Health,Health - OldHealth,nullptr,nullptr,nullptr);
}
STracerBot.cpp
void ASTracerBot::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 ASTracerBot::NotifyActorBeginOverlap(AActor* OtherActor)
{
Super::NotifyActorBeginOverlap(OtherActor);
//如果没有开始倒计时自爆,且没有爆炸
if (!bStartSelfDestruction && !bExploded)
{
//...
//如果碰到的是玩家,就用定时器每0.5秒伤害自己一次,一次20滴血
if (MyCharacter)
{
//设置自爆的定时器也要在服务器上
if (GetLocalRole() == ROLE_Authority)
{
//SetTimer(时间句柄,调用者,调用的方法,调用间隔,是否循环,离第一次调用的延迟)
GetWorldTimerManager().SetTimer(TimerHandle_SelfDamage, this, &ASTracerBot::DamageSelf, 0.5f, true, 0.0f);
}
//...
}
}
}
3.运行结果:两端的角色都能被AI球炸死,特效音效正常。
挑战:AI群体Buff
1.AI球附近有多个同类时,闪烁并增加伤害。
StracerBot.h
//检测附近同类的函数
UFUNCTION()
void OnCheckNearbyBots();
//伤害等级
int32 PowerLevel;
StracerBot.cpp
void ASTracerBot::BeginPlay()
{
Super::BeginPlay();
//只有在服务端上的AI球才寻路
if (GetLocalRole() == ROLE_Authority)
{
//获取到下一个导航点并赋值
NextPathPoint = GetNextPathPoint();
//设置每秒调用检测附近AI球同类的OnCheckNearbyBots函数
FTimerHandle TimerHandle;
GetWorldTimerManager().SetTimer(TimerHandle, this, &ASTracerBot::OnCheckNearbyBots, 1.0f, true);
}
}
void ASTracerBot::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 ASTracerBot::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 ASTracerBot::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)
{
ASTracerBot* Bot = Cast<ASTracerBot>(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);
}
2.材质修改,参数PowerLevelAlpha控制材质闪烁。
蓝图如图:
文章目录
- CoopGame06-AI基础
- AI导航
- 1.创建继承Pawn类的AI角色球STracerBot的C++类,。
- 2.创建继承STracerBotC++类的BP_TracerBot蓝图类。
- 3.给蓝图类设置静态网格体:复制默认的球体网格体到新建文件夹Meshes中,重命名为SM_TracerBot,打开静态网格体设置其编译设置,构建比例为0.2。
- 4.创建材质M_TracerBot,先给个白色基础颜色,并设置网格体SM_TracerBot的材质为这个。
- 5.放大场地,摆放一些简单的阻挡物,做一个简单的场景,拖入BP_TracerBot.
- 6.在放置Actor窗口,选择体积,选择导航网格体边界体积拖入场景内,并调节大小覆盖场景,按p可显示导航面积。
- 7.Build.cs文件中添加导航系统
- 8.获取导航路径的下一个点
- 9.用作用力移动AI角色球
- 与玩家互动
- 联机AI
- 挑战:AI群体Buff