虚幻引擎C++开发学习(二)上

您所在的位置:网站首页 a&hci论文 虚幻引擎C++开发学习(二)上

虚幻引擎C++开发学习(二)上

#虚幻引擎C++开发学习(二)上| 来源: 网络整理| 查看: 265

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

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

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

Classes and Object oriented programming(类和面向对象的编程).Components and ActorsPointers and memory managementBlocking out levels in BSPCollisions, Trigger volumes,and Line tracingInput 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 = RedWarning = YellowDisplay = 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); } }

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



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3