斯坦福课程 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. SQL函数count(),sum()理解

    ①准备-创建测试表: create table test ( id SMALLINT, name varchar(10) ); 插入数据: insert into test values(0,'张三' ...

  2. Unity - Windows获取屏幕分辨率、可用区域

    直接搜索最多的就是使用System.Windows.Form.Screen类,但因为unity用的是mono,不能正常使用这个方法 可使用win32api获取,这里只尝试了获取主要屏幕的分辨率,而且没 ...

  3. c# 使用打印机打印并设置打印位置及宽高

    1.在界面中使用自带的控件printDocument 2.将以下函数绑定到控件的PrintPage事件 private void printDocument1_PrintPage(object sen ...

  4. PTA乙级1038C++哈希解法

    #include"bits/stdc++.h" using namespace std; int main() { int a,b[105]={0}; long i,n,K; ci ...

  5. 洛谷P3392 涂国旗(暴力枚举)

    # 涂国旗 ## 题目描述 某国法律规定,只要一个由 $N \times M$ 个小方块组成的旗帜符合如下规则,就是合法的国旗.(毛熊:阿嚏--) - 从最上方若干行(至少一行)的格子全部是白色的: ...

  6. c#装饰器模式详解

    基础介绍:   动态地给一个对象添加一些额外的职责.适用于需要扩展一个类的功能,或给一个类添加多个变化的情况.   装饰器,顾名思义就是在原有基础上添加一些功能.   大家都只知道如果想单纯的给原有类 ...

  7. Redis常用命令-实战篇

    目录 写在前面 连接操作命令 持久化 远程服务控制 对value操作的命令 操作字符串String命令 List Set Hash 写在前面 java 操作 redis 太常见了,基本上有需要的系统, ...

  8. [学习笔记]TypeScript查缺补漏(二):类型与控制流分析

    @ 目录 类型约束 基本类型 联合类型 控制流分析 instanceof和typeof 类型守卫和窄化 typeof判断 instanceof判断 in判断 内建函数,或自定义函数 赋值 布尔运算 保 ...

  9. mysql 代码适配 postgresql 适配改写,优化案例(行转列 + 标量子查询改写)

    最近在适配个MySQL应用的项目,各种SQL改成PG兼容的语法真的是脑壳痛,今天遇到个有意思的案例. 原 MySQL SQL语句: SELECT DISTINCT l.MALL_NAME '项目', ...

  10. React 中常用技术

    可以少去理解一些不必要的概念,而多去思考为什么会有这样的东西,它解决了什么问题,或者它的运行机制是什么? 1. React 中导出和导入 1.1 ES6 解析 ES6 的模块化的基本规则或特点: 每一 ...