斯坦福课程 UE4 C++ ActionRoguelike游戏实例教程 0.绪论

概述

本文章对应课程第十一章 43、44节。本文讲述PawnSensingComponent中的视觉感知的使用,以及对AI角色平滑转身进行一点小优化。

目录

  1. 添加PawnSensingComponent
  2. 平滑转身

添加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赋为我们的黑板键。

记得修改TargetActorKey

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

根据自身需要修改参数

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

重温蓝图

因此不会进行任何实质性的寻路行为。别忘了我们自定义的SBTService_CheckAttackRange里也有相关逻辑,由于TargetActor为空指针,因此不会执行后面的逻辑。

PS:课程中这里在Failed时添加了一个默认值,默认返回玩家对象。出于笔者自认为的合理性,这里就不添加了。

在看到主角之前是处于一个傻站着的状态

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

看到玩家后输出Debug信息,开始攻击玩家

优化角色旋转

剩下一些小细节。在观察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和更平滑的转身的更多相关文章

  1. 《Genesis-3D开源游戏引擎--横版格斗游戏制作教程04:技能的输入与检测》

    4.技能的输入与检测 概述: 技能系统的用户体验,制约着玩家对整个游戏的体验.游戏角色的技能华丽度,连招的顺利过渡,以及逼真的打击感,都作为一款游戏的卖点吸引着玩家的注意.开发者在开发游戏初期,会根据 ...

  2. Web前端性能优化教程04:压缩组件

    本文是Web前端性能优化系列文章中的第四篇,主要讲述内容:压缩组件.完整教程可查看:Web前端性能优化 基础知识 gzip编码:gzip是GUNzip的缩写,是使用无损压缩算法的一种,最早是用于Uni ...

  3. Cocos2d-x3.0游戏实例《不要救我》第十篇(结束)——使用Json配置数据类型的怪物

    如今我们有2种类型的怪物,并且创建的时候是写死在代码里的,这是要作死的节奏~ 所以.必须可配置.不然会累死人的. ; i < size; ++i) { int id = root[i][&quo ...

  4. Cocos2d-x3.0游戏实例之《别救我》第八篇——TiledMap实现关卡编辑器

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/musicvs/article/details/25368273 好吧.我真心全然搞不懂.我如今仅仅只 ...

  5. 《Genesis-3D开源游戏引擎完整实例教程-2D射击游戏篇:简介及目录》(附上完整工程文件)

    G-3D引擎2D射击类游戏制作教程 游戏类型: 打飞机游戏属于射击类游戏中的一种,可以划分为卷轴射击类游戏. 视觉表现类型为:2D 框架简介: Genesis-3D引擎不仅为开发者提供一个3D游戏制作 ...

  6. Python导出Excel为Lua/Json/Xml实例教程(一):初识Python

    Python导出Excel为Lua/Json/Xml实例教程(一):初识Python 相关链接: Python导出Excel为Lua/Json/Xml实例教程(一):初识Python Python导出 ...

  7. Web 开发中应用 HTML5 技术的10个实例教程

    HTML5 作为下一代网站开发技术,无论你是一个 Web 开发人员或者想探索新的平台的游戏开发者,都值得去研究.借助尖端功能,技术和 API,HTML5 允许你创建响应性.创新性.互动性以及令人惊叹的 ...

  8. 值得 Web 开发人员收藏的20个 HTML5 实例教程

    当开始学习如何创建 Web 应用程序或网站的时候,最流行的建议之一就是阅读教程,并付诸实践.也有大量的 Web 开发的书,但光有理论没有实际行动是无用的.现在由于网络的发展,我们有很多的工具可以用于创 ...

  9. 对《[Unity官方实例教程 秘密行动] Unity官方教程《秘密行动》(十二) 角色移动》的一些笔记和个人补充,解决角色在地形上移动时穿透问题。

    这里素材全是网上找的. 教程看这里: [Unity官方实例教程 秘密行动] Unity官方教程<秘密行动>(九) 角色初始设定 一.模型设置: 1.首先设置模型的动作无限循环. 不设置的话 ...

  10. Cocos2d-x3.0游戏实例《不要救我》第一章——前言

    我们可以学习? 这是一个非常easy游戏.但更多的东西用(对于初学者).至少,对于它的一个例子,有点多. 笨木头花心贡献.啥?花心?不呢.是用心~ 转载请注明,原文地址:http://www.benm ...

随机推荐

  1. 开发app软件成本计算参考

    目录 1. 设计成本 2. 前端开发成本 3. 后端开发成本 4. 测试成本 5. 上架试运营成本 app软件开发已成为人们生活中不可或缺的一部分.无论是在娱乐.通讯.信息.健康等方面,都有数不清的a ...

  2. .NET 数据库大数据 方案(插入、更新、删除、查询 、插入或更新)

    1.功能介绍 (需要版本5.0.45) 海量数据操作ORM性能瓶颈在实体转换上面,并且不能使用常规的Sql去实现 当列越多转换越慢,SqlSugar将转换性能做到极致,并且采用数据库最佳API 操作数 ...

  3. css的认知与样式

      目录 1. 介绍css 2. CSS语法 3. CSS注释 4. CSS中的颜色值 5. CSS长度单位 6. html引入CSS的三种方法 6.1 行内样式(内联样式) 6.2   内嵌样式 6 ...

  4. 爽。。。一键导出 MySQL 表结构,告别手动梳理表结构文档了。。。

    背景 系统需要交付,客户要求提供交维材料,包括系统的表结构,安排开发人员进行梳理,效率比较慢,遂自己花点时间捣鼓一下,发现有此插件,记录一下方便与同事分享 前提条件 必须有 go语言环境,有的话直接看 ...

  5. 记一次 openSUSE Tumbleweed 下安装 k8s

    出现的问题 因为没有K8s基础的而踩了不少坑. kubeadm kubelet 最好指定版本安装,因为kubelet的版本需要小于等于kubeadm的版本,否则就会报错. 运行 kubeadm ini ...

  6. 完蛋!我被 Out of Memory 包围了!

    是极致魅惑.洒脱自由的Java heap space? 是知性柔情.温婉大气的GC overhead limit exceeded? 是纯真无邪.活泼可爱的Metaspace? 如果以上不是你的菜,那 ...

  7. 一个基于百度飞桨封装的.NET版本OCR工具类库 - PaddleOCRSharp

    前言 大家有使用过.NET开发过OCR工具吗?今天给大家推荐一个基于百度飞桨封装的.NET版本OCR工具类库:PaddleOCRSharp. OCR工具有什么用? OCR(Optical Charac ...

  8. HBuilderx 创建 、运行uniapp项目

    uni-app官网介绍的 通过 HBuilderX 可视化界面 跟着小颖来创建一个自己的小程序 创建小程序 依次点击HBuilderx 左上方的按钮:文件->新建->项目 然后打开该界面, ...

  9. 【Javaweb】动态web工程目录介绍

    src 存放自己编写的Java源代码 web 专门用来存放web工程的资源文件(html页面.css文件.js文件等等) WEB-INF 是一个受服务器保护的目录,浏览器无法直接访问此目录的内容 we ...

  10. cmake的单目录和多目录的使用(Linux和Windows)

    Windows上的使用是用VS2022创建一个cmake项目 然后就可以自动生成CMakeLists.txt和对应的cpp和头文件,其中CMakeLists.txt是最关键的,后面那两个没有也行,自己 ...