CoopGame09-进阶AI

type: Post
status: Published
date: 2022/10/25
slug: CoopGame09
summary: UE4 C++多人游戏入门
category: Unreal

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

0.内容

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

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

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

2.设置BP_AdvancedAI蓝图类

设置自身

CoopGame09-进阶AI

设置网格体

CoopGame09-进阶AI

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

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

CoopGame09-进阶AI

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

CoopGame09-进阶AI

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

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

CoopGame09-进阶AI

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

CoopGame09-进阶AI

测试2追踪测试:

CoopGame09-进阶AI

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

CoopGame09-进阶AI

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

CoopGame09-进阶AI

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

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

CoopGame09-进阶AI

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

CoopGame09-进阶AI

10.修改行为树BT_AdvancedAI

CoopGame09-进阶AI

CoopGame09-进阶AI

CoopGame09-进阶AI

11.修改环境查询

CoopGame09-进阶AI

2.AI感知和行为树服务

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

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

CoopGame09-进阶AI

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

CoopGame09-进阶AI

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

CoopGame09-进阶AI

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

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

CoopGame09-进阶AI

CoopGame09-进阶AI

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

CoopGame09-进阶AI

CoopGame09-进阶AI

4.进击的AI

1.修改角色代码

SCharacter.h
public:
    UFUNCTION(BlueprintCallable,Category="Player")
    void StartFire();

    UFUNCTION(BlueprintCallable,Category="Player")
    void StopFire();

2.新建任务蓝图Task_AttackTarget,继承BTTask_Blueprint BaseBT任务蓝图基础,并编写

CoopGame09-进阶AI

3.修改行为树BT_AdvancedAI

CoopGame09-进阶AI

4.修改AI的攻击伤害。

1.SWeapon.h
    /* 子弹散射角度 */
    UPROPERTY(EditDefaultsOnly, Category = "Weapon", meta = (ClampMin=0.0f))
    float BulletSpread;
2.SWeapon.cpp
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蓝图类。

CoopGame09-进阶AI

5.别开枪,是友军。

1.人形AI不打友军。

SHealthComponent.h
    //队伍编号
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "HealthComponent")
    uint8 TeamNum;

    //判断两个Actor是否为友军
    UFUNCTION(BlueprintCallable, BlueprintPure, Category = "HealthComponent")
    static bool IsFriendly(AActor* ActorA, AActor* ActorB);
SHealthComponent.cpp
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
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

CoopGame09-进阶AI

3.优化球形AI,并让球形AI不伤害友军。

STracerBot.h
protected:

    FTimerHandle TimerHandle_RefreshPath;

    void RefreshPath();
STracerBot.cpp
//调试用
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行为树的修改,一个装饰器用来检查血量,一个查询用来查找逃离地点,一个任务用来回血

CoopGame09-进阶AI

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

CoopGame09-进阶AI

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

CoopGame09-进阶AI

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

Untitled 29.webp

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

CoopGame09-进阶AI

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

CoopGame09-进阶AI

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

CoopGame09-进阶AI

文章目录