This commit is contained in:
2023-06-29 11:55:02 +08:00
commit 36e95249b1
1236 changed files with 464197 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
---
title: UE消息对话框使用
date: 2022-09-28 14:41:08
excerpt:
tags:
rating: ⭐
---
```c++
void FSimpleEditorsModule::PluginButtonClicked()
{
//消息框显示的消息内容
FText DialogText = FText::Format(
LOCTEXT("PluginButtonDialogText", "Add code to {0} in {1} to override this button's actions"),
FText::FromString(TEXT("FSimpleEditorsModule::PluginButtonClicked()")),
FText::FromString(TEXT("SimpleEditors.cpp"))
);
EAppReturnType::Type ReturnType = FMessageDialog::Open(EAppMsgType::OkCancel, DialogText);
if (ReturnType == EAppReturnType::Type::Ok)
{
//消息框OK按钮被点击执行
UE_LOG(LogTemp,Log,TEXT("Click OK Button."))
FMessageDialog::ShowLastError(); //弹出默认的系统原生消息对话框
}
else
{
//消息框Cancel按钮被点击执行
UE_LOG(LogTemp, Log, TEXT("Click Cancel Button."))
FMessageDialog::Debugf(DialogText); //弹出默认的OK消息对话框
}
}
```

View File

@@ -0,0 +1,168 @@
---
title: UTexture2D的读取与写入数据并且保存成Asset
date: 2022-11-04 10:11:39
excerpt:
tags:
rating: ⭐
---
## UTexture2D的读取与写入数据
在阅读@YivanLee的文章
https://zhuanlan.zhihu.com/p/45171840
我发现“**向UTexture2D写入数据**”这节有些纰漏:
1. 作者没有说明Texture2D->PlatformData->Mips[0].BulkData.LockReadOnly()返回空指针的详细情况。
2. 作者实现的写入是临时性的。
在查阅国外资料时我在isaratech的网站里找到解决方案在此我将进行简单说明。原文地址在结尾会介绍
## 从UTexture2D读取数据
读取方法如下:
```
const FColor* FormatedImageData = static_cast<const FColor*>( MyTexture2D->PlatformData->Mips[0].BulkData.LockReadOnly());
for(int32 X = 0; X < MyTexture2D->SizeX; X++)
{
    for (int32 Y = 0; Y < MyTexture2D->SizeY; Y++)
    {
        FColor PixelColor = FormatedImageData[Y * MyTexture2D->SizeX + X];
        // Do the job with the pixel
    }
}
MyTexture2D->PlatformData->Mips[0].BulkData.Unlock();
```
但有的时候LockReadOnly()会返回空指针这个时候你需要去检查UTexture2D的设置
1. CompressionSettings 是否为VectorDisplacementmap。
2. MipGenSettings 是否为NoMipmaps。
3. SRGB 是否为未勾选状态。
但通常情况下都不可能同时符合这3个条件所以我们需要将UTexture2D的3个选项设置成上述这3个状态。在处理所需操作后再进行恢复。代码如下
```
TextureCompressionSettings OldCompressionSettings = MyTexture2D->CompressionSettings; TextureMipGenSettings OldMipGenSettings = MyTexture2D->MipGenSettings; bool OldSRGB = MyTexture2D->SRGB;
MyTexture2D->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap;
MyTexture2D->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps;
MyTexture2D->SRGB = false;
MyTexture2D->UpdateResource();
const FColor* FormatedImageData = static_cast<const FColor*>( MyTexture2D->PlatformData->Mips[0].BulkData.LockReadOnly());
for(int32 X = 0; X < MyTexture2D->SizeX; X++)
{
    for (int32 Y = 0; Y < MyTexture2D->SizeY; Y++)
    {
        FColor PixelColor = FormatedImageData[Y * MyTexture2D->SizeX + X];
        //做若干操作
    }
}
MyTexture2D->PlatformData->Mips[0].BulkData.Unlock();
Texture->CompressionSettings = OldCompressionSettings;
Texture->MipGenSettings = OldMipGenSettings;
Texture->SRGB = OldSRGB;
Texture->UpdateResource();
```
## 向UTexture2D写入数据
写入操作也是类似不过我们需要调用若干UPackage与FAssetRegistryModule的代码。
下文中的Asset相关操作需要在包含头文件
># include "AssetRegistryModule.h"
同时需要在项目模块c#文件的PublicDependencyModuleNames.AddRange中加入"AssetTools"。
### 取得Package的指针
这里可以使用CreatePackage来创建新的包或是调用FindPackage来取得已有包的UPackage指针。包这个概念来源于UE3与UDK时代当时引擎所有的资源都存放在一个一个的包中修改过资源后还需要选中并且点击保存包选项才能真正保存但是UE4淡化了这个概念。
你可以把包理解为Content下各个文件夹。
```
//创建名为PackageName值的包
//PackageName为FString类型
FString AssetPath = TEXT("/Game/")+ PackageName+ TEXT("/");
AssetPath += TextureName;
UPackage* Package = CreatePackage(NULL, *AssetPath);
Package->FullyLoad();
```
FindPackage本人还没用过不过用法应该差不多。
### 取得UTexture2D的指针
你可以创建一个新的UTexture2D也可以通过蓝图指定。
```
//创建
UTexture2D* NewTexture = NewObject<UTexture2D>(Package, *TextureName, RF_Public | RF_Standalone | RF_MarkAsRootSet);
NewTexture->AddToRoot();            
NewTexture->PlatformData = new FTexturePlatformData();  
NewTexture->PlatformData->SizeX = TextureWidth;
NewTexture->PlatformData->SizeY = TextureHeight;
NewTexture->PlatformData->NumSlices = 1;
//设置像素格式
NewTexture->PlatformData->PixelFormat = EPixelFormat::PF_B8G8R8A8;
```
### 写入数据
```
//创建一个uint8的数组并取得指针
//这里需要考虑之前设置的像素格式
uint8* Pixels = new uint8[TextureWidth * TextureHeight * 4];
for (int32 y = 0; y < TextureHeight; y++)
{
    for (int32 x = 0; x < TextureWidth; x++)
    {
        int32 curPixelIndex = ((y * TextureWidth) + x);
        //这里可以设置4个通道的值
        //这里需要考虑像素格式之前设置了PF_B8G8R8A8那么这里通道的顺序就是BGRA
        Pixels[4 * curPixelIndex] = 100;
        Pixels[4 * curPixelIndex + 1] = 50;
        Pixels[4 * curPixelIndex + 2] = 20;
        Pixels[4 * curPixelIndex + 3] = 255;
    }
}
//创建第一个MipMap
FTexture2DMipMap* Mip = new FTexture2DMipMap();
NewTexture->PlatformData->Mips.Add(Mip);
Mip->SizeX = TextureWidth;
Mip->SizeY = TextureHeight;
//锁定Texture让它可以被修改
Mip->BulkData.Lock(LOCK_READ_WRITE);
uint8* TextureData = (uint8*)Mip->BulkData.Realloc(TextureWidth * TextureHeight * 4);
FMemory::Memcpy(TextureData, Pixels, sizeof(uint8) * TextureHeight * TextureWidth * 4);
Mip->BulkData.Unlock();
//通过以上步骤,我们完成数据的临时写入
//执行完以下这两个步骤编辑器中的asset会显示可以保存的状态如果是指定Asset来获取UTexture2D的指针的情况下
NewTexture->Source.Init(TextureWidth, TextureHeight, 1, 1, ETextureSourceFormat::TSF_BGRA8, Pixels);
NewTexture->UpdateResource();
```
### 创建Asset并清理无用数据
```
Package->MarkPackageDirty();
FAssetRegistryModule::AssetCreated(NewTexture);
//通过asset路径获取包中文件名
FString PackageFileName = FPackageName::LongPackageNameToFilename(AssetPath, FPackageName::GetAssetPackageExtension());
//进行保存
bool bSaved = UPackage::SavePackage(Package, NewTexture, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, *PackageFileName, GError, nullptr, true, true, SAVE_NoError);
delete[] Pixels;
```
之后你就可以在编辑器中找到新生成的Asset。如果你是向选中的Asset写入数据执行到上一步调用UPackage::SavePackage即可进行保存。
## 总结
本文包含以下知识:
1. UTexture2D的部分结构、属性以及读取设置方法。
2. UPackage部分的部分操作。
3. Asset的创建操作。
读者可以执行通过阅读UPackage、FAssetRegistryModule的代码学习Asset更多的Asset操作。
## 个人推荐的isaratech网站文章
网站
https://isaratech.com/
生成一个新的贴图Asset保存
https://isaratech.com/save-a-procedurally-generated-texture-as-a-new-asset/
生成一个新的带有节点的MaterialAsset
https://isaratech.com/ue4-programmatically-create-a-new-material-and-inner-nodes/

View File

@@ -0,0 +1,186 @@
---
title: UnrealEngine的命令行操作方式
date: 2023-04-27 11:15:14
excerpt:
tags: CommandLet
rating: ⭐
---
# 前言
最近想实现使用UnrealEditor-Cmd进行资产处理并且渲染的功能这里简单归纳一下。
# 命令行启动方式
## CommandLet
继承UCommandLet重写Run()相关逻辑写在里面即可。启动参数大致如下:
```bash
UnrealEditor-Cmd.exe ProjectPath -run=CommandletName
```
这里很推荐去看一下`UImportAssetsCommandlet`里面实现了使用Json传参的方法。
### 变相执行ConsoleCommand
创建一个CommandLet并且接收参数并在最后下列代码即可。
```c++
GEditor->Exec(World, TEXT("MAP REBUILD ALLDIRTYFORLIGHTING"));
```
## Python
- 官方文档https://docs.unrealengine.com/4.27/en-US/ProductionPipelines/ScriptingAndAutomation/Python/
```bash
UnrealEditor-Cmd.exe ProjectPath -ExecutePythonScript="c:\my_script.py"
```
还存在另一种方法编辑器启动时环境最小不包含UI或渲染。该方法执行起来非常快但是加载脚本需要交互的关卡和其他种类资源时比较棘手。在命令行中添加以下参数-`run=pythonscript -script=<script_file>`比如:
```bash
UnrealEditor-Cmd.exe -run=pythonscript -script="c:\\my_script.py"
```
## AutomationTest
一个另类的思路就是使用自动测试工具。通过`IMPLEMENT_COMPLEX_AUTOMATION_TEST`实现一个自动测试类之后实现`RunTest`即可。启动参数:
```bash
UnrealEditor-Cmd.exe ProjectPath -AutomationTestName -Execcmds="Command-Line Arguments"
```
大致过程可以参考视频https://www.youtube.com/watch?v=kJd5-jY46Gk
视频中的启动参数:
```python
import subprocess
engine = "C:/work/Epic/UE_5.0/Engine/Binaries/win64/UnrealEditor.exe"
project = "C:/Work/Prototypes/CompileBlueprintProj/CompileBlueprintProj.uproject"
log_location = "C:/Work/Prototypes/CompileBlueprintProj/blueprint_results.log"
cmd =[
engine,project,
f"-abslog={flog_location}",
"-editortest",
"-Execcmds= "Automation SetFilter Stress, Automation listAutomation RunTest Project.Blueprints.compile Blueprints\"","-testexit=\ "Automation Test Queue Empty\"",
cmd = " ".join(cmd)
subprocess.run(cmd)
```
引擎内的FCompileBlueprintsTest具体代码
```c++
IMPLEMENT_COMPLEX_AUTOMATION_TEST(FCompileBlueprintsTest, "Project.Blueprints.Compile Blueprints", EAutomationTestFlags::EditorContext | EAutomationTestFlags::StressFilter)
/************************************************************************/
/* FCompileBlueprintsTest */
/************************************************************************/
/** Requests a enumeration of all blueprints to be loaded */
void FCompileBlueprintsTest::GetTests(TArray<FString>& OutBeautifiedNames, TArray <FString>& OutTestCommands) const
{
FBlueprintAutomationTestUtilities::CollectTestsByClass(UBlueprint::StaticClass(), OutBeautifiedNames, OutTestCommands, /*bool bIgnoreLoaded =*/false);
}
bool FCompileBlueprintsTest::RunTest(const FString& Parameters)
{
UE_LOG(LogBlueprintAutomationTests, Log, TEXT("Beginning compile test for %s"), *Parameters);
return FBlueprintAutomationTestUtilities::CompileBlueprint(Parameters);
}
/**
* Simulates the user pressing the blueprint's compile button (will load the * blueprint first if it isn't already). ** @param BlueprintAssetPath The asset object path that you wish to compile. * @return False if we failed to load the blueprint, true otherwise
*/
static bool CompileBlueprint(const FString& BlueprintAssetPath)
{
UBlueprint* BlueprintObj = Cast<UBlueprint>(StaticLoadObject(UBlueprint::StaticClass(), NULL, *BlueprintAssetPath));
if (!BlueprintObj || !BlueprintObj->ParentClass)
{
UE_LOG(LogBlueprintAutomationTests, Error, TEXT("Failed to compile invalid blueprint, or blueprint parent no longer exists."));
return false;
}
UPackage* const BlueprintPackage = BlueprintObj->GetOutermost();
// compiling the blueprint will inherently dirty the package, but if there
// weren't any changes to save before, there shouldn't be after
bool const bStartedWithUnsavedChanges = (BlueprintPackage != nullptr) ? BlueprintPackage->IsDirty() : true;
FKismetEditorUtilities::CompileBlueprint(BlueprintObj, EBlueprintCompileOptions::SkipGarbageCollection);
if (BlueprintPackage != nullptr)
{
BlueprintPackage->SetDirtyFlag(bStartedWithUnsavedChanges);
}
return true;
}
```
# 编辑器内调用
## Python
- 官方文档https://docs.unrealengine.com/4.27/en-US/ProductionPipelines/ScriptingAndAutomation/Python/
将OutputLog的ConsoleCommand的类型从CMD=>Python之后输入Python命令即可。
## CustomConsoleCommand
除了UFUNCTION中指定Exec之外因为这个只能在部分类中实现并不通用就是使用`IConsoleManager::Get().RegisterConsoleCommand()`,卸载函数为`IConsoleManager::Get().UnregisterConsoleObject()`一般会在Module的StartupModule()/ShutdownModule()或者Subsystem的对应函数中进行注册/卸载。用法如下:
```c++
IConsoleManager::Get().RegisterConsoleCommand(
TEXT("ConsoleCommandName"),
TEXT("Useage Info"),
FConsoleCommandDelegate::CreateStatic(&UYourClass::Function),
ECVF_Default);
```
相关的FConsoleCommandDelegate委托都位于IConsoleManager.h根据需求选择。
# 其他
## CommandLet Server
```c++
int32 FEditorDomainSaveServer::Run()
{
if (!TryInitialize())
{
Shutdown();
return 1;
}
while (PollShouldRun())
{
bool bIsIdle = true;
TickPendingPackages(bIsIdle);
PollIncomingConnections(bIsIdle);
PollConnections(bIsIdle);
TickMaintenance(bIsIdle);
}
Shutdown();
return 0;
}
void FEditorDomainSaveServer::TickMaintenance(bool bIsIdle)
{
using namespace UE::EditorDomainSave;
SetIdle(bIsIdle);
double CurrentTime = FPlatformTime::Seconds();
if (bIsIdle)
{
if (!HasExpectedConnections() && CurrentTime - IdleStartTime > Constants::ServerAbdicationCooldownSeconds)
{
if (TryAbdicate())
{
return;
}
}
}
double CollectGarbageCooldownSeconds = bIsIdle ?
Constants::CollectGarbageIdleCooldownSeconds :
Constants::CollectGarbageActiveCooldownSeconds;
bool bCollectedGarbageAfterIdle = bIsIdle && LastGarbageTime >= IdleStartTime;
if (!bCollectedGarbageAfterIdle && CurrentTime - LastGarbageTime > CollectGarbageCooldownSeconds)
{
CollectGarbage(RF_NoFlags);
LastGarbageTime = FPlatformTime::Seconds();
}
if (bIsIdle)
{
FPlatformProcess::Sleep(Constants::ServerIdleSleepPeriodSeconds);
}
}
```
## 日志写入文件
```bash
f"-abslog={log_location}"
```