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

概述

本篇文章对应Lecture 18 – Creating Buffs, World Interaction, 73节。本文将会重构以前实现过的SurInteractionComponent,实现在玩家注释可交互物体时,可以出现可交互提示,效果如下:

在文章的最后,我会放出所有相关的代码。

优化交互

在几十节课之前,我们学习过如何与场景中的物体进行交互。其中有一项就是定义了一个交互组件(SurInteractionComponent),它允许角色在按下交互键时,如果视线方向有可交互物体,即可与物体进行交互,触发物体的Interact函数。

本篇文章则要对这个组件进行一次升级,当我们注视可交互物体时,可以直接出现一个交互提示(UMG控件),提醒我们这个物体是可以交互的;当我们按下交互键时,即可对物体进行交互。具体怎么升级呢,让我们边做边说吧。


要实现这个功能,我们需要做到以下几点:

  1. 当我们注视一个Actor时,要获取这个Actor的引用;反之,当我们视线离开这个Actor时,取消对这个Actor的引用
  2. 当我们获得这个Actor的引用时,要生成交互提示控件,并在这个Actor周围显示这个控件;反之,隐藏这个控件
  3. 当我们按下交互键(根据自己的设置)时,判断我们是否拿到了目标Actor的引用(指针是否为空)。如果拿到了,则执行交互相关逻辑。

以上就是我们本文要实现的全部需求,为此,我们需要为我们的交互组件(SurInterationComponent)添加以下主要成员。

主要有

  1. 当前注视的Actor
  2. 要生成的控件类,这里选用之前实现的SurWorldUserWidget子类,它可以选定一个Actor,并将自己在视口中依附在Actor周围。
  3. 控件实例,根据第二点的控件类生成一个实例
//当前可交互的物体
UPROPERTY() //将其标记为UPEOPERTY, 监控其生命周期,避免空悬指针的发生
AActor* FocusActor; //指定生成控件的类
UPROPERTY(EditDefaultsOnly, Category = "UI")
TSubclassOf<USurWorldUserWidget> DefaultWidgetClass; //当有可交互的物体时,会生成指定的控件
UPROPERTY()
USurWorldUserWidget* DefaultWidgetInstance; public:
void FindBestInteractable();

我们将执行交互(PrimaryInteract)和寻找交互对象(FindBestInteractable)分离了开来。这是因为我们是要通过“注视”来寻找交互物体,并且实时判断该物体能不能交互。因此,我们需要把之前PrimaryInteract的寻找交互对象的大部分逻辑移动过来,并对其进行一些修改,其最核心的逻辑如下:

  1. 在一开始时将FocusActor置为空。
  2. 做射线检测,如果命中了物体,并且物体实现了Interact接口,则将物体作为FocusActor
  3. 如果FocusActor不为空,则尝试生成控件。这里生成控件的方式有些类似于单例模式中的懒汉模式,只有当第一次需要生成控件的时候才实例化,而不是在构造函数中就实例化了这个控件。
  4. 如果FocusActor不为空,且拥有控件实例,则将其添加到视口中
  5. 如果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。这部分的操作我们已经做过很多次了,这里就不赘述了。

控件结构

ParentSizeBox属性

设置文本字体等属性

有闲情逸致的话还可以给他加个小动画。


设置好之后,将其设置到InteractionComponent,这样实例化的控件就是这个类了。

设置DefaultWidgetClass

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

总结

本节课我们升级了很久以前实现的交互组件,成功让其在注视可交互物体时出现提示信息。

到目前为止,我们已经学习了制作单机游戏的大部分能力(入门),事实上,我们已经可以利用我们掌握的这些能力,制作出一个精巧的小游戏来了,至少,在面对一个新的游戏需求时,我们可以拥有某一方面的思路,并将其付诸实践。

在接下来的课程中,课程老师将会带领我们走入多人游戏的大门,后面的东西难度确实有点大啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊阿

全部代码

// 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.优化交互,实现看到物体时出现交互提示的更多相关文章

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

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

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

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

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

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

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

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

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

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

  6. 3Ds Max实例教程-制作女战士全过程

    3Ds Max制作“女战神” 作者:Diego Rodríguez 使用软件:3Ds Max,Photoshop 3Ds Max下载:http://wm.makeding.com/iclk/?zone ...

  7. 值得 Web 开发人员学习的20个 jQuery 实例教程

    这篇文章挑选了20个优秀的 jQuery 实例教程,这些 jQuery 教程将帮助你把你的网站提升到一个更高的水平.其中,既有网站中常用功能的的解决方案,也有极具吸引力的亮点功能的实现方法,相信通过对 ...

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

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

  9. 《HTML5与CSS3实例教程》

    <HTML5与CSS3实例教程> 基本信息 作者: (美)Brian P. Hogan 译者: 卢俊祥 丛书名: 图灵程序设计丛书 出版社:人民邮电出版社 ISBN:97871153634 ...

  10. 如何使用Xcode分析调试在真机运行的UE4 IOS版游戏

    写本文的是因为UE4 官方文档虽然也有,但主要讲的是是用UE4Editor把游戏打成一个IPA包的形式发布的方法 而对于想通过Xcode分析UE4的渲染流程来学习或优化的朋友,那官方文档的资料还是不够 ...

随机推荐

  1. 5. 用Rust手把手编写一个Proxy(代理), 通讯协议建立, 为内网穿透做准备

    用Rust手把手编写一个Proxy(代理), 通讯协议建立, 为内网穿透做准备 项目 ++wmproxy++ gite: https://gitee.com/tickbh/wmproxy github ...

  2. 微服务使用openfeign调用单点的会话失效问题

    项目Springcloud,认证中心方式实现SSO使用开源框架Sa-Token 本身的单独访问每个客户端服务的单点就没有问题.然后单点通过Fegin调用就不好使了! 主要使用的Sa-Token的微服务 ...

  3. Berkeley

    2019年Berkeley预测Serverless将取代Serverful计算,成为云计算的计算新范式.Serverless为应用程序开发提供了一种全新的系统架构,其凭借着弹性伸缩省事省心,按需付费更 ...

  4. day1 C语言:对于P1055 ISBN号码的代码优化及多解

    day1 C语言:对于P1055 ISBN号码的代码优化及多解 先看题目 直接说最优解,其他方法后置 第一部分 1.第一个点是数据的输入,本人第一的想法是直接用int类型去接受数据,但因为" ...

  5. Pushpin:开源即时通信神器,让你的API秒变实时API,轻松实现WebSocket,HTTP流和HTTP长轮询等服务

    作为一个开发者,你可能已经利用过REST API来构建和集成各种应用.REST API是基于HTTP协议的交互模式,它使得客户端和服务器可以通过请求和响应来进行数据交换,简单.灵活.通用. 然而,当你 ...

  6. 使用ClosedXml查询Excel文件数据,匹配时间并显示

    使用Nuget包管理器安装ClosedXml包,VS没网在https://www.nuget.org/ 下载后,包源本地安装至项目 函数: private void SelectGrab(Cancel ...

  7. np.array和np.ndarry 的区别

    np.array和np.ndarray都是NumPy中用于创建多维数组的函数. np.ndarray是NumPy中的多维数组类,它是一种可变的数组,可以通过修改数组中的元素来改变其内容.使用np.nd ...

  8. 字符串匹配|kmp笔记

    很久之前学的了. 我很懒,不太喜欢画图. 做个笔记回忆一下: kmp 朴素比对字符串 所谓字符串匹配,是这样一种问题:"字符串 T 是否为字符串 S 的子串?如果是,它出现在 S 的哪些位置 ...

  9. liunx远程管理常用命令笔记

    1,关机/重启 shutdown -r now : 立刻重启的命令 2,查看或配置网卡信息 2.1  网卡和 IP 地址 2.2  ifconfig 用了管道和grep 查找到 IP 地址 2.3 p ...

  10. Mac 终端命令查看WiFi连接日志【原创】

    写这篇文章的原因是因为经常通过钉钉打上班卡忘记打卡了,我们标准上班时间是上午8:00-10:00 ,对应下班时间是 17:00-19:00  ,有时8:30到公司,就开始忙碌了,作为程序员有时后一忙就 ...