我记性不怎么好。在学习UE4的碰到一些新的知识,记录在这里,算是学习笔记。都是写基础的,但是频繁使用的语句
在这里再发个牢骚就是。UE4比起U3D ,那简直规矩太多了,他好像什么都想给客户弄好,但是最后弄的臃肿繁琐。记不住的就很容易犯错。这难道就是U3D 能火的原因之一。就像中文和英文,中文每个字差不多就代表一种具象或者抽象的事物,合起来有可以代表其他各种事物,但是这样下来就有好多好多字得记,真佩服古代造字人的智慧,比起智慧中文完胜英文。但是比起易学,易用!有可数数量的字母,相互组合,就能拼接成各种单词。就像给了你基本的框架,你用这些搭建就好了。这样在 学习还有传播 方便 占据 优势。所以,UE4像不像中文?博大而精神,高深而智慧? U3D 像不像 英文? 极简归一,实用易懂?
类命名前缀
虚幻引擎为您提供在构建过程中生成代码的工具。这些工具拥有一些类命名规则。如命名与规则不符,将触发警告或错误。下方的类前缀列表说明了命名的规则。
- 派生自 Actor 的类前缀为
A
,如 AController。 - 派生自 对象 的类前缀为
U
,如 UComponent。 - 枚举 的前缀为
E
,如 EFortificationType。 - 接口 类的前缀通常为
I
,如 IAbilitySystemInterface。 - 模板 类的前缀为
T
,如 TArray。 - 派生自 SWidget(Slate UI)的类前缀为
S
,如 SButton。 - 其余类的前缀均为 字母
F
,如 FVector。
数据类型
数字类型
因为不同平台基础类型的尺寸不同,如 short、int 和 long,UE4 提供了以下类型,可用作替代品:int8/uint8 :8 位带符号/不带符号 整数
int16/uint16 :16 位带符号/不带符号 整数
int32/uint32 :32 位带符号/不带符号 整数
int64/uint64 :64 位带符号/不带符号整数
标准 浮点 (32-bit) 和 双倍(64-bit)类型也支持浮点数。虚幻引擎拥有一个模板 TNumericLimits,用于找到数值类型支持的最小和最大范围。如需了解详情,请查阅此 链接 。
字符串
FString
FString 是一个可变字符串,类似于 std::string。FString 拥有许多方法,便于简单地使用字符串。使用 TEXT() 宏可新建一个 FString:
FString MyStr = TEXT("Hello, Unreal 4!")
FText
FText 与 FString 相似,但用于本地化文本。使用 NSLOCTEXT 宏可新建一个 FText。此宏拥有默认语言的命名空间、键和一个数值。
FText MyText = NSLOCTEXT("Game UI", "Health Warning Message", "Low Health!")
也可使用 LOCTEXT 宏,只需要在每个文件上定义一次命名空间。确保在文件底层取消它的定义
// 在 GameUI.cpp 中 #define LOCTEXT_NAMESPACE "Game UI" //... FText MyText = LOCTEXT("Health Warning Message", "Low Health!") //... #undef LOCTEXT_NAMESPACE // 文件末端
FName
FName 将经常反复出现的字符串保存为辨识符,以便在对比时节约内存和 CPU 时间。FName 不会在引用完整字符串的每个对象间对其进行多次保存,而是使用一个映射到给定字符串的较小存储空间 索引。这会单次保存字符串内容,在字符串用于多个对象之间时节约内存。检查 NameA.Index 是否等于 NameB.Index 可对两个字符串进行快速对比,避免对字符串中每个字符进行相等性检查。TCHAR
TCHARs 用于存储不受正在使用的字符集约束的字符。平台不同,它们也可能存在不同。UE4 字符串在后台使用 TCHAR 阵列将数据保存在 UTF-16 编码中。使用返回 TCHAR 的重载解引用运算符可以访问原始数据。
- 容器
类 | 说明 | 对应U3D 的 |
---|---|---|
TArray | 常用的主要容器。作用与std::vector 类似 |
List |
TMap | 键值对的合集,与 std::map 相似 | Dictionary |
TSet | 保存唯一值的合集,与 std::set 相似 | HashSet |
虚幻C++部分代码解释
C++和 蓝图
虚幻提供两种创建游戏:C++ 和蓝图。程序员可以通过C++ 来添加基础游戏性体统。设计师(美术人员)即可在这个系统上创建关卡或者游戏的自定义游戏性。简单来说,程序员编写的C++是一个游戏的主要关键功能的构建方。而设计师所掌握的蓝图,则起到游戏功能的辅助作用。类向导
而在创建一个UE4C++对象的时候。类中的会有一下结构。
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
public:
// 设置该 actor 属性的默认值
AMyActor();
// 游戏开始时或生成时调用
virtual void BeginPlay() override;
// 每帧调用
virtual void Tick( float DeltaSeconds ) override;
};
字段 | 作用 |
---|---|
BeginPlay() | 和U3D 的 Start()方法一样。在进入游戏状态的时候运行 |
Tick() | 没帧调用和U3D 的 Update() 一样。如果不需要此功能。必须在构造函数中说明。如下代码 |
AMyActor::AMyActor()
{
// 将此 actor 设为每帧调用 Tick()。不需要时可将bCanEverTick设置为false以关闭此功能,来提高性能。
PrimaryActorTick.bCanEverTick = true;
}
- 使属性出现在编辑器中
类创建好之后。现在可以创建一些属(设计师可是虚幻编辑器中设置这些属性)。可以使用 特殊宏UPROPERTY()
来轻松的将属性公开至编辑器中可视。比如:UPROPERTY(EditAnywhere)
宏即可。
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
UPROPERTY(EditAnywhere)
int32 TotalDamage;
...
};
之后。你就可以在编辑器中看到变量TotalDamage对应的数值。并且可以对数值进行调节。
除此之外还可以加入其他的属性。
属性 | 作用 | 用法 |
---|---|---|
EditAnywhere | 使这个变量出现在编辑器中可编辑 | EditAnywhere |
Category | 使此属性与相关属性出现在一个部分 | Category=”Damage” |
BlueprintReadWrite | 使属性为可读取和可编写状态 | BlueprintReadWrite |
BlueprintReadOnly | 使属性为可只读取状态 | BlueprintReadOnly |
VisibleAnywhere | 意味着属性在虚幻编辑器中为可见状态,但是不可编辑 | VisibleAnywhere |
Transient | 意味着无法从硬盘对其进行保存或加载 | Transient |
更多属性修饰符 |
除此之外,还可以通过宏定义来是一些属性在其关联属性被更改的时候也做出相应的改变:
void AMyActor::PostInitProperties()
{
Super::PostInitProperties();
CalculateValues();
}
void AMyActor::CalculateValues()
{
DamagePerSecond = TotalDamage / DamageTimeInSeconds;
}
//通过WITH_EDITOR定义使这个函数的目标对象在编辑器中被更改时引擎将通知和运行这个函数。
#if WITH_EDITOR
void AMyActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
CalculateValues();
Super::PostEditChangeProperty(PropertyChangedEvent);
}
#endif
- 蓝图调用C++中的函数
上面说了如果对蓝图公开属性。那函数呢?对呀函数我们又得需要另一个宏来定义。
UFUNCTION(BlueprintCallable, Category="Damage")
void CalculateValues();
如上。 可以通过 UFUNCTION
宏来把C++函数对反射系统公开。 而 BlueprintCallable将其对蓝图虚拟机公开。这样在在蓝图中,右键点击快捷键菜单就可以使用这个函数。
- AActor简单介绍
AActor 这个 类,被作为游戏场景中一个基本的对象。所以可以放在关卡场景中的对象都可延展自这个类,比如:AStaticMeshActor、ACameraActor 和 APointLight 等等 actor。
这个类派生在 UObject。
AActor 的生命周期为:
函数 | Are |
---|---|
BeginPlay() | 和U3D 的 Start()方法一样。在进入游戏状态的时候运行 |
Tick() | 每帧调用和U3D 的 Update() 一样。如果不需要此功能。必须在构造函数中说明。 |
EndPlay | 对象离开游戏进程时调用 |
- 虚幻反射系统
再用UE4 C++ 编程的时候。同会看到好多非C++ 语句的字段。例如,他会在一个类上加上UCLASS()
等等。这些都是UE4使用其自身的反射实现,来启动动态功能,比如,垃圾回收,序列化,网络复制和蓝图/C++通信。这些功能你可以根据需求选择的加入,只要为相应的类型添加正确的标记就可生成反射数据。 一下是一些基础标记:
标记 | 说明 |
---|---|
UCLASS() | 告知虚幻引擎生成类的反射数据。类必须派生自 UObject。 |
USTRUCT() | 告知虚幻引擎生成结构体的反射数据。 |
GENERATED_BODY() | UE4 使用它替代为类型生成的所有必需样板文件代码。就是说这个类不直接使用父类的声明。但是,你必须得去实现,自己去声明,否则就会报错。更多信息 |
UPROPERTY() | 使 UCLASS 或 USTRUCT 的成员变量可用作 UPROPERTY。UPROPERTY 用途广泛。它允许变量被复制、被序列化,并可从蓝图中进行访问。垃圾回收器还使用它们来追踪对 UObject 的引用数。 |
UFUNCTION() | 使 UCLASS 或 USTRUCT 的类方法可用作 UFUNCTION。UFUNCTION 允许类方法从蓝图中被调用,并在其他资源中用作 RPC |
eg:
#include "MyObject.generated.h" //这个头文件包含虚幻引擎所有反射数据。必须在声明类型的头文件中将此文件作为最后的 include 包含。
UCLASS(Blueprintable)
class UMyObject : public UObject
{
GENERATED_BODY()
public:
MyUObject();
UPROPERTY(BlueprintReadOnly, EditAnywhere)
float ExampleProperty;
UFUNCTION(BlueprintCallable)
void ExampleFunction();
};
您还会注意到,可以在标记上添加额外的说明符。此处已添加部分常用说明符用于展示。通过说明符可对类型拥有的特定行为进行说明。
Blueprintable - 此类可由蓝图延展。
BlueprintReadOnly - 此属性只可从蓝图读取,不可写入。
Category - 定义此属性出现在编辑器 Details 视图下的部分。用于组织。
BlueprintCallable - 可从蓝图调用此函数。
说明符太多,无法一一列举于此,以下链接可用作参考:
- 对象/Actor 迭代器
就是 对应 U3D 的 for 循环。先查找关卡中所以的派生自UObject的 类的实例。然后遍历他们。
// 将找到当前所有的 UObjects(此处可以写入你自定义的类的类名) 实例
for (TObjectIterator<UObject> It; It; ++It)
{
UObject* CurrentObject = *It;
UE_LOG(LogTemp, Log, TEXT("Found UObject named:%s"), *CurrentObject.GetName());
}
但是这个在PIE(Play In Editor)
中使用的话可能产生意外后果。 因为编辑器已被加载,除编辑器正在使用的对象外,对象迭代器还将返回为游戏世界实例创建的全部 UObject。
而 Actor 迭代器 呢还和 对象迭代器的使用不一样,但套路就那么多。
APlayerController* MyPC = GetMyPlayerControllerFromSomewhere();
UWorld* World = MyPC->GetWorld();
// 和对象迭代器一样,您可提供一个特定类,只获取为该类的对象
// 或从该类派生的对象
for (TActorIterator<AEnemy> It(World); It; ++It)
{
// ...
}
这个迭代只能用于派生自 AActor 对象。
创建 actor 迭代器时,需要为其赋予一个指向 UWorld 实例的指针。许多 UObject 类(如 APlayerController)会提供 GetWorld 方法,助您一臂之力。如不确定,可在 UObject 上检查 ImplementsGetWorld 方法,确认其是否应用 GetWorld 方法。
- Actors 和垃圾回收
Actor 通常不会被垃圾自动回收。他在生成之后,必须使用Destroy() 来删除。而且他们不会立刻删除,会在垃圾回收阶段被清理。
常见情况下 actors 带有 Uobject 属性。
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY()
MyGCType* SafeObject;
MyGCType* DoomedObject;
AMyActor(const FObjectInitializer& ObjectInitializer)
:Super(ObjectInitializer)
{
// 创建两个对象。他们自动成为根集的一部分。
// 这个指定了UPROPERTY。SafeObjet将不会被垃圾回收,因为他从根集对象出到达。
SafeObject = NewObject<MyGCType>();
//DoomedObject 因为未将标记为 UPROPERTY,因此回收器并不知道其正在被引用,而会将它逐渐销毁。
DoomedObject = NewObject<MyGCType>();
}
};
void SpawnMyActor(UWorld* World, FVector Location, FRotator Rotation)
{
World->SpawnActor<AMyActor>(Location, Rotation);
}
由上面代码。可知,UObject 被垃圾回收时,对其的所有 UPROPERTY 引用将被设为 nullptr。那么 上面的 属性 SafeObject 将会为 nullptr。 所以为了安全的检测一个对象是否被垃圾回收。我们应该在使用的时候进行判断:
if (MyActor->SafeObject != nullptr)
{
// 使用 SafeObject
}