IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 虚幻引擎C++开发学习(二)上 -> 正文阅读

[游戏开发]虚幻引擎C++开发学习(二)上

上一章的内容都还是基础知识,游戏逻辑较为简单,语法也比价简单,主要的目的还是基础,所以没有过多的记录。

这一章会构建一个小游戏(关于关卡构建的部分这里省略掉了,是一个简单的房屋构建和光照布置,这些内容在之前的博客中有所介绍),所以会尽量记录有关c++的内容。

这一章的一章的涉及内容有:

  • Classes and Object oriented programming(类和面向对象的编程).
  • Components and Actors
  • Pointers and memory management
  • Blocking out levels in BSP
  • Collisions, Trigger volumes,and Line tracing
  • Input binding

关于输入绑定和关卡设计相关的在前面的文章中都有所提到。

正文:

一、项目设置和基础概念

我们重新创建一个不含初学者包的C++项目。

当引擎能显示界面时,创建成功了

我们可以在项目列表中,找到Source-Escape-Escape.cpp

1.1 Pointers+Classes

先介绍下指针

Pointers are memory address.

pointer syntax(指针语法):

?上面的三种方式都可以,我们举例子来说明,假设我们有:

AActor* SomeActor;

AActor class 有一个方法GetName(),后面会用到。

我们可以用下面的方式使用(基础):

SomeActor->GetName();

关于inheritance(继承)

在虚幻中:

举例:Character “is a” Pawn,Pawn “is an” Actor

  • 一个actor拥有一个pawn的所有特征都将默认具有。
  • 一个 pawn 默认拥有的任何东西,一个Actor也会在这种类型的继承中继承。

相同的例子:Dog “is a” Mammal,Mamm “is an” Animal.

1.2 Components

关于Components(组件)

组件非常适合共享共同的行为或特性。Actor可以拥有自定义组件(上一章中有提到)。

那怎样创建一个World position component。

在那之前,我们可以进入类查看器:

?在里面搜索pawn,可以看到:

?回到组价,我们可以随意拖入一个球体,然后选中它,添加组件,新建c++组件:

?对其进行命名并且创建,我们可以在vs code的目录结构中看到:

?可以注意到在文件的最上方有一句:

// Fill out your copyright notice in the Description page of Project Settings.

我们可以在项目设置中,对版权声明进行修改。

放在这部分的代码会在每帧执行:

void UWorldPosition::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	// ...
}

1.3 关于UE_LOG的介绍

UE_LOG(Category, Verbosity, TEXT("Message"));

UE_LOG(LogTemp, Warning, TEXT("Hello!"));
  • Error = Red
  • Warning = Yellow
  • Display = Grey

我们可以在vs code中,在BeginPlay输入:

UE_LOG(LogTemp, Warning, TEXT("This is a warning"));

然后我们可以编译,并打开输出日志,运行游戏,就可以看到(输出两次是因为我这里有两个object):

1.4 FVector

我们可以先查看官方文档:

https://docs.unrealengine.com/4.27/en-US/API/Runtime/Core/Math/FVector/https://docs.unrealengine.com/4.27/en-US/API/Runtime/Core/Math/FVector/定义是:A vector in 3-D space composed of components (X, Y, Z) with floating point precision.

我们也可以在里面找到此类的函数(如果我们需要的话):

?一个简单的方式获取位置信息:

FString ObjectName = GetOwner()->GetName();

FString ObjectPosition = GetOwner()->GetActorLocation().ToString();

UE_LOG(LogTemp, Warning,TEXT("%s Location in world is : %s"),*ObjectName,*ObjectPosition);

关于这一阶段的问题:

1 ObjectName?is an?FString.? Why do we have to use?*ObjectName?in our?UE_LOG?rather than just?ObjectName?

UE_LOG is expecting a TCHAR array,and the *effectively converts the string to this type.?

二 为场景中的门添加代码

关于如何用蓝图使门进行碰撞检测,并开启的部分,在前面的文章中有介绍过。

我们首先为门,创建一个新的c++组件,需要注意的是,我们需要将门修改为可移动类型。因为如果一直是静态对象,是不能在游戏中修改的。

?2.1 获取信息

我们可以用下面的句子获取位置信息:

GetOwner()->GetActorRotation()

要使用GetOwner,别忘了加上:

#include "GameFramework/Actor.h"

但是这个会给我们一个FRotator,这样我们需要查找一下文档。

?2.2 设置信息

这样我们就能获取信息,但是我们想要做的是设置旋转,这就需要SetActorRotation()。

//FRotator CurrentRotation = GetOwner()->GetActorRotation();

//CurrentRotation.Yaw = 90.f;
FRotator OpenDoor ={0.f,90.f,0.f};

GetOwner()->SetActorRotation(OpenDoor );

我们进入游戏,门就被正常打开了

?2.3 动态的开门

不过这样只会在我们进入游戏时,将门打开,不能在游戏中看到门打开的过程。

我们不将代码放在BeginPlay中,这次放在TickComponent中。

我们首先将最终的目标Yaw值(90度)定义成私有数据TargetYaw,然后获得当前的旋转值,和上面一样:

float CurrentYaw = GetOwner()->GetActorRotation().Yaw;

然后定义一个OpenDoor(这里的定义会在下面遭到修改):

FRotator OpenDoor(0.f,TargetYaw,0.f);

我们使用Fmath::Lerp来修改OpenDoor:

OpenDoor.Yaw = FMath::Lerp(CurrentYaw, TargetYaw,0.02f);

它的用法,在官方文档中有:

?但是我们这里用的线性插值,它有一些问题。这里的门角度会一直接近90度,但是不会到达90度。而且和电脑性能有关的,如果你的电脑性能足够好,可以在一秒内跑很多帧,那么门关上的速度会更快。

那怎样获得理想的关门效果?

我们使用Fmath::FInterpConstantTo4?

OpenDoor.Yaw = FMath::FInterpConstantTo(CurrentYaw, TargetYaw,DeltaTime,45);

2.4 Open the Door anywhere

要实现这个,我们要取消之前对TargetYaw的赋值,重新定义:

float InitialYaw;
float CurrentYaw;
float TargetYaw;

然后我们对BeginPlay函数和TickComponent函数进行简单的修改:

void UOpenDoor::BeginPlay()
{
	Super::BeginPlay();
	InitialYaw = GetOwner()->GetActorRotation().Yaw;
	CurrentYaw = InitialYaw;
	TargetYaw = InitialYaw + 90.f;
	
}
void UOpenDoor::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	CurrentYaw = FMath::Lerp(CurrentYaw,TargetYaw,DeltaTime * 1.f);
	FRotator DoorRotator = GetOwner()->GetActorRotation();
	DoorRotator.Yaw = CurrentYaw;
	GetOwner()->SetActorRotation(DoorRotator);
}

?我们可以从虚幻引擎中,复制一个相同的门,然后编译c++代码。

在游戏开始时,就可以看到两个门同时缓慢打开了。

2.4 在虚幻引擎中修改参数

但是有个问题是,我们每次要修改旋转值,还要在代码中修改。而且不能对单个门的旋转角度进行修改。我们需要对这个问题改进。

有个很简单的方法,我们在TargetYaw定义的地方输入:

UPROPERTY(EditAnywhere, Category = "Damage")

这样在编译后,我们选择门的OpenDoor组件,可以看到:

?这样我们可以对每个门进行设置,并将代码改为:

TargetYaw += InitialYaw;

设置一个90度,一个为50度:

2.5 设置触发体积开门

我们对开门这一动作进一步细化,我们希望玩家在达成一定条件后,门才能开启。

我们将使用触发体积,Trigger volume来实现。首先先在引擎中创建一个触发体积,然后我们回到VS code中。

我们加入新的头文件,并保证OpenDoor在最下方:

#include "Engine/TriggerVolume.h"

然后添加

UPROPERTY(EditAnywhere, Category = "Trigger")
ATriggerVolume* PressurePlate;

这样我们可以在选中组件,设置PressurePlate为我们刚刚在虚幻引擎中创建的触发体积。?

然后我们需要保证,当玩家进入触发体积时,有对应操作。

我们还需要创建一个:

UPROPERTY(EditAnywhere, Category = "Open")
AActor* ActorThatOpen;

我们需要为其分配,但是DefaultPawn只在运行的时候出现,我们没办法在未运行时选中。

所以我们需要先运行,然后弹出,再选中对应组件(为了进行测试):

接着我们回到代码中,并对代码进行重构。我们需要创建一个新的函数OpenDoor,并将之前的部分操作移动到新的函数中。?

void OpenDoor(float DeltaTime);

然后我们加入判断:

if (PressurePlate->IsOverlappingActor(ActorThatOpen))
{
	OpenDoor(DeltaTime);
}

然后编译,进行测试。用上面的操作添加DefaultPawn,然后走进触发体积,门是可以打开的。

但是我们在游戏中不能按照这个方式来开门关门,所以我们要进行修改。

首先在进入游戏BeginPlay时,加入判断,防止出现PressurePlate未被分配的情况。

if(!PressurePlate)
{
	//如果,没有在选项中分配PressurePlate
	UE_LOG(LogTemp, Error, TEXT("%s Has the OpenDoor component on it , but no pressureplate set"),*GetOwner()->GetName());
}

其次,我们要加入两个新的头文件:

#include "Engine/World.h"
#include "GameFramework/PlayerController.h"

关于FirstPlayerController,如果我们的游戏未设置为本地多人游戏,则每个客户端上将只有一个 PlayerController。

我们在BeginPlay的判断下,加入:

ActorThatOpen = GetWorld()->GetFirstPlayerController()->GetPawn();

现在我们就可以正常和触发体积互动,开门了。

至于关门,就很简单了,这里就不多赘述。可以自己尝试。

2.6 当门开启一定时间后自动关闭

我们对功能进行扩充,如果玩家在一定时间内没有关闭们,我们就自动关门。我们可以使用GetTimeSecond。

它会返回:time in seconds since world was brought up for play

重新定义两个变量:

float DoorLastOpen = 0.f;
float DoorCloseDelay = 2.f;

然后加入新的判断:

if (PressurePlate && PressurePlate->IsOverlappingActor(ActorThatOpen))
{
	OpenDoor(DeltaTime);

	DoorLastOpen = GetWorld()->GetTimeSeconds();
}
else
{
	if (GetWorld()->GetTimeSeconds() - DoorLastOpen > DoorCloseDelay)
	{
		CloseDoor(DeltaTime);
	}
}

由于篇幅过多,本章将分成两部分。剩余的内容在下一部分继续介绍。

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-12-08 14:08:08  更:2021-12-08 14:09:12 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 20:36:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码