UE基础知识

编译链接

源码编译

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宏的作用?
  1. 放函数声明前用于暴露(导出)该函数

  2. 放类声明前用于暴露(导出)该类的所有内容

    特别注意:_API前面的内容一定要大写!否则会编译不通过

  • MinimalAPI说明符的作用?

    暴露(导出)该类的类型信息让其它模块可以

    1. Cast到该类型
    2. 继承该类型 (该类型的所有定义在.cpp文件的虚函数都需要导出)
    3. 使用内联函数

编辑器

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/

FromToExample
FNameFStringTestHUDString = TestHUDName.ToString();
FNameFTextTestHUDText = FText::FromName(TestHUDName);
FStringFNameTestHUDName = FName(*TestHUDString);
FStringFTextTestHUDText = FText::FromString(TestHUDString);
FTextFStringTestHUDString = TestHUDText.ToString();
FTextFNameThere is no direct conversion from FText to FName. Instead, convert to FString and then to FName.
FStringint32int32 TestInt = FCString::Atoi(*MyFString);
FStringfloatfloat TestFloat = FCString::Atof(*MyFString);
int32FStringFString TestString = FString::FromInt(MyInt);
floatFStringFString 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

Licensed under CC BY-NC-SA 4.0