《调教UE5:编辑器拓展指南》自定义世界大纲

您所在的位置:网站首页 ideasvn忽略target文件夹 《调教UE5:编辑器拓展指南》自定义世界大纲

《调教UE5:编辑器拓展指南》自定义世界大纲

2023-03-16 18:03| 来源: 网络整理| 查看: 265

《调教UE5》系列记录的是笔者在开发UE引擎中总结的些许经验。文中所有观点和结论仅代表个人见解,作为自学笔记和日后反思之参考,亦可能存在谬误或过时之处。如有错漏和不当,恳请多加指正。目录不可忽略的事前准备 ~ ㅤ创建 ExtendSceneOutliner 模块ㅤ创建 ExtendSceneOutlinerStyle 样式集注册 ISceneOutlinerColumn ~ ㅤ创建 SceneOutlinerLockColumn 类ㅤ注册 SceneOutlinerLockColumn 类实现 ConstructRowWidget ~ 程序示例 ~ ㅤ锁定 Actor 移动ㅤㅤ单件锁定移动ㅤㅤ批量锁定移动ㅤ锁定 Actor 选择ㅤㅤ单件锁定选择ㅤㅤ批量锁定选择

\large\textbf{若是繁星的孩子,定不会被这点须臾的小事难倒。} \\

本章将探索自定义世界大纲的方法。有关拓展世界大纲模块的方法相关的资料较少,笔者仅从实用的角度编写此章,尚未深度探索源码。在此总结的方法难免存在疏漏,权作抛砖引玉之能效,如有理解不当之处,恳请多加指正。

不可忽略的事前准备 ~ ♡

在正式开始之前,我们来进行一些不可或缺的预前准备。

目前,本指南基于UE5.0.3引擎版本进行编写,读者需前往下载对应的引擎版本,以免在尝试过程中因引擎版本差异造成不必要的麻烦。

为了拥有相对易读的内容,我们来为本章内容准备一个独立的模块。

创建 ExtendSceneOutliner 模块

在项目 Source 文件夹下新建“ExtendSceneOutliner”文件夹,并组织如下文件结构。我们不打算将此模块用于其他模块中,因此可以不需要“Public”和“Private”文件夹。

来到 ExtendSceneOutliner.h 中声明模块。

// ExtendSceneOutliner.h #pragma once #include "Modules/ModuleInterface.h" class FExtendSceneOutliner : public IModuleInterface { public: virtual void StartupModule() override; virtual void ShutdownModule() override; virtual ~FExtendSceneOutliner() {} };

来到 ExtendSceneOutliner.cpp 中实现模块。

// ExtendSceneOutliner.cpp #pragma once #include "ExtendSceneOutliner.h" IMPLEMENT_MODULE(FExtendSceneOutliner, ExtendSceneOutliner) void FExtendSceneOutliner::StartupModule() { IModuleInterface::StartupModule(); } void FExtendSceneOutliner::ShutdownModule() { IModuleInterface::ShutdownModule(); }

来到 ExtendSceneOutliner.Build.cs 中设置模块依赖项。

// ExtendSceneOutliner.Build.cs using UnrealBuildTool; public class ExtendSceneOutliner : ModuleRules { public ExtendSceneOutliner(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "SceneOutliner", "Slate", "SlateCore", "UnrealEd" }); PrivateDependencyModuleNames.AddRange(new string[] { }); } }

来到 Editor.Target.cs 文件中,添加项目模块依赖。此处我们的项目名为“ExtendEditor”。

// ExtendEditorEditor.Target.cs using UnrealBuildTool; using System.Collections.Generic; public class ExtendEditorEditorTarget : TargetRules { public ExtendEditorEditorTarget( TargetInfo Target) : base(Target) { Type = TargetType.Editor; DefaultBuildSettings = BuildSettingsVersion.V2; ExtraModuleNames.AddRange( new string[] { "ExtendEditor", "ExtendSceneOutliner" } ); } }

来到 .uproject 文件中,配置模块的启动方式。

// ExtendEditor.uproject { "FileVersion": 3, "EngineAssociation": "5.0", "Category": "", "Description": "", "Modules": [ { "Name": "ExtendEditor", "Type": "Runtime", "LoadingPhase": "Default" }, { "Name": "ExtendSceneOutliner", "Type": "Editor", "LoadingPhase": "Default" } ], "Plugins": [ { "Name": "ModelingToolsEditorMode", "Enabled": true, "TargetAllowList": [ "Editor" ] } ] }

重启代码编辑器并编译,完成“FExtendSceneOutliner”模块创建。

{♡☘♡☘♡ \quad今天的捉弄结束了 \quad ♡☘♡☘♡}\\

创建 ExtendSceneOutlinerStyle 样式集

接着来创建一个样式集,以供在 SceneOutliner 的界面上显示图标。

可以在 《调教UE5:编辑器拓展指南》编辑器拓展基础 中的“FSlateStyleSet”小节查看有关样式集的详细说明,此处不再重复展开。

在 ExtendSceneOutliner 文件夹下新建文件 ExtendSceneOutlinerStyle.h 和 ExtendSceneOutlinerStyle.cpp。

来到 ExtendSceneOutlinerStyle.h 中声明 FExtendSceneOutlinerStyle 类。

// ExtendSceneOutlinerStyle.h #pragma once class FExtendSceneOutlinerStyle { public: // 模块启动时,注册该样式集到中央管理库 static void Initialize(); static FName GetStyleSetName(); static TSharedPtr GetStyleSet(); private: static TSharedRef CreateSlateStyleSet(); private: inline static TSharedPtr StyleSet = nullptr; inline static const FName StyleSetName = FName("ExtendSceneOutlinerStyle"); };

来到 ExtendSceneOutlinerStyle.cpp 中实现 FExtendSceneOutlinerStyle 类。我们将图标资源放在项目文件夹下的“Resource”文件夹下。该文件夹需要自己创建。

// ExtendSceneOutlinerStyle.cpp #pragma once #include "ExtendSceneOutlinerStyle.h" #include "Styling/SlateStyle.h" #include "Styling/SlateStyleRegistry.h" #include "Styling/StyleColors.h" FName FExtendSceneOutlinerStyle::GetStyleSetName() { return StyleSetName; } TSharedPtr FExtendSceneOutlinerStyle::GetStyleSet() { return StyleSet; } void FExtendSceneOutlinerStyle::Initialize() { if(!StyleSet.IsValid()) { StyleSet = CreateSlateStyleSet(); FSlateStyleRegistry::RegisterSlateStyle(*StyleSet); } } TSharedRef FExtendSceneOutlinerStyle::CreateSlateStyleSet() { TSharedRef SlateStyleSet = MakeShareable(new FSlateStyleSet(StyleSetName)); const FString RootPath = FPaths::ProjectDir() + TEXT("/Resource/"); SlateStyleSet->SetContentRoot(RootPath); { const FVector2D IconeSize(16.f, 16.f); FSlateImageBrush* SlateImageBrush = new FSlateImageBrush(RootPath + TEXT("Lock.png"), IconeSize); SlateStyleSet->Set("SceneOutliner.Lock", SlateImageBrush); } { const FVector2D IconeSize(16.f, 16.f); FSlateImageBrush* SlateImageBrush = new FSlateImageBrush(RootPath + TEXT("Unlock.png"), IconeSize); SlateStyleSet->Set("SceneOutliner.Unlock", SlateImageBrush); } { const FVector2D IconeSize(16.f, 16.f); const FCheckBoxStyle SelectionLockToggleButtonStyle = FCheckBoxStyle() .SetCheckBoxType(ESlateCheckBoxType::ToggleButton) .SetPadding(FMargin(10.f)) .SetUncheckedImage(FSlateImageBrush(RootPath + TEXT("/Unlock.png"), IconeSize, FStyleColors::White25)) .SetUncheckedHoveredImage(FSlateImageBrush(RootPath + TEXT("/Unlock.png"), IconeSize, FStyleColors::AccentBlue)) .SetUncheckedPressedImage(FSlateImageBrush(RootPath + TEXT("/Unlock.png"), IconeSize, FStyleColors::Foreground)) .SetCheckedImage(FSlateImageBrush(RootPath + TEXT("/Lock.png"), IconeSize, FStyleColors::Foreground)) .SetCheckedHoveredImage(FSlateImageBrush(RootPath + TEXT("/Lock.png"), IconeSize, FStyleColors::AccentBlack)) .SetCheckedPressedImage(FSlateImageBrush(RootPath + TEXT("/Lock.png"), IconeSize, FStyleColors::AccentGray)); SlateStyleSet->Set("SceneOutliner.LockToggle", SelectionLockToggleButtonStyle); } return SlateStyleSet; }

来到 ExtendSceneOutliner.cpp 的模块启动函数,将样式集初始化函数添加到其中。

// ExtendSceneOutliner.cpp #include "ExtendSceneOutlinerStyle.h" void FExtendSceneOutliner::StartupModule() { FExtendSceneOutlinerStyle::Initialize(); }

至此我们的准备工作就完成了。

{♡☘♡☘♡ \quad今天的捉弄结束了 \quad ♡☘♡☘♡}\\

注册 ISceneOutlinerColumn ~ ♡

SceneOutliner 的拓展是以“列”为单位进行的。ISceneOutlinerColumn 是我们用于拓展的接口,它允许我们以列为单位拓展 SceneOutliner 上的 Widget。

为了实现拓展,我们需要创建一个继承自 ISceneOutlinerColumn 的自定义类,并实现一些必须的函数。然后,我们将这个自定义类注册到 SceneOutlinerModule 管理的 ColumnMap 中。

创建 SceneOutlinerLockColumn 类

首先来创建 FSceneOutlinerLockColumn 类。在 ExtendSceneOutliner 文件夹下新建文件 SceneOutlinerLockColumn.h 和 SceneOutlinerLockColumn.cpp。

来到 SceneOutlinerLockColumn.h 中,声明 FSceneOutlinerLockColumn 类。

FSceneOutlinerLockColumn 需要包含三个必要的重载函数,和一个构造函数。但除此之外我们还需要提供一个 GetID() 静态函数,否则在注册 FSceneOutlinerLockColumn 会提示找不到该函数。

ConstructHeaderRowColumn() 和 ConstructRowWidget() 是两个重要函数。

ConstructHeaderRowColumn() 帮助我们在标题头生成新的 Widget。ConstructRowWidget() 帮助我们在每个项目行生成新的 Widget。

// SceneOutlinerLockColumn.h #pragma once #include "ISceneOutlinerColumn.h" class FSceneOutlinerLockColumn : public ISceneOutlinerColumn { public: FSceneOutlinerLockColumn(ISceneOutliner& SceneOutliner) {} static FName GetID() {return FName("SceneOutlinerExtendColumn");} virtual FName GetColumnID() override {return GetID();} // 在标题头添加新 Widget virtual SHeaderRow::FColumn::FArguments ConstructHeaderRowColumn() override; // 在项目行添加新 Widget virtual const TSharedRef ConstructRowWidget(FSceneOutlinerTreeItemRef TreeItem, const STableRow& Row) override; };

来到 SceneOutlinerLockColumn.cpp 实现 FSceneOutlinerLockColumn 类。

我们在标题头处添加一个 SImage 小部件,并在每个项目行暂时添加一个空的小部件。

// SceneOutlinerLockColumn.cpp #pragma once #include "SceneOutlinerLockColumn.h" #include "ExtendSceneOutlinerStyle.h" #include "Styling/SlateStyle.h" SHeaderRow::FColumn::FArguments FSceneOutlinerLockColumn::ConstructHeaderRowColumn() { SHeaderRow::FColumn::FArguments ConstructedHeaderRowColumn = SHeaderRow::Column(GetColumnID()) .FixedWidth(24.f) .HAlignHeader(HAlign_Center) .VAlignHeader(VAlign_Center) .HAlignCell(HAlign_Center) .VAlignCell(VAlign_Center) .DefaultTooltip(FText::FromString(TEXT("Lock the transformation of actor"))) [ SNew(SImage) .ColorAndOpacity(FSlateColor::UseForeground()) .Image(FExtendSceneOutlinerStyle::GetStyleSet().Get()->GetBrush("SceneOutliner.Lock")) ]; return ConstructedHeaderRowColumn; } const TSharedRef FSceneOutlinerLockColumn::ConstructRowWidget(FSceneOutlinerTreeItemRef TreeItem, const STableRow& Row) { // 暂时返回一个空 Widget return SNullWidget::NullWidget; }

注册 SceneOutlinerLockColumn 类

然后我们来到 ExtendSceneOutliner 文件中为这个 Column 进行注册。

我们要将 FSceneOutlinerColumnInfo 添加到 SceneOutlinerModule 管理的 ColumnMap 中。FSceneOutlinerColumnInfo 记录了该 Column 的可见性,应该出现的位置等等。

// ExtendSceneOutliner.h #pragma once #include "Modules/ModuleInterface.h" class FExtendSceneOutliner : public IModuleInterface { public: virtual void StartupModule() override; virtual void ShutdownModule() override; virtual ~FExtendSceneOutliner() {} private: void InitSceneOutlinerColumn(); void UninitSceneOutlinerColumn(); TSharedRef OnCreateSceneOutlinerLockColumnInfo(class ISceneOutliner& SceneOutliner); };

// ExtendSceneOutliner.cpp #include "Modules/ModuleManager.h" #include "SceneOutlinerModule.h" #include "SceneOutlinerLockColumn.h" void FExtendSceneOutliner::StartupModule() { IModuleInterface::StartupModule(); FExtendSceneOutlinerStyle::Initialize(); InitSceneOutlinerColumn(); } void FExtendSceneOutliner::ShutdownModule() { IModuleInterface::ShutdownModule(); UninitSceneOutlinerColumn(); FExtendSceneOutlinerStyle::Uninitialize(); } void FExtendSceneOutliner::InitSceneOutlinerColumn() { FSceneOutlinerColumnInfo SceneOutlinerLockColumnInfo( ESceneOutlinerColumnVisibility::Visible, // 可见性 1, // Column 出现的位置 FCreateSceneOutlinerColumn::CreateRaw( this, &FExtendSceneOutliner::OnCreateSceneOutlinerLockColumnInfo) ); FSceneOutlinerModule& SceneOutlinerModule = FModuleManager::LoadModuleChecked(TEXT("SceneOutliner")); SceneOutlinerModule.RegisterDefaultColumnType(SceneOutlinerLockColumnInfo); } TSharedRef FExtendSceneOutliner::OnCreateSceneOutlinerLockColumnInfo( ISceneOutliner& SceneOutliner) { return MakeShareable(new FSceneOutlinerLockColumn(SceneOutliner)); } void FExtendSceneOutliner::UninitSceneOutlinerColumn() { FSceneOutlinerModule& SceneOutlinerModule = FModuleManager::LoadModuleChecked(TEXT("SceneOutliner")); SceneOutlinerModule.UnRegisterColumnType(); }

编译并重启编辑器,可以看到在 SceneOutliner 上添加了一个小图标。

{♡☘♡☘♡ \quad今天的捉弄结束了 \quad ♡☘♡☘♡}\\

实现 ConstructRowWidget ~ ♡

先来实现一个简单的 ConstructRowWidget。我们的目标是添加一个切换开关按钮,当按钮在两个状态之间切换时打印不同的提示消息,并注明是哪一个 Actor 上的开关发生了变化。

首先,我们要使用 ConstructRowWidget() 传入的 FSceneOutlinerTreeItemRef 参数,它的类型是 ISceneOutlinerTreeItem。

它是一个树状结构节点,有以下这些子类。

我们要从输入中检索 Actor 对象,因此先将其转换为 FActorTreeItem。转换完毕之后还不能直接使用,否则在引擎初始化阶段还没有任何具体项目,会造成空指针错误,所以需要检查一下是否转换成功,否则就返回一个空 Widget。

// SceneOutlinerLockColumn.cpp const TSharedRef FSceneOutlinerLockColumn::ConstructRowWidget(FSceneOutlinerTreeItemRef TreeItem, const STableRow& Row) { FActorTreeItem* ActorTreeItem = TreeItem->CastTo(); // 检查是否转换成功 if(!ActorTreeItem || !ActorTreeItem->IsValid()) return SNullWidget::NullWidget; const FCheckBoxStyle& ToggleButtonStyle = FExtendSceneOutlinerStyle::GetStyleSet()->GetWidgetStyle( FName("SceneOutliner.LockToggle")); TSharedRef ConstructedRowWidget = SNew(SCheckBox) .Visibility(EVisibility::Visible) .Type(ESlateCheckBoxType::ToggleButton) .Style(&ToggleButtonStyle) .HAlign(HAlign_Center) .IsChecked(ECheckBoxState::Unchecked) .OnCheckStateChanged( this, &FSceneOutlinerLockColumn::OnLockToggleStateChanged, ActorTreeItem->Actor); return ConstructedRowWidget; } void FSceneOutlinerLockColumn::OnLockToggleStateChanged(ECheckBoxState NewState, TWeakObjectPtr Actor) { if(NewState == ECheckBoxState::Checked) { FString ActorName = Actor.Get()->GetActorLabel(); GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Cyan, TEXT("Locked ") + ActorName); return; } if(NewState == ECheckBoxState::Unchecked) { FString ActorName = Actor.Get()->GetActorLabel(); GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Cyan, TEXT("Unlock ") + ActorName); return; } return; }

// SceneOutlinerLockColumn.h #pragma once #include "ISceneOutlinerColumn.h" class FSceneOutlinerLockColumn : public ISceneOutlinerColumn { ... private: void OnLockToggleStateChanged(ECheckBoxState NewState, TWeakObjectPtr Actor); };

编译并重启编辑器,可以看到相应项目类型的项目行按钮已经生成了。

{♡☘♡☘♡ \quad今天的捉弄结束了 \quad ♡☘♡☘♡}\\

程序示例 ~ ♡

下面我们通过两个示例来演示 SceneOutliner 如何与场景内容互动,以及如何刷新 SceneOutliner 状态。

锁定 Actor 移动

我们希望通过大纲视图的按钮来快速锁定 Actor 移动,而不必每次通过 Transform 次级菜单来寻找 LockMovement 选项。

单件锁定移动

先仅考虑单个按钮的情况,当我们点击某个 FActorTreeItem 时,希望能够锁定该 Actor 的移动。

来到 SceneOutlinerLockColumn.h 中,添加一个新的函数。

// SceneOutlinerLockColumn.h #pragma once #include "ISceneOutlinerColumn.h" class FSceneOutlinerLockColumn : public ISceneOutlinerColumn { ... private: void OnLockToggleStateChanged(ECheckBoxState NewState, TWeakObjectPtr Actor); // 替代上面事件的新函数 void OnLockToggleStateChanged_LockMovement_SelectedActor(ECheckBoxState NewState, TWeakObjectPtr Actor); };

来到 SceneOutlinerLockColumn.cpp 中实现新函数。在锁定或解锁一个 Actor 后,还需要刷新视口中 Actor 的选择状态以应用修改。

// SceneOutlinerLockColumn.cpp #pragma once #include "SceneOutlinerLockColumn.h" #include "ExtendSceneOutlinerStyle.h" #include "Styling/SlateStyle.h" #include "ActorTreeItem.h" #include "Subsystems/EditorActorSubsystem.h" const TSharedRef FSceneOutlinerLockColumn::ConstructRowWidget(FSceneOutlinerTreeItemRef TreeItem, const STableRow& Row) { FActorTreeItem* ActorTreeItem = TreeItem->CastTo(); if(!ActorTreeItem || !ActorTreeItem->IsValid()) return SNullWidget::NullWidget; // 检测 Actor 锁定移动的状态 const bool bIsActorLocked = ActorTreeItem->Actor->IsLockLocation(); const FCheckBoxStyle& ToggleButtonStyle = FExtendSceneOutlinerStyle::GetStyleSet()->GetWidgetStyle( FName("SceneOutliner.LockToggle")); TSharedRef ConstructedRowWidget = SNew(SCheckBox) .Visibility(EVisibility::Visible) .Type(ESlateCheckBoxType::ToggleButton) .Style(&ToggleButtonStyle) .HAlign(HAlign_Center) .IsChecked(bIsActorLocked ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) .OnCheckStateChanged( this, // 替换为新函数 &FSceneOutlinerLockColumn::OnLockToggleStateChanged_LockMovement_SelectedActor, ActorTreeItem->Actor); return ConstructedRowWidget; } void FSceneOutlinerLockColumn::OnLockToggleStateChanged_LockMovement_SelectedActor(ECheckBoxState NewState, TWeakObjectPtr Actor) { UEditorActorSubsystem* EditorActorSubsystem = GEditor->GetEditorSubsystem(); if(NewState == ECheckBoxState::Checked) { // 锁定 Actor 移动 Actor->SetLockLocation(true); // 刷新 Actor 选择状态 EditorActorSubsystem->SetActorSelectionState(Actor.Get(), false); EditorActorSubsystem->SetActorSelectionState(Actor.Get(), true); return; } if(NewState == ECheckBoxState::Unchecked) { // 解锁 Actor 移动 Actor->SetLockLocation(false); // 刷新 Actor 选择状态 EditorActorSubsystem->SetActorSelectionState(Actor.Get(), false); EditorActorSubsystem->SetActorSelectionState(Actor.Get(), true); return; } return; }

编译并重启编辑器,测试按钮效果。

{♡☘♡☘♡ \quad今天的捉弄结束了 \quad ♡☘♡☘♡}\\

批量锁定移动

现在更进一步,我们希望实现批量选择的锁定移动。通过获取场景中的 ActorSelection,我们能够得到当前选中 Actor 的选择集,然后依次对每一个 Actor 进行设定操作。

一个新的问题是,此前在单件锁定中我们通过点击按钮就可以切换对应 FActorTreeItem 的锁定图标,但我们无法在同一时间逐个点击那么多个按钮,因此需要在点击操作时触发一次 SceneOutliner 刷新,并在ConstructRowWidget() 执行时自动检测 Actor 的锁定状态,并设置相应的按钮图标。

// SceneOutlinerLockColumn.h #pragma once #include "ISceneOutlinerColumn.h" class FSceneOutlinerLockColumn : public ISceneOutlinerColumn { ... private: void OnLockToggleStateChanged(ECheckBoxState NewState, TWeakObjectPtr Actor); void OnLockToggleStateChanged_LockMovement_SelectedActor(ECheckBoxState NewState, TWeakObjectPtr Actor); // 替代上面事件的新函数 void OnLockToggleStateChanged_LockMovement_SelectedActors(ECheckBoxState NewState, TWeakObjectPtr Actor); };

// SceneOutlinerLockColumn.cpp #pragma once #include "SceneOutlinerLockColumn.h" #include "ExtendSceneOutlinerStyle.h" #include "Styling/SlateStyle.h" #include "ActorTreeItem.h" #include "Subsystems/EditorActorSubsystem.h" #include "Selection.h" #include "LevelEditor.h" #include "ISceneOutliner.h" const TSharedRef FSceneOutlinerLockColumn::ConstructRowWidget(FSceneOutlinerTreeItemRef TreeItem, const STableRow& Row) { FActorTreeItem* ActorTreeItem = TreeItem->CastTo(); if(!ActorTreeItem || !ActorTreeItem->IsValid()) return SNullWidget::NullWidget; // 检查 Actor 的锁定状态 const bool bIsActorLocked = ActorTreeItem->Actor->IsLockLocation(); const FCheckBoxStyle& ToggleButtonStyle = FExtendSceneOutlinerStyle::GetStyleSet()->GetWidgetStyle( FName("SceneOutliner.LockToggle")); TSharedRef ConstructedRowWidget = SNew(SCheckBox) .Visibility(EVisibility::Visible) .Type(ESlateCheckBoxType::ToggleButton) .Style(&ToggleButtonStyle) .HAlign(HAlign_Center) // 通过检查到的状态设置图标 .IsChecked(bIsActorLocked ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) .OnCheckStateChanged( this, // 替换为新函数 &FSceneOutlinerLockColumn::OnLockToggleStateChanged_LockMovement_SelectedActors, ActorTreeItem->Actor); return ConstructedRowWidget; } void FSceneOutlinerLockColumn::OnLockToggleStateChanged_LockMovement_SelectedActors(ECheckBoxState NewState, TWeakObjectPtr Actor) { FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked(TEXT("LevelEditor")); TSharedPtr SceneOutliner = LevelEditorModule.GetFirstLevelEditor()->GetSceneOutliner(); UEditorActorSubsystem* EditorActorSubsystem = GEditor->GetEditorSubsystem(); // 获取 ActorSelection USelection* SelectedActors = GEditor->GetSelectedActors(); if(NewState == ECheckBoxState::Checked) { // 如果没有选中任何 Actor,仅发生了按钮的点击事件 if(SelectedActors->Num() == 0) { Actor->SetLockLocation(true); EditorActorSubsystem->SetActorSelectionState(Actor.Get(), false); EditorActorSubsystem->SetActorSelectionState(Actor.Get(), true); } else { for(FSelectionIterator It(*SelectedActors); It; ++It) { AActor* SelectedActor = Cast( *It ); SelectedActor->SetLockLocation(true); } for(FSelectionIterator It(*SelectedActors); It; ++It) { AActor* SelectedActor = Cast( *It ); EditorActorSubsystem->SetActorSelectionState(SelectedActor, false); EditorActorSubsystem->SetActorSelectionState(SelectedActor, true); } } // 设置完毕后刷新 SceneOutliner 重新生成图标 if(SceneOutliner.IsValid()) { SceneOutliner->FullRefresh(); } return; } if(NewState == ECheckBoxState::Unchecked) { if(SelectedActors->Num() == 0) { Actor->SetLockLocation(false); EditorActorSubsystem->SetActorSelectionState(Actor.Get(), false); EditorActorSubsystem->SetActorSelectionState(Actor.Get(), true); } else { for(FSelectionIterator It(*SelectedActors); It; ++It) { AActor* SelectedActor = Cast( *It ); SelectedActor->SetLockLocation(false); } for(FSelectionIterator It(*SelectedActors); It; ++It) { AActor* SelectedActor = Cast( *It ); EditorActorSubsystem->SetActorSelectionState(SelectedActor, false); EditorActorSubsystem->SetActorSelectionState(SelectedActor, true); } } if(SceneOutliner.IsValid()) { SceneOutliner->FullRefresh(); } return; } return; }

编译并重启编辑器,测试按钮效果。

{♡☘♡☘♡ \quad今天的捉弄结束了 \quad ♡☘♡☘♡}\\

锁定 Actor 选择

有时候,我们希望在选择物体或者使用 Ctrl+Alt+鼠标左键框选 Actor 的时候,避免选中特定的对象。

我们来制作一个简易的锁定选择工具。

这里实现锁定选择的逻辑是通过 Actor 的 Tags 关键字。当我们希望禁用一个 Actor 的选择时,首先为这个 Actor 添加“Locked”关键字,随后,在选择事件中检查每个 Actor 的 Tags 是否存在“Locked”,如果存在则立即取消该 Actor 选择。

我们应该将这个检测的逻辑封装在一个单独的类中,但此处为了简便,就直接将逻辑写在 ExtendSceneOutliner 模块中。

来到 ExtendSceneOutliner.h 中,添加相关函数声明和变量声明。

// ExtendSceneOutliner.h class FExtendSceneOutliner : public IModuleInterface { private: void InitCustomSelectionEvent(); void UninitCustomSelectionEvent(); void OnActorSelected(UObject* SelectedObject); bool GetEditorActorSubsystem(); public: bool CheckIsActorSelectionLocked(AActor* SelectedActor); void LockActorSelection(AActor* ActorToProcess); void UnlockActorSelection(AActor* ActorToProcess); void SetActorSelectionState(AActor* Actor, bool bShouldBeSelected); private: TWeakObjectPtr WeakEditorActorSubsystem; FDelegateHandle SelectObjectEventHandle; };

来到 ExtendSceneOutliner.cpp 中,实现相关函数。

这里的关键函数是 InitCustomSelectionEvent() 和 OnActorSelected(),前者负责将选择事件注册到 USelection::SelectObjectEvent 的回调中。

接着,我们就可以在其他地方利用 LockActorSelection() 和 UnlockActorSelection() 为 Actor 设置 Tags,利用 SetActorSelectionState() 来为 Actor 设置选定状态。

// ExtendSceneOutliner.cpp #include "Selection.h" #include "Subsystems/EditorActorSubsystem.h" void FExtendSceneOutliner::InitCustomSelectionEvent() { USelection* UserSelection = GEditor->GetSelectedActors(); SelectObjectEventHandle = UserSelection->SelectObjectEvent.AddRaw(this, &FExtendSceneOutliner::OnActorSelected); } void FExtendSceneOutliner::UninitCustomSelectionEvent() { USelection* UserSelection = GEditor->GetSelectedActors(); UserSelection->SelectObjectEvent.Remove(SelectObjectEventHandle); } void FExtendSceneOutliner::OnActorSelected(UObject* SelectedObject) { if(!GetEditorActorSubsystem()) return; if(AActor* SelectedActor = Cast(SelectedObject)) { if(CheckIsActorSelectionLocked(SelectedActor)) { WeakEditorActorSubsystem.Get()->SetActorSelectionState(SelectedActor, false); } } } bool FExtendSceneOutliner::GetEditorActorSubsystem() { if(!WeakEditorActorSubsystem.IsValid()) { WeakEditorActorSubsystem = GEditor->GetEditorSubsystem(); } return WeakEditorActorSubsystem.IsValid(); } bool FExtendSceneOutliner::CheckIsActorSelectionLocked(AActor* SelectedActor) { if(!SelectedActor) return false; return SelectedActor->ActorHasTag(FName("Locked")); } void FExtendSceneOutliner::LockActorSelection(AActor* ActorToProcess) { if(!ActorToProcess) return; if(!ActorToProcess->ActorHasTag(FName("Locked"))) { ActorToProcess->Tags.Add(FName("Locked")); } } void FExtendSceneOutliner::UnlockActorSelection(AActor* ActorToProcess) { if(!ActorToProcess) return; if(ActorToProcess->ActorHasTag(FName("Locked"))) { ActorToProcess->Tags.Remove(FName("Locked")); } } void FExtendSceneOutliner::SetActorSelectionState(AActor* Actor, bool bShouldBeSelected) { WeakEditorActorSubsystem->SetActorSelectionState(Actor, bShouldBeSelected); }

单件锁定选择

首先依旧来考虑相对简单的情况。在 SceneOutlinerLockColumn.h 中添加函数。

// SceneOutlinerLockColumn.h #pragma once #include "ISceneOutlinerColumn.h" class FSceneOutlinerLockColumn : public ISceneOutlinerColumn { ... private: void OnLockToggleStateChanged(ECheckBoxState NewState, TWeakObjectPtr Actor); void OnLockToggleStateChanged_LockMovement_SelectedActor(ECheckBoxState NewState, TWeakObjectPtr Actor); void OnLockToggleStateChanged_LockMovement_SelectedActors(ECheckBoxState NewState, TWeakObjectPtr Actor); // 替代上面事件的新函数 void OnLockToggleStateChanged_LockSelection_SelectedActor(ECheckBoxState NewState, TWeakObjectPtr Actor); };

// SceneOutlinerLockColumn.cpp #pragma once #include "SceneOutlinerLockColumn.h" #include "ExtendSceneOutlinerStyle.h" #include "Styling/SlateStyle.h" #include "ActorTreeItem.h" #include "Subsystems/EditorActorSubsystem.h" #include "Selection.h" #include "LevelEditor.h" #include "ISceneOutliner.h" #include "ExtendSceneOutliner.h" const TSharedRef FSceneOutlinerLockColumn::ConstructRowWidget(FSceneOutlinerTreeItemRef TreeItem, const STableRow& Row) { FActorTreeItem* ActorTreeItem = TreeItem->CastTo(); if(!ActorTreeItem || !ActorTreeItem->IsValid()) return SNullWidget::NullWidget; // 此处的检测条件发生了变化,我们检测 Actor 是否携带对应的 Tags FExtendSceneOutliner& ExtendSceneOutlinerModule = FModuleManager::LoadModuleChecked(TEXT("ExtendSceneOutliner")); const bool bIsActorLocked = ExtendSceneOutlinerModule.CheckIsActorSelectionLocked(ActorTreeItem->Actor.Get()); const FCheckBoxStyle& ToggleButtonStyle = FExtendSceneOutlinerStyle::GetStyleSet()->GetWidgetStyle( FName("SceneOutliner.LockToggle")); TSharedRef ConstructedRowWidget = SNew(SCheckBox) .Visibility(EVisibility::Visible) .Type(ESlateCheckBoxType::ToggleButton) .Style(&ToggleButtonStyle) .HAlign(HAlign_Center) .IsChecked(bIsActorLocked ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) .OnCheckStateChanged( this, &FSceneOutlinerLockColumn::OnLockToggleStateChanged_LockSelection_SelectedActor, ActorTreeItem->Actor); return ConstructedRowWidget; } void FSceneOutlinerLockColumn::OnLockToggleStateChanged_LockSelection_SelectedActor(ECheckBoxState NewState, TWeakObjectPtr Actor) { FExtendSceneOutliner& ExtendSceneOutlinerModule = FModuleManager::LoadModuleChecked(TEXT("ExtendSceneOutliner")); // 当开关状态发生改变时,执行对应操作 if(NewState == ECheckBoxState::Checked) { ExtendSceneOutlinerModule.LockActorSelection(Actor.Get()); ExtendSceneOutlinerModule.SetActorSelectionState(Actor.Get(), false); return; } if(NewState == ECheckBoxState::Unchecked) { ExtendSceneOutlinerModule.UnlockActorSelection(Actor.Get()); return; } return; }

编译并重启编辑器,测试按钮效果。

{♡☘♡☘♡ \quad今天的捉弄结束了 \quad ♡☘♡☘♡}\\

批量锁定选择

与批量锁定移动类似。

// SceneOutlinerLockColumn.h #pragma once #include "ISceneOutlinerColumn.h" class FSceneOutlinerLockColumn : public ISceneOutlinerColumn { ... private: void OnLockToggleStateChanged(ECheckBoxState NewState, TWeakObjectPtr Actor); void OnLockToggleStateChanged_LockMovement_SelectedActor(ECheckBoxState NewState, TWeakObjectPtr Actor); void OnLockToggleStateChanged_LockMovement_SelectedActors(ECheckBoxState NewState, TWeakObjectPtr Actor); void OnLockToggleStateChanged_LockSelection_SelectedActor(ECheckBoxState NewState, TWeakObjectPtr Actor); // 替代上面事件的新函数 void OnLockToggleStateChanged_LockSelection_SelectedActors(ECheckBoxState NewState, TWeakObjectPtr Actor); };

// SceneOutlinerLockColumn.cpp void FSceneOutlinerLockColumn::OnLockToggleStateChanged_LockSelection_SelectedActors(ECheckBoxState NewState, TWeakObjectPtr Actor) { FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked(TEXT("LevelEditor")); TSharedPtr SceneOutliner = LevelEditorModule.GetFirstLevelEditor()->GetSceneOutliner(); FExtendSceneOutliner& ExtendSceneOutlinerModule = FModuleManager::LoadModuleChecked(TEXT("ExtendSceneOutliner")); USelection* SelectedActors = GEditor->GetSelectedActors(); if(NewState == ECheckBoxState::Checked) { if(SelectedActors->Num() == 0) { ExtendSceneOutlinerModule.LockActorSelection(Actor.Get()); ExtendSceneOutlinerModule.SetActorSelectionState(Actor.Get(), false); } else { for(FSelectionIterator It(*SelectedActors); It; ++It) { AActor* SelectedActor = Cast( *It ); ExtendSceneOutlinerModule.LockActorSelection(SelectedActor); } SelectedActors->DeselectAll(); } if(SceneOutliner.IsValid()) { SceneOutliner->FullRefresh(); } return; } if(NewState == ECheckBoxState::Unchecked) { if(SelectedActors->Num() == 0) { ExtendSceneOutlinerModule.UnlockActorSelection(Actor.Get()); } else { for(FSelectionIterator It(*SelectedActors); It; ++It) { AActor* SelectedActor = Cast( *It ); ExtendSceneOutlinerModule.UnlockActorSelection(SelectedActor); } } if(SceneOutliner.IsValid()) { SceneOutliner->FullRefresh(); } return; } return; }

编译并重启编辑器,测试按钮效果。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3