



























过年花了一周用AI + 教程:Create Custom Editor Tools by Vince Petrelli(崇高敬意) 快速搞了一波UE API.毕竟搞游戏的不会点UE API都不好意思打招呼
教程属于初级阶段,但是对于UE API学习是非常好的切入点
Delegate设计的还是很有意思,其实在当今,lambda已经可以打天下了。没必要走FOnDelegate::CreateXXX
Reflection 大大简化了代码
注:可能跟原教程代码不一样。
1: Slate GUI

AdvanceDeletionWidgets.h

// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "Widgets/SCompoundWidget.h" #include "TypeDefine.hpp" class SAdvanceDeletionTab: public SCompoundWidget { SLATE_BEGIN_ARGS(SAdvanceDeletionTab){} SLATE_ARGUMENT(FString, TestString) SLATE_ARGUMENT(ArrayAssetDataPtr , AssetsData) SLATE_END_ARGS() public: void Construct(const FArguments&); private: TArray<AssetDataPtr> storedAssetsData; TArray<AssetDataPtr> displayedAssetsData; // SListView Create TSharedRef<SListView<AssetDataPtr>> ConstructListView(); TSharedPtr<SListView<AssetDataPtr>> constructedListView; void refreshListView(); void OnNavigateToContentView(AssetDataPtr adp); TSharedRef<ITableRow> OnGenerateRow(AssetDataPtr item, const TSharedRef<STableViewBase> & ownerTable); // CheckBox Create TArray<TSharedPtr<SCheckBox>> checkBoxes; TSharedRef<SCheckBox> ConstructCheckBox(const AssetDataPtr & adp); void OnCheckBoxStateChanged(ECheckBoxState newState, AssetDataPtr adp); // TextBlock Create TSharedRef<STextBlock> ConstructTextBlock(const FText &text, const FSlateFontInfo font); // ----------------- SComboBox Start-------------------- TArray<StringPtr> ComboBoxMenuItems; TSharedRef<SComboBox<StringPtr>> ConstructComboBox(); TSharedRef<SWidget> OnGenerateComboBoxWidget(StringPtr row); void OnComboBoxSelectionChanged(StringPtr selectedItem, ESelectInfo::Type selectType); TSharedPtr<STextBlock> ComboBoxDisplayTextBlock; // ----------------- SComboBox end--------------------- // ------------------DeleteAll SelectAll DeselectAll START---------------------------- TSharedRef<SButton> ConstructDeleteAllButton(); TSharedRef<SButton> ConstructSelectAllButton(); TSharedRef<SButton> ConstructDeselectAllButton(); TSharedRef<STextBlock> ConstructButtonContent(const FString &text); //event FReply OnDeleteAllButtonClicked(); FReply OnSelectAllButtonClicked(); FReply OnDeselectAllButtonClicked(); TArray<AssetDataPtr> toDeleteAssets; // GUI Selected Assets: from check box // ------------------DeleteAll SelectAll DeselectAll END ---------------------------- };
View Code
AdvanceDeletionWidgets.cpp
// Fill out your copyright notice in the Description page of Project Settings. #include "SlateWidgets/AdvanceDeletionWidgets.h" #include "DebugHeader.hpp" #include "SlateBasics.h" #include "SuperManager.h" #define LIST_ALL TEXT("List All Assets") #define LIST_UNUSED TEXT("List Unused Assets") void SAdvanceDeletionTab::Construct(const FArguments&InArgs) { bCanSupportFocus = true; // -------- Data In -------- FString testString = InArgs._TestString; storedAssetsData = InArgs._AssetsData; displayedAssetsData = storedAssetsData; // -------- Data In ----------- // ------ Clear some Array --------- toDeleteAssets.Empty(); checkBoxes.Empty(); // ------ Clear some Array --------- // ------ ComboBoxMenus ------- ComboBoxMenuItems.Add(MakeShared<FString>(LIST_ALL) ); ComboBoxMenuItems.Add(MakeShared<FString>(LIST_UNUSED)); // ------ ComboBoxMenus ------- FSlateFontInfo titleFont = FCoreStyle::Get().GetFontStyle(FName("EmbossedText")); titleFont.Size = 30; ChildSlot[ //SNew(STextBlock).Text(FText::FromString(testString)) SNew(SVerticalBox) +SVerticalBox::Slot().AutoHeight() [ SNew(STextBlock) .Text(FText::FromString(InArgs._TestString) ) .Font(titleFont) .Justification(ETextJustify::Type::Center) .ColorAndOpacity(FColor::Yellow) ] // Drop And Help Text +SVerticalBox::Slot().AutoHeight() [ SNew(SHorizontalBox) +SHorizontalBox::Slot().AutoWidth() [ ConstructComboBox() ] ] // Asset List +SVerticalBox::Slot().VAlign(VAlign_Fill) [ SNew(SScrollBox) +SScrollBox::Slot() [ ConstructListView() ] ] // Button List +SVerticalBox::Slot().AutoHeight() [ SNew(SHorizontalBox) +SHorizontalBox::Slot().Padding(5.0f) [ ConstructDeleteAllButton() ] +SHorizontalBox::Slot().Padding(5.0f) [ ConstructSelectAllButton() ] +SHorizontalBox::Slot().Padding(5.0f) [ ConstructDeselectAllButton() ] ] ]; } TSharedRef<SComboBox<StringPtr>> SAdvanceDeletionTab::ConstructComboBox() { auto ret = SNew(SComboBox<StringPtr>) .OptionsSource(&ComboBoxMenuItems) .OnGenerateWidget(this, &SAdvanceDeletionTab::OnGenerateComboBoxWidget) .OnSelectionChanged(this, &SAdvanceDeletionTab::OnComboBoxSelectionChanged) [ SAssignNew(ComboBoxDisplayTextBlock, STextBlock) .Text(FText::FromString("List Assets Option") ) ]; return ret; } TSharedRef<SWidget> SAdvanceDeletionTab::OnGenerateComboBoxWidget(StringPtr row){ auto ret = SNew(STextBlock).Text(ConvertToText(*row.Get()) ); return ret; } void SAdvanceDeletionTab::OnComboBoxSelectionChanged(StringPtr selectedItem, ESelectInfo::Type selectType) { FSuperManagerModule & SuperMangerModule = FModuleManager::LoadModuleChecked<FSuperManagerModule>("SuperManager"); FString selectedText = *selectedItem; ComboBoxDisplayTextBlock->SetText(ConvertToText(selectedText)); if (selectedText.Equals(LIST_ALL,ESearchCase::CaseSensitive)){ Print(FColor::Green, "Selected ALL TYPE" ); displayedAssetsData = storedAssetsData; refreshListView(); } if (selectedText.Equals(LIST_UNUSED,ESearchCase::CaseSensitive)){ Print(FColor::Green, "Selected UNUSED TYPE" ); SuperMangerModule.filterUnusedAssets(storedAssetsData, displayedAssetsData); refreshListView(); } } TSharedRef<SListView<AssetDataPtr>> SAdvanceDeletionTab::ConstructListView(){ auto view = SNew(SListView<AssetDataPtr>) .ListItemsSource(&displayedAssetsData) .OnGenerateRow(this, &SAdvanceDeletionTab::OnGenerateRow) .OnMouseButtonClick(this, &SAdvanceDeletionTab::OnNavigateToContentView); constructedListView = view.ToSharedPtr(); return view; } void SAdvanceDeletionTab::OnNavigateToContentView(AssetDataPtr adp){ FSuperManagerModule & SuperMangerModule = FModuleManager::LoadModuleChecked<FSuperManagerModule>("SuperManager"); SuperMangerModule.SyncCBToClickedAssetAssetForAssetList(adp->GetSoftObjectPath().ToString() ); } void SAdvanceDeletionTab::refreshListView(){ // 1. Clear GUI selected assets that will to delete toDeleteAssets.Empty(); // 2. Clear GUI Checked box checkBoxes.Empty(); if (constructedListView.IsValid()) constructedListView->RebuildList(); } TSharedRef<ITableRow> SAdvanceDeletionTab::OnGenerateRow(AssetDataPtr item, const TSharedRef<STableViewBase>& ownerTable) { FSlateFontInfo classTextFont = FCoreStyle::Get().GetFontStyle(FName("EmbossedText")); auto assetNameFont = classTextFont; classTextFont.Size = 10; assetNameFont.Size = 15; // SHorizontalBox // CheckBox - AssetClassName - AssetName - Button const FText name = ConvertToText(item->AssetName); const FText className = ConvertToText(item->GetClass()->GetFName()); TSharedRef<STableRow<AssetDataPtr>> ret = SNew(STableRow<AssetDataPtr>, ownerTable).Padding(FMargin(5.f)) [ SNew(SHorizontalBox) +SHorizontalBox::Slot().HAlign(HAlign_Left).VAlign(VAlign_Center).FillWidth(0.05f) [ ConstructCheckBox(item) // CheckBox ] +SHorizontalBox::Slot().HAlign(HAlign_Center).VAlign(VAlign_Fill).FillWidth(0.6f) [ ConstructTextBlock(className, classTextFont) // class Name ] +SHorizontalBox::Slot().HAlign(HAlign_Left).VAlign(VAlign_Fill) [ ConstructTextBlock(name, assetNameFont) // asset Name ] +SHorizontalBox::Slot().HAlign(HAlign_Right) [ SNew(SButton).Text(FText::FromString("Delete") ).OnClicked_Lambda([this,item](){ FSuperManagerModule & SuperMangerModule = FModuleManager::LoadModuleChecked<FSuperManagerModule>("SuperManager"); const auto isDeleted = SuperMangerModule.removeSingleAsset(*item.Get()); if (isDeleted){ if (storedAssetsData.Contains(item) ) storedAssetsData.Remove(item); if (displayedAssetsData.Contains(item)) displayedAssetsData.Remove(item); refreshListView(); } return FReply::Handled(); }) ] ]; return ret; } TSharedRef<SCheckBox> SAdvanceDeletionTab::ConstructCheckBox(const AssetDataPtr& adp){ auto ret = SNew(SCheckBox) .Visibility(EVisibility::Visible) .Type(ESlateCheckBoxType::Type::CheckBox) .OnCheckStateChanged(this, &SAdvanceDeletionTab::OnCheckBoxStateChanged, adp); checkBoxes.Add(ret); return ret; } void SAdvanceDeletionTab::OnCheckBoxStateChanged(ECheckBoxState newState, AssetDataPtr adp){ Print(FColor::Black, FString("Selection Changed:") + ConvertToFString(adp->AssetName) ); switch (newState) { case ECheckBoxState::Checked: toDeleteAssets.AddUnique(adp); break; case ECheckBoxState::Unchecked: if (toDeleteAssets.Contains(adp)) toDeleteAssets.Remove(adp); break; case ECheckBoxState::Undetermined: break; } } TSharedRef<STextBlock> SAdvanceDeletionTab::ConstructTextBlock(const FText& text, const FSlateFontInfo font){ return SNew(STextBlock).Text(text).Font(font).ColorAndOpacity(FColor::White); } // Event for ButtonTab : DeleteAll SelectAll DeselectAll TSharedRef<SButton> SAdvanceDeletionTab::ConstructDeleteAllButton(){ auto ret = SNew(SButton) .ContentPadding(FMargin(5.f)) .OnClicked(this, &SAdvanceDeletionTab::OnDeleteAllButtonClicked); ret->SetContent(ConstructButtonContent("Delete All")); return ret; } TSharedRef<SButton> SAdvanceDeletionTab::ConstructSelectAllButton() { auto ret = SNew(SButton) .ContentPadding(FMargin(5.f)) .OnClicked(this, &SAdvanceDeletionTab::OnSelectAllButtonClicked); ret->SetContent(ConstructButtonContent("Select All")); return ret; } TSharedRef<SButton> SAdvanceDeletionTab::ConstructDeselectAllButton() { auto ret = SNew(SButton) .ContentPadding(FMargin(5.f)) .OnClicked(this, &SAdvanceDeletionTab::OnDeselectAllButtonClicked); ret->SetContent(ConstructButtonContent("Deselect All")); return ret; } FReply SAdvanceDeletionTab::OnDeleteAllButtonClicked(){ if (toDeleteAssets.Num()==0){ ShowMessageDialog("No Assets, Delete Error, Please select assets to delete", EAppMsgType::Ok); } TArray<FAssetData> willDeleteAssets; // delete this assets use FSuperManager for (const auto & item : toDeleteAssets){ willDeleteAssets.Add(*item.Get()); } FSuperManagerModule & SuperMangerModule = FModuleManager::LoadModuleChecked<FSuperManagerModule>("SuperManager"); if (SuperMangerModule.removeMultiAssets(willDeleteAssets) ) { // 1. Remove ListView SourceItem Data for (const auto & item : toDeleteAssets){ if (storedAssetsData.Contains(item) ) storedAssetsData.Remove(item); if (displayedAssetsData.Contains(item) ) displayedAssetsData.Remove(item); } // 2. Refresh GUI refreshListView(); } return FReply::Handled(); } FReply SAdvanceDeletionTab::OnSelectAllButtonClicked(){ for (auto &checkbox: checkBoxes) { if (not checkbox->IsChecked()) { //checkbox->SetIsChecked(ECheckBoxState::Checked); checkbox->ToggleCheckedState(); } } return FReply::Handled(); } FReply SAdvanceDeletionTab::OnDeselectAllButtonClicked(){ for (auto &checkbox: checkBoxes) { if (checkbox->IsChecked() ) { //checkbox->SetIsChecked(ECheckBoxState::Unchecked); checkbox->ToggleCheckedState(); } } return FReply::Handled(); } TSharedRef<STextBlock> SAdvanceDeletionTab::ConstructButtonContent(const FString& text) { FSlateFontInfo font = FCoreStyle::Get().GetFontStyle(FName("EmbossedText")); font.Size = 14; return SNew(STextBlock).Text(FText::FromString(text)).Font(font).Justification(ETextJustify::Center); }
SuperManger.h

// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Modules/ModuleManager.h" #include "TypeDefine.hpp" #include "ActorActions/BuildActorActions.h" class FSuperManagerModule : public IModuleInterface { public: /** IModuleInterface implementation */ virtual void StartupModule() override; virtual void ShutdownModule() override; public: bool removeSingleAsset(FAssetData ad); bool removeMultiAssets(TArray<FAssetData> ads); void SyncCBToClickedAssetAssetForAssetList(const FString &assetPathToSync); void filterUnusedAssets(const TArray<AssetDataPtr> &inAssets, TArray<AssetDataPtr> &outAssets); private: void InitCBMenuExtension(); TSharedRef<FExtender> CustomCBMenuExtension(const TArray<FString>& selectedPaths ); void AddCBMenuEntry(FMenuBuilder & builder); void OnDeleteUnusedAsset(); void OnDeleteEmptyFolder(); // Deletion Tab void InitDeletionTab(); void OnShowAdvancedDeletionTab(); private: TArray<FString> selectedPathsToDelete; private: TArray<AssetDataPtr> buildSelectedAssetsData() const; BuildActorActions buildActorActions; };
View Code
SuperManger.cpp

// Copyright Epic Games, Inc. All Rights Reserved. #include "SuperManager.h" #include "ContentBrowserModule.h" #include "DebugHeader.hpp" #include "EditorAssetLibrary.h" #include "EditorUtilityLibrary.h" #include "ObjectTools.h" #include "AssetRegistry/AssetRegistryModule.h" #include "SlateWidgets/AdvanceDeletionWidgets.h" #include "CustomStyle/SuperManagerStyle.h" #define LOCTEXT_NAMESPACE "FSuperManagerModule" void FSuperManagerModule::StartupModule() { FSuperManagerStyle::InitializeIcons(); // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module InitCBMenuExtension(); InitDeletionTab(); buildActorActions.Init(); } void FSuperManagerModule::ShutdownModule() { FSuperManagerStyle::Shutdown(); buildActorActions.ShutDown(); // 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. } void FSuperManagerModule::InitCBMenuExtension(){ FContentBrowserModule & CtentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser"); auto &extenders = CtentBrowserModule.GetAllPathViewContextMenuExtenders(); /* Method 01 FContentBrowserMenuExtender_SelectedPaths customCMenuDelegate; customCMenuDelegate.BindRaw(this , &FSuperManagerModule::CustomCBMenuExtension); extenders.Add(customCMenuDelegate);*/ // Method 02 extenders.Add(FContentBrowserMenuExtender_SelectedPaths::CreateRaw(this, &FSuperManagerModule::CustomCBMenuExtension)); } TSharedRef<FExtender> FSuperManagerModule::CustomCBMenuExtension(const TArray<FString>& selectedPaths) { TSharedRef<FExtender> Extender {new FExtender()}; if (selectedPaths.Num()>0){ Print(FColor::Red, "Going to FSuperManagerModule::CustomCBMenuExtension"); Extender->AddMenuExtension(FName{"Delete"}, EExtensionHook::After, TSharedPtr<FUICommandList>(), FMenuExtensionDelegate::CreateRaw(this, &FSuperManagerModule::AddCBMenuEntry)); selectedPathsToDelete = selectedPaths; } return Extender; } void FSuperManagerModule::AddCBMenuEntry(FMenuBuilder& builder){ builder.AddMenuEntry(FText::FromString("Delete Unused Asset"), FText::FromString("Delete Unused Asset"), FSlateIcon(FSuperManagerStyle::StyleSetName, "ContentBrowser.DeleteUnusedAssets"), FExecuteAction::CreateRaw(this, &FSuperManagerModule::OnDeleteUnusedAsset ) ); // Lambda Method remove and use LoadModuleChecked builder.AddMenuEntry(FText::FromString("Delete Unused Asset method 2"), FText::FromString("Delete Unused Asset 2"), FSlateIcon(FSuperManagerStyle::StyleSetName, "ContentBrowser.DeleteUnusedAssets2"), FExecuteAction::CreateLambda([this] { TArray<FAssetData> ToRemoveAssetData; if (selectedPathsToDelete.Num()!=1 ){ ShowMessageDialog(TEXT("Delete Unused asset support one folder path")); return; } TArray<FString> assets = UEditorAssetLibrary::ListAssets(selectedPathsToDelete[0]); FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry"); for (const auto &assetPath: assets) { TArray<FName> Referencers; const auto &assetData = UEditorAssetLibrary::FindAssetData(assetPath); AssetRegistryModule.Get().GetReferencers(assetData.PackageName, Referencers); Referencers.Remove(assetData.PackageName); if (Referencers.Num() == 0){ ToRemoveAssetData.Add(assetData); } } if (ToRemoveAssetData.Num() > 0) ObjectTools::DeleteAssets(ToRemoveAssetData); } ) ); builder.AddMenuEntry(FText::FromString("DeleteEmptyFolder"), FText::FromString("Delete Empty Folder"), FSlateIcon(), FExecuteAction::CreateRaw(this, &FSuperManagerModule::OnDeleteEmptyFolder)); // Our Slate Tab builder.AddMenuEntry(FText::FromString("Advanced Deletion"), FText::FromString("Advanced Assets Deletion"), FSlateIcon(FSuperManagerStyle::StyleSetName, "ContentBrowser.DeleteUnusedAssets2"), FExecuteAction::CreateRaw(this, &FSuperManagerModule::OnShowAdvancedDeletionTab)); } void FSuperManagerModule::OnDeleteUnusedAsset() { Print(FColor::Green, "Going to FSuperManagerModule::OnDeleteUnusedAsset"); if (selectedPathsToDelete.Num()!=1 ) { ShowMessageDialog(TEXT("Delete Unused asset support one folder path")); return; } TArray<FAssetData> ToRemoveAssetData; TArray<FString> assets = UEditorAssetLibrary::ListAssets(selectedPathsToDelete[0]); for (const auto &assetPath: assets){ Print(FColor::Green, assetPath); if (assetPath.Contains("Developers") or assetPath.Contains("Collections")) continue; if (not UEditorAssetLibrary::DoesAssetExist (assetPath)) continue; // NEED TO SAVE LEVEL, THEN CAN CHECK THE REF const auto &refs = UEditorAssetLibrary::FindPackageReferencersForAsset(assetPath); if ( refs.Num() ==0 ){ // Remove unused asset const auto &assetData = UEditorAssetLibrary::FindAssetData(assetPath); ToRemoveAssetData.Add(assetData); } } if (ToRemoveAssetData.Num() > 0) ObjectTools::DeleteAssets(ToRemoveAssetData); } void FSuperManagerModule::OnDeleteEmptyFolder() { Print(FColor::Green, "Going to FSuperManagerModule::OnDeleteEmptyFolder"); if (selectedPathsToDelete.Num()!=1 ){ ShowMessageDialog(TEXT("Delete Unused asset support one folder path")); return; } const auto assetPaths = UEditorAssetLibrary::ListAssets(selectedPathsToDelete[0], true , true); TArray<FString> messageWillDeletedFolders; FString message; for (const auto &folderPath: assetPaths){ if (folderPath.Contains("Collections")) continue; if (folderPath.Contains("Developers")) continue; if (folderPath.Contains("_ExternalActors_")) continue; if (folderPath.Contains("_ExternalObjects_")) continue; Print(FColor::Green, folderPath + "--->:"+ FString::FromInt(UEditorAssetLibrary::DoesDirectoryExist(folderPath))); if (not UEditorAssetLibrary::DoesDirectoryExist(folderPath) ) continue; Print(FColor::Yellow, folderPath + ":"+ FString::FromInt(UEditorAssetLibrary::DoesDirectoryHaveAssets(folderPath))); const auto internalPaths = UEditorAssetLibrary::ListAssets(folderPath, false , false); if (internalPaths.Num() ==0 ) { messageWillDeletedFolders.Add(folderPath); message.Append(folderPath); message.Append(TEXT("\n")); } if (messageWillDeletedFolders.Num()!= 0 ) { auto retMsgType = ShowMessageDialog(message, EAppMsgType::YesNo); if (retMsgType != EAppReturnType::Yes) return ; for (const auto &toDel: messageWillDeletedFolders){ UEditorAssetLibrary::DeleteAsset(toDel); } } /* ERROR BUG HERE if (not UEditorAssetLibrary::DoesDirectoryHaveAssets(folderPath)) { messageWillDeletedFolders.Add(folderPath); message.Append(folderPath); message.Append(TEXT("\n")); }*/ } } void FSuperManagerModule::InitDeletionTab(){ FGlobalTabmanager::Get()->RegisterNomadTabSpawner(FName("AdvancedDeletionTab"), // ID FOnSpawnTab::CreateLambda([this](const FSpawnTabArgs &args){ return SNew(SDockTab).TabRole(NomadTab)[ SNew(SAdvanceDeletionTab) .TestString("I 'm From SuperManager.cpp") .AssetsData(buildSelectedAssetsData()) ]; }) ) .SetDisplayName(FText::FromString(TEXT("NEXT Advanced Deletion Tab"))) .SetIcon(FSlateIcon(FSuperManagerStyle::StyleSetName, "ContentBrowser.ADT")); } void FSuperManagerModule::OnShowAdvancedDeletionTab(){ FGlobalTabmanager::Get()->TryInvokeTab(FName("AdvancedDeletionTab")); } TArray<AssetDataPtr> FSuperManagerModule::buildSelectedAssetsData() const { TArray<AssetDataPtr> ret; if (selectedPathsToDelete.Num()!=1 ){ ShowMessageDialog(TEXT("Delete Unused asset support one folder path")); return ret; } TArray<FString> assets = UEditorAssetLibrary::ListAssets(selectedPathsToDelete[0]); for (const auto &assetPath: assets){ if (assetPath.Contains("Developers") or assetPath.Contains("Collections")) continue; if (not UEditorAssetLibrary::DoesAssetExist (assetPath)) continue; const auto assetData = UEditorAssetLibrary::FindAssetData(assetPath); ret.Add(MakeShared<FAssetData>(assetData)); } return ret; } bool FSuperManagerModule::removeSingleAsset(FAssetData ad){ return UEditorAssetLibrary::DeleteAsset(ad.GetSoftObjectPath().ToString()); // ForceDelete } bool FSuperManagerModule::removeMultiAssets(TArray<FAssetData> ads) { if (ObjectTools::DeleteAssets(ads) > 0 ) return true; return false; } void FSuperManagerModule::SyncCBToClickedAssetAssetForAssetList(const FString& assetPathToSync){ TArray<FString> assetsPathToSync; assetsPathToSync.Add(assetPathToSync); UEditorAssetLibrary::SyncBrowserToObjects(assetsPathToSync); } void FSuperManagerModule::filterUnusedAssets(const TArray<AssetDataPtr>& inAssets, TArray<AssetDataPtr>& outAssets) { outAssets.Empty(); for (const auto &asset: inAssets){ auto refs = UEditorAssetLibrary::FindPackageReferencersForAsset(asset->GetSoftObjectPath().ToString()); if (refs.IsEmpty()) outAssets.Add(asset); } } #undef LOCTEXT_NAMESPACE IMPLEMENT_MODULE(FSuperManagerModule, SuperManager)
View Code
2: Actor操作 WBP + C++


#pragma once #include "CoreMinimal.h" #include "EditorUtilityWidget.h" #include "QuickActorActionsWidget.generated.h" UENUM(BlueprintType) enum class E_DuplicateType: uint8 { X UMETA(DisplayName = "X offset"), Y UMETA(DisplayName = "Y offset"), Z UMETA(DisplayName = "Z offset"), }; USTRUCT(BlueprintType) struct FRandomActorRotation { GENERATED_BODY(); UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Actor Batch Transform") bool bXRandom{false}; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Actor Batch Transform") bool bYRandom{false}; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Actor Batch Transform") bool bZRandom{true}; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Actor Batch Transform", meta=(EditCondition= "bXRandom")) FVector2f XMinMax{-45,45}; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Actor Batch Transform", meta=(EditCondition= "bYRandom")) FVector2f YMinMax{-45,45}; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Actor Batch Transform", meta=(EditCondition= "bZRandom")) FVector2f ZMinMax{-45, 45}; }; UCLASS() class SUPERMANAGER_API UQuickActorActionsWidget : public UEditorUtilityWidget { GENERATED_BODY() public: UFUNCTION(BlueprintCallable) void SelectActorsWithSameName(); UFUNCTION(BlueprintCallable) void DuplicateActors(); UFUNCTION(BlueprintCallable) void RandomRotationActors(); UFUNCTION(BlueprintCallable) void ResetRotationActors(); UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Actor Batch Duplication") E_DuplicateType DuplicateType = E_DuplicateType::X; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Actor Batch Duplication") int32 CopyNum = 4; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Actor Batch Duplication") float OffsetDist = 300; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "RandomRotationTransform") FRandomActorRotation RandomRotation; private: UPROPERTY() class UEditorActorSubsystem *EditorActorSubsystem; void GetEditorActorSubsystem(); };
cpp:
// Fill out your copyright notice in the Description page of Project Settings. #include "ActorActions/QuickActorActionsWidget.h" #include "DebugHeader.hpp" #include "Subsystems/EditorActorSubsystem.h" void UQuickActorActionsWidget::GetEditorActorSubsystem() { if (not EditorActorSubsystem) EditorActorSubsystem = GEditor->GetEditorSubsystem<UEditorActorSubsystem>(); } void UQuickActorActionsWidget::SelectActorsWithSameName(){ Print(FColor::Black,"start select same name actors"); GetEditorActorSubsystem(); auto actors = EditorActorSubsystem->GetSelectedLevelActors(); if (actors.IsEmpty()){ ShowNotifyInfo("No Selection, Please Select one actor"); return; } if (actors.Num() > 1){ ShowNotifyInfo("Please Select one actor"); return; } FString selectActorLabel = actors[0]->GetActorLabel(); FString nameToSearch = selectActorLabel.LeftChop(3);// 去除尾 Print(FColor::Green,nameToSearch); for (auto *actor: EditorActorSubsystem->GetAllLevelActors()) { if (not actor->GetActorLabel().Contains(nameToSearch)) continue; EditorActorSubsystem->SetActorSelectionState(actor, true); } //EditorActorSubsystem->SetSelectedLevelActors(ActorsToSelect); } void UQuickActorActionsWidget::DuplicateActors(){ auto calOffsetLocation = [this](int incrementNum) { FVector offsetLocation{0,0,0}; switch (DuplicateType) { case E_DuplicateType::X: offsetLocation.X = OffsetDist * (incrementNum+1); break; case E_DuplicateType::Y: offsetLocation.Y = OffsetDist * (incrementNum+1); break; case E_DuplicateType::Z: offsetLocation.Z = OffsetDist * (incrementNum+1); break; default: break; } return offsetLocation; }; for (auto *actor: EditorActorSubsystem->GetSelectedLevelActors()){ for (int i=0;i<CopyNum;i++){ auto *copyedActor = EditorActorSubsystem->DuplicateActor(actor); copyedActor->AddActorLocalOffset(calOffsetLocation(i)); } } } void UQuickActorActionsWidget::RandomRotationActors() { for (auto *actor: EditorActorSubsystem->GetSelectedLevelActors()){ if (RandomRotation.bXRandom) // Roll { auto v = FMath::RandRange(RandomRotation.XMinMax.X, RandomRotation.XMinMax.Y ); actor->AddActorLocalRotation(FRotator{0,v,v}); } if (RandomRotation.bYRandom) // Pitch { auto v = FMath::RandRange(RandomRotation.YMinMax.X, RandomRotation.YMinMax.Y ); actor->AddActorLocalRotation(FRotator{v,v,0}); } if (RandomRotation.bZRandom) // YAW { auto v = FMath::RandRange(RandomRotation.ZMinMax.X, RandomRotation.ZMinMax.Y ); actor->AddActorLocalRotation(FRotator{0,v,0}); } } } void UQuickActorActionsWidget::ResetRotationActors(){ for (auto *actor: EditorActorSubsystem->GetSelectedLevelActors()){ actor->SetActorRotation(FRotator{0,0,0}); } }
3: 子Menu创建:扩展ActorAsset 对着视窗一个Actor鼠标右键:

#pragma once struct BuildActorActions { void Init(); void ShutDown(); private: void InitLevelEditorExtension(); void AddMenuEntry(FMenuBuilder& MainMenu); void buildSubMenu(FMenuBuilder& subMenu); void onLockActorSelectionButtonClicked(); void onUnlockActorSelectionButtonClicked(); // Selection Event void InitCustomSelectionEvent(); };
cpp:
#include "ActorActions/BuildActorActions.h" #include "DebugHeader.hpp" #include "LevelEditor.h" #include "Selection.h" void BuildActorActions::Init() { //GEngine InitCustomSelectionEvent(); InitLevelEditorExtension(); } void BuildActorActions::ShutDown() { } void BuildActorActions::InitLevelEditorExtension() { auto &levelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>(TEXT("LevelEditor")); TArray<FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors> &levelMenuExtenders = levelEditorModule.GetAllLevelViewportContextMenuExtenders(); levelMenuExtenders.Add(FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors::CreateLambda([this](const TSharedRef<FUICommandList> guiCommandList, const TArray<AActor*> SelectedActors) { // Ret: TSharedRef<FExtender> TSharedRef<FExtender> extender = MakeShareable(new FExtender()); if (SelectedActors.IsEmpty()) return extender; extender->AddMenuExtension(FName{"ActorOptions"}, EExtensionHook::Before, guiCommandList,FMenuExtensionDelegate::CreateRaw(this,&BuildActorActions::AddMenuEntry) ); return extender; })); } void BuildActorActions::AddMenuEntry(FMenuBuilder& MainMenu) { /* * NSLOCTEXT( Namespace, Key, DefaultText ) * Namespace(命名空间):用于组织文本的逻辑分组,避免不同模块间的 Key 冲突(通常使用模块名或功能名)。 Key(键):该文本的唯一标识符,在同一命名空间下必须唯一。 DefaultText(默认文本):开发时使用的源语言文本(例如英语),在翻译文件中会以此为基础生成其他语言的版本。 * */ MainMenu.AddSubMenu(NSLOCTEXT("BuildActorActions", "Actor Action Extensions", "Actor Action Extensions"), NSLOCTEXT("BuildActorActions", "SubMenu1Tooltip", "First sub action"), FNewMenuDelegate::CreateRaw(this, &BuildActorActions::buildSubMenu) ); } void BuildActorActions::buildSubMenu(FMenuBuilder& subMenu) { subMenu.AddMenuEntry(NSLOCTEXT("BuildActorActions", "Lock Actor Selection", "Lock Actor Selection"), NSLOCTEXT("BuildActorActions", "Command1Tooltip", "Execute Command A"), FSlateIcon(), FExecuteAction::CreateRaw(this, &BuildActorActions::onLockActorSelectionButtonClicked) ); subMenu.AddMenuEntry(NSLOCTEXT("BuildActorActions", "Unlock Actor Selection", "Unlock Actor Selection"), NSLOCTEXT("BuildActorActions", "Unlock Actor Selection", "Execute Command A"), FSlateIcon(), FExecuteAction::CreateRaw(this, &BuildActorActions::onUnlockActorSelectionButtonClicked) ); } void BuildActorActions::onLockActorSelectionButtonClicked(){ Print(FColor::Green, "Locked"); } void BuildActorActions::onUnlockActorSelectionButtonClicked(){ Print(FColor::Green, "UnLocked"); } void BuildActorActions::InitCustomSelectionEvent() { USelection *UserSelection = GEditor->GetSelectedActors(); UserSelection->SelectObjectEvent.AddLambda([](UObject* selectObj){ Print(FColor::Green, selectObj->GetName()); }); }
4: 修改曲线Visualizer


先把build改下PostEngineInit
{ "FileVersion": 3, "Version": 1, "VersionName": "1.0", "FriendlyName": "SuperManager", "Description": "", "Category": "Other", "CreatedBy": "liuyangping", "CreatedByURL": "", "DocsURL": "", "MarketplaceURL": "", "SupportURL": "", "CanContainContent": true, "IsBetaVersion": false, "IsExperimentalVersion": false, "Installed": false, "Modules": [ { "Name": "SuperManager", "Type": "Editor", "LoadingPhase": "PostEngineInit" } ] }

CooperSpline:

#pragma once #include "CoreMinimal.h" #include "Components/SplineComponent.h" #include "CooperSpline.generated.h" UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) class SUPERMANAGER_API UCooperSpline : public USplineComponent { GENERATED_BODY() public: // Sets default values for this component's properties UCooperSpline(); protected: // Called when the game starts virtual void BeginPlay() override; public: // Called every frame virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; };
View Code
CooperSplineVisualizer:
#include "CoreMinimal.h" #include "Editor/ComponentVisualizers/Public/SplineComponentVisualizer.h" class CooperSplineVisualizer: public FSplineComponentVisualizer { public: void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override; };
#include "CooperGeomtry/CooperSplineVisualizer.h" #include "CooperGeomtry/CooperSpline.h" void CooperSplineVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) { // 1. 必须先调用父类的绘制,否则原本的路径线会消失 FSplineComponentVisualizer::DrawVisualization(Component, View, PDI); // 2. 转换成你的自定义类 const UCooperSpline* MySpline = Cast<UCooperSpline>(Component); if (!MySpline) return; // 3. 打印日志确认是否运行到这里 //UE_LOG(LogTemp, Warning, TEXT("CooperSpline Visualizer is Drawing! Points: %d"), MySpline->GetNumberOfSplinePoints()); for (int32 i = 0; i < MySpline->GetNumberOfSplinePoints(); i++) { FVector p = MySpline->GetLocationAtSplinePoint(i, ESplineCoordinateSpace::World); // 4. 将半径改大到 100.f,颜色改成最扎眼的红色,防止太细看不见 DrawWireSphere(PDI, p, FColor::Red, 10.0f, 10, SDPG_Foreground); } }
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。