六一的部落格


关关难过关关过,前路漫漫亦灿灿。




说明

AI / EQS / Custom Test


验证EQS寻找补给时会把不可用补给考虑在内


补给不可用逻辑

补给被领取后, 设置在碰撞检测中忽略补给, 同时设置补给不可见, 使用定时器恢复碰撞和不可见选项


存在的问题

补给虽然无法参与碰撞, 在场景中也不可见, 但仍作为Actor存在于场景中, 于是NPC可以正常捕获


验证

  1. 初始状态


  2. 设置场景中一个补给不可见

    • 选中BP_STUAmmoPickup1

    • 设置CollisionComponent不可见

      Details > Rendering > Visible 取消勾选: 注意到, 碰撞胶囊已不可见


    • 设置StaticMesh不可见

      Details > Rendering > Visible 取消勾选


  3. 移动EQS_TestPawn, 仍可以捕获不可见补给


  4. 恢复补给状态


考虑解决方案

  • 自定义生成器: 筛选可见Actor
  • 自定义生成器限制条件: 筛选可见Actor

考虑如何实现自定义生成器限制条件

  • 判断组件可见性

    根组件的选择很灵活, 在一些方案里, 即使根组件不可见, Actor也许能被正常观察; 而根组件可见, Actor也不一定能被正常观察到
  • 为补给添加接口: 判断补给是否可用

    相应地, 该限制条件只能用于补给

创建C++类

-
基类 EnvQueryTest
名称 EnvQueryTest_PickupCouldBeTaken
路径 AI/EQS
属性 Public

学习EnvQueryTest及其派生类


派生类命名规则

引擎使用该模板识别限制条件在EQS蓝图编辑器的名称

EnvQueryTest_子类别



筛选Actor接口

EnvQueryTest

每次EQS运行时, 调用该函数; 在该函数中, 对捕获Actor进行筛选

public

1// Function that does the actual work
2virtual void RunTest(FEnvQueryInstance& QueryInstance) const { checkNoEntry(); }

筛选Actor逻辑

参考限制条件Trace EnvQueryTest_Trace


构造函数

1UEnvQueryTest_Trace::UEnvQueryTest_Trace(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
2{
3    Cost = EEnvTestCost::High;
4    ValidItemType = UEnvQueryItemType_VectorBase::StaticClass();
5    SetWorkOnFloatValues(false);
6
7    Context = UEnvQueryContext_Querier::StaticClass();
8    TraceData.SetGeometryOnly();
9}
  1. Cost

    说明限制条件逻辑的复杂度, 复杂度越高, 开销就越大

    使用枚举类型EEnvTestCost对其进行赋值

    复杂度参考用于待测试Actor的数学运算

  2. ValidItemType

    说明测试对象的数据类别, 数组或者元素

    -
    UEnvQueryItemType_VectorBase 数组
    UEnvQueryItemType_ActorBase 元素
  3. SetWorkOnFloatValues

    限制条件部分参数的模式由FilterType决定, SetWorkOnFloatValues使用枚举类型EEnvTestFilterType对FilterType进行设置

    -
    Minimum 数值
    Maximum 数值
    Range 数值
    Match 布尔类型

    参数为true时, FilterType值为Range; 参数为false, FilterType值为Match: 这两个相当于是两种默认类型

    如果FilterType为Range, 通常可以在蓝图编辑器中根据需求设置数值的其他模式Minimum和Maximum

     1void UEnvQueryTest::SetWorkOnFloatValues(bool bWorkOnFloats)
     2{
     3    bWorkOnFloatValues = bWorkOnFloats;
     4
     5    // Make sure FilterType is set to a valid value.
     6    if (bWorkOnFloats)
     7    {
     8        if (FilterType == EEnvTestFilterType::Match)
     9        {
    10            FilterType = EEnvTestFilterType::Range;
    11        }
    12    }
    13    else
    14    {
    15        if (FilterType != EEnvTestFilterType::Match)
    16        {
    17            FilterType = EEnvTestFilterType::Match;
    18        }
    19
    20        // Scoring MUST be Constant for boolean tests.
    21        ScoringEquation = EEnvTestScoreEquation::Constant;
    22    }
    23
    24    UpdatePreviewData();
    25}

UEnvQueryTest_Trace::RunTest

  1. FEnvQueryInstance::ItemIterator

    EQS专用的迭代器类型

  2. 获取Actor位置

    1const FVector ItemLocation = GetItemLocation(QueryInstance, It.GetIndex()) + FVector(0, 0, ItemZ);            
    
  3. 通用模板: 遍历捕获目标

    1for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It)
    2{
    3    const auto ItemActor = GetItemActor(QueryInstance, It.GetIndex()); // 后续需对ItemActor作出修改时, 移除const限定符
    4
    5    // ...
    6}
  4. 设置Actor通过筛选

    -
    TestPurpose 枚举类型TEnumAsByteEEnvTestPurpose, 取值 Filter Only , Score Only , Filter and Score
    FilterType
    Score 如果时布尔类型, 为true, 通过筛选; 为false, 未通过. 如果是数值, 为权重值
    bExpected 为true, Score决定筛选情况; 为false, 对Score对应的结果取反
    1It.SetScore(TestPurpose, FilterType, true, true);
  5. 设置Actor未通过筛选

    -
    InStatus 使用枚举类型EEnvItemStatus对其赋值, 表示是否通过筛选: FailedPassed
    Score 使用默认值
    1It.ForceItemState(EEnvItemStatus::Failed);

修改补给基类

  • 提供测试机制: 返回设置值
  • 通过定时器状态判断: 若定时器开启, 认为补给无效

添加属性: 开启测试

protected

ShootThemUp: Pickups/STUBasePickup.h

1UPROPERTY(EditAnywhere, BlueprintReadWrite)
2bool EnableTest = true;

添加属性: 测试结果

protected

ShootThemUp: Pickups/STUBasePickup.h

1UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditCondition = "EnableTest"))
2bool CouldBeTakenTest = true;

添加属性: 存放定时器描述符

private

ShootThemUp: Pickups/STUBasePickup.h

1FTimerHandle RespawnTimerHandle;

屏蔽定时器描述符变量定义

ShootThemUp: Pickups/STUBasePickup.cpp

1// PickupWasTaken

添加接口: 判断补给是否可用

public

ShootThemUp: Pickups/STUBasePickup.h

1bool CouldBeTaken() const;

ShootThemUp: Pickups/STUBasePickup.cpp

 1bool ASTUBasePickup::CouldBeTaken() const
 2{
 3    if (EnableTest)
 4    {
 5        return CouldBeTakenTest;
 6    }
 7    else
 8    {
 9        return !GetWorldTimerManager().IsTimerActive(RespawnTimerHandle);
10    }
11}

实现EnvQueryTest_PickupCouldBeTaken


构造函数

-
低开销
单个元素
使用布尔类型

public

ShootThemUp: AI/EQS/EnvQueryTest_PickupCouldBeTaken.h

1UEnvQueryTest_PickupCouldBeTaken(const FObjectInitializer &ObjectInitializer);

ShootThemUp: AI/EQS/EnvQueryTest_PickupCouldBeTaken.cpp

1#include "EnvironmentQuery/Items/EnvQueryItemType_ActorBase.h"
2
3UEnvQueryTest_PickupCouldBeTaken::UEnvQueryTest_PickupCouldBeTaken(const FObjectInitializer &ObjectInitializer) //
4: Super (ObjectInitializer)
5{
6    Cost = EEnvTestCost::Low;
7    ValidItemType = UEnvQueryItemType_ActorBase::StaticClass();
8    SetWorkOnFloatValues(false);
9}

RunTest

public

ShootThemUp: AI/EQS/EnvQueryTest_PickupCouldBeTaken.h

1virtual void RunTest(FEnvQueryInstance& QueryInstance) const override;

ShootThemUp: AI/EQS/EnvQueryTest_PickupCouldBeTaken.cpp

 1#include "Pickups/STUBasePickup.h"
 2
 3void UEnvQueryTest_PickupCouldBeTaken::RunTest(FEnvQueryInstance& QueryInstance) const
 4{
 5    for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It)
 6    {
 7        const auto ItemActor = GetItemActor(QueryInstance, It.GetIndex());
 8        const auto PickupActor = Cast<ASTUBasePickup>(ItemActor);
 9        if (!PickupActor) continue;
10
11        const auto CouldBeTaken = PickupActor->CouldBeTaken();
12        if (CouldBeTaken)
13        {
14            It.SetScore(TestPurpose, FilterType, true, true);
15        }
16        else
17        {
18            It.ForceItemState(EEnvItemStatus::Failed);
19        }
20    }
21}

使用PickupCouldBeTaken

  1. 为EQS_FindAmmoPickup的生成器添加PickupCouldBeTaken

    Add Test > Pickup Could be Taken


  2. 选中EQS_TestPawn, 合适补给被标记为绿色


  3. 选择一个被标记为绿色的补给, 使之状态无效

    勾选 Enable Test , 取消 Could Be Taken Test 勾选


  4. 移动EQS_TestPawn, 该补给不再有效



注意

  1. 通过勾选和取消勾选生成器的限制条件, 可以对若干指定项进行测试, 且立即生效



  2. 当前, PickupCouldBeTaken > Details > Filter > Bool Match 是否勾选无区别. 而在限制条件的设计中, 期望其勾选状态的改变应该导致相反的结果


EnvQueryTest取反逻辑

  1. 获取BoolMatch值: 决定是否对筛选结果取反

    EnvQueryTest_Trace

    BoolValue是一个特殊类型, 通过成员函数GetValue获取布尔值

    1UObject* DataOwner = QueryInstance.Owner.Get();
    2BoolValue.BindData(DataOwner, QueryInstance.QueryID);
    3
    4bool BoolMatchValue = BoolValue.GetValue();
  2. 使用SetScore统一设置是否通过筛选

    Score 筛选结果
    true 通过筛选
    false 未通过筛选
    Bool Match 是否对结果取反
    true
    false 对结果取反
    1It.SetScore(TestPurpose, FilterType, Score, BoolMatchValue);

实现EnvQueryTest_PickupCouldBeTaken结果取反

CouldBeTaken
true 可以被拾取
false 无法被拾取
Bool Match -
true
false 对结果取反
 1void UEnvQueryTest_PickupCouldBeTaken::RunTest(FEnvQueryInstance& QueryInstance) const
 2{
 3    const auto DataOwner = QueryInstance.Owner.Get();
 4    BoolValue.BindData(DataOwner, QueryInstance.QueryID);
 5
 6    const auto BoolMatchValue = BoolValue.GetValue();
 7    for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It)
 8    {
 9        const auto ItemActor = GetItemActor(QueryInstance, It.GetIndex());
10        const auto PickupActor = Cast<ASTUBasePickup>(ItemActor);
11        if (!PickupActor) continue;
12
13        const auto CouldBeTaken = PickupActor->CouldBeTaken();
14        It.SetScore(TestPurpose, FilterType, CouldBeTaken, BoolMatchValue);
15    }
16}

查看取反效果

EQS_FindAmmoPickup > PickupCouldBeTaken > Details > Filter > Bool Match

默认勾选


取消勾选


为EQS_FindHealthPickup生成器添加限制条件PickupCouldBeTaken


设置补给默认关闭测试

默认关闭测试. 之前默认开启. 不用挨个关闭补给测试

ShootThemUp: Pickups/STUBasePickup.h

1UPROPERTY(EditAnywhere, BlueprintReadWrite)
2bool EnableTest = false;

自定义生成器限制条件



说明

AI / EQS / Custom Test


验证EQS寻找补给时会把不可用补给考虑在内


补给不可用逻辑

补给被领取后, 设置在碰撞检测中忽略补给, 同时设置补给不可见, 使用定时器恢复碰撞和不可见选项


存在的问题

补给虽然无法参与碰撞, 在场景中也不可见, 但仍作为Actor存在于场景中, 于是NPC可以正常捕获


验证

  1. 初始状态


  2. 设置场景中一个补给不可见

    • 选中BP_STUAmmoPickup1

    • 设置CollisionComponent不可见

      Details > Rendering > Visible 取消勾选: 注意到, 碰撞胶囊已不可见


    • 设置StaticMesh不可见

      Details > Rendering > Visible 取消勾选


  3. 移动EQS_TestPawn, 仍可以捕获不可见补给


  4. 恢复补给状态


考虑解决方案

  • 自定义生成器: 筛选可见Actor
  • 自定义生成器限制条件: 筛选可见Actor

考虑如何实现自定义生成器限制条件

  • 判断组件可见性

    根组件的选择很灵活, 在一些方案里, 即使根组件不可见, Actor也许能被正常观察; 而根组件可见, Actor也不一定能被正常观察到
  • 为补给添加接口: 判断补给是否可用

    相应地, 该限制条件只能用于补给

创建C++类

-
基类 EnvQueryTest
名称 EnvQueryTest_PickupCouldBeTaken
路径 AI/EQS
属性 Public

学习EnvQueryTest及其派生类


派生类命名规则

引擎使用该模板识别限制条件在EQS蓝图编辑器的名称

EnvQueryTest_子类别



筛选Actor接口

EnvQueryTest

每次EQS运行时, 调用该函数; 在该函数中, 对捕获Actor进行筛选

public

1// Function that does the actual work
2virtual void RunTest(FEnvQueryInstance& QueryInstance) const { checkNoEntry(); }

筛选Actor逻辑

参考限制条件Trace EnvQueryTest_Trace


构造函数

1UEnvQueryTest_Trace::UEnvQueryTest_Trace(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
2{
3    Cost = EEnvTestCost::High;
4    ValidItemType = UEnvQueryItemType_VectorBase::StaticClass();
5    SetWorkOnFloatValues(false);
6
7    Context = UEnvQueryContext_Querier::StaticClass();
8    TraceData.SetGeometryOnly();
9}
  1. Cost

    说明限制条件逻辑的复杂度, 复杂度越高, 开销就越大

    使用枚举类型EEnvTestCost对其进行赋值

    复杂度参考用于待测试Actor的数学运算

  2. ValidItemType

    说明测试对象的数据类别, 数组或者元素

    -
    UEnvQueryItemType_VectorBase 数组
    UEnvQueryItemType_ActorBase 元素
  3. SetWorkOnFloatValues

    限制条件部分参数的模式由FilterType决定, SetWorkOnFloatValues使用枚举类型EEnvTestFilterType对FilterType进行设置

    -
    Minimum 数值
    Maximum 数值
    Range 数值
    Match 布尔类型

    参数为true时, FilterType值为Range; 参数为false, FilterType值为Match: 这两个相当于是两种默认类型

    如果FilterType为Range, 通常可以在蓝图编辑器中根据需求设置数值的其他模式Minimum和Maximum

     1void UEnvQueryTest::SetWorkOnFloatValues(bool bWorkOnFloats)
     2{
     3    bWorkOnFloatValues = bWorkOnFloats;
     4
     5    // Make sure FilterType is set to a valid value.
     6    if (bWorkOnFloats)
     7    {
     8        if (FilterType == EEnvTestFilterType::Match)
     9        {
    10            FilterType = EEnvTestFilterType::Range;
    11        }
    12    }
    13    else
    14    {
    15        if (FilterType != EEnvTestFilterType::Match)
    16        {
    17            FilterType = EEnvTestFilterType::Match;
    18        }
    19
    20        // Scoring MUST be Constant for boolean tests.
    21        ScoringEquation = EEnvTestScoreEquation::Constant;
    22    }
    23
    24    UpdatePreviewData();
    25}

UEnvQueryTest_Trace::RunTest

  1. FEnvQueryInstance::ItemIterator

    EQS专用的迭代器类型

  2. 获取Actor位置

    1const FVector ItemLocation = GetItemLocation(QueryInstance, It.GetIndex()) + FVector(0, 0, ItemZ);            
    
  3. 通用模板: 遍历捕获目标

    1for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It)
    2{
    3    const auto ItemActor = GetItemActor(QueryInstance, It.GetIndex()); // 后续需对ItemActor作出修改时, 移除const限定符
    4
    5    // ...
    6}
  4. 设置Actor通过筛选

    -
    TestPurpose 枚举类型TEnumAsByteEEnvTestPurpose, 取值 Filter Only , Score Only , Filter and Score
    FilterType
    Score 如果时布尔类型, 为true, 通过筛选; 为false, 未通过. 如果是数值, 为权重值
    bExpected 为true, Score决定筛选情况; 为false, 对Score对应的结果取反
    1It.SetScore(TestPurpose, FilterType, true, true);
  5. 设置Actor未通过筛选

    -
    InStatus 使用枚举类型EEnvItemStatus对其赋值, 表示是否通过筛选: FailedPassed
    Score 使用默认值
    1It.ForceItemState(EEnvItemStatus::Failed);

修改补给基类

  • 提供测试机制: 返回设置值
  • 通过定时器状态判断: 若定时器开启, 认为补给无效

添加属性: 开启测试

protected

ShootThemUp: Pickups/STUBasePickup.h

1UPROPERTY(EditAnywhere, BlueprintReadWrite)
2bool EnableTest = true;

添加属性: 测试结果

protected

ShootThemUp: Pickups/STUBasePickup.h

1UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditCondition = "EnableTest"))
2bool CouldBeTakenTest = true;

添加属性: 存放定时器描述符

private

ShootThemUp: Pickups/STUBasePickup.h

1FTimerHandle RespawnTimerHandle;

屏蔽定时器描述符变量定义

ShootThemUp: Pickups/STUBasePickup.cpp

1// PickupWasTaken

添加接口: 判断补给是否可用

public

ShootThemUp: Pickups/STUBasePickup.h

1bool CouldBeTaken() const;

ShootThemUp: Pickups/STUBasePickup.cpp

 1bool ASTUBasePickup::CouldBeTaken() const
 2{
 3    if (EnableTest)
 4    {
 5        return CouldBeTakenTest;
 6    }
 7    else
 8    {
 9        return !GetWorldTimerManager().IsTimerActive(RespawnTimerHandle);
10    }
11}

实现EnvQueryTest_PickupCouldBeTaken


构造函数

-
低开销
单个元素
使用布尔类型

public

ShootThemUp: AI/EQS/EnvQueryTest_PickupCouldBeTaken.h

1UEnvQueryTest_PickupCouldBeTaken(const FObjectInitializer &ObjectInitializer);

ShootThemUp: AI/EQS/EnvQueryTest_PickupCouldBeTaken.cpp

1#include "EnvironmentQuery/Items/EnvQueryItemType_ActorBase.h"
2
3UEnvQueryTest_PickupCouldBeTaken::UEnvQueryTest_PickupCouldBeTaken(const FObjectInitializer &ObjectInitializer) //
4: Super (ObjectInitializer)
5{
6    Cost = EEnvTestCost::Low;
7    ValidItemType = UEnvQueryItemType_ActorBase::StaticClass();
8    SetWorkOnFloatValues(false);
9}

RunTest

public

ShootThemUp: AI/EQS/EnvQueryTest_PickupCouldBeTaken.h

1virtual void RunTest(FEnvQueryInstance& QueryInstance) const override;

ShootThemUp: AI/EQS/EnvQueryTest_PickupCouldBeTaken.cpp

 1#include "Pickups/STUBasePickup.h"
 2
 3void UEnvQueryTest_PickupCouldBeTaken::RunTest(FEnvQueryInstance& QueryInstance) const
 4{
 5    for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It)
 6    {
 7        const auto ItemActor = GetItemActor(QueryInstance, It.GetIndex());
 8        const auto PickupActor = Cast<ASTUBasePickup>(ItemActor);
 9        if (!PickupActor) continue;
10
11        const auto CouldBeTaken = PickupActor->CouldBeTaken();
12        if (CouldBeTaken)
13        {
14            It.SetScore(TestPurpose, FilterType, true, true);
15        }
16        else
17        {
18            It.ForceItemState(EEnvItemStatus::Failed);
19        }
20    }
21}

使用PickupCouldBeTaken

  1. 为EQS_FindAmmoPickup的生成器添加PickupCouldBeTaken

    Add Test > Pickup Could be Taken


  2. 选中EQS_TestPawn, 合适补给被标记为绿色


  3. 选择一个被标记为绿色的补给, 使之状态无效

    勾选 Enable Test , 取消 Could Be Taken Test 勾选


  4. 移动EQS_TestPawn, 该补给不再有效



注意

  1. 通过勾选和取消勾选生成器的限制条件, 可以对若干指定项进行测试, 且立即生效



  2. 当前, PickupCouldBeTaken > Details > Filter > Bool Match 是否勾选无区别. 而在限制条件的设计中, 期望其勾选状态的改变应该导致相反的结果


EnvQueryTest取反逻辑

  1. 获取BoolMatch值: 决定是否对筛选结果取反

    EnvQueryTest_Trace

    BoolValue是一个特殊类型, 通过成员函数GetValue获取布尔值

    1UObject* DataOwner = QueryInstance.Owner.Get();
    2BoolValue.BindData(DataOwner, QueryInstance.QueryID);
    3
    4bool BoolMatchValue = BoolValue.GetValue();
  2. 使用SetScore统一设置是否通过筛选

    Score 筛选结果
    true 通过筛选
    false 未通过筛选
    Bool Match 是否对结果取反
    true
    false 对结果取反
    1It.SetScore(TestPurpose, FilterType, Score, BoolMatchValue);

实现EnvQueryTest_PickupCouldBeTaken结果取反

CouldBeTaken
true 可以被拾取
false 无法被拾取
Bool Match -
true
false 对结果取反
 1void UEnvQueryTest_PickupCouldBeTaken::RunTest(FEnvQueryInstance& QueryInstance) const
 2{
 3    const auto DataOwner = QueryInstance.Owner.Get();
 4    BoolValue.BindData(DataOwner, QueryInstance.QueryID);
 5
 6    const auto BoolMatchValue = BoolValue.GetValue();
 7    for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It)
 8    {
 9        const auto ItemActor = GetItemActor(QueryInstance, It.GetIndex());
10        const auto PickupActor = Cast<ASTUBasePickup>(ItemActor);
11        if (!PickupActor) continue;
12
13        const auto CouldBeTaken = PickupActor->CouldBeTaken();
14        It.SetScore(TestPurpose, FilterType, CouldBeTaken, BoolMatchValue);
15    }
16}

查看取反效果

EQS_FindAmmoPickup > PickupCouldBeTaken > Details > Filter > Bool Match

默认勾选


取消勾选


为EQS_FindHealthPickup生成器添加限制条件PickupCouldBeTaken


设置补给默认关闭测试

默认关闭测试. 之前默认开启. 不用挨个关闭补给测试

ShootThemUp: Pickups/STUBasePickup.h

1UPROPERTY(EditAnywhere, BlueprintReadWrite)
2bool EnableTest = false;