斯坦福课程 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. 用go封装一下二级认证功能

    用go封装一下二级认证 本篇为用go设计开发一个自己的轻量级登录库/框架吧 - 秋玻 - 博客园 (cnblogs.com)的二级认证业务篇,会讲讲二级认证业务的实现,给库/框架增加新的功能. 源码: ...

  2. Oracle:字符串的拼接、截取、查找、替换

    一.拼接:1.使用"||"来拼接字符串: select '拼接'||'字符串' as Str from dual; 2.使用concat(param1,param2)函数实现: s ...

  3. 漏洞扫描与安全加固之Apache Axis组件

    一.Apache Axis组件高危漏洞自查及整改 Apache Axis组件存在由配置不当导致的远程代码执行风险. 1. 影响版本 Axis1 和Axis2各版本均受影响 2. 处置建议 1)禁用此服 ...

  4. WPF 中引入依赖注入(.NET 通用主机)

    WPF 中引入依赖注入(.NET 通用主机) 在网上看到的文章都是通过 App.cs 中修改配置进行的,这样侵入性很高而且服务主机是通过 App 启动时加载的而不是服务主机加载的 App 有一点违反原 ...

  5. MAC版本vmware无法识别虚拟机网卡适配器

    一.问题 莫名其妙的突然mac上的vmware无法识别网络适配器了 二.解决过程 1.重装vmware-无效 2.降级安装vmware-无效 3.安装pd虚拟机,并使用sudo命令启动-偶尔有效 4. ...

  6. 机器学习|K邻近(K Nearest-Neighbours)

    本文从概念.原理.距离函数.K 值选择.K 值影响..优缺点.应用几方面详细讲述了 KNN 算法 K 近临(K Nearest-Neighbours) 一种简单的监督学习算法,惰性学习算法,在技术上并 ...

  7. [C++]STL - 队列(Queue) 栈(Stack) 链表(list)

    STL - 队列(Queue) 栈(Stack) 链表(list) Queue 队列 结构特征 这是一种线性储存结构 其数据有先进先出的特点 这种特点被称为FIFO(First In First Ou ...

  8. Acwing127周赛第三题 构造矩阵 (套路)

    题目链接:构造矩阵 题目描述 我们希望构造一个 n×m 的整数矩阵. 构造出的矩阵需满足: 每一行上的所有元素之积均等于 k. 每一列上的所有元素之积均等于 k. 保证 k 为 1 或 −1. 请你计 ...

  9. 通过mybatis-plus的自定义拦截器实现控制 mybatis-plus的全局逻辑删除字段的控制 (修改其最终执行的sql中的where条件)

    需求:过滤部分请求不实现mybatis-plus的逻辑删除 看到网上关于mybatis-plus的自定义拦截器的文章有的少 想了想自己写了一篇 欢迎参考 指正 通过springboot的拦截器 在请求 ...

  10. 大数据分析/机器学习基础之matplotlib绘图篇

    目录 一.前言 我的运行环境 二.什么是matplotlib? 三.安装及导入 四.matplotlib的使用 一.前言 本人因在学习基于python的机器学习相关教程时第一次接触到matplotli ...