编译链接
源码编译
1
| git clone -b 4.27.2-release --depth=1 git@github.com:EpicGames/UnrealEngine.git
|
https://zhuanlan.zhihu.com/p/107516361
https://github.com/EpicGames/UnrealEngine
UE模块、类相关
- [YourModuleName]_API宏的作用?
放函数声明前用于暴露(导出)该函数
放类声明前用于暴露(导出)该类的所有内容
特别注意:_API前面的内容一定要大写!否则会编译不通过
MinimalAPI说明符的作用?
暴露(导出)该类的类型信息让其它模块可以
- Cast到该类型
- 继承该类型 (该类型的所有定义在.cpp文件的虚函数都需要导出)
- 使用内联函数
编辑器
Content Browser
https://docs.unrealengine.com/5.0/zh-CN/content-browser-interface-in-unreal-engine/
可以对一个文件夹右键 add to favorites 功能,可以显示 favorites:

关卡
新建
File -> New Level,头两个是 World Partition 的关卡。
UI

勾掉 auto wrap text,就可以控制自动换行的文本大小(有wrap控制的情况下?)。
资产
Texture
Texture Stream 与 LOD Bias
https://polycount.com/discussion/200525/ue4-texture-settings-help-mip-gen-settings-lod-bias-power-of-two-mode
ue 的贴图可以勾选 Never Stream,此时会取消纹理流送;否则默认开启纹理流送。
关闭纹理流送,就可以保证每次都加载贴图原生大小,保证最高分辨率,但是可能有性能影响,一般ui可以开。
纹理流送首先加载最小的mipmap,然后逐渐增加;如果 LOD Bias 为0,则最后加载到原贴图最大版本为止;为1则为第一级。例如 2048 的贴图,LOD Bias 为1,则纹理流送到 1024 为止。
Texture Group
纹理组,可以一起设置贴图的各种设置:默认压缩、mip生成、lod bias等等。
C++
ue反射相关
入门:
https://zhuanlan.zhihu.com/p/400473355
https://www.cnblogs.com/ghl_carmack/p/5701862.html
https://ikrima.dev/ue4guide/engine-programming/uobject-reflection/uobject-reflection/
Class Default Object, 简称CDO
UCLASS宏为UObject提供了一个描述其基于虚幻的类型的UCLASS的引用。每个UCLASS都维护一个名为“类默认对象”的对象,简称CDO。
CDO本质上是一个默认的“模板”对象,由类构造函数生成,之后未修改。可以为给定的Object实例检索UCLASS和CDO,尽管它们通常应该被认为是只读的。可以使用GetClass()函数随时访问Object实例的UCLASS。
CDO是在引擎初始化时创建的,当引擎为每个类生成UClass对象时。每个UClass的实例都是在引擎初始化期间创建的,并被分配为该UClass的CDO。并且包含在反射系统中,如在编辑器可以操作类蓝图。Obj.cpp可以看到引擎CDO初始化创建。
因此一个 UClass* 是无法直接Cast转型成其他类型的,因为没有实例化;我们可以用 TSubclassOf 先转为其他类型,之后获取 CDO 得到默认对象(GetDefaultObject<xxx>
)再调用它的相关函数之类的。
获取UObject属性值
https://zhuanlan.zhihu.com/p/61042237
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // 获取属性名为PropertyName的属性的值
UObject* Object = ...;
FName PropertyName = ...;
FProperty* Property = Object->GetClass()->FindPropertyByName(PropertyName);
if(Property)
{
void* ValuePtr = Property->ContainerPtrToValuePtr<void>(Object);
if(ValuePtr)
{
FString Value;
Property->ExportTextItem(Value, ValuePtr, nullptr, nullptr, PPF_None);
UE_LOG(LogTemp, Log, TEXT("Property %s value is %s"), *PropertyName.ToString(), *Value);
}
}
|
标记宏
可以使用UENUM()、UCLASS()、USTRUCT()、UFUNCTION()、UPROPERTY()来标记不同的类型和成员变量,标记也可以包含额外的描述关键字。
每个描述的关键字(例如EditAnywhere或BlueprintCallable)都在ObjectMacros.h中有一个镜像,有一个简短的描述。当不知道一个关键字的意思时,可以去ObjectMacros.h中去查看
更方便地是直接看文档:
https://docs.unrealengine.com/5.3/zh-CN/ufunctions-in-unreal-engine/
类说明符 UCLASS
https://docs.unrealengine.com/5.3/zh-CN/class-specifiers/
蓝图 UPARAM(ref)
https://docs.unrealengine.com/5.3/zh-CN/exposing-gameplay-elements-to-blueprints-visual-scripting-in-unreal-engine/
UPROPERTY
https://docs.unrealengine.com/4.27/en-US/ProgrammingAndScripting/GameplayArchitecture/Properties/Specifiers/
WITH_EDITORONLY_DATA 宏
用 WITH_EDITOR 包裹反射属性会在非 Editor 时报错,这是因为 UHT 不会检测宏条件,导致这部分属性也被反射生成了;此时要用 WITH_EDITORONLY_DATA 宏,
UE 对这个宏做了特殊处理。
https://ue5wiki.com/wiki/41080/
gc
参考:
https://unrealcommunity.wiki/memory-management-6rlf3v4i
https://dev.epicgames.com/documentation/zh-cn/unreal-engine/unreal-object-handling-in-unreal-engine
继承了 UObject,并且被标记了 UPROERTY 的指针对象 Obj ,会被 GC 追踪。gc完了之后这个指针就变成nullptr了。
1
2
| // 阻塞式gc
CollectGarbage(RF_NoFlags);
|
指针和引用
参考:
https://zhuanlan.zhihu.com/p/604213414
类指针
UClass* 和 TSubClassOf
https://dev.epicgames.com/documentation/zh-cn/unreal-engine/typed-object-pointer-properties-in-unreal-engine
软引用和硬引用
硬引用在资产加载时的逻辑:当A资产持有B资产的硬引用时,A资产被加载到内存中时,B资产也会被加载到内存中。
软引用在资产加载时的逻辑:当A资产持有B资产的软引用是,A资产被加载到内存中时,B资产不会被加载。
软引用本质上存的是资产的路径。
软对象引用:TSoftObjectPtr<T> SoftObjRef;
软类引用:TSoftClassPtr<T> SoftClassRef;
因此软引用主要是用来做异步加载的,参考:
https://docs.unrealengine.com/5.3/zh-CN/asynchronous-asset-loading-in-unreal-engine/
https://docs.unrealengine.com/5.3/zh-CN/referencing-assets-in-unreal-engine/
https://zhuanlan.zhihu.com/p/351106187
源码分析
看类:FSoftObjectPath
有成员变量:

看注释也知道他是指向一个 Level 中的 Object 的路径,因此持有软引用的时候加载并不会加载资产,可以用 IsPending()
方法来判断是否可以访问。
继承关系:TSoftObjectPtr 只是对 FSoftObjectPtr 的一个 Wrapper,FSoftObjectPtr 继承自 TPersistentObjectPtr<FSoftObjectPath>
智能指针
https://dev.epicgames.com/documentation/zh-cn/unreal-engine/smart-pointers-in-unreal-engine
在结构体 A 中使用 UPROPERTY 标记了一个 UObject 类的指针,但是这个 A 本身在 class B 中却没有被标记 UPROPERTY,此时引用链断开。建议使用 TWeakObjectPtr
字符串处理
FString、FName、FText互转
https://docs.unrealengine.com/5.3/zh-CN/string-handling-in-unreal-engine/
From | To | Example |
---|
FName | FString | TestHUDString = TestHUDName.ToString(); |
FName | FText | TestHUDText = FText::FromName(TestHUDName); |
FString | FName | TestHUDName = FName(*TestHUDString); |
FString | FText | TestHUDText = FText::FromString(TestHUDString); |
FText | FString | TestHUDString = TestHUDText.ToString(); |
FText | FName | There is no direct conversion from FText to FName. Instead, convert to FString and then to FName. |
FString | int32 | int32 TestInt = FCString::Atoi(*MyFString); |
FString | float | float TestFloat = FCString::Atof(*MyFString); |
int32 | FString | FString TestString = FString::FromInt(MyInt); |
float | FString | FString TestString = FString::SanitizeFloat(MyFloat); |
Enum 转 FString
1
2
3
4
5
| // 带上 Enum 前缀,返回 YourEnum::EnumValue
UEnum::GetValueAsString(YourEnum::EnumValue)
// 不带前缀,返回 EnumValue
UEnum::GetDisplayValueAsText(YourEnum::EnumValue)
|
https://forums.unrealengine.com/t/conversion-of-enum-to-string/337869/26
常见操作
https://zhuanlan.zhihu.com/p/163587790
中文乱码
使用 UTF8_TO_TCHAR:
1
| TextBlock->SetText(FText::FromString(UTF8_TO_TCHAR("否")));
|
本地化
本地化主要是根据LOCTEXT
或者NSLOCTEXT
将这两个里面的LOCTEXT(“key”, “value”)
, 找到这个key,然后根据不同语言,将我们的Value替换掉
添加namespace域,一方面为了过滤,一方面避免了key冲突。
LOCTEXT用法
前后必须要加LOCTEXT_NAMESPACE 和LOCTEXT_NAMESPACE定义一个作用域,
代表下面的字符串都在这里作用域里头,引擎好通过这个域去搜索所有的LOCTEXT
1
2
3
4
5
| #define LOCTEXT_NAMESPACE "SlateMain"
.Text(LOCTEXT("SMainSlate_ButtonSlateAnimation_Text3333", "测试Slate动画"))
#undef LOCTEXT_NAMESPACE
|
NSLOCTEXT用法
不用加LOCTEXT_NAMESPACE 和LOCTEXT_NAMESPACE。但是三个参数,第一个也是作用域
1
| .Text(LOCTEXT("SlateMain", "SMainSlate_ButtonSlateAnimation_Text3333", "测试Slate动画"))
|
UE5在编辑器的 Tools->Localization Dashboard 中配置:

参考:
https://blog.csdn.net/u011718663/article/details/117785611
https://forums.unrealengine.com/t/define-loctext-namespace-something/438086
委托(Delegate)
https://zhuanlan.zhihu.com/p/126630820
https://zhuanlan.zhihu.com/p/460092901
委托分 单播/多播(Multicast, 能否绑定多个函数)、静态/动态(Dynamic, 支持序列化、可以给蓝图用)
其中一个 wbp 按钮 UButton 的 OnClicked 事件属于 DYNAMIC_MULTICAST_DELEGATE(DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnButtonClickedEvent)
),因此不能绑定 lambda 函数(动态多播只能绑定某个 object 上的 ufunction);
但是我们可以拿到他的 slate,再对 slate 绑定 lambda,参考:
https://benui.ca/unreal/using-same-function-for-many-ubuttons/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| void UBUIUWTestWindow::NativeConstruct()
{
Super::NativeConstruct();
// Imagine we have an array of buttons already populated
for (int32 i = 0; i < Buttons.Num(); ++i)
{
UButton* Button = Buttons[i];
SButton* ButtonWidget = (SButton*)&(Button->TakeWidget().Get());
ButtonWidget->SetOnClicked(FOnClicked::CreateLambda([this, i]()
{
OnClicked(i);
return FReply::Handled();
}));
}
}
void UBUIUWTestWindow::OnClicked(int32 Index)
{
// Find the button and do whatever we want
// The argument passed in here doesn't have to be an int32
}
|
路径
各种资源的路径格式:
1
2
3
4
5
6
7
8
9
| ObjectPath: /Engine/EngineDamageTypes/DmgTypeBP_Environmental.DmgTypeBP_Environmental
PackageName: /Engine/EngineDamageTypes/DmgTypeBP_Environmental
ObjectName: DmgTypeBP_Environmental
Filename: ../../../Engine/Content/EngineDamageTypes/DmgTypeBP_Environmental
PackagePath: /Engine/EngineDamageTypes
ShortName: DmgTypeBP_Environmental
AssetName: DmgTypeBP_Environmental
AssetPackageExtension: .uasset
MapPackageExtension: .umap
|
路径转换函数:
1
2
3
4
5
6
7
8
9
10
| FString ObjectPath = TEXT("/Engine/EngineDamageTypes/DmgTypeBP_Environmental.DmgTypeBP_Environmental");
FString PackageName = FPackageName::ObjectPathToPackageName(ObjectPath);
FString ObjectName = FPackageName::ObjectPathToObjectName(ObjectPath);
FString Filename = FPackageName::LongPackageNameToFilename(PackageName);
FString PackageName2 = FPackageName::FilenameToLongPackageName(Filename);
FString PackagePath = FPackageName::GetLongPackagePath(PackageName);
FString ShortName = FPackageName::GetLongPackageAssetName(PackageName);
FString AssetName = FPackageName::GetShortName(PackageName);
FString AssetPackageExtension = FPackageName::GetAssetPackageExtension();
FString MapPackageExtension = FPackageName::GetMapPackageExtension();
|
想得到 ObjectPath,我一直没发现好办法,但是从 PackageName 到 ObjectPath 可以先得到 FAssetData 过度:
1
2
3
4
| FAssetRegistryModule& AssetRegistryModule = FModuleManager::GetModuleChecked<FAssetRegistryModule>("AssetRegistry");
TArray<FAssetData> AssetDataArray;
AssetRegistryModule.Get().GetAssetsByPackageName(*AssetPackageName, AssetDataArray);
|
之后再加载可以:
1
2
3
4
5
6
7
8
| // 1
UObject* ObjectPtr = AssetDataArray[0].GetAsset();
// 2
UObject* ObjectPtr = AssetDataArray[0].GetSoftObjectPath().TryLoad();
// 3
UWorld* World = LoadObject<UWorld>(nullptr, *AssetDataArray[0].GetObjectPathString());
|
从 UObject 到各种路径:
1
2
3
4
5
6
7
8
9
10
| // PackageName
FString a = Obj->GetPackage()->GetFName().ToString();
FString b;
Obj->GetPackage()->GetName(c);
Obj->GetPackage()->GetLoadedPath().GetPackageFName()
// AssetName
FName c = Obj->GetFName();
|
从 PackageName 到绝对路径:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| // 参考 Engine\Plugins\Editor\AssetSearch\Source\Private\FileInfoDatabase.cpp AddOrUpdateFileInfo 函数
const FString PackageName = InAssetData.PackageName.ToString();
const FString Extension = (InAssetData.PackageFlags & PKG_ContainsMap) ? FPackageName::GetMapPackageExtension() : FPackageName::GetAssetPackageExtension();
FString FilePath;
if (!FPackageName::TryConvertLongPackageNameToFilename(PackageName, FilePath, Extension))
{
return false;
}
const FString FullFilePath = FPaths::ConvertRelativePathToFull(FilePath);
// 旧方法
FString Filename = FPackageName::LongPackageNameToFilename(PackageName);
FString AbsPath;
FString AssetAbsPath = FPaths::ConvertRelativePathToFull(Filename) + FPackageName::GetAssetPackageExtension();
FString MapAbsPath = FPaths::ConvertRelativePathToFull(Filename) + FPackageName::GetMapPackageExtension();
if (FPaths::FileExists(AssetAbsPath))
{
AbsPath = AssetAbsPath;
}
else if (FPaths::FileExists(MapAbsPath))
{
AbsPath = AssetAbsPath;
}
|
https://zhuanlan.zhihu.com/p/152201635
FAssetData
有时希望不加载这个资产,只通过 FAssetData 就拿到基本的信息进行分析。
从 FAssetData 中拿到 Class 信息:
1
2
3
4
5
6
7
8
| FAssetData AssetData;
FString AssetClassPathString = AssetData.AssetClassPath.ToString();
const UClass* AssetClass = LoadObject<UClass>(nullptr, *AssetClassPathString);
if (!AssetClass) return;
if (AssetClass->IsChildOf(UStaticMesh::StaticClass()))
{
// 如果是 static mesh
}
|
如果是希望提取某个 property,我们可以先对这个 property 打上 AssetRegistrySearchable 的宏标记:
1
2
| UPROPERTY(AssetRegistrySearchable)
int32 Test;
|
然后就可以从 FAssetData 里拿:
1
2
3
4
5
| FString TagValue;
if (AssetData->GetTagValue("Test", TagValue))
{
}
|
异步加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| void UGameCheatManager::GrantItems()
{
TArray<FSoftObjectPath> ItemsToStream;
FStreamableManager& Streamable = UAssetManager::GetStreamableManager();
for(int32 i = 0; i < ItemList.Num(); ++i)
{
ItemsToStream.AddUnique(ItemList[i].ToStringReference());
}
Streamable.RequestAsyncLoad(ItemsToStream, FStreamableDelegate::CreateUObject(this, &UGameCheatManager::GrantItemsDeferred));
}
void UGameCheatManager::GrantItemsDeferred()
{
for(int32 i = 0; i < ItemList.Num(); ++i)
{
UGameItemData* ItemData = ItemList[i].Get();
if(ItemData)
{
MyPC->GrantItem(ItemData);
}
}
}
|
https://dev.epicgames.com/documentation/zh-cn/unreal-engine/asynchronous-asset-loading-in-unreal-engine?application_version=5.4
复制 Duplicate
1
2
3
4
5
6
7
8
9
10
| // 1. Duplicate Object
YourType* NewObject = DuplicateObject<YourType>(const TObjectPtr<T>& SourceObject, UObject* Outer, const FName Name = NAME_None)
// 2. Duplicate Actor
UEditorActorSubsystem* EditorActorSubsystem = GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
YourType* NewActor = Cast<YourType>(EditorActorSubsystem->DuplicateActor(YourActor, World));
// 3. Duplicate Asset
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
UObject* NewAsset = AssetToolsModule.Get().DuplicateAsset("NewAssetName", "NewAssetPath", YourAsset);
|
Slate专栏
快速入门指南
官方教程:
https://docs.unrealengine.com/5.0/zh-CN/slate-editor-window-quickstart-guide-for-unreal-engine/
但是官方教程有一点错误,详细代码可以看:
https://github.com/yegorsw/UE_SimpleSlatePlugin/tree/main
细节面板自定义
https://zhuanlan.zhihu.com/p/463008792
https://imzlp.com/posts/26919/
其中若是对TArray类型自定义,我是参考的ue源代码,参考链接:
https://forums.unrealengine.com/t/how-to-use-ipropertytypecustomization-to-display-tarray/414634/7
最后翻看ue代码:FSpriteDetailsCustomization::BuildTextureSection
1
2
3
| TSharedRef<FDetailArrayBuilder> PropertyValuesBuilder = MakeShareable(new FDetailArrayBuilder(PropertyValuesHandle.ToSharedRef()));
PropertyValuesBuilder->OnGenerateArrayElementWidget(FOnGenerateArrayElementWidget::CreateSP(this, &FKCustomPropertyDetails::GeneratePropertyValueWidget));
StructBuilder.AddCustomBuilder(PropertyValuesBuilder);
|
关闭代码文件优化
https://zhuanlan.zhihu.com/p/563164847
可以按vs的方式:
1
2
| #pragma optimize("", off)
#pragma optimize("", on)
|
配置文件
https://docs.unrealengine.com/5.2/zh-CN/configuration-files-in-unreal-engine/
其他
单例写法
可以参考官方 UToolMenus::Singleton
或者 https://zhuanlan.zhihu.com/p/539811243