【UE4C++-ActionRougelike-11】BT-Task和EQS


【UE4C++-ActionRougelike-11】BT-Task和EQS

1、远程攻击

1.1 创建行为树任务节点

以BTTaskNode为父类创建行为树任务节点MyBTTask_RangedAttack。重写重写UBTTaskNode 的 ExecuteTask 函数,用于在行为树中执行任务逻辑。

UCLASS()
class ACTIONROGUELIKE_API UMyBTTask_RangedAttack : public UBTTaskNode
{
	GENERATED_BODY()

protected:
	virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;

	UPROPERTY(EditAnywhere, Category = "AI")
	TSubclassOf<AActor> ProjectileClass;
};

在MyBTTask_RangedAttack.cpp中实现ExecuteTask方法。

EBTNodeResult::Type UMyBTTask_RangedAttack::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
	//获取当前任务节点所属的 AI 控制器
	AAIController* MyAIController = OwnerComp.GetAIOwner();
	if (ensure(MyAIController))
	{
		//获取 AI 控制器的控制角色
		ACharacter* MyPawn = Cast<ACharacter>(MyAIController->GetPawn());
		//如果控制角色为空,则返回 EBTNodeResult::Failed 表示任务失败。
		if (MyPawn == nullptr)
		{
			return EBTNodeResult::Failed;
		}
		//获取枪口位置(MuzzleLocation)
		FVector MuzzleLocation = MyPawn->GetMesh()->GetSocketLocation("Muzzle_01");

		//获取黑板中保存的目标角色(TargetActor)
		AActor* TargetActor = Cast<AActor>(OwnerComp.GetBlackboardComponent()->GetValueAsObject("TargetActor"));
		//如果目标角色为空,则返回 EBTNodeResult::Failed 表示任务失败
		if (TargetActor == nullptr)
		{
			return EBTNodeResult::Failed;
		}

		//计算从枪口位置到目标角色位置的方向向量(Direction),并将其转换为旋转
		FVector Direction = TargetActor->GetActorLocation() - MuzzleLocation;
		FRotator MuzzleRotation = Direction.Rotation();

		FActorSpawnParameters Params;
		Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
		Params.Instigator = MyPawn;
        
		AActor* NewProj = GetWorld()->SpawnActor<AActor>(ProjectileClass, MuzzleLocation, MuzzleRotation, Params);
		
		//如果新生成的投射物实例不为空,则返回 EBTNodeResult::Succeeded 表示任务成功,否则返回 EBTNodeResult::Failed 表示任务失败
		return NewProj ? EBTNodeResult::Succeeded : EBTNodeResult::Failed;
	}	
	return EBTNodeResult::Failed;
}

1.2 修改行为树

远程攻击行为树

Default Focus(默认焦点)节点来指定AI角色在开始执行行为树时应该关注的默认目标。

Cooldown(冷却)指的是在某个行为节点执行完毕后,需要等待一定的时间间隔后才能再次执行该节点的行为。

Within Attack Range?黑板节点的Observer aborts设置为Lower Priority,Key Query为Is Set。表示当WithinAttackRange设置为真时执行下面的节点;为假或者未设置时中止该节点和全部子树节点。

1.3 演示效果

远程攻击效果展示

2、EQS

2.1 创建EQS查询

新建一个**环境查询(Environment Query)**文件,命名为Query_FindNearByLocation。在Root下添加生成器类型,这里选择Point Donut。右键单击生成器,并选择要添加的测试:Distance测试和Trace测试。

环境查询文件
Donut节点设置
  • 设置节点**内半径(Inner Radius)**和外半径(Outer Radius),表示生成点和情景之间的最小距离和最大距离。

  • 设置圆弧方向(Arc Direction),选择 两点(Two Points)用于确定圆弧方向。

  • 设置圆弧角(Arc Angle),定义圆弧的角度

  • 启用使用螺旋模式(Use Spiral Pattern),圆环将以螺旋模式旋转

  • 设置中心(Center),作为搜索中心的情境

Point Donut节点设置
Distance节点设置
  • 设置**过滤器类型(Filter Type)MininumFloat Value Min设置为合适值。表示任何超出最小浮点值(Float Value Min)**属性所指定的范围的数值都将被剔除。
  • 设置计分因子(Scoring Factor),表示计分公式最后要乘以规格化分数的权重(因子),设置为-1.0,则离AI越近的点得分越高,为了让AI每次选择离它最近的点,从而每次尽可能的少走。
Distance测试设置
Trace节点设置
  • 设置**从情境追踪(Trace from Context)**,选择自定义情景QueryContext_TargetActor,该情景我们会在2.2介绍。
  • 禁用布尔匹配(Bool Match),为了授予计分因子而需要匹配的值(true或false)。执行测试时,如果不匹配此值,将不会改变分数。例如当 Trace 测试节点执行射线检测,Bool Match 参数设置为 False,这意味着如果射线没有命中对象,将会视为测试成功,返回测试成功时的值(ValueOnSuccess),否则返回测试失败时的值(ValueOnFail)。
Trace测试设置

2.2 创建情景

以EnvQueryContext_BlueprintBase为父类创建自定义情景类蓝图QueryContext_TargetActor,蓝图中对ProvideSingleActor函数进行覆盖,该函数功能返回单个Actor。可以通过任何想要的方法获取该Actor。此处函数功能为返回AIController中的TargetActor。

QueryContext_TargetActor蓝图

2.3 将EQS用于行为树

在行为树中添加Tasks节点:Run EQSQuery,并且分配要执行的 **查询模板(Query Template)**(Query_FindNearByLocation)和它应该返回的 **黑板键(MoveToLocation)运行模式(Run Mode)设置为从前25%中随机选择项目(Single Random Item from Best 5%)**。表示从得分在总分75%100%的项目中随机选择。删除先前的黑板节点OutSide Attack Range?,使AI会绕着玩家进行更灵活走动(攻击范围内完成连续攻击后也会移动)

Run EQSQuery节点设置

2.3 演示效果

EQS效果展示

2.4 总结

EQS内容详见官方文档,对本课程中所用的EQS模块进行总结:

**环境查询(Environment Query)**:执行各种场景查询操作。这些节点可以在 EQS 蓝图中使用,用于定义查询规则和生成查询结果

  • 场景查询使用流程: 从行为树调用场景查询,具体的场景查询将使用它的 生成器(Generator),引用 情境(Contexts),调用各种 测试(Test),共同为行为树提供权重最高的查询结果。

生成器(Generator)生成器 用于产生将要测试和加权的位置或Actor(统称为 项目(Item)),权重最高的位置或Actor会返回到行为树。具体内容见官方文档

  • Donut节点:按照用户指定的 环数 产生从 中心 情境辐射出的基于环形区域的追踪

测试(Test):执行 测试 来确定在给定的情景中下从生成器产生的哪个 项目(Item) 是”最佳”选择。具体内容见官方文档

  • Distance节点:将返回项目(Item)和选择的 距离(Distance To) 属性之间的直线距离。如果”距离(Distance To)”是多个位置,它会取所有距离的平均值。
    • 测试模式(Test Mode):用于测试距离的方法
      • Distance 3D(在3D空间中)
      • Distance 2D(在XY平面的2D上)
      • Distance Z(沿Z轴)
      • Distance Z(Absolute)(Z(绝对)轴)
    • 距离(Distance To):用于测量距离的情境

情境(Contexts)情境 为使用的所有测试生成器提供参考框架,具体参考官方文档

3、AI感知组件

3.1 添加AI感知组件

在MyAICharacter中添加AI感知组件UPawnSensingComponent,可以将先前写在AIController中BeginPlay里的设置黑板键TargetActor的逻辑注释掉了,这里会用UPawnSensingComponent来实现将看到的Actor赋值给黑板键TargetActor。

class ACTIONROGUELIKE_API AMyAICharacter : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AMyAICharacter();

protected:
	UPROPERTY(VisibleAnywhere, Category = "Components")
	UPawnSensingComponent* PawnSensingComp;

	UFUNCTION()
	void OnPawnSeen(APawn* Pawn);

	virtual void PostInitializeComponents() override;

};

在MyAICharacter.cpp中实现功能

AMyAICharacter::AMyAICharacter()
{
	PawnSensingComp = CreateDefaultSubobject<UPawnSensingComponent>("PawnSensingComp");
}

//用于处理当角色看到其他角色时的逻辑
void AMyAICharacter::OnPawnSeen(APawn* Pawn)
{
	AAIController* AIC = Cast<AAIController>(GetController());
	//当AI看到角色对象时,设置"TargetActor" 的黑板键的值为检测到的角色对象 Pawn。
	if (AIC)
	{
		UBlackboardComponent* BBComp = AIC->GetBlackboardComponent();

		BBComp->SetValueAsObject("TargetActor", Pawn);

		DrawDebugString(GetWorld(), GetActorLocation(), "PLAYER SPOTTED", nullptr, FColor::White, 4.0f, true);
	}
}

//组件初始化完成后对OnSeePawn事件进行函数绑定
void AMyAICharacter::PostInitializeComponents()
{
	Super::PostInitializeComponents();
	//将 OnPawnSeen 函数与 UPawnSensingComponent 的 OnSeePawn 事件绑定,从而实现了当角色看到其他角色时触发 OnPawnSeen 函数的逻辑
	PawnSensingComp->OnSeePawn.AddDynamic(this, &AMyAICharacter::OnPawnSeen);
}

3.2 设置AI感知组件属性

在MyAICharacter蓝图中可以设置AI感知组件的属性。

AI感知组件

3.3 演示效果

AI感知组件演示效果

4、优化AI动画

4.1 转身动画优化

AI转身动画会出现割裂感,不像玩具一样能够流畅的转身。取消勾选AIMyCharacter蓝图中Pawn->Use Controller Rotation Yaw,勾选Character Movement->Use Controller Desired Rotation

修改前:

优化前转身动画

修改后:

优化后转身动画

4.2 动画混合空间

Target Weight Interpolation Speed Per Sec(目标权重插值速度每秒),该属性可以用于控制动画在不同状态之间的过渡速度,从而影响角色动画的流畅度和自然性。

4.3 断言Assert

使用好Assert能够后期更好的维护项目和发现错误,用AIController中的行为树举例。

void AMyAIController::BeginPlay()
{
	Super::BeginPlay();
	//运行行为树
	if (ensureMsgf(BehaviorTree, TEXT("Behavior Tree is nullptr, Please assign Behavior Tree in your AIController")))
	{	
		RunBehaviorTree(BehaviorTree);
	}
}

如果运行后BehaviorTree是空指针,则输出日志会根据你编写的提示告诉你该怎么做。

输出日志

文章作者: Woilin
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Woilin !
评论
  目录