在UE插件开发中,时常会用到场景预览窗口的功能,也常常会有点选场景里的物体而同步改变工具界面的需求,网上教程多为讲解如何打开一个预览界面。在最近的一次需求开发中,我粗读了关卡编辑器和蓝图编辑器的Viewport代码,从中筛选出了点选的相关逻辑,本文记录了一个源码中寻找须要功能的过程。设计模式
功能:关卡编辑器下的点选Actor数组
相关的类(主要是LevelEditor模块):编辑器
一、FLevelEditorViewportClient、FEditorViewportClientide
二、LevelViewportClickedHandlers函数
三、SLevelViewport工具
四、UUnrealEdEngine(依赖模块UnrealEd)this
复现方式:在FLevelEditorViewportClient的InputKey方法下打断点而后进去看调用栈插件
virtual bool InputKey(FViewport* Viewport, int32 ControllerId, FKey Key, EInputEvent Event, float AmountDepressed = 1.f, bool bGamepad=false) override;
他的调用栈以下图:设计
能够看到他是在处理一个MouseButtonDown的事件,UE的按键事件遵循一个职责链的设计模式,由上层的SViewport(我猜想是整个UE的Viewport,没有去考证)先进行处理,而后一路传递到下面的子类FLevelEditorViewportClient,调用到InputKeycode
而后这个InputKey作了不少的事情,包括:计算click的location,检测有其余的按键按下(Alt
Ctrl
),还有一些处理光照和大气的代码(这些作移植的时候能够删掉),比较关键的是他中间调用了父类的InputKey
bool bHandled = FEditorViewportClient::InputKey(InViewport,ControllerId,Key,Event,AmountDepressed,bGamepad);
父类作的事情也不少,但最重要的仍是他调用ProcessClickInViewport函数,这个函数组出了一个HHitProxy对象,这个函数内部调用到了ProcessClick,ProcessClick又被FLevelEditorViewportClient重写了,所以点击代码核心就是重写InputKey和重写ProcessClick函数
一个HHitJProxy对象里包括了点选的是哪一个Actor,点选了他的哪一个Component
编辑器能够根据点击操做的不一样(双击、按住Alt点击等)去让界面作对应的变化,好比在蓝图编辑器下就只显示Comp被选中的轮廓,在关卡编辑器下就是优先选中Actor,若是这个Actor有父Actor那么会优先选中父Actor之类的,这就是ProcessClick函数里应该作的事情。
他首先是判断选中是一个什么对象(WidgetAxis、Actor、xxxVert等等),咱们关注的主要是若是他是一个Actor,那么在移植的时候有些没必要要的分支就均可以删掉了(WidgetAxis不要动,貌似是选中坐标轴的)
在Actor有关的逻辑里他列举了若是要选中Comp的几个条件:
// We want to process the click on the component only if: // 1. The actor clicked is already selected // 2. The actor selected is the only actor selected // 3. The actor selected is blueprintable // 4. No components are already selected and the click was a double click // 5. OR, a component is already selected and the click was NOT a double click const bool bActorAlreadySelectedExclusively = GEditor->GetSelectedActors()->IsSelected(ConsideredActor) && (GEditor->GetSelectedActorCount() == 1); const bool bActorIsBlueprintable = FKismetEditorUtilities::CanCreateBlueprintOfClass(ConsideredActor->GetClass()); const bool bComponentAlreadySelected = GEditor->GetSelectedComponentCount() > 0; const bool bWasDoubleClick = (Click.GetEvent() == IE_DoubleClick); const bool bSelectComponent = bActorAlreadySelectedExclusively && bActorIsBlueprintable && (bComponentAlreadySelected != bWasDoubleClick); if (bSelectComponent) { LevelViewportClickHandlers::ClickComponent(this, ActorHitProxy, Click); } else { LevelViewportClickHandlers::ClickActor(this, ConsideredActor, Click, true); }
LevelViewportClickHandlers是一个命名空间,这块代码有些函数没有xx_API,表示没有dll导出,不是对其余模块公开的,所以能够将其内部的关键函数实现抄出来造成本身的版本
其余的error,通常引用头文件+添加模块依赖就能够解决
当ActorSelection有变更的时候,通常会作一些事件广播,能够实现一些本来被选中的物体接到事件取消选中的外轮廓等等的效果,这块的代码的位置比较复杂,在LevelEditor中,他享受的待遇很好,直接给写到了UnrealEdEngine里
void UUnrealEdEngine::UpdateFloatingPropertyWindowsFromActorList(const TArray<UObject*>& ActorList, bool bForceRefresh) { FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked<FLevelEditorModule>(TEXT("LevelEditor")); LevelEditor.BroadcastActorSelectionChanged(ActorList, bForceRefresh); }
能够看到他其实就是把一个Actor数组传进来而后刷新一下
但咱们的待遇就没这么好了,须要本身手动调一下这个事件,我选择将其添加刚刚抄出来的命名空间下的ClickActor的SelectActor的后面
TArray<UObject*> Objects; Objects.Add(Actor); FModelShapeEditorModule::BroadcastActorSelectionChanged(Objects);
这里我搞了个静态函数来作这件事
关卡编辑器他在这个模块的实现类上注册了一个OnActorSelectionChanged,用于同步ActorDetail面板的变化(若是没有能够去掉这段)
void SLevelEditor::OnActorSelectionChanged(const TArray<UObject*>& NewSelection, bool bForceRefresh) { for( auto It = AllActorDetailPanels.CreateIterator(); It; ++It ) { TSharedPtr<SActorDetails> ActorDetails = It->Pin(); if( ActorDetails.IsValid() ) { ActorDetails->SetObjects(NewSelection, bForceRefresh || bNeedsRefresh); } else { // remove stray entries here } } bNeedsRefresh = false; }
在SLevelViewport里他注册了一个同名函数,而后里面负责修改ViewportClient里的EngineShowFlags,SetSelectionOutline和选中的外轮廓有关,这块须要修改修改抄过来
void SLevelViewport::OnActorSelectionChanged(const TArray<UObject*>& NewSelection, bool bForceRefresh) { // On the first actor selection after entering Game View, enable the selection show flag if (IsVisible() && IsInGameView() && NewSelection.Num() != 0) { if( LevelViewportClient->bAlwaysShowModeWidgetAfterSelectionChanges ) { LevelViewportClient->EngineShowFlags.SetModeWidgets(true); } LevelViewportClient->EngineShowFlags.SetSelection(true); LevelViewportClient->EngineShowFlags.SetSelectionOutline(GetDefault<ULevelEditorViewportSettings>()->bUseSelectionOutline); } bNeedToUpdatePreviews = true; }
把两个核心的函数以及委托实现了,基本上就能够选中Actor了,而且能够有外轮廓,按下w
键能够显示坐标轴
可是此时拖拽坐标轴还不能改变物体在world中的位置,还须要重写不少函数
功能:拖拽Axis,改变他在world中的位置
须要重写或复制的函数
virtual bool InputAxis(FViewport* Viewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime, int32 NumSamples, bool bGamepad) override; virtual void TrackingStarted( const struct FInputEventState& InInputState, bool bIsDraggingWidget, bool bNudge ) override; virtual void TrackingStopped() override; virtual void Tick(float DeltaSeconds) override; /** Project the specified actors into the world according to the current drag parameters */ void ProjectActorsIntoWorld(const TArray<AActor*>& Actors, FViewport* Viewport, const FVector& Drag, const FRotator& Rot); virtual bool InputWidgetDelta( FViewport* Viewport, EAxisList::Type CurrentAxis, FVector& Drag, FRotator& Rot, FVector& Scale ) override; void ApplyDeltaToActor( AActor* InActor, const FVector& InDeltaDrag, const FRotator& InDeltaRot, const FVector& InDeltaScale ); void ApplyDeltaToActors( const FVector& InDrag, const FRotator& InRot, const FVector& InScale ); void ApplyDeltaToComponent(USceneComponent* InComponent, const FVector& InDeltaDrag, const FRotator& InDeltaRot, const FVector& InDeltaScale); /** Helper functions for ApplyDeltaTo* functions - modifies scale based on grid settings */ void ModifyScale( AActor* InActor, FVector& ScaleDelta, bool bCheckSmallExtent = false ) const; /** * Helper function for ApplyDeltaTo* functions - modifies scale based on grid settings. * Currently public so it can be re-used in FEdModeBlueprint. */ void ModifyScale( USceneComponent* InComponent, FVector& ScaleDelta ) const; void ValidateScale(const FVector& InOriginalPreDragScale, const FVector& CurrentScale, const FVector& BoxExtent, FVector& ScaleDelta, bool bCheckSmallExtent = false) const; /** @return Returns true if the delta tracker was used to modify any selected actors or BSP. Must be called before EndTracking(). */ bool HaveSelectedObjectsBeenChanged() const;
仍是和以前同样的思路,可是须要注意,若是有对应的父类函数,通常都调用一下,函数中涉及的变量也要一并摘出本身的版本,在Tracking函数里用到了很多变量
private: FTrackingTransaction TrackingTransaction; /** A map of actor locations before a drag operation */ mutable TMap<TWeakObjectPtr<AActor>, FTransform> PreDragActorTransforms;
功能:点击蓝图类底下的Comp
相关类:
一、SSCSEditorViewport
二、FBlueprintEditor
三、SListView
四、SSCSEditor
这里就不一步一步写了,由于我也没有细看
注意的点,粗看是点击事件先由TreeWidget接受响应,而后把viewport里的渲染响应绑定到了ListView的OnSelectionChange上(指每一个UI条目的选中)
抄源码功能总结就一句话——多退少补。
子明
2021.8.11