前言
参照官方教程,记录一些值得注意的点。
Actor
可以被放到关卡中的对象。
联网时可以被复制的基本类型。
生成Actor的方法
1 | AActor* UWorld::SpawnActor |
其中,UClass
大概是
1 | UClass class = XXXClass::StaticClass(); |
更简便的方法是通过模板类实现的。下面这段代码的第一个参数是Owner
,第二个是Instigator
。
1 | MyHUD = SpawnActor<AHUD>(this, Instigator); |
销毁方法
Actor
继承自UObject
,使用Destroy销毁即可。
反射系统
GENERATE_BODY()
实际上是插入一些必要的模板- [ ] 具体插入了啥,还需要有时间的时候再研究下
- [ ] 反射头文件,也不知道插入了啥,查看一些博客研究下
常用说明符
UCLASS
Blueprintable
该类可以由蓝图扩展NotBlueprintable
与上一条相反Abstract
防止该类被实例化
构造函数生成CDO
构造函数中初始化的变量值,会形成类默认对象(CDO)。
UFUNCTION
说几个值得一提的函数说明符和元数据。
蓝图相关
BlueprintImplementablEvent
此函数可以在蓝图中实现BlueprintNativeEvent
有原生的默认实现,但是可以在蓝图中覆盖实现
元数据:
DisplayName="Blueprint Node Name"
在蓝图中显示的名字HidePin="Parameter"
在蓝图中隐藏引脚,只能隐藏一个HideSelfPin
在蓝图中隐藏self引脚ShortToolTip="Short tooltip"
提示ToolTip="Hand-written tooltip"
提示
参数说明符
Out
传入引用给函数,函数可以对其进行更改
Tips
C++中定义了一个函数,传入了一个引用类型。如果多加点什么的话,到了蓝图层会发现参数变成了输出。为了避免这种情况,需要加
UPARAM(ref)
,比如
1
2
3 > UFUNCTION(BlueprintCallable)
> void ModifyVector(UPARAM(ref) FVector& Vector);
>
用以实现工具
- ‘CallInEditor’ 可以在details中调用,一般拿来做工具啥的,挺有意思的
Exec
此函数可从游戏内控制台执行。仅在特定类中声明时,Exec命令才有效。
网络
ServiceRequest
此函数为RPC(远程过程调用)服务请求。这意味着 NetMulticast 和 Reliable。ServiceResponse
此函数为RPC服务响应。这意味着 NetMulticast 和 Reliable。Server
仅在服务器上运行Unreliable
此函数将通过网络复制,但是可能会因带宽限制或网络错误而失败
调试
DevelopmentOnly
只有在开发时会被执行
Property
位掩码
参考Property。
可以在Details面板中显示多选项。
1 | UENUM(Meta = (Bitflags)) |
Event相关
对于DYNAMIC_MULTICAST_DELEGATE
类型的Event,可以定义BlueprintCallable
,从蓝图发送事件。
对于delegate来说,如果定义为BlueprintAssignable
,可以在蓝图绑定事件。
USTRUCT
参考结构体。
属于反射系统,但是不属于UObject
生态圈,无法垃圾回收系统处理。但是,快。
支持UPROPERTY
,但是不支持UFUNCTION
(虽然原生c++ struct支持定义函数)。
蓝图split的支持
如果想要让结构体在蓝图中可以执行split
,那么必须定义为Blueprintable
。
蓝图中break和make
- make:
HasNativeBreak="Module.Class.Function"
- break:
HasNativeMake="Module.Class.Function"
UINTERFACE
UE4里面的接口比较古怪。要定义一个接口,总共要定义两个类。其中,第一个类只是给反射系统用,是个空类,必须以U开头并继承于UInterface
。第二个类才是真正的接口所在,名字以I
开头,其余部分与前者相同,且不需要继承于任何类。
1 |
|
如果接口既想要被蓝图实现,又想在C++先有个默认实现,那么需要做几件事:
- 接口中的函数需要被定义为
BlueprintCallable, BlueprintNativeEvent
- C++实现接口,要实现接口的方法,但是不是直接实现,而是要在方法名后面加上
_Implementation
猜测当UFUCTION
定义了BlueprintNativeEvent
之后,UE4发现某某类实现了该接口,那么GENERATED_BODY()
里会做两件事:
- 接口类中会因为
GENERATED_BODY()
出现一个xxx_Implementation()
的函数 - 实现类再去override这个函数,刚好
举个完整的例子:
1 | UINTERFACE(MinimalAPI, Blueprintable) |
判断类是否实现了接口
1 | bool bIsImplemented = OriginalObject->GetClass()->ImplementsInterface(UReactToTriggerInterface::StaticClass()); // bIsImplemented will be true if OriginalObject implements UReactToTriggerInterface. |
UObject/Actor迭代器
构造完了就可以用。
1 | TObjectIterator<UObject> it() |
拿到value的方法:
1 | UObject *value = *it; |
垃圾回收
狭义来说,只针对UObject。
广义上来说,还可以通过:
- 定义类继承自
FGCObject
并重写必要的方法AddReferencedObjects()
- 智能指针
USTRUCT
是不会被自动回收的,如果真的希望USTRUCT
被内存管理,那么使用智能指针便是。
回收时机
Destroy
只是做了个标志位,真正回收的时间还要等系统决定。
标记了Destroy
和真正被回收之间的这个状态,称为一条腿踏进了鬼门关,简称等死,可以用IsPendingKill()
函数来判定。
命名规范
不照着命名规范走,有可能就直接编译错误了。
- 派生自 Actor 的类带有 A 前缀,如 AController。
- 派生自 Object 的类带有 U 前缀,如 UComponent。
- Enums 的前缀是 E,如 EFortificationType。
- Interface 的前缀通常是 I,如 IAbilitySystemInterface。
- Template 的前缀是 T,如 TArray。
- 派生自 SWidget 的类(Slate UI)带有前缀 S,如 SButton。
- 其他类的前缀为字母F ,如 FVector。
数字类型
- int8/uint8
- int16/uint16
- int32/uint32
- int64/uint64
- float (32 bit)
- double (64 bit)
字符串处理
FString
是一个可变字符串。
创建使用TEXT
。
多说一句
TEXT()
返回的其实是WCHAR
数组,只不过被隐式转换了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 > FORCEINLINE FString(const CharType* Src)
> {
> if (Src && *Src)
> {
> int32 SrcLen = TCString<CharType>::Strlen(Src) + 1;
> int32 DestLen = FPlatformString::ConvertedLength<TCHAR>(Src, SrcLen);
> Data.AddUninitialized(DestLen);
>
> FPlatformString::Convert(Data.GetData(), DestLen, Src, SrcLen);
> }
> }
> '''
转换成数字:
```cpp
int32 TestInt = FCString::Atoi(*MyFString);
float TestFloat = FCString::Atof(*MyFString);
数字转换成string:
1 | FString TestString = FString::FromInt(MyInt); |
FText
跟FString差不多,主要是多了个本地化的功能。
创建:
1 | FText MyText = NSLOCTEXT("Game UI", "Health Warning Message", "Low Health!") |
FName
一个字「快」。
其实也是字符串。
一般用作常出现的字符串,在内存中只出现一份拷贝。不区分大小写,也不可变,无法被操作。
可以提高性能。用作字符串匹配时(例如依名寻物时),可以直接用索引值来匹配,而不需要每个字符分别对比。
TCHAR
字符串的真实载体。
一般啥时候用到呢?比如FString::Printf(TEXT("%S"))
中的%s
,其实需要的就是一个TCHAR
数组。
FString
重写过其*
操作符,用以返回其指向的WCHAR
数组的地址。
1 | // File: UnrealString.h |
新建
1 | const TCHAR* message = TEXT("Hello world."); |
TEXT
的本质是给字符串前面加了个L
,也就是说前面的处理后,会变成L"Hello world."
。
TCHAR
的处理
UE4在FChar
类中封装了一些静态函数,可以处理WCHAR
数组。
容器
TArray
获取array长度:
1 | int32 ArraySize = Array.Num(); |
直接取值(说明重写了[]
):
1 | int32 Index = 0; |
添加新元素
1 | ActorArray.Add(NewActor) |
如果数组中不存在新元素,那么添加新元素
1 | ActorArray.AddUnique(NewActor); |
移除元素:
1 | ActorArray.Remove(NewActor); |
TArray是支持垃圾回收的,前提是元素必须都继承于UObject
。
TMap
字典。
TSet
集合。
容器迭代器
创建迭代器
1 | auto Iterator = xxxSet.CreateIterator(); |
获取迭代器索引
1 | int32 Index = Iterator.GetIndex(); |
复位到第一个元素
1 | Iterator.Reset(); |
for-each
除了map以外都是普通的for-each,无须赘述。
map的话for-each返回的是一个键值对,用的时候可以这么用:
1 | for (auto& KeyValuePair : XXXMap) |
自定义散列函数
Set和Map的键是需要能够转换成哈希值的。
UE4提供的能够作为键的类型,都是实现了GetTypeHash()
函数的,能够返回唯一的一个哈希值。
如果我们要实现自己的类,并且让它能够作为键,那么需要实现:
GetTypeHash()
operator==
1 | class FMyClass |