Unreal Engine 5 C++ Advanced Action RPG 五章笔记

第五章 Enemy AI

2-Perparing Enemy For Combat(为战斗准备敌人)

  • 现在我们主要解决两件事,一件是AI控制器,一件是行为树
    在这里插入图片描述
  • AI拥有AI避障、AI感官、设置团队ID区分敌我
    在这里插入图片描述
  • 行为树要讨论的事件
    在这里插入图片描述
  • AI控制器要讨论的事件
    在这里插入图片描述
  • 在讨论AI回避之前,为了让它正常工作,我们必须获取其中的一个属性
    在这里插入图片描述
  • 为了回避功能正常,我们需要准确反应胶囊体半径,将之前调过胶囊体的大小恢复成默认,网格尺寸调整就行,调胶囊体尺寸要调整他的高与宽,不要使用缩放
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

3-Crowd Following Component(人群跟随组件) ps.这集理论避障

  • AI回避的构建方法两种
    在这里插入图片描述
  • RVO(反向速度障碍)避障的两种方法
    • 使用代理远离彼此:使用次方法会忽略障碍物和导航网格,因此当在移动的时候,AI很可能脱离导航网格边界体积,导致卡在某个位置
      在这里插入图片描述
      在这里插入图片描述
  • Detour Crowd Avoidance(绕道人群回避)
    • 该方法知道其他的代理,会生成一条新的路径来绕过彼此,而且还知道我们导航网格的大小
      在这里插入图片描述
  • 新建一个AI控制器,然后重写这个组件类,因此我们不需要使用这个新的路径跟踪组件,而是需要使用的他的子类,这意味着,我们需要重写WarriorAIController类中的此类
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 然后创建一个子蓝图添加到敌人身上
    在这里插入图片描述
    在这里插入图片描述

4-AI Perception

  • 添加感官系统
  • 头文件
    • #include “Perception/AIPerceptionComponent.h”
    • #include “Perception/AISenseConfig_Sight.h”
      在这里插入图片描述
      在这里插入图片描述
  • 委托函数的参数变量是从这里来的
    在这里插入图片描述
    在这里插入图片描述

5-Generic Team ID

  • 设置团队编号,重写IGenericTeamAgentInterface接口中的GetTeamAttitudeTowards函数,这里这个函数记得加Override
    在这里插入图片描述
    在这里插入图片描述
  • 然后在角色控制器中设置角色的团队属性,这里这个函数记得加Override
    在这里插入图片描述
    在这里插入图片描述
  • 最后在AI感官更新信息的回调函数中测试一下信息
    在这里插入图片描述
    在这里插入图片描述

6-Behavior Tree

  • 行为树,这节内容不怎么多,看看即可
    在这里插入图片描述

7-Configure AI Avoidance(配置AI避障)ps.这集需要多研究

  • 使用AI.Crowd.DebugSelectedActors 1命令可以查看避障调试绘图
  • 新建三个变量用来控制AI避障状态
    在这里插入图片描述
  • 在BeginPlay中设置,避障的参数
    在这里插入图片描述
  • 这几个参数与回避质量最相关的值,具体每个参数的作用看视频,上面这个数组对应我们代码中的那四个质量,我们使用最高质量所以调节Index[3],这里设置为0,当AI到达不了角色位置就会停下来
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

8-Behavior Tree Node Types

  • 虽然绕道回避设置很不错了,但它仍然不能神奇地解决我们在战斗中可能遇到的所有问题。
    在这里插入图片描述
  • 这节课就写了一个获取玩家距离敌人的服务蓝图任务
    在这里插入图片描述在这里插入图片描述

9-Observer Aborts(观察中止 )

  • 首先讨论两个问题,一个是是否进入扫射,一个是判断自己是否离角色远了
    在这里插入图片描述
  • None:无
  • Self:当结果为假时中止自己
  • Lower Priority:当结果为真时,中止其他优先级低的分支
  • Both:以上两点都有
    在这里插入图片描述
  • 具体看视频

10-Orient To Target Actor(瞄准目标Actor)

  • 新建一个BTService类来瞄准目标Actor
    在这里插入图片描述
  • UBTService_OrientToTargetActor.cpp
// Fill out your copyright notice in the Description page of Project Settings.


#include "AI/BTService_OrientToTargetActor.h"

UBTService_OrientToTargetActor::UBTService_OrientToTargetActor()
{
	// 设置蓝图中显示的服务节点名称
	NodeName = TEXT("Native Orient Rotation To Target Actor");

	// 初始化服务节点的通知标志,确保该行为树服务能够正常工作并响应事件
	INIT_SERVICE_NODE_NOTIFY_FLAGS();

	RotationInterpSpeed = 5.f;//设置旋转插值速度,默认为5.0f。这个值控制角色朝向目标时的旋转速率
	Interval = 0.f;//这是父类中的变量,定义服务的后续节拍之间的时间跨度
	RandomDeviation = 0.f;//这是父类中的变量,为服务的间隔添加随机范围

	// 添加对象过滤器,确保InTargetActorKey只能选择AActor类型的对象
	InTargetActorKey.AddObjectFilter(this, GET_MEMBER_NAME_CHECKED(ThisClass, InTargetActorKey), 
		AActor::StaticClass());
}

void UBTService_OrientToTargetActor::InitializeFromAsset(UBehaviorTree& Asset)
{
	Super::InitializeFromAsset(Asset);

	// 获取行为树关联的黑板资产
	if (UBlackboardData* BBAsset = GetBlackboardAsset())
	{
		// 解析所选黑板键,确保InTargetActorKey指向正确的黑板键
		InTargetActorKey.ResolveSelectedKey(*BBAsset);
	}
}

FString UBTService_OrientToTargetActor::GetStaticDescription() const
{
	// 获取选定黑板键的名称描述
	const FString KeyDescription = InTargetActorKey.SelectedKeyName.ToString();

	// 返回静态描述字符串,包括选定黑板键和默认服务描述
	return FString::Printf(TEXT("Orient Rotation To %s Key %s"), *KeyDescription,
		*GetStaticServiceDescription());
}

  • 重写TickNode函数
    在这里插入图片描述
  • 设置朝目标转向逻辑
    在这里插入图片描述
  • 行为树
    在这里插入图片描述

11-Environment Query System

  • 这集将EQS理论知识,可以多看看

12-Custom Query Context

  • EQS使用知识,可以多看看

13-Toggle Strafing State

  • 新建一个基类任务,后续敌人的任务可以继承这个,方便修改
    在这里插入图片描述
  • 基础这个基类任务,重写执行任务的函数,进行修改AI的扫射状态,当打开这个状态的的时候,就为其设置移动速度添加拥有其标签,否则就当前最大速度与黑板键的速度是否一样,进行设置速度,然后删除其拥有标签
    在这里插入图片描述
  • 在控制器中设置黑板键的默认速度
    在这里插入图片描述
  • 行为树逻辑
    在这里插入图片描述

14-Calculate Direction(动画蓝图方面的C++硬编码)

  • 计算运动的方向,首先添加这个模块
    在这里插入图片描述
  • 添加变量计算方向
    在这里插入图片描述
    在这里插入图片描述
  • 在基类动画实例中创建一个检测是否拥有具体指定的此标签的函数
    在这里插入图片描述
    在这里插入图片描述
  • 在敌人的基类动画蓝图中进行判断,有没有拥有扫射的标签进行切换不同的混合空间姿势
    在这里插入图片描述

15-Strafing Blend Space

在这里插入图片描述
在这里插入图片描述

16-Compute Success Chance(计算成功几率)

  • 流程
    在这里插入图片描述

17-Dot Product Test(这集是关于避障的)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 点积测试:点积背后的概念很简单,因此它比较两个向量的方向,然后您将返回这两个向量的差异程度,如果它们完全相反,则点积将返回-1的值,如果它们垂直,则返回值为0,如果它们平行,则返回值1
    在这里插入图片描述
    在这里插入图片描述

18-Enemy Melee Ability

  • 看看就行

19-Activate Ability By Tag

  • 在UWarriorAbilitySystemComponent类中新建一个用来激活能力的函数
    在这里插入图片描述
    在这里插入图片描述
  • 激活能力
    在这里插入图片描述
  • 注意的问题
    在这里插入图片描述
    在这里插入图片描述

20-Is Target Pawn Hostile

  • 在UWarriorFunctionLibrary类中添加一个辅助函数,用来判断目标是否有敌意
    在这里插入图片描述
    在这里插入图片描述
  • 然后在AWarriorWeaponBase类中修改一下之前的碰撞检测执行委托的事件函数,因为之前的会伤害到同伴,这次加了个检查敌意的辅助函数就不会攻击到同伴了
    在这里插入图片描述
    在这里插入图片描述
  • 在UEnemyCombatComponent中重写父类的碰撞到目标函数,进行测试
    在这里插入图片描述
    在这里插入图片描述
  • 结果
    在这里插入图片描述

21-Notify Pawn Hostile

  • 通知攻击碰撞
    在这里插入图片描述
  • 测试
    在这里插入图片描述
    在这里插入图片描述

22-Make Enemy Damage Effect Spec Handle

  • 设置敌人的伤害效果规范处理,和角色的差不多的
    在这里插入图片描述
    在这里插入图片描述
  • 然后在敌人的攻击GA里面创建一个函数用来造成伤害使用
    在这里插入图片描述
    在这里插入图片描述

23-Apply Enemy Damage

  • 看看就行

24-Motion Warping(运动变形)

  • 运动扭曲:动态调整角色的根部运动以与目标对齐
    在这里插入图片描述
  • 首先我们需要在项目里面打开Motion Warping这个插件
    在这里插入图片描述
  • 添加这个模块MotionWarping
    在这里插入图片描述
  • 创建这个组件
    在这里插入图片描述
    在这里插入图片描述
  • 在蒙太奇中创建这个运动扭曲的状态通知,然后设置转向目标名字,使用运动转向时在动画序列中一定要打开跟运动
    在这里插入图片描述

25-Update Motion Warp Target

  • 创建一个服务进行频率更新运动扭曲目标,跟着视频做有些小问题,下面是修改过的
    在这里插入图片描述
    在这里插入图片描述
  • 注意点:行为树的这个内置节点可以让敌人先旋转后再进行下一步攻击任务,但是这个节点必须开启控制器旋转与关闭方向旋转
    在这里插入图片描述
  • 也就说这里是相反的,但是由于我们AI要进行扫射行走,所以我们不能使用这个内置节点进行旋转,此方案在这不可用
    在这里插入图片描述

26-Construct Native BT Task(ps.定义任务节点的内存结构)

  • 构建原生BT任务
  • UBTTask_RotateToFaceTarget:
    在这里插入图片描述
    在这里插入图片描述
  • 为什么要为任务节点结构分配内存:是为了确保每个行为树实例在运行时有独立的状态数据,这在多线程或多实例环境中尤为重要。
    在这里插入图片描述
  • 结果
    在这里插入图片描述

27-Rotate Enemy In Task

  • 重写Execute Task与TickTask函数,添加一个用来判断是否到达旋转精度的辅助函数
    在这里插入图片描述
    在这里插入图片描述
  • 总结
    在这里插入图片描述

BTTask_RotateToFaceTarget.h

// Fill out your copyright notice in the Description page of Project Settings.
#pragma once

#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "BTTask_RotateToFaceTarget.generated.h"

// 定义任务节点的内存结构
struct FRotateToFaceTargetTaskMemory
{
	// 使用弱对象指针存储拥有者 Pawn 和目标演员,避免循环引用导致的内存泄漏
	TWeakObjectPtr<APawn> OwningPawn;
	TWeakObjectPtr<AActor> TargetActor;

	// 检查两个指针是否都有效
	bool IsValid() const
	{
		return OwningPawn.IsValid() && TargetActor.IsValid();
	}

	// 重置两个指针
	void Reset()
	{
		OwningPawn.Reset();
		TargetActor.Reset();
	}
};


/**
 * 
 */
UCLASS()
class WARRIOR_API UBTTask_RotateToFaceTarget : public UBTTaskNode
{
	GENERATED_BODY()

	UBTTask_RotateToFaceTarget();

	//~ Begin UBTNode Interface
	virtual void InitializeFromAsset(UBehaviorTree& Asset) override;
	virtual uint16 GetInstanceMemorySize() const override;
	virtual FString GetStaticDescription() const override;
	//~ End UBTNode Interface

	virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
	virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;

	//判断是否到达精度
	bool HasReachedAnglePercision(APawn* QueryPawn, AActor* TargetActor) const;

	// 角度精度,当角色朝向与目标之间的角度差小于该值时认为已正确朝向
	UPROPERTY(EditAnywhere,Category = "Face Target")
	float AnglePrecision;

	// 旋转插值速度,控制角色旋转到目标方向的速度
	UPROPERTY(EditAnywhere, Category = "Face Target")
	float RotationInterpSpeed;

	// 黑板键选择器,用于从黑板中获取目标演员
	UPROPERTY(EditAnywhere, Category = "Face Target")
	FBlackboardKeySelector InTargetToFaceKey;

};

BTTask_RotateToFaceTarget.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "AI/BTTask_RotateToFaceTarget.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "AIController.h"
#include "Kismet/KismetMathLibrary.h"

UBTTask_RotateToFaceTarget::UBTTask_RotateToFaceTarget()
{
	NodeName = TEXT("Native Rotate To Face Target Actor");// 设置蓝图中显示的任务节点名称

	// 设置默认的角度精度和旋转插值速度
	AnglePrecision = 10.f;
	RotationInterpSpeed = 5.f;

	bNotifyTick = true;									// 启用 Tick 通知,使得任务可以在每帧更新
	bNotifyTaskFinished = true;							// 启用任务完成通知,以便在任务完成后收到通知
	bCreateNodeInstance = false;						// 确保不会创建节点实例(通常不需要更改)
	
	// 初始化任务节点的通知标志,确保该行为树服务能够正常工作并响应事件
	INIT_TASK_NODE_NOTIFY_FLAGS();

	// 添加对象过滤器,确保InTargetActorKey只能选择AActor类型的对象
	InTargetToFaceKey.AddObjectFilter(this, GET_MEMBER_NAME_CHECKED(ThisClass, InTargetToFaceKey), 
		AActor::StaticClass());
}

void UBTTask_RotateToFaceTarget::InitializeFromAsset(UBehaviorTree& Asset)
{
	Super::InitializeFromAsset(Asset);

	// 获取行为树关联的黑板资产,并解析所选黑板键
	if (UBlackboardData* BBAsset = GetBlackboardAsset())
	{
		InTargetToFaceKey.ResolveSelectedKey(*BBAsset);
	}
}

uint16 UBTTask_RotateToFaceTarget::GetInstanceMemorySize() const
{
	return sizeof(FRotateToFaceTargetTaskMemory);// 返回任务节点内存结构的大小,用于分配内存
}

FString UBTTask_RotateToFaceTarget::GetStaticDescription() const
{
	// 获取选定黑板键的名称描述
	const FString KeyDescription = InTargetToFaceKey.SelectedKeyName.ToString();

	// 返回静态描述字符串,包括选定黑板键和角度精度信息
	return FString::Printf(TEXT("Smoothly rotates to face %s Key until the angle precision %s is reached"),
		*KeyDescription, *FString::SanitizeFloat(AnglePrecision));
}

EBTNodeResult::Type UBTTask_RotateToFaceTarget::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
	// 从黑板组件中获取目标演员的对象引用
	UObject* ActorObject = OwnerComp.GetBlackboardComponent()->GetValueAsObject(InTargetToFaceKey.SelectedKeyName);
	AActor* TargetActor = Cast<AActor>(ActorObject);

	APawn* OwningPawn = OwnerComp.GetAIOwner()->GetPawn();

	// 将节点内存转换为任务特定的内存结构
	FRotateToFaceTargetTaskMemory* Memory = CastInstanceNodeMemory<FRotateToFaceTargetTaskMemory>(NodeMemory);
	check(Memory);

	// 设置内存中的拥有者 Pawn 和目标演员
	Memory->OwningPawn = OwningPawn;
	Memory->TargetActor = TargetActor;
	
	if (!Memory->IsValid())
	{
		return EBTNodeResult::Failed;
	}

	if (HasReachedAnglePercision(OwningPawn, TargetActor))// 如果已经达到了角度精度要求,则重置内存并返回成功结果
	{
		Memory->Reset();
		return EBTNodeResult::Succeeded;
	}
	return EBTNodeResult::InProgress;// 否则,返回正在执行的结果,表示任务还在进行中
}

void UBTTask_RotateToFaceTarget::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
	// 将节点内存转换为任务特定的内存结构
	FRotateToFaceTargetTaskMemory* Memory = CastInstanceNodeMemory<FRotateToFaceTargetTaskMemory>(NodeMemory);

	if (!Memory->IsValid())
	{
		FinishLatentTask(OwnerComp, EBTNodeResult::Failed);
	}

	// 如果已经达到了角度精度要求,则重置内存并结束任务,返回成功结果
	if (HasReachedAnglePercision(Memory->OwningPawn.Get(), Memory->TargetActor.Get()))
	{
		Memory->Reset();
		FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
	}
	else
	{
		// 使用插值函数平滑地将当前旋转调整为目标朝向旋转
		const FRotator LookAtRot = UKismetMathLibrary::FindLookAtRotation(Memory->OwningPawn->GetActorLocation(),
			Memory->TargetActor->GetActorLocation());

		const FRotator TargetRot = FMath::RInterpTo(Memory->OwningPawn->GetActorRotation(), LookAtRot,
			DeltaSeconds, RotationInterpSpeed);

		Memory->OwningPawn->SetActorRotation(TargetRot);
	}
}

bool UBTTask_RotateToFaceTarget::HasReachedAnglePercision(APawn* QueryPawn, AActor* TargetActor) const
{
	const FVector OwnerForward = QueryPawn->GetActorForwardVector();
	// 获取从查询对象到目标演员的归一化方向向量
	const FVector OwnerToTargetNormalized =
		(TargetActor->GetActorLocation() - QueryPawn->GetActorLocation()).GetSafeNormal();
	// 计算两个向量之间的点积
	const float DotResult = FVector::DotProduct(OwnerForward, OwnerToTargetNormalized);
	// 通过反余弦函数计算角度差(以度为单位)
	const float AngleDiff = UKismetMathLibrary::DegAcos(DotResult);
	// 返回角度差是否小于等于设定的角度精度
	return AngleDiff <= AnglePrecision;
}

测试结果

  • 行为树逻辑
    在这里插入图片描述
    请添加图片描述

28-Melee Attack Branch

  • 关于为什么是这个逻辑,多看看视频
  • 行为树逻辑
    在这里插入图片描述

29-Does Actor Have Tag Decorator

  • 添加一个被攻击状态下的的标签
    在这里插入图片描述
    在这里插入图片描述
  • 新建一个装饰器,用来判断当前Actor有没有标签
    在这里插入图片描述

30-Duration Gameplay Effect(虚幻5.4与虚幻5.3有改动位置)

  • 这集版本之间有改动位置,可以多看看视频
  • 这节课讨论向敌人添加受到攻击的状态标签,用GE进行添加,因为在敌人受到伤害的时候就添加标签,不在受到伤害的时候就将其删除
    在这里插入图片描述
  • 在受击的GA中应用此GE
    在这里插入图片描述
  • 行为树逻辑
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 运行结果
    在这里插入图片描述

31-Should Abort All Logic

  • 添加一个装饰器用来判断玩家或者AI是否死亡,死亡就不在执行AI行为树逻辑
    在这里插入图片描述
    在这里插入图片描述

32-Guardian Attack Sound FX

  • 添加音效,看看就行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值