案例
头文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| #pragma once
#include "CoreMinimal.h"
#include "Commandlets/Commandlet.h"
#include "Commandlets/ResavePackagesCommandlet.h"
#include "XXXCommandlet.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(LogXXXCommandlet, All, All);
UCLASS()
class UXXXCommandlet : public UResavePackagesCommandlet
{
GENERATED_BODY()
public:
UXXXCommandlet();
virtual int32 Main(const FString& InCommandline) override;
};
|
源文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| #include "XXXCommandlet.h"
DEFINE_LOG_CATEGORY(LogXXXCommandlet);
UXXXCommandlet::UXXXCommandlet()
{
}
int32 UXXXCommandlet::Main(const FString& InCommandline)
{
// 可以解析参数
TArray<FString> Tokens;
TArray<FString> InSwitches;
TMap<FString, FString> ParamsMap;
ParseCommandLine(*InCommandline, Tokens, InSwitches, ParamsMap);
// do some thing
return 0;
}
|
启动/调试时在vs中增加参数:-skipcompile -run=XXX
这里的 XXX 换成你想要的名字,UResavePackagesCommandlet 也可以换成 UCommandlet
本地测试:
1
| .\UnrealEditor.exe E:\Work\xxx.uproject -skipcompile -run=XXX
|
小技巧
参数
skipcompile参数作用
字面意思就是跳过编译,源码对应位置:

AllowCommandletRendering
commandlet 下默认没有渲染,需要加上 -AllowCommandletRendering 打开
tick
Commandlet 无法tick,但是看ue源码发现大世界有个这样的函数:
1
| FWorldPartitionHelpers::FakeEngineTick
|
里面是调用了
1
| CommandletHelpers::TickEngine
|
资源检索
很多需要用资源的地方比如 AssetRegistryModule.Get().GetDependencies 之类的,需要提前注册到 AssetRegistryModule 里:
1
2
3
4
| UE_LOG(LogTemp, Display, TEXT("Commandlet Start Load All Assets!"));
FAssetRegistryModule& AssetRegistryModule = FModuleManager::GetModuleChecked<FAssetRegistryModule>("AssetRegistry");
AssetRegistryModule.Get().SearchAllAssets(/*bSynchronousSearch =*/true);
UE_LOG(LogTemp, Display, TEXT("Commandlet Finish Load All Assets!"));
|
一般情况上面这样就够了,再严谨点可以用 FEvent 额外保护确保执行完成了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| // 准备 AsssetRegistry
FEvent* AssetRegistryLoadedEvent = FPlatformProcess::GetSynchEventFromPool(true);
// Ensure AssetRegistryModule is initialized
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
// 初始化 AssetRegistry
AssetRegistry.SearchAllAssets(true);
// Wait until the AssetRegistry is fully loaded
AssetRegistry.OnFilesLoaded().AddLambda([AssetRegistryLoadedEvent]()
{
AssetRegistryLoadedEvent->Trigger();
});
if (!AssetRegistry.IsLoadingAssets())
{
AssetRegistryLoadedEvent->Trigger();
}
// Wait for the AssetRegistry to be fully loaded
AssetRegistryLoadedEvent->Wait();
FPlatformProcess::ReturnSynchEventToPool(AssetRegistryLoadedEvent);
|
http
因为这种网络相关,我们很多时候需要确保拿到数据再同步执行后续操作,或是直接执行回调的方式,此时也可以通过 FEvent 来保证,通过让 HttpManager 不断 tick 去确保拿到数据,示例代码如下:
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
| FEvent* ReadyEvent = FPlatformProcess::GetSynchEventFromPool(true);
TSharedPtr<FJsonObject> ResponseJson;
// Create HTTP request
TSharedRef<IHttpRequest> Request = FHttpModule::Get().CreateRequest();
Request->SetURL(Url);
Request->SetVerb(Verb);
// Set headers
for (const auto& Header : Headers)
{
Request->SetHeader(Header.Key, Header.Value);
}
// Set content if provided
if (!Content.IsEmpty())
{
Request->SetContentAsString(Content);
}
// Set completion callback
Request->OnProcessRequestComplete().BindLambda([ReadyEvent, &ResponseJson](FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSuccess)
{
if (bSuccess && HttpResponse.IsValid())
{
ResponseJson = MakeShareable(new FJsonObject());
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(HttpResponse->GetContentAsString());
FJsonSerializer::Deserialize(Reader, ResponseJson);
}
ReadyEvent->Trigger();
});
// Process the request
if (!Request->ProcessRequest())
{
FPlatformProcess::ReturnSynchEventToPool(ReadyEvent);
UE_LOG(LogTemp, Error, TEXT("Failed to start HTTP request: %s"), *Url);
return false;
}
// Wait for the request to complete
while (!ReadyEvent->Wait(FTimespan::FromMilliseconds(100)))
{
// Tick the HTTP manager to process the request
FHttpModule::Get().GetHttpManager().Tick(0.1f);
}
ReadyEvent->Reset();
FPlatformProcess::ReturnSynchEventToPool(ReadyEvent);
|
贴图
我想在 Commandlet 下拿到贴图的 Resource Size,跟编辑器打开贴图的 Detail 信息里的 Resource Size 等同,把源码挖来应该是这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // 尝试从PlatformData获取资源大小
FTexturePlatformData** PlatformDataPtr = Texture->GetRunningPlatformData();
if (PlatformDataPtr && PlatformDataPtr[0])
{
FTexturePlatformData* PlatformData = PlatformDataPtr[0];
// 获取当前LOD bias
const FStreamableRenderResourceState SRRState = Texture->GetStreamableResourceState();
const int32 ActualMipBias = SRRState.IsValid() ? (SRRState.ResidentFirstLODIdx() + SRRState.AssetLODBias) : Texture->GetCachedLODBias();
// 使用GetPayloadSize获取资源大小
ResourceSizeBytes = PlatformData->GetPayloadSize(ActualMipBias);
}
else
{
// 如果无法从PlatformData获取,则使用GetResourceSizeBytes
ResourceSizeBytes = Texture->GetResourceSizeBytes(EResourceSizeMode::Exclusive);
}
// 转换为KB
return FMath::DivideAndRoundNearest(ResourceSizeBytes, static_cast<int64>(1024));
|
但是发现 Commandlet 环境下会有问题,最后发现需要补上一些前置代码:
1
2
3
| FTextureCompilingManager::Get().FinishCompilation({Texture});
Texture->SetForceMipLevelsToBeResident(30.0f);
Texture->WaitForStreaming();
|
完整代码如下:
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
26
27
28
29
30
31
32
33
| // 获取Texture的Resource Size(KB)
int64 GetTextureResourceSizeKB(UTexture* Texture)
{
if (!Texture)
return 0;
FTextureCompilingManager::Get().FinishCompilation({Texture});
Texture->SetForceMipLevelsToBeResident(30.0f);
Texture->WaitForStreaming();
int64 ResourceSizeBytes = 0;
// 尝试从PlatformData获取资源大小
FTexturePlatformData** PlatformDataPtr = Texture->GetRunningPlatformData();
if (PlatformDataPtr && PlatformDataPtr[0])
{
FTexturePlatformData* PlatformData = PlatformDataPtr[0];
// 获取当前LOD bias
const FStreamableRenderResourceState SRRState = Texture->GetStreamableResourceState();
const int32 ActualMipBias = SRRState.IsValid() ? (SRRState.ResidentFirstLODIdx() + SRRState.AssetLODBias) : Texture->GetCachedLODBias();
// 使用GetPayloadSize获取资源大小
ResourceSizeBytes = PlatformData->GetPayloadSize(ActualMipBias);
}
else
{
// 如果无法从PlatformData获取,则使用GetResourceSizeBytes
ResourceSizeBytes = Texture->GetResourceSizeBytes(EResourceSizeMode::Exclusive);
}
// 转换为KB
return FMath::DivideAndRoundNearest(ResourceSizeBytes, static_cast<int64>(1024));
}
|
参考
https://zhuanlan.zhihu.com/p/512610557
https://zhuanlan.zhihu.com/p/377903983