虚幻引擎C++开发学习(二)上 |
您所在的位置:网站首页 › a&hci论文 › 虚幻引擎C++开发学习(二)上 |
上一章的内容都还是基础知识,游戏逻辑较为简单,语法也比价简单,主要的目的还是基础,所以没有过多的记录。 这一章会构建一个小游戏(关于关卡构建的部分这里省略掉了,是一个简单的房屋构建和光照布置,这些内容在之前的博客中有所介绍),所以会尽量记录有关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): 我们可以先查看官方文档: https://docs.unrealengine.com/4.27/en-US/API/Runtime/Core/Math/FVector/ 我们也可以在里面找到此类的函数(如果我们需要的话): 一个简单的方式获取位置信息: 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++组件,需要注意的是,我们需要将门修改为可移动类型。因为如果一直是静态对象,是不能在游戏中修改的。 我们可以用下面的句子获取位置信息: GetOwner()->GetActorRotation()要使用GetOwner,别忘了加上: #include "GameFramework/Actor.h"但是这个会给我们一个FRotator,这样我们需要查找一下文档。 这样我们就能获取信息,但是我们想要做的是设置旋转,这就需要SetActorRotation()。 //FRotator CurrentRotation = GetOwner()->GetActorRotation(); //CurrentRotation.Yaw = 90.f; FRotator OpenDoor ={0.f,90.f,0.f}; GetOwner()->SetActorRotation(OpenDoor );我们进入游戏,门就被正常打开了 不过这样只会在我们进入游戏时,将门打开,不能在游戏中看到门打开的过程。 我们不将代码放在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++代码。 在游戏开始时,就可以看到两个门同时缓慢打开了。 但是有个问题是,我们每次要修改旋转值,还要在代码中修改。而且不能对单个门的旋转角度进行修改。我们需要对这个问题改进。 有个很简单的方法,我们在TargetYaw定义的地方输入: UPROPERTY(EditAnywhere, Category = "Damage")这样在编译后,我们选择门的OpenDoor组件,可以看到: 这样我们可以对每个门进行设置,并将代码改为: TargetYaw += InitialYaw;设置一个90度,一个为50度: 我们对开门这一动作进一步细化,我们希望玩家在达成一定条件后,门才能开启。 我们将使用触发体积,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 |