斯坦福 UE4 C++ ActionRoguelike游戏实例教程 16.优化交互,实现看到物体时出现交互提示
斯坦福课程 UE4 C++ ActionRoguelike游戏实例教程 0.绪论
概述
本篇文章对应Lecture 18 – Creating Buffs, World Interaction, 73节。本文将会重构以前实现过的SurInteractionComponent,实现在玩家注释可交互物体时,可以出现可交互提示,效果如下:

在文章的最后,我会放出所有相关的代码。
优化交互
在几十节课之前,我们学习过如何与场景中的物体进行交互。其中有一项就是定义了一个交互组件(SurInteractionComponent),它允许角色在按下交互键时,如果视线方向有可交互物体,即可与物体进行交互,触发物体的Interact函数。
本篇文章则要对这个组件进行一次升级,当我们注视可交互物体时,可以直接出现一个交互提示(UMG控件),提醒我们这个物体是可以交互的;当我们按下交互键时,即可对物体进行交互。具体怎么升级呢,让我们边做边说吧。
要实现这个功能,我们需要做到以下几点:
- 当我们注视一个Actor时,要获取这个Actor的引用;反之,当我们视线离开这个Actor时,取消对这个Actor的引用
- 当我们获得这个Actor的引用时,要生成交互提示控件,并在这个Actor周围显示这个控件;反之,隐藏这个控件
- 当我们按下交互键(根据自己的设置)时,判断我们是否拿到了目标Actor的引用(指针是否为空)。如果拿到了,则执行交互相关逻辑。
以上就是我们本文要实现的全部需求,为此,我们需要为我们的交互组件(SurInterationComponent)添加以下主要成员。
主要有
- 当前注视的Actor
- 要生成的控件类,这里选用之前实现的SurWorldUserWidget子类,它可以选定一个Actor,并将自己在视口中依附在Actor周围。
- 控件实例,根据第二点的控件类生成一个实例
//当前可交互的物体
UPROPERTY() //将其标记为UPEOPERTY, 监控其生命周期,避免空悬指针的发生
AActor* FocusActor;
//指定生成控件的类
UPROPERTY(EditDefaultsOnly, Category = "UI")
TSubclassOf<USurWorldUserWidget> DefaultWidgetClass;
//当有可交互的物体时,会生成指定的控件
UPROPERTY()
USurWorldUserWidget* DefaultWidgetInstance;
public:
void FindBestInteractable();
我们将执行交互(PrimaryInteract)和寻找交互对象(FindBestInteractable)分离了开来。这是因为我们是要通过“注视”来寻找交互物体,并且实时判断该物体能不能交互。因此,我们需要把之前PrimaryInteract的寻找交互对象的大部分逻辑移动过来,并对其进行一些修改,其最核心的逻辑如下:
- 在一开始时将FocusActor置为空。
- 做射线检测,如果命中了物体,并且物体实现了Interact接口,则将物体作为FocusActor
- 如果FocusActor不为空,则尝试生成控件。这里生成控件的方式有些类似于单例模式中的懒汉模式,只有当第一次需要生成控件的时候才实例化,而不是在构造函数中就实例化了这个控件。
- 如果FocusActor不为空,且拥有控件实例,则将其添加到视口中
- 如果FocusActor为空,则将控件实例从视口中移除。
void USurInteractionComponent::FindBestInteractable()
{
bool bDebugDraw = CVarDebugDrawInteraction.GetValueOnGameThread();
...//
...//射线检测部分代码
...//
FocusActor = nullptr;
AActor* HitActor = Hit.GetActor();
if(HitActor)
{
//判断碰撞体是否实现了我们需要的接口
if(HitActor->Implements<USurGameInterface>())
{
FocusActor = HitActor;
if(bDebugDraw)
{
//用于Debug
DrawDebugSphere(GetWorld(), Hit.ImpactPoint, TraceRadius, 32, FColor::Green, false, 3);
}
}
}
else
{
if(bDebugDraw)
{
DrawDebugSphere(GetWorld(), Hit.ImpactPoint, TraceRadius, 32, FColor::Red, false, 3);
}
}
//如果当前有聚焦ACTOR,就生成控件
if(FocusActor)
{
if(DefaultWidgetInstance == nullptr && ensure(DefaultWidgetClass))
{
DefaultWidgetInstance = CreateWidget<USurWorldUserWidget>(GetWorld(), DefaultWidgetClass);
}
if(DefaultWidgetInstance)
{
DefaultWidgetInstance->AttachedActor = FocusActor;
if(!DefaultWidgetInstance->IsInViewport())
{
DefaultWidgetInstance->AddToViewport();
}
}
}
else
{
if(DefaultWidgetInstance)
{
DefaultWidgetInstance->RemoveFromParent();
}
}
}
可以看到,核心逻辑基本围绕FocusActor在运行,也只有注释可交互物体时,我们才能获得FocusActor。
当然,以上操作需要时刻运行。值得一提的是,课程里将FindBestInteractable放在TickComponent并不是最好的做法。如果一个游戏有60帧的话,那么我们一秒钟就得运行该函数60次,实际上很多时候我们并不需要执行那么多次,也许我们可以设置一个定时器,让其间隔更长的时间,也可以修改Component类的TickInterval,这样可以提高程序运行的效率。
void USurInteractionComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
//在每一Tick都查找可交互物体,这个做法比较消耗资源
//更好的做法是使用一个定时器,每隔一段时间检测一次,效率会相对高一些
FindBestInteractable();
}
接下来创建要显示的交互提示控件,继承自USurWorldUserWidget, 起名为DefaultWidgetInstance。这部分的操作我们已经做过很多次了,这里就不赘述了。



有闲情逸致的话还可以给他加个小动画。
设置好之后,将其设置到InteractionComponent,这样实例化的控件就是这个类了。

最后进入游戏,可以看到当视线转移到可交互物体上时,就会出现提示控件。

总结
本节课我们升级了很久以前实现的交互组件,成功让其在注视可交互物体时出现提示信息。
到目前为止,我们已经学习了制作单机游戏的大部分能力(入门),事实上,我们已经可以利用我们掌握的这些能力,制作出一个精巧的小游戏来了,至少,在面对一个新的游戏需求时,我们可以拥有某一方面的思路,并将其付诸实践。
在接下来的课程中,课程老师将会带领我们走入多人游戏的大门,后面的东西难度确实有点大啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊阿
全部代码
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "SurInteractionComponent.generated.h"
class USurWorldUserWidget;
//交互组件,附加在Actor上允许其与其他物体交互
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class FPSPROJECT_API USurInteractionComponent : public UActorComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
USurInteractionComponent();
void FindBestInteractable();
protected:
// Called when the game starts
virtual void BeginPlay() override;
//当前可交互的物体
UPROPERTY() //将其标记为UPEOPERTY, 监控其生命周期,避免空悬指针的发生
AActor* FocusActor;
//指定生成控件的类
UPROPERTY(EditDefaultsOnly, Category = "UI")
TSubclassOf<USurWorldUserWidget> DefaultWidgetClass;
//当有可交互的物体时,会生成指定的控件
UPROPERTY()
USurWorldUserWidget* DefaultWidgetInstance;
UPROPERTY(EditDefaultsOnly, Category = "Trace")
float TraceDistance;
UPROPERTY(EditDefaultsOnly, Category = "Trace")
float TraceRadius;
UPROPERTY(EditDefaultsOnly, Category = "Trace")
TEnumAsByte<ECollisionChannel> CollisionChannel;
public:
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
void PrimaryInteract();
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "SurInteractionComponent.h"
#include "DrawDebugHelpers.h"
#include "SurGameInterface.h"
#include "SurWorldUserWidget.h"
#include "GameFramework/Character.h"
static TAutoConsoleVariable<bool> CVarDebugDrawInteraction(TEXT("su.InteractionDebugDeaw"), false, TEXT("Enable Debug Line for Interact Component."), ECVF_Cheat);
// Sets default values for this component's properties
USurInteractionComponent::USurInteractionComponent()
{
PrimaryComponentTick.bCanEverTick = true;
TraceDistance = 1000.f;
TraceRadius = 50.f;
CollisionChannel = ECC_WorldDynamic;
}
void USurInteractionComponent::FindBestInteractable()
{
bool bDebugDraw = CVarDebugDrawInteraction.GetValueOnGameThread();
FHitResult Hit;
FVector CtrlerLocation;//控制器的位置
FRotator CtrlerRotation;
APawn* MyOnwer = Cast<APawn>(GetOwner());
if(!MyOnwer) return;
CtrlerLocation = MyOnwer->GetPawnViewLocation();
CtrlerRotation = MyOnwer->GetControlRotation();
FVector End = CtrlerLocation + (CtrlerRotation.Vector() * TraceDistance);
FCollisionObjectQueryParams ObjectQueryParams;//查询参数
ObjectQueryParams.AddObjectTypesToQuery(CollisionChannel);//选择查询WorldDynamic类型的对象
FCollisionShape Shape;
Shape.SetSphere(TraceRadius);
GetWorld()->SweepSingleByObjectType(Hit, CtrlerLocation, End, FQuat::Identity ,ObjectQueryParams, Shape);
FocusActor = nullptr;
AActor* HitActor = Hit.GetActor();
if(HitActor)
{
//判断碰撞体是否实现了我们需要的接口
if(HitActor->Implements<USurGameInterface>())
{
FocusActor = HitActor;
if(bDebugDraw)
{
//用于Debug
DrawDebugSphere(GetWorld(), Hit.ImpactPoint, TraceRadius, 32, FColor::Green, false, 3);
}
}
}
else
{
if(bDebugDraw)
{
DrawDebugSphere(GetWorld(), Hit.ImpactPoint, TraceRadius, 32, FColor::Red, false, 3);
}
}
//如果当前有聚焦ACTOR,就生成控件
if(FocusActor)
{
if(DefaultWidgetInstance == nullptr && ensure(DefaultWidgetClass))
{
DefaultWidgetInstance = CreateWidget<USurWorldUserWidget>(GetWorld(), DefaultWidgetClass);
}
if(DefaultWidgetInstance)
{
DefaultWidgetInstance->AttachedActor = FocusActor;
if(!DefaultWidgetInstance->IsInViewport())
{
DefaultWidgetInstance->AddToViewport();
}
}
}
else
{
if(DefaultWidgetInstance)
{
DefaultWidgetInstance->RemoveFromParent();
}
}
}
// Called when the game starts
void USurInteractionComponent::BeginPlay()
{
Super::BeginPlay();
// ...
}
// Called every frame
void USurInteractionComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
//在每一Tick都查找可交互物体,这个做法比较消耗资源
//更好的做法是使用一个定时器,每隔一段时间检测一次,效率会相对高一些
FindBestInteractable();
}
void USurInteractionComponent::PrimaryInteract()
{
if(!FocusActor)
{
UE_LOG(LogTemp, Warning, TEXT("No FocusActor to Interact."));
return;
}
APawn* MyPawn = Cast<APawn>(GetOwner());
if(ensure(MyPawn))
{
ISurGameInterface::Execute_Interact(FocusActor, MyPawn);
}
}


斯坦福 UE4 C++ ActionRoguelike游戏实例教程 16.优化交互,实现看到物体时出现交互提示的更多相关文章
- 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 开发人员收藏的20个 HTML5 实例教程
当开始学习如何创建 Web 应用程序或网站的时候,最流行的建议之一就是阅读教程,并付诸实践.也有大量的 Web 开发的书,但光有理论没有实际行动是无用的.现在由于网络的发展,我们有很多的工具可以用于创 ...
- 3Ds Max实例教程-制作女战士全过程
3Ds Max制作“女战神” 作者:Diego Rodríguez 使用软件:3Ds Max,Photoshop 3Ds Max下载:http://wm.makeding.com/iclk/?zone ...
- 值得 Web 开发人员学习的20个 jQuery 实例教程
这篇文章挑选了20个优秀的 jQuery 实例教程,这些 jQuery 教程将帮助你把你的网站提升到一个更高的水平.其中,既有网站中常用功能的的解决方案,也有极具吸引力的亮点功能的实现方法,相信通过对 ...
- Web 开发中应用 HTML5 技术的10个实例教程
HTML5 作为下一代网站开发技术,无论你是一个 Web 开发人员或者想探索新的平台的游戏开发者,都值得去研究.借助尖端功能,技术和 API,HTML5 允许你创建响应性.创新性.互动性以及令人惊叹的 ...
- 《HTML5与CSS3实例教程》
<HTML5与CSS3实例教程> 基本信息 作者: (美)Brian P. Hogan 译者: 卢俊祥 丛书名: 图灵程序设计丛书 出版社:人民邮电出版社 ISBN:97871153634 ...
- 如何使用Xcode分析调试在真机运行的UE4 IOS版游戏
写本文的是因为UE4 官方文档虽然也有,但主要讲的是是用UE4Editor把游戏打成一个IPA包的形式发布的方法 而对于想通过Xcode分析UE4的渲染流程来学习或优化的朋友,那官方文档的资料还是不够 ...
随机推荐
- socket应用的例子
当使用 C 语言实现 Socket 编程时,可以通过系统提供的网络库来实现网络通信.以下是一个简单的示例,演示了如何创建一个简单的服务器和客户端,实现客户端向服务器发送消息并接收回复的功能. 服务器端 ...
- Django-rest-framework框架——请求与响应、视图组件
目录 一 请求与响应 1.1 Request 1.1.1.1 常用属性 1).data 2).query_params 1.2 Response 1.1.2.1 构造方式 1.1.2.2 常用属性 1 ...
- Java虚拟机(JVM):第六幕:自动内存管理 - 选择合适的垃圾收集器
前言:在虚拟机的世界里面,内置了很多的垃圾收集器,但并不是说最先进的就是最好的.有一句话说的好"因地制宜": 一.Epsilon收集器 是一个无操作的收集器,但是贴切的来说是&qu ...
- 云端golang开发,无需本地配置,能上网就能开发和运行
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 需求 学习golang的时候,需要一个IDE,还需要一 ...
- 前端工程化&&自动化部署&&model抽离
你不知道的前端 MVVM 模式中的数据层(万字长文,教你造轮子) 实现了 Model 层抽离的全部想法, 后端返回的接口--model(错误处理,返回统一格式,洗数据,缓存)--再拿这个处理过的数据 ...
- 深入解析css-笔记
前言 本文章是根据<深入解析CSS>一书所作的学习笔记,书中的知识点基本都概括在这.希望对您有帮助,另外本博客是通过word笔记文档导入,虽然后续对内容和代码相关进行了一些格式处理,但还是 ...
- 我整理了一份Flink流计算入门教程清单(转)
好久不见! 作为技术出身的我,不太会写软文广告,今天就直接来个硬广.之前与人民邮电出版社合作的<Flink原理与实践>经过一年多时间的打磨和润色,这两天终于与大家见面了,恳请各位朋友多多支 ...
- DNS 服务 docker-bind 的部署使用
前言 前面使用 nginx 代理转发了几个域名到服务器,但是每次添加一个域名都需要在客户端添加一行 hosts 文件,无疑是繁琐的,其中也提到可以使用 DNS 来实现自动解析域名 到指定服务器的功能, ...
- 【MISC】[MoeCTF 2022]cccrrc --crc32爆破
附件下载下来为压缩包,需要密码,查看该压缩包的内容 此处发现里面四个txt文件均已被加密,但是每个txt的内容都只有四个字节,符合crc32爆破条件,直接上脚本: import binascii im ...
- 旺店通·企业奇门和用友BIP接口打通对接实战
旺店通·企业奇门和用友BIP接口打通对接实战 接通系统:旺店通·企业奇门 旺店通是北京掌上先机网络科技有限公司旗下品牌,国内的零售云服务提供商,基于云计算SaaS服务模式,以体系化解决方案,助力零售企 ...