自学内容网 自学内容网

UE5: Content browser工具编写02

DebugHeader.h 中的全局变量,已经在一个cpp file中被include了,如果在另一个cpp file中再include它,就会有一些conflicts。先全部给加一个static

  • Add static keyword to debug functions
  • Wrap all the functions inside of a namespace
  • print message to the screen when pressing delete unused assets
#pragma once
#include "Misc/MessageDialog.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Framework/Notifications/NotificationManager.h"

static void Print(const FString& Message, const FColor& Color)
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 8.f, Color, Message);
}
}

static void PrintLog(const FString& Message)
{
UE_LOG(LogTemp, Warning, TEXT("%s"),*Message);
}

static EAppReturnType::Type ShowMsgDialog(EAppMsgType::Type MsgType, const FString& Message,
bool bShowMsgAsWarning = true) 
{
if (bShowMsgAsWarning == true) 
{
FText MsgTitle = FText::FromString(TEXT("Warning"));

return FMessageDialog::Open(MsgType, FText::FromString(Message), &MsgTitle);
//return FMessageDialog::Open(MsgType, FText::FromString(TEXT("Warning: ") + Message));
}

else
{
return FMessageDialog::Open(MsgType, FText::FromString(Message));
}
}

static void ShowNotifyInfo(const FString& Message)
{
FNotificationInfo NotifyInfo(FText::FromString(Message));
NotifyInfo.bUseLargeFont = true;
NotifyInfo.FadeOutDuration = 7.f;

FSlateNotificationManager::Get().AddNotification(NotifyInfo);
}

EditorAssetLibrary.h里面,ListAssets的功能

UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Asset")
static TArray<FString> ListAssets(const FString& DirectoryPath, bool bRecursive = true, bool bIncludeFolder = false);

ListAssets 函数中,bRecursive 是一个布尔变量,通常用来控制函数是否以递归方式列出某个目录下的所有资产(Assets)。(if we go inside of the subfolder)

  • bRecursivetrue 时,函数会递归遍历指定目录以及其所有子目录,并列出其中的所有资产。
  • bRecursivefalse 时,函数只会列出指定目录中的资产,而不会深入子目录。

bool bIncludeFolder

假设你的资产结构目录如下

/Assets
├── File1.uasset
├── Folder1
│   └── File2.uasset
└── Folder2
  • bIncludeFolder = true:
    输出可能是:File1.uasset, Folder1/, Folder1/File2.uasset, Folder2/

  • bIncludeFolder = false:
    输出可能是:File1.uasset, Folder1/File2.uasset

在content folder里面,不要碰collections和developers目录。DO NOT TOUCH ROOT FOLDER (COLLECTIONS/DEVELOPERS)

示例代码

// Copyright Epic Games, Inc. All Rights Reserved.

#include "SuperManager.h"
#include "ContentBrowserModule.h"
#include "DebugHeader.h"
#include "EditorAssetLibrary.h"
#include "ObjectTools.h"//DeleteAssets()

#define LOCTEXT_NAMESPACE "FSuperManagerModule"

void FSuperManagerModule::StartupModule()
{
InitContentBrowserExtension();
}


void FSuperManagerModule::ShutdownModule()
{
 //This function may be called during shutdown to clean up your module.  For modules that support dynamic reloading,
 //we call this function before unloading the module.
}

#pragma region ContentBrowserMenuExtension
void FSuperManagerModule::InitContentBrowserExtension()
{
FContentBrowserModule& ContentBrowserModule = 
FModuleManager::LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));

TArray<FContentBrowserMenuExtender_SelectedPaths>& ContentBrowserModuleMenuExtenders = 
ContentBrowserModule.GetAllPathViewContextMenuExtenders();

//FContentBrowserMenuExtender_SelectedPaths CustomContentBrowserMenuDelegate;
//CustomContentBrowserMenuDelegate.BindRaw(this, &FSuperManagerModule::CustomContentBrowserMenuExtender);

//ContentBrowserModuleMenuExtenders.Add(CustomContentBrowserMenuDelegate);
//***same with the two lines below

ContentBrowserModuleMenuExtenders.Add(FContentBrowserMenuExtender_SelectedPaths::
CreateRaw(this, &FSuperManagerModule::CustomContentBrowserMenuExtender));

}

TSharedRef<FExtender> FSuperManagerModule::CustomContentBrowserMenuExtender(const TArray<FString>& SelectedPaths)
{
TSharedRef<FExtender> MenuExtender(new FExtender());

if (SelectedPaths.Num() > 0) {
//define the position
//-> 是一个运算符,用于访问指针所指向对象的成员。

MenuExtender->AddMenuExtension(FName("Delete"),//name of extension hook
//we want to insert after DELETE(extension hook)
EExtensionHook::After,
TSharedPtr<FUICommandList>(),//no hotkey
//first binding
            FMenuExtensionDelegate::CreateRaw(this, &FSuperManagerModule::AddContentBrowserMenuEntry));
    //second binding

FolderPathsSelected = SelectedPaths;//then we have access to the folders that the user has currently selected
};

return MenuExtender;
}

void FSuperManagerModule::AddContentBrowserMenuEntry(FMenuBuilder& MenuBuilder)
{//in this function we can define all the details for our menu entry //and we can use the menu builder to do so
MenuBuilder.AddMenuEntry
(
FText::FromString(TEXT("Delete Unused Assets")),
FText::FromString(TEXT("Safely delete all unused assets under folder")),
FSlateIcon(),//empty placeholder
//SECOND BINDING
FExecuteAction::CreateRaw(this, &FSuperManagerModule::OnDeleteUnusedAssetButtonClicked)
);
}

void FSuperManagerModule::OnDeleteUnusedAssetButtonClicked()
{
if (FolderPathsSelected.Num() > 1)//delete stuff in multiple folders will cause some unexpected behaviors
{
DebugHeader::ShowMsgDialog(EAppMsgType::Ok, TEXT("Please select ONE folder"));
return;
}

//DebugHeader::Print(TEXT("Currently selected folder: ")+ FolderPathsSelected[0], FColor::Green);
//pure debug, can be deleted later

TArray<FString> AssetsPathName = UEditorAssetLibrary::ListAssets(FolderPathsSelected[0]);

if (AssetsPathName.Num() == 0)
{
DebugHeader::ShowMsgDialog(EAppMsgType::Ok, TEXT("No Asset found under selected folder"));
return;
}

//not finished yet
//DebugHeader::ShowMsgDialog(EAppMsgType::Ok, TEXT("Delete Unused Assets"));
//DebugHeader::Print(TEXT("Unused Assets Deleted"), FColor::Red);

FString Message = FString::Printf(TEXT("A total of %d found.\nConfirm deletion?"), AssetsPathName.Num());
//%d 是一个格式说明符,用于表示一个整数(int 类型)
EAppReturnType::Type ConfirmResult =
DebugHeader::ShowMsgDialog(EAppMsgType::OkCancel, Message);

//error   error   //EAppReturnType::Type ConfirmResult = DebugHeader::ShowMsgDialog(EAppMsgType::OkCancel, TEXT("A total of ") + FString::Printf(AssetsPathName.Num()) + TEXT(" found.\nConfirm deletion?"));

if (ConfirmResult == EAppReturnType::Cancel)return;

TArray<FAssetData>UnusedAssetsDataArray;

for (const FString& AssetPathNameWWWWW : AssetsPathName)
{
if (AssetPathNameWWWWW.Contains(TEXT("Collections")) || AssetPathNameWWWWW.Contains(TEXT("Developers")))
{
continue;//不执行操作,继续循环
}
//DO NOT TOUCH ROOT FOLDER (COLLECTIONS/DEVELOPERS)

if (!UEditorAssetLibrary::DoesAssetExist(AssetPathNameWWWWW)) continue;
//if we don't do this, sometimes we will get some error message saying that Assets does not exist

TArray<FString> AssetReferencers = 
UEditorAssetLibrary::FindPackageReferencersForAsset(AssetPathNameWWWWW);
//it is same with(delete asset from selection)

if (AssetReferencers.Num() == 0)//this asset is unused
//error   error   E0137expression must be a modifiable lvalue
//报错的中文意思是:“表达式必须是可修改的左值。”
//这是因为在这段代码里,你错误地使用了单等号 = ,而不是双等号 == ,在比较语句中使用了赋值运算符。
{
const FAssetData UnusedAssetData = UEditorAssetLibrary::FindAssetData(AssetPathNameWWWWW);
UnusedAssetsDataArray.Add(UnusedAssetData);
}
//塞入“无用资产”动态数组

if (UnusedAssetsDataArray.Num() > 0)
{
ObjectTools::DeleteAssets(UnusedAssetsDataArray);
}

else
{
DebugHeader::ShowMsgDialog(EAppMsgType::Ok, TEXT("No Unused Asset found under selected folder"));
}
}

}

#pragma endregion



#undef LOCTEXT_NAMESPACE

IMPLEMENT_MODULE(FSuperManagerModule, SuperManager)

可以将 C++ 中的 delegate 比作一个 “邮递员”

比喻解释:

  1. 邮递员:邮递员负责将信件从一个地方送到另一个地方。你把信件(消息、请求等)交给邮递员,告诉他该去哪个地址送达。

  2. 委托的角色

    • 发送者:在你的代码中,发送者(或发布者)就像是给邮递员信件的人。发送者生成一个事件(例如某个操作完成)并把它交给邮递员(委托)。
    • 邮递员(委托):邮递员不处理信件的内容,只负责把信件送到指定的地址。在委托的情况下,它会将事件传递给事先注册好的接收者(或订阅者)。
    • 接收者:接收者就像是收件人,当邮递员把信件送到他们那里时,他们会根据信件的内容采取相应的行动。

具体流程:

  • 注册接收者:想象一下,收件人提前告诉邮递员自己的地址(注册委托)。这样,当邮递员接收到信件时,他就知道要把信送到哪个地方。
  • 发送事件:当某个事件发生(例如按钮被点击),发送者会把这个事件的信息交给邮递员。
  • 处理事件:邮递员把信息传递给注册的接收者,接收者根据收到的信息执行相应的操作(例如,更新界面、执行某个功能等)。

优点:

  • 解耦合:使用邮递员的方式可以避免发送者和接收者之间的直接联系。发送者不需要知道接收者是谁,只需把事件交给邮递员。这样可以让代码更灵活,方便维护和扩展。
  • 多个接收者:就像一个邮递员可以把同一封信件送到多个收件人一样,委托可以有多个绑定的接收者,所有的接收者都会在事件发生时被通知。

总结:

将 C++ 中的 delegate 比作“邮递员”可以帮助你理解它的工作原理和用途:通过一个中介,将事件或消息从发送者传递给接收者,同时保持二者之间的解耦。这种方式在事件驱动编程和回调机制中非常有效。

使用 C++ 中的 delegate 可以实现一些直接调用函数所无法实现的功能:

1. 解耦合

  • 使用直接调用:如果直接调用一个函数,调用者需要了解被调用函数的具体实现和参数。这使得代码之间的耦合度增加。
  • 使用委托:通过委托,发送者和接收者之间没有直接关系。发送者只需要发布事件,接收者可以在不影响发送者的情况下独立处理事件。这种解耦合使得代码更加灵活,方便修改和扩展。

2. 动态绑定

  • 使用直接调用:在编译时,调用的函数是固定的,无法在运行时改变。
  • 使用委托:委托允许在运行时动态绑定多个回调函数。你可以根据不同的条件选择不同的回调,这种动态性在事件处理和响应用户输入时非常有用。

3. 多重订阅

  • 使用直接调用:直接调用通常只能调用一个函数。
  • 使用委托:一个委托可以绑定多个接收者,所有订阅这个委托的函数都会被调用。这种特性非常适合于需要通知多个对象的情况,如用户界面事件(按钮点击、状态变化等)。

4. 异步处理

  • 使用直接调用:直接调用函数通常是同步的,即调用者会等待函数执行完成后才能继续执行下一步。
  • 使用委托:委托可以与异步编程结合使用,允许事件在后台线程中处理,调用者可以继续执行其他任务,而不必等待事件处理完成。

5. 简化事件处理

  • 使用直接调用:如果要处理多种类型的事件,代码中会有大量的条件语句,增加了复杂性。
  • 使用委托:通过委托,可以创建更为简洁和结构化的事件处理代码,注册和注销事件处理器变得简单直观。

6. 回调机制

  • 使用直接调用:没有灵活的回调机制,调用者在调用函数时必须事先知道它将要做什么。
  • 使用委托:允许在某些操作完成时进行回调,例如在异步操作完成后执行特定的代码,或在游戏状态改变时更新界面。

例子:

假设你有一个游戏,玩家按下按钮时要播放音效并更新得分。使用直接调用时,按钮的点击处理代码需要直接调用音效播放函数和得分更新函数,这会使得代码紧密耦合。使用委托,你可以在按钮被点击时发布一个事件,多个处理函数(音效播放、得分更新)可以同时订阅这个事件并处理。


原文地址:https://blog.csdn.net/charon8778/article/details/142580806

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!