斯坦福 UE4 C++ ActionRoguelike游戏实例教程 04.角色感知组件PawnSensingComponent和更平滑的转身
斯坦福课程 UE4 C++ ActionRoguelike游戏实例教程 0.绪论
概述
本文章对应课程第十一章 43、44节。本文讲述PawnSensingComponent中的视觉感知的使用,以及对AI角色平滑转身进行一点小优化。
目录
- 添加PawnSensingComponent
- 平滑转身
添加PawnSensingComponent
修改代码
在之前的课程中,若要让AI选取目标,都是预先在程序里获取目标的对象。要做到更真实的AI,我们想让AI在“看到”玩家后才获取玩家控制的角色对象,甚至可以自由转换目标。本小节的主要内容就是实现这个目标。
课程提到,要想让AI角色拥有感知世界的能力,通常有两种做法,一种是使用AI感知系统,还有一种就是本文要讲述的PawnSensingComponent组件。相较于前者,后者显得十分原始,但是优点在于易于理解和拓展性强。本节课实现的功能并不复杂,使用PawnSensingComponent组件完全可以胜任本节课的任务。
PawnSensingComponent顾名思义,可以让拥有该组件的Actor获得感知Pawn的能力。常用的有视觉感知和听觉感知,本节重点使用视觉感知。具体的做法,我们边做边说。
阅读源码可以发现,视觉感知使用的是射线检测获取的目标对象,感兴趣的读者也可以自己试着实现一下。
和添加其他组件一样,PawnSensingComponent需要引入头文件#include "Perception/PawnSensingComponent.h",并在构造函数里进行创建。
//.h
UPROPERTY(VisibleAnywhere, Category = "AI")
UPawnSensingComponent* PawnSensingComp;
//.cpp
PawnSensingComp = CreateDefaultSubobject<UPawnSensingComponent>("PawnSensingComp");
翻阅UPawnSensingComponent源码,我们注意到PawnSensingComponent定义了一个FSeePawnDelegate OnSeePawn委托。熟悉C#或JAVA的读者可能会知道,委托实际上就是绑定了一系列的回调函数,在需要的时候可以一起调用。当AI角色看到Pawn类型的对象后,就会调用OnSeePawn里绑定的函数。我们所需要做的,就是按照委托定义的函数签名,将我们自定义的函数绑定到委托里。这里我们依葫芦画瓢,创建一个void OnPawnSeen(APawn* Pawn)函数。
//.h
UPROPERTY(EditDefaultsOnly, Category = "AI")
FName TargetActorKey;
UFUNCTION()
void OnPawnSeen(APawn* Pawn);
//.cpp
void ASurAiCharacter::OnPawnSeen(APawn* Pawn)
{
AAIController* AIC = Cast<AAIController>(GetController());
if(AIC)
{
UBlackboardComponent* BBComp = AIC->GetBlackboardComponent();
BBComp->SetValueAsObject(TargetActorKey, Pawn);
DrawDebugString(GetWorld(), GetActorLocation(), "PLAYER SPOTTED", nullptr, FColor::White);
}
}
文章末我会放出完整代码。
OnPawnSeen的逻辑也很简单,参数Pawn指的是看到的Pawn类型对象,当调用该函数时,看到的Pawn类型对象会作为参数传进来,我们将其存储到黑板里,之后行为树就可以根据黑板里的Pawn对象来执行判断距离等一系列逻辑了。
与课程中不一样的是,我将TargetActorKey暴露给蓝图,这样我们就可以在UE编辑器里修改要绑定的黑板键了。
然后别忘了将自定义的函数绑定到委托里。
void ASurAiCharacter::PostInitializeComponents()
{
Super::PostInitializeComponents();
PawnSensingComp->OnSeePawn.AddDynamic(this, &ASurAiCharacter::OnPawnSeen);
}
还有一件事,我们不希望之前的代码影响到这次的实验。把之前在代码里获取玩家对象的相关代码删掉或者注释掉。
//SurAiController.cpp
void ASurAIController::BeginPlay()
{
Super::BeginPlay();
if(ensure(BehaviorTree))
{
RunBehaviorTree(BehaviorTree);
}
//GetPlayerPawn可以是这个关卡的任意对象,这里传入this就行
// APawn* MyPawn = UGameplayStatics::GetPlayerPawn(this, 0);
// if(MyPawn)
// {
// GetBlackboardComponent()->SetValueAsVector("MoveToLocation", MyPawn->GetActorLocation());
// GetBlackboardComponent()->SetValueAsObject("TargetActor", MyPawn);
// }
}
修改蓝图
编译代码,进入蓝图。首先把我们暴露在蓝图的TargetActorKey赋为我们的黑板键。

点击PawnSensingComp,如图所示,从胶囊体放射出去的圆锥状线条是视野范围,外圈的圆环是听觉范围。从细节面板中可以看到感知组件的一系列参数,我们可以根据自己的需要进行修改。这里仅修改了视觉角度。注意到到视点比人物要高点儿,可以在自身(self)属性栏里修改基础眼高度,这里就不展示了。

修改完成,运行游戏,AI在看不见我们的时候,黑板键TargetActor是空指针,从上节课定义的蓝图可以知道,这里执行的是Cast Failed,不会选取任何目标。

因此不会进行任何实质性的寻路行为。别忘了我们自定义的SBTService_CheckAttackRange里也有相关逻辑,由于TargetActor为空指针,因此不会执行后面的逻辑。
PS:课程中这里在Failed时添加了一个默认值,默认返回玩家对象。出于笔者自认为的合理性,这里就不添加了。

看到玩家后,TargetActor被赋值,输出Debug文字,AI开始自动寻路。

优化角色旋转
剩下一些小细节。在观察AI角色移动时,我们注意到AI角色在转向时是一帧转向,期间没有任何过渡,显得十分突兀。为了优化这一点,我们可以在MovementComponent组件里勾选使用控制器所需的旋转。该选项将使角色按照旋转速率平滑地旋转到目标角度。

要想使上述选项生效,我们还需要取消勾选自身细节面板里的使用控制器旋转Yaw,这样AI控制器不再强制设置角色当前的Yaw,实现Movement组件完全控制角色的旋转。

最后,课程还提到使用ensureMsgf宏来生成更详细的错误日志,这对于若干年后的debug可以起到提高效率的作用。读者可以自行了解使用
完整代码
//SurAiCharacter.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "SurAiCharacter.generated.h"
class UPawnSensingComponent;
UCLASS()
class FPSPROJECT_API ASurAiCharacter : public ACharacter
{
GENERATED_BODY()
public:
ASurAiCharacter();
protected:
virtual void BeginPlay() override;
UPROPERTY(VisibleAnywhere, Category = "AI")
UPawnSensingComponent* PawnSensingComp;
UPROPERTY(EditDefaultsOnly, Category = "AI")
FName TargetActorKey;
public:
virtual void Tick(float DeltaTime) override;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
UFUNCTION()
void OnPawnSeen(APawn* Pawn);
void PostInitializeComponents() override;
};
//SurAIController.cpp
#include "Ai/SurAiCharacter.h"
#include "AIController.h"
#include "DrawDebugHelpers.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Perception/PawnSensingComponent.h"
// Sets default values
ASurAiCharacter::ASurAiCharacter()
{
PrimaryActorTick.bCanEverTick = true;
PawnSensingComp = CreateDefaultSubobject<UPawnSensingComponent>("PawnSensingComp");
}
// Called when the game starts or when spawned
void ASurAiCharacter::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void ASurAiCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Called to bind functionality to input
void ASurAiCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
void ASurAiCharacter::OnPawnSeen(APawn* Pawn)
{
AAIController* AIC = Cast<AAIController>(GetController());
if(AIC)
{
UBlackboardComponent* BBComp = AIC->GetBlackboardComponent();
BBComp->SetValueAsObject(TargetActorKey, Pawn);
DrawDebugString(GetWorld(), GetActorLocation(), "PLAYER SPOTTED", nullptr, FColor::White);
}
}
void ASurAiCharacter::PostInitializeComponents()
{
Super::PostInitializeComponents();
PawnSensingComp->OnSeePawn.AddDynamic(this, &ASurAiCharacter::OnPawnSeen);
}
//SurAIController.cpp 这里是将不需要的代码删除掉,这里以注释作为标识
#include "Ai/SurAIController.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Kismet/GameplayStatics.h"
void ASurAIController::BeginPlay()
{
Super::BeginPlay();
if(ensure(BehaviorTree))
{
RunBehaviorTree(BehaviorTree);
}
//GetPlayerPawn可以是这个关卡的任意对象,这里传入this就行
// APawn* MyPawn = UGameplayStatics::GetPlayerPawn(this, 0);
// if(MyPawn)
// {
// GetBlackboardComponent()->SetValueAsVector("MoveToLocation", MyPawn->GetActorLocation());
// GetBlackboardComponent()->SetValueAsObject("TargetActor", MyPawn);
// }
}
参考链接
基于C++代码的UE4学习(三十九)——为AI增加感官(视觉与听觉) https://blog.csdn.net/weixin_43654485/article/details/108152408
斯坦福 UE4 C++ ActionRoguelike游戏实例教程 04.角色感知组件PawnSensingComponent和更平滑的转身的更多相关文章
- 《Genesis-3D开源游戏引擎--横版格斗游戏制作教程04:技能的输入与检测》
4.技能的输入与检测 概述: 技能系统的用户体验,制约着玩家对整个游戏的体验.游戏角色的技能华丽度,连招的顺利过渡,以及逼真的打击感,都作为一款游戏的卖点吸引着玩家的注意.开发者在开发游戏初期,会根据 ...
- Web前端性能优化教程04:压缩组件
本文是Web前端性能优化系列文章中的第四篇,主要讲述内容:压缩组件.完整教程可查看:Web前端性能优化 基础知识 gzip编码:gzip是GUNzip的缩写,是使用无损压缩算法的一种,最早是用于Uni ...
- Cocos2d-x3.0游戏实例《不要救我》第十篇(结束)——使用Json配置数据类型的怪物
如今我们有2种类型的怪物,并且创建的时候是写死在代码里的,这是要作死的节奏~ 所以.必须可配置.不然会累死人的. ; i < size; ++i) { int id = root[i][&quo ...
- Cocos2d-x3.0游戏实例之《别救我》第八篇——TiledMap实现关卡编辑器
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/musicvs/article/details/25368273 好吧.我真心全然搞不懂.我如今仅仅只 ...
- 《Genesis-3D开源游戏引擎完整实例教程-2D射击游戏篇:简介及目录》(附上完整工程文件)
G-3D引擎2D射击类游戏制作教程 游戏类型: 打飞机游戏属于射击类游戏中的一种,可以划分为卷轴射击类游戏. 视觉表现类型为:2D 框架简介: Genesis-3D引擎不仅为开发者提供一个3D游戏制作 ...
- Python导出Excel为Lua/Json/Xml实例教程(一):初识Python
Python导出Excel为Lua/Json/Xml实例教程(一):初识Python 相关链接: Python导出Excel为Lua/Json/Xml实例教程(一):初识Python Python导出 ...
- Web 开发中应用 HTML5 技术的10个实例教程
HTML5 作为下一代网站开发技术,无论你是一个 Web 开发人员或者想探索新的平台的游戏开发者,都值得去研究.借助尖端功能,技术和 API,HTML5 允许你创建响应性.创新性.互动性以及令人惊叹的 ...
- 值得 Web 开发人员收藏的20个 HTML5 实例教程
当开始学习如何创建 Web 应用程序或网站的时候,最流行的建议之一就是阅读教程,并付诸实践.也有大量的 Web 开发的书,但光有理论没有实际行动是无用的.现在由于网络的发展,我们有很多的工具可以用于创 ...
- 对《[Unity官方实例教程 秘密行动] Unity官方教程《秘密行动》(十二) 角色移动》的一些笔记和个人补充,解决角色在地形上移动时穿透问题。
这里素材全是网上找的. 教程看这里: [Unity官方实例教程 秘密行动] Unity官方教程<秘密行动>(九) 角色初始设定 一.模型设置: 1.首先设置模型的动作无限循环. 不设置的话 ...
- Cocos2d-x3.0游戏实例《不要救我》第一章——前言
我们可以学习? 这是一个非常easy游戏.但更多的东西用(对于初学者).至少,对于它的一个例子,有点多. 笨木头花心贡献.啥?花心?不呢.是用心~ 转载请注明,原文地址:http://www.benm ...
随机推荐
- 开发app软件成本计算参考
目录 1. 设计成本 2. 前端开发成本 3. 后端开发成本 4. 测试成本 5. 上架试运营成本 app软件开发已成为人们生活中不可或缺的一部分.无论是在娱乐.通讯.信息.健康等方面,都有数不清的a ...
- .NET 数据库大数据 方案(插入、更新、删除、查询 、插入或更新)
1.功能介绍 (需要版本5.0.45) 海量数据操作ORM性能瓶颈在实体转换上面,并且不能使用常规的Sql去实现 当列越多转换越慢,SqlSugar将转换性能做到极致,并且采用数据库最佳API 操作数 ...
- css的认知与样式
目录 1. 介绍css 2. CSS语法 3. CSS注释 4. CSS中的颜色值 5. CSS长度单位 6. html引入CSS的三种方法 6.1 行内样式(内联样式) 6.2 内嵌样式 6 ...
- 爽。。。一键导出 MySQL 表结构,告别手动梳理表结构文档了。。。
背景 系统需要交付,客户要求提供交维材料,包括系统的表结构,安排开发人员进行梳理,效率比较慢,遂自己花点时间捣鼓一下,发现有此插件,记录一下方便与同事分享 前提条件 必须有 go语言环境,有的话直接看 ...
- 记一次 openSUSE Tumbleweed 下安装 k8s
出现的问题 因为没有K8s基础的而踩了不少坑. kubeadm kubelet 最好指定版本安装,因为kubelet的版本需要小于等于kubeadm的版本,否则就会报错. 运行 kubeadm ini ...
- 完蛋!我被 Out of Memory 包围了!
是极致魅惑.洒脱自由的Java heap space? 是知性柔情.温婉大气的GC overhead limit exceeded? 是纯真无邪.活泼可爱的Metaspace? 如果以上不是你的菜,那 ...
- 一个基于百度飞桨封装的.NET版本OCR工具类库 - PaddleOCRSharp
前言 大家有使用过.NET开发过OCR工具吗?今天给大家推荐一个基于百度飞桨封装的.NET版本OCR工具类库:PaddleOCRSharp. OCR工具有什么用? OCR(Optical Charac ...
- HBuilderx 创建 、运行uniapp项目
uni-app官网介绍的 通过 HBuilderX 可视化界面 跟着小颖来创建一个自己的小程序 创建小程序 依次点击HBuilderx 左上方的按钮:文件->新建->项目 然后打开该界面, ...
- 【Javaweb】动态web工程目录介绍
src 存放自己编写的Java源代码 web 专门用来存放web工程的资源文件(html页面.css文件.js文件等等) WEB-INF 是一个受服务器保护的目录,浏览器无法直接访问此目录的内容 we ...
- cmake的单目录和多目录的使用(Linux和Windows)
Windows上的使用是用VS2022创建一个cmake项目 然后就可以自动生成CMakeLists.txt和对应的cpp和头文件,其中CMakeLists.txt是最关键的,后面那两个没有也行,自己 ...