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)
- 当
bRecursive
为true
时,函数会递归遍历指定目录以及其所有子目录,并列出其中的所有资产。 - 当
bRecursive
为false
时,函数只会列出指定目录中的资产,而不会深入子目录。
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 E0137 expression 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 比作一个 “邮递员”。
比喻解释:
-
邮递员:邮递员负责将信件从一个地方送到另一个地方。你把信件(消息、请求等)交给邮递员,告诉他该去哪个地址送达。
-
委托的角色:
- 发送者:在你的代码中,发送者(或发布者)就像是给邮递员信件的人。发送者生成一个事件(例如某个操作完成)并把它交给邮递员(委托)。
- 邮递员(委托):邮递员不处理信件的内容,只负责把信件送到指定的地址。在委托的情况下,它会将事件传递给事先注册好的接收者(或订阅者)。
- 接收者:接收者就像是收件人,当邮递员把信件送到他们那里时,他们会根据信件的内容采取相应的行动。
具体流程:
- 注册接收者:想象一下,收件人提前告诉邮递员自己的地址(注册委托)。这样,当邮递员接收到信件时,他就知道要把信送到哪个地方。
- 发送事件:当某个事件发生(例如按钮被点击),发送者会把这个事件的信息交给邮递员。
- 处理事件:邮递员把信息传递给注册的接收者,接收者根据收到的信息执行相应的操作(例如,更新界面、执行某个功能等)。
优点:
- 解耦合:使用邮递员的方式可以避免发送者和接收者之间的直接联系。发送者不需要知道接收者是谁,只需把事件交给邮递员。这样可以让代码更灵活,方便维护和扩展。
- 多个接收者:就像一个邮递员可以把同一封信件送到多个收件人一样,委托可以有多个绑定的接收者,所有的接收者都会在事件发生时被通知。
总结:
将 C++ 中的 delegate 比作“邮递员”可以帮助你理解它的工作原理和用途:通过一个中介,将事件或消息从发送者传递给接收者,同时保持二者之间的解耦。这种方式在事件驱动编程和回调机制中非常有效。
使用 C++ 中的 delegate 可以实现一些直接调用函数所无法实现的功能:
1. 解耦合:
- 使用直接调用:如果直接调用一个函数,调用者需要了解被调用函数的具体实现和参数。这使得代码之间的耦合度增加。
- 使用委托:通过委托,发送者和接收者之间没有直接关系。发送者只需要发布事件,接收者可以在不影响发送者的情况下独立处理事件。这种解耦合使得代码更加灵活,方便修改和扩展。
2. 动态绑定:
- 使用直接调用:在编译时,调用的函数是固定的,无法在运行时改变。
- 使用委托:委托允许在运行时动态绑定多个回调函数。你可以根据不同的条件选择不同的回调,这种动态性在事件处理和响应用户输入时非常有用。
3. 多重订阅:
- 使用直接调用:直接调用通常只能调用一个函数。
- 使用委托:一个委托可以绑定多个接收者,所有订阅这个委托的函数都会被调用。这种特性非常适合于需要通知多个对象的情况,如用户界面事件(按钮点击、状态变化等)。
4. 异步处理:
- 使用直接调用:直接调用函数通常是同步的,即调用者会等待函数执行完成后才能继续执行下一步。
- 使用委托:委托可以与异步编程结合使用,允许事件在后台线程中处理,调用者可以继续执行其他任务,而不必等待事件处理完成。
5. 简化事件处理:
- 使用直接调用:如果要处理多种类型的事件,代码中会有大量的条件语句,增加了复杂性。
- 使用委托:通过委托,可以创建更为简洁和结构化的事件处理代码,注册和注销事件处理器变得简单直观。
6. 回调机制:
- 使用直接调用:没有灵活的回调机制,调用者在调用函数时必须事先知道它将要做什么。
- 使用委托:允许在某些操作完成时进行回调,例如在异步操作完成后执行特定的代码,或在游戏状态改变时更新界面。
例子:
假设你有一个游戏,玩家按下按钮时要播放音效并更新得分。使用直接调用时,按钮的点击处理代码需要直接调用音效播放函数和得分更新函数,这会使得代码紧密耦合。使用委托,你可以在按钮被点击时发布一个事件,多个处理函数(音效播放、得分更新)可以同时订阅这个事件并处理。