斯坦福 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的渲染流程来学习或优化的朋友,那官方文档的资料还是不够 ...
随机推荐
- Python基础——计算机组成原理、操作系统概述、编程语言的由来、编程语言分类、python介绍、 安装Cpython解释器、 第一个python程序
文章目录 一 引子: 1.1 什么是语言?什么是编程语言?为何要有编程语言? 1.2 什么是编程?为什么要编程? 二 计算机组成原理 2.1.什么是计算机? 2.2.为什么要用计算机? 2.3.计算机 ...
- xgo多线程
import threading import time #导入xgoedu from xgoedu import XGOEDU from xgolib import XGO #导入xgolib # ...
- Jmeter连接数据库sql语句操作,查询后取值做变量
第一步 :导入jar包 第二步 :创建JDBC Reques 第三步 :创建JDBC Connection Configuration 第四步:在request中输入数据进行操作 Query Typ ...
- .NET 8 候选版本 2 (RC2) 现已可用
.NET 8 候选版本 2 (RC2) 现已可用,并包含了许多 ASP.NET Core 的出色新改进! 这是我们计划在今年晚些时候发布的最终 .NET 8 版本之前分享的最后一个候选版本..NET ...
- 动态规划的状态设计 | bot 讲课の补题
sto james1badcreeper orz. 好厉害的题,但是怎么有人补了三天才补完呢? CF1810G The Maximum Prefix 线性 dp,怎么有 bot 说题目难度在 *240 ...
- 传纸条(lgP1006)
终于有一道一遍过的题了/kk/kk 发现前几道都很难(总之暂时没想出来)就先把这个写了. 其实这题四维 dp 好像能过,但既然写了就写正解吧... 因为路径正着走和反着走都是一样的,所以问题就是求从左 ...
- springboot整合jpa sqlite
前言 最近有关项目需要用到SQLITE,我先是使用Mybatis去连接SQLITE,然后发现SQLITE对BLOB支持不好,在网上看到相关教程可以写mapper.xml文件,加一个handler解决B ...
- HelloGitHub 社区动态,开启新的篇章!
今天这篇文章是 HelloGitHub 社区动态的第一篇文章,所以我想多说两句,聊聊为啥开启这个系列. 我是 2016 年创建的 HelloGitHub,它从最初的一份分享开源项目的月刊,现如今已经成 ...
- HarmonyOS 高级特性
引言 本章将探讨 HarmonyOS 的高级特性,包括分布式能力.安全机制和性能优化.这些特性可以帮助你构建更强大.更安全.更高效的应用. 目录 HarmonyOS 的分布式能力 HarmonyOS ...
- 开发工具使用:CubeMX、KEIL MDK-ARM
来源:成电<微机原理与嵌入式系统>漆强 第四章 STM32CubeMX软件的使用 来源:成电<微机原理与嵌入式系统>漆强 第五章 MDK-ARM软件的使用 一.STM32的Cu ...