虚幻4游戏引擎C++编程官网例程解析
- 官网例程
- 源代码
- FloatingActor.h
- FloatingActor.cpp
- 内容说明
- pragma once
- 头文件
- UClass宏
- AActor类
- Private私有部分
- Public公有部分
- protected保护部分
- 源文件
- 构造函数
- CreateDefaultSubobject函数
- TEXT()宏
- SetupAttachment函数
- ConstructorHelpers::FObjectFinder类
- SetStaticMesh函数
- SetRelativeLocation函数
- FVector类
- BeginPlay函数
- Super类
- Tick函数
- 使物体在指定区间来回移动的巧妙办法:
- FRotator类
- SetActorLocationAndRotation函数
官网例程
创建首个代码项目,并添加新的C++类 官方源代码里,创建了一个叫FloatingActor的类。这个类的功能是创建一个可以旋转且上下浮动的模型
源代码
FloatingActor.h
// 版权所有 1998-2019 Epic Games, Inc。保留所有权利。
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/StaticMeshComponent.h"
#include "FloatingActor.generated.h"
UCLASS()
class QUICKSTART_API AFloatingActor : public AActor
{
GENERATED_BODY()
public:
// 设置此Actor属性的默认值
AFloatingActor();
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* VisualMesh;
protected:
// 游戏开始时或生成时调用
virtual void BeginPlay() override;
public:
// 逐帧调用
virtual void Tick(float DeltaTime) override;
};
FloatingActor.cpp
// 版权所有 1998-2019 Epic Games, Inc。保留所有权利。
#include "FloatingActor.h"
// 设置默认值
AFloatingActor::AFloatingActor()
{
// 将此Actor设为逐帧调用Tick()。如无需此功能,可关闭以提高性能。
PrimaryActorTick.bCanEverTick = true;
VisualMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
VisualMesh->SetupAttachment(RootComponent);
static ConstructorHelpers::FObjectFinder<UStaticMesh> CubeVisualAsset(TEXT("/Game/StarterContent/Shapes/Shape_Cube.Shape_Cube"));
if (CubeVisualAsset.Succeeded())
{
VisualMesh->SetStaticMesh(CubeVisualAsset.Object);
VisualMesh->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
}
}
// 游戏开始时或生成时调用
void AFloatingActor::BeginPlay()
{
Super::BeginPlay();
}
// 逐帧调用
void AFloatingActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
FVector NewLocation = GetActorLocation();
FRotator NewRotation = GetActorRotation();
float RunningTime = GetGameTimeSinceCreation();
float DeltaHeight = (FMath::Sin(RunningTime + DeltaTime) - FMath::Sin(RunningTime));
NewLocation.Z += DeltaHeight * 20.0f; //Scale our height by a factor of 20
float DeltaRotation = DeltaTime * 20.0f; //Rotate by 20 degrees per second
NewRotation.Yaw += DeltaRotation;
SetActorLocationAndRotation(NewLocation, NewRotation);
}
内容说明
pragma once
#pragma once //告诉编译器,本头文件只引用一次,保证头文件只被编译一次
头文件
#include "CoreMinimal.h" //头文件包含一套来自UE4核心编程环境的普遍存在类型
#include "GameFramework/Actor.h" //UE4 Actor类的声明头文件
#include "Components/StaticMeshComponent.h" //UE4 静态网格体组件头文件
#include "FloatingActor.generated.h"
/*
从UnrealHeaderTool导出的生成代码
UE4将生成所有反射数据并放入该文件中。
必须将该文件作为声明类型的标头文件中的最后一个包含语句,将其包含进去,不然会报错
*/
UCLASS()
class QUICKSTART_API AFloatingActor : public AActor
{};
UClass宏
UCLASS 宏为 UObject 提供一个对 UCLASS 的引用,描述其是基于虚幻引擎的类型
AActor类
AActor类是当前类的父类。Actor是所有能被放置于关卡中的对象的基类。Actor is the base class for an Object that can be placed or spawned in a level.(Unreal Engine Document原文)
private:
GENERATED_BODY()
public:
AFloatingActor(); //类默认构造函数
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* VisualMesh;
// 逐帧调用函数,
virtual void Tick(float DeltaTime) override;
protected:
// 游戏开始时或生成时调用
virtual void BeginPlay() override;
Private私有部分
GENERATED_BODY()宏用来产生某些预设方法。其产生的档案会放在$YouProject\Intermediate\Build\Win64\UE4\Inc\YouProject\xxx.generated.h里。
GENERATED_BODY()宏与GENERATED_UCLASS_BODY()宏的区别:
- 在4.6版本之前使用GENERATED_UCLASS_BODY() ,4.6版本之后使用GENERATED_BODY()
- GENERATED_BODY()宏与构造函数相关 GENERATED_BODY macro参考资料
Public公有部分
AFloatingActor()函数是当前类的构造函数,函数名称与类名称相同,实现部分在cpp文件中。
UPROPERTY(VisibleAnywhere) UStaticMeshComponent* VisualMesh
这是一个类型是UStaticMeshComponent(静态网格体组件,俗称模型),叫做VisualMesh的对象指针,个人认为它叫做pVIsualMesh更合理一些。
UPRORERTY(VisibleAnywhere)宏将变量公开到蓝图和虚幻编辑器
参数 | 作用 |
VIsibleAnywhere | 任何地方可见 |
AdvancedDisplay | 属性在“详细信息”面板的高级下拉列表中 |
EditAnywhere | 任何地方可编辑 |
Category = “CategoryPath” | 在详细信息中显示的属性的类别,使用管道符定义嵌套层级 |
BluprintReadOnly | 设置为蓝图只读,蓝图中只提供变量的get函数 |
BlueprintReadWrite | 设置为蓝图读写,在蓝图中为被修饰的变量提供 Get 和 Set 函数 |
virtual void TIck(DeltaTIme) override函数
Tick的意思是滴答,在游戏代码中代表每帧执行一次的函数。参数为DeltaTime,类型为单精浮点,表示和上一帧之间的间隔。函数的返回值为空。virtual关键词修饰了函数是个虚函数,运行时会覆盖父类的TIck函数。override关键字确保该函数为虚函数并覆写来自基类的虚函数,在派生类的成员函数中使用override时,如果基类中无此函数,或基类中的函数并不是虚函数,编译器会给出相关错误信息。
protected保护部分
virtual void BeginPlay() override
类实例化生成时调用一次
源文件
#include "FloatingActor.h" //引用头文件
构造函数
//构造函数
AFloatingActor::AFloatingActor()
{
// 将此Actor设为逐帧调用Tick()。如无需此功能,可关闭以提高性能。
PrimaryActorTick.bCanEverTick = true;
VisualMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
VisualMesh->SetupAttachment(RootComponent);
static ConstructorHelpers::FObjectFinder<UStaticMesh> CubeVisualAsset(TEXT("/Game/StarterContent/Shapes/Shape_Cube.Shape_Cube"));
if (CubeVisualAsset.Succeeded())
{
VisualMesh->SetStaticMesh(CubeVisualAsset.Object);
VisualMesh->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
}
}
CreateDefaultSubobject函数
功能:
该函数是个模板函数,用于创建组件或子对象,然后返回指向新建组件内存区域的指针
原型:
/**
* 创建一个组件或子对象.
* @param TReturnType 函数返回类型
* @param SubobjectName 新组件名称
* @param bTransient 如果将组件分配给瞬态属性,则为True。
* 这不会使组件本身瞬态,但会阻止其继承父默认值,默认为false
*/
template<class TReturnType>
TReturnType* CreateDefaultSubobject(FName SubobjectName, bool bTransient = false)
{
UClass* ReturnType = TReturnType::StaticClass();
return static_cast<TReturnType*>(CreateDefaultSubobject(
SubobjectName,
ReturnType,
ReturnType,
/*bIsRequired =*/ true,
bTransient));
}
有关其中的TReturnType::StaticClass()说明:
StaticsClass()实质上检索了所述类型的反射数据,它是虚幻引擎内置的反射系统的一部分。 当一个类包含UClass(),GENERATED_UCLASS_BODY()等时,这些宏将使用Unreal Header Tool将在编译时处理的反射系统。调用StaticClass()函数仅使用instance->GetClass()函数返回UObject实例的类型。
TEXT()宏
功能:
将原本的字符串最前端加上L,将原本多字节字符转换为统一的Unicode编码,根据平台的不同会在字符串前面加u或者L,宏代码中“##“表示连接两个字符串
注意:宏替换是在预编译阶段,此时的字符串和真正的字符串是有差异的,TEXT(“Mesh”)会变成L"Mesh"或者u"Mesh",而不是"LMesh"或者"uMesh"。
原型:
// 如果我们没有针对TEXT宏的特定于平台的定义,请立即进行定义。
#if !defined(TEXT) && !UE_BUILD_DOCS
#if PLATFORM_TCHAR_IS_CHAR16
#define TEXT_PASTE(x) u ## x
#else
#define TEXT_PASTE(x) L ## x
#endif
#define TEXT(x) TEXT_PASTE(x)
#endif
CSDN的代码着色有点问题,下面这张是VS里的截图
SetupAttachment函数
功能:将调用的类附加到指定组件下
原型:
/**
* 初始化注册组件时要附加的所需的Attach Parent和SocketName。
* 通常打算从其所属Actor的构造函数中调用,
* 并且在未注册组件时,应优先于AttachToComponent进行调用
* @param InParent 依附到的父组件
* @param InSocketName 要附加到父项上的可选套接字,默认NAME_None
*/
void SetupAttachment(USceneComponent* InParent, FName InSocketName = NAME_None);
ConstructorHelpers::FObjectFinder类
static ConstructorHelpers::FObjectFinder<UStaticMesh> CubeVisualAsset(TEXT("/Game/StarterContent/Shapes/Shape_Cube.Shape_Cube"));
if (CubeVisualAsset.Succeeded())
{
pVisualMesh->SetStaticMesh(CubeVisualAsset.Object);
pVisualMesh->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
}
顾名思义,这是一个帮助寻找资源的类。UStaticMesh表示要寻找的是静态网格体。添加这行代码会让FloatingActor拥有默认的静态网格体,当然也可以在属性菜单里修改。调用的构造函数有一个Unicode字符串参数,传递在磁盘上的资源路径。创建出来的变量名叫CubeVisualAsset,是一个static变量,这里有个重要的点,为什么要让这个变量声明称static?
static是静态局部变量:用于函数体内部修饰变量,这种变量的生存期长于该函数,变量存在于全局区,变量的生命周期是定义后直到程序结束运行,而不是这个函数返回时就释放。我们需要让这个Cube在游戏运行时一直显示,但这个变量又是在函数内部创建的,这时候就需要使用static修饰,来确保变量的内容一直存在。
原型:
struct COREUOBJECT_API ConstructorHelpers
{
public:
template<class T>
struct FObjectFinder : public FGCObject
{
T* Object; //这个结构不禁让我联想到string
...
};
...
};
接下来就是将创建的资源传递给pVIsualMesh,if语句来确保传递资源前资源已正确加载(假设磁盘上的资源因为意外情况移除,程序也能正常运行)
如果从VS开启调试
打开UE4的时候会报警告
SetStaticMesh函数
功能:
更改此实例使用的静态网格体
原型:
/** Change the StaticMesh used by this instance. */
UFUNCTION(BlueprintCallable, Category="Components|StaticMesh")
virtual bool SetStaticMesh(class UStaticMesh* NewMesh);
UFUNCTION宏修饰函数,BlueprintCallable表示BP脚本可调用这个函数,Category="Components|StaticMesh"表示函数在BP内的目录
SetRelativeLocation函数
功能:
设置静态网格体的相对位置
FORCEINLINE_DEBUGGABLE void USceneComponent::SetRelativeLocation(FVector NewLocation, bool bSweep=false, FHitResult* OutSweepHitResult=nullptr, ETeleportType Teleport = ETeleportType::None)
{
SetRelativeLocationAndRotation(NewLocation, RelativeRotationCache.RotatorToQuat(GetRelativeRotation()), bSweep, OutSweepHitResult, Teleport);
}
FVector类
是由浮点精度的分量(X,Y,Z)组成的3-D空间中的向量结构体
struct FVector
{
public:
/** Vector's X component. */
float X;
/** Vector's Y component. */
float Y;
/** Vector's Z component. */
float Z;
};
有如下静态变量可供调用
CORE_API const FVector FVector::ZeroVector(0.0f, 0.0f, 0.0f);
CORE_API const FVector FVector::OneVector(1.0f, 1.0f, 1.0f);
CORE_API const FVector FVector::UpVector(0.0f, 0.0f, 1.0f);
CORE_API const FVector FVector::DownVector(0.0f, 0.0f, -1.0f);
CORE_API const FVector FVector::ForwardVector(1.0f, 0.0f, 0.0f);
CORE_API const FVector FVector::BackwardVector(-1.0f, 0.0f, 0.0f);
CORE_API const FVector FVector::RightVector(0.0f, 1.0f, 0.0f);
CORE_API const FVector FVector::LeftVector(0.0f, -1.0f, 0.0f);
BeginPlay函数
作用:游戏开始时或生成时调用的代码
Super类
Super是UE进行层次结构升级的方式,具体的类型取决于父类,在这个项目中它的定义如下:
typedef TArray<T, TInlineAllocator> Super;
Tick函数
作用:每帧执行一次
参数DeltaTime,跟上一帧的间隔时间
程序源代码与思路:
// 逐帧调用
void AFloatingActor::Tick(float DeltaTime)
{
//调用父类的Tick函数
Super::Tick(DeltaTime);
//创建一个FVector变量存储当前位置信息
FVector NewLocation = GetActorLocation();
//创建一个FRotator变量存储当前旋转信息
FRotator NewRotation = GetActorRotation();
//创建一个单精浮点存储程序运行时间
float RunningTime = GetGameTimeSinceCreation();
//变化的比例=sin(当前时间)-sin(上一帧时间)
float DeltaHeight = (FMath::Sin(RunningTime + DeltaTime) - FMath::Sin(RunningTime));
//新的Z轴坐标=变化的比例*范围系数
NewLocation.Z += DeltaHeight * 20.0f; //Scale our height by a factor of 20
//变化的角度=变化时间*系数
float DeltaRotation = DeltaTime * 20.0f; //Rotate by 20 degrees per second
NewRotation.Yaw += DeltaRotation;
//设置Actor的坐标和角度
SetActorLocationAndRotation(NewLocation, NewRotation);
}
使物体在指定区间来回移动的巧妙办法:
利用sin函数的特性 y∈[-1,1],而且是连续的函数,可以计算出每次的距离,而不受帧数的影响
FRotator类
struct FRotator
{
public:
/** Rotation around the right axis (around Y axis), Looking up and down (0=Straight Ahead, +Up, -Down) */
float Pitch;
/** Rotation around the up axis (around Z axis), Running in circles 0=East, +North, -South. */
float Yaw;
/** Rotation around the forward axis (around X axis), Tilting your head, 0=Straight, +Clockwise, -CCW. */
float Roll;
};
分别对应绕Y轴,Z轴,X轴旋转(PS:我真怀疑写UE4的程序员以前是开飞机的)
按照正视方向为x,建立三维坐标系,想象自己是一架飞机
轴 | 旋转 | 说明 | 正方向 |
X | Roll | 滚筒 | 右倾(飞机向右滚动) |
Y | Pitch | 俯仰 | 仰起(机头向上) |
Z | Yaw | 偏航 | 向右(飞机向右转动) |
SetActorLocationAndRotation函数
功能:
设置Actor位置和旋转
原型:
bool SetActorLocationAndRotation(FVector NewLocation, FRotator NewRotation, bool bSweep=false, FHitResult* OutSweepHitResult=nullptr, ETeleportType Teleport = ETeleportType::None);
和之前的SetActorRelativeLocation类似,不过这个设置的是绝对坐标和绝对旋转