UE4點選源碼分析

在UE插件開發中,時常會用到場景預覽窗口的功能,也常常會有點選場景裏的物體而同步改變工具界面的需求,網上教程多爲講解如何打開一個預覽界面。在最近的一次需求開發中,我粗讀了關卡編輯器和藍圖編輯器的Viewport代碼,從中篩選出了點選的相關邏輯,本文記錄了一個源碼中尋找須要功能的過程。設計模式

LevelEditor

點擊Actor

功能:關卡編輯器下的點選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數組傳進來而後刷新一下

但咱們的待遇就沒這麼好了,須要本身手動調一下這個事件,我選擇將其添加剛剛抄出來的命名空間下的ClickActorSelectActor的後面

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

功能:拖拽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;

BlueprintEditor

功能:點擊藍圖類底下的Comp

相關類:

一、SSCSEditorViewport

二、FBlueprintEditor

三、SListView

四、SSCSEditor

這裏就不一步一步寫了,由於我也沒有細看

注意的點,粗看是點擊事件先由TreeWidget接受響應,而後把viewport裏的渲染響應綁定到了ListView的OnSelectionChange上(指每一個UI條目的選中)

總結

抄源碼功能總結就一句話——多退少補

子明

2021.8.11

相關文章
相關標籤/搜索