UE5には Component Visualizer という機能(クラス)があります。
Component Visualizerを活用すると、コンポーネントの値を視覚化することができます。視覚化されるUIはランタイムでは描画されずエディタ上で編集時にのみ描画されます。また、その視覚化されたUIをマウスで操作し、値を編集することも可能です。
エンジン内部では、ライトの範囲表示やSplineの線など様々な箇所の描画で使用されています。
今回はそのComponent Visualizerの使用方法について紹介します。
- 0. 準備
- 1. 点の描画
- 2. 線の描画
- 3. スプライトの描画
- 4. ワイヤーフレームの描画
- 5. テキストの描画
- 6. マニュピレータでの制御
- 7. コンテキストメニューの追加
- まとめ
- 参考記事
0. 準備
プラグインの作成
Component Visualizerはクラスのロードタイミングがデフォルトのままでは使えず、専用のモジュールでロードタイミングを変更する必要があります。そのため、まずはComponent Visualizerを使用するために専用のプラグインを作成します。
プラグインを作成したら、早速 ".uplugin" を開き、"LoadingPhase" を "PostEngineInit" に変更します。
"Modules": [ { "Name": "MyComponentVisualizer", "Type": "Runtime", "LoadingPhase": "PostEngineInit" } ]
独自のComponent Visualizerを作成
FComponentVisualizer を継承した独自のクラスを作成します。
#include "ComponentVisualizer.h" class FMyComponentVisualizer : public FComponentVisualizer { };
"ComponentVisualizer.h"をインクルードするために、"Build.cs" に "UnrealEd" モジュールを追加します。
PrivateDependencyModuleNames.AddRange( new string[] { "CoreUObject", "Engine", "Slate", "SlateCore", "UnrealEd", // 追加 } );
Component Visualizerに登録
Component Visualizerを使用するにはコンポーネントを登録する必要があります。
まずはActorComponentを継承した独自のコンポーネントを作成します。
UCLASS(ClassGroup = Custom, meta = (BlueprintSpawnableComponent)) class MYCOMPONENTVISUALIZER_API UMyActorComponent : public UActorComponent { GENERATED_BODY() public: UMyActorComponent(); protected: virtual void BeginPlay() override; public: virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; };
次にStartupModule関数で独自のComponent Visualizerに作成したコンポーネントを登録、ShutdownModule関数で登録を解除する処理を追加します。
#include "MyComponentVisualizer.h" #include "MyActorComponent.h" #include "UnrealEdGlobals.h" #include "Editor/UnrealEdEngine.h" #define LOCTEXT_NAMESPACE "FMyComponentVisualizerModule" void FMyComponentVisualizerModule::StartupModule() { TSharedPtr<FMyComponentVisualizer> visualizer = MakeShareable(new FMyComponentVisualizer); GUnrealEd->RegisterComponentVisualizer(UMyActorComponent::StaticClass()->GetFName(), visualizer); visualizer->OnRegister(); } void FMyComponentVisualizerModule::ShutdownModule() { GUnrealEd->UnregisterComponentVisualizer(UMyActorComponent::StaticClass()->GetFName()); } #undef LOCTEXT_NAMESPACE IMPLEMENT_MODULE(FMyComponentVisualizerModule, MyComponentVisualizer)
描画の基本
ここまでできたら準備完了です。
FComponentVisualizer の関数をオーバーライドしてデータを描画する処理を書いていきます。
基本的には DrawVisualization関数で図形を描画し、DrawVisualizationHUD関数でHUD上に文字やテクスチャを描画します。
DrawVisualization関数では、引数のFPrimitiveDrawInterfaceからDraw系関数を呼ぶことで線やスプライトなどを描画できます。
DrawVisualizationHUD関数では、引数のFCanvasから文字などを描画できます。
1. 点の描画
点を描画するには、DrawPoint関数を呼び出します。
MyActorComponentにFVectorの配列を用意して、配列の要素に点を描画するようにしたいと思います。まずは配列の変数を用意します。
UPROPERTY(EditInstanceOnly)
TArray<FVector> Points;
DrawVisualization関数で引数のComponentをキャストして、MyActorComponentのPointsごとにDrawPoint関数で点を描画します。
void FMyComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) { if (const UMyActorComponent* myComp = Cast<const UMyActorComponent>(Component)) { // 点の描画 for (int32 i = 0; i < myComp->Points.Num(); i++) { PDI->DrawPoint(myComp->Points[i], FColor::Red, 10.f, SDPG_Foreground); } } }
DrawPointの引数で点の色や描画方法などを指定します。
引数名 | 説明 | |
---|---|---|
1 | Position | 点の座標。 |
2 | Color | 点の色。 |
3 | PointSize | 点のサイズ。 |
4 | DepthPriorityGroup | 描画方法。 SDPG_Foreground: 常に前面に描画されます。 SDPG_World: ワールド座標を基準に描画されます。 |
2. 線の描画
線を描画するには、DrawLine関数を呼び出します。
点と同じようにPointsの要素間に線を描画するようにDrawVisualization関数に描画処理を追加します。
void FMyComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) { if (const UMyActorComponent* myComp = Cast<const UMyActorComponent>(Component)) { const int32 n = myComp->Points.Num(); // 点の描画 for (int32 i = 0; i < n; i++) { PDI->DrawPoint(myComp->Points[i], FColor::Red, 10.f, SDPG_Foreground); } // 線の描画 if (n >= 2) { for (int32 i = n-1; i >= 1; i--) { PDI->DrawLine(myComp->Points[i-1], myComp->Points[i], FColor::Cyan, SDPG_Foreground, 2.f, 0.f, true); } } } }
DrawLineの引数で線の色や描画方法などを指定します。
引数名 | 説明 | |
---|---|---|
1 | Start | 始点。 |
2 | End | 終点。 |
3 | Color | 線の色。 |
4 | DepthPriorityGroup | 描画方法。 SDPG_Foreground: 常に前面に描画されます。 SDPG_World: ワールド座標を基準に描画されます。 |
5 | Thickness | 線の太さ。 |
6 | DepthBias | 深度のバイアス。 |
7 | bScreenSpace | trueの場合、スクリーンスペースで描画されるためカメラが離れても同じ見た目で描画されます。(デフォルトはfalse) |
DrawLine関数をDrawTranslucentLine関数に変更すると半透明の線を描画できます。アルファ値は第三引数のColorで指定します。
3. スプライトの描画
スプライト(テクスチャ)を描画するには、DrawSprite関数を呼び出します。
まずはMyActorComponentにUTexture2Dの変数を用意します。
UPROPERTY(EditInstanceOnly)
TObjectPtr<UTexture2D> Sprite;
用意したSprite変数をDrawSprite関数の引数として渡し、アクターの座標に描画します。
void FMyComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) { if (const UMyActorComponent* myComp = Cast<const UMyActorComponent>(Component)) { if (myComp->Sprite) { PDI->DrawSprite(myComp->GetOwner()->GetActorLocation(), 10.f, 10.f, myComp->Sprite->GetResource(), FColor::White, SDPG_Foreground, 0.f, 0.f, 0.f, 0.f); } } }
DrawSpriteの引数でテクスチャや描画方法などを指定します。
引数名 | 説明 | |
---|---|---|
1 | Position | 点の座標。 |
2 | SizeX | スプライトのXサイズ。 |
3 | SizeY | スプライトのYサイズ。 |
4 | Sprite | スプライト(テクスチャ)。 |
5 | Color | スプライトの色。 |
6 | DepthPriorityGroup | 描画方法。 SDPG_Foreground: 常に前面に描画されます。 SDPG_World: ワールド座標を基準に描画されます。 |
7~10 | U, UL, V, VL | スプライトのUV。 |
11 | BlendMode | ブレンドモード。デフォルトではMasked。 |
12 | OpacityMaskRefVal | ブレンドモードがMasked時のOpacity値。 |
4. ワイヤーフレームの描画
DrawWire系の関数でワイヤーフレームの図形を描画できます。
関数名 | 描画される図形 |
---|---|
DrawWireSphere | 球 |
DrawWireSphereAutoSides | 球 辺の数は自動で設定されます。 |
DrawWireCylinder | 円筒 |
DrawWireCapsule | カプセル |
DrawWireChoppedCone | カプセル 上下の球の半径を個別に設定できます。 |
DrawWireCone | 円錐 |
DrawWireSphereCappedCone | 円錐 キャップ上に円弧がある円錐を描画します。 |
DrawOrientedWireBox | 立方体 |
DrawDirectionalArrow | 矢印 |
DrawConnectedArrow | 矢印 |
DrawWireStar | 米印 |
DrawDashedLine | 点線 |
DrawWireDiamond | ひし形 |
DrawCoordinateSystem | 座標系の描画。 |
5. テキストの描画
テキストを描画するには、DrawVisualizationHUD関数で引数のFCanvasからDrawShadowedString関数かDrawShadowedText関数を呼び出します。
「1」「2」で描画した点(Points変数)の位置にテキストを描画する処理を書きました。描画の前にワールド座標からスクリーン座標への変換なども行っています。
#include "CanvasTypes.h" void FMyComponentVisualizer::DrawVisualizationHUD(const UActorComponent* Component, const FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) { if (const UMyActorComponent* myComp = Cast<const UMyActorComponent>(Component)) { // スクリーン位置の計算 const FIntRect rect = Canvas->GetViewRect(); const float halfX = rect.Width() / 2.f; const float halfY = rect.Height() / 2.f; const float offsetY = 12.f; for (int32 i = 0; i < myComp->Points.Num(); i++) { // ワールド座標からスクリーン座標に変換 const FVector pos = FVector(View->Project(myComp->Points[i])); const float drawX = FMath::FloorToFloat(halfX + pos.X * halfX); const float drawY = FMath::FloorToFloat(halfY + -1.f * pos.Y * halfY); // 描画 Canvas->DrawShadowedString(drawX, (drawY + offsetY), *FString::Printf(TEXT("Points[%d]"), i), GEngine->GetLargeFont(), FColor::White, FColor::Black); } } }
DrawShadowedStringの引数で描画する位置や文字色などを指定します。
引数名 | 説明 | |
---|---|---|
1 | StartX | テキストの座標X。 |
2 | StartY | テキストの座標Y。 |
3 | Text | 描画する文字列。 |
4 | Font | フォント。 |
5 | Color | 文字の色。 |
6 | ShadowColor | 影の色。 |
6. マニュピレータでの制御
Component Visualizerは描画だけではなく、描画した図形をビューポート上で編集することもできます。
今回は「1」「2」で記述した点と線を描画し、操作したい点をマニピュレーターで制御できるようにしたいと思います。
VisProxyの作成
まずは入力の受信に必要なHComponentVisProxyというクラスを継承した独自のクラスを作成します。
struct HMyComponentVisProxy : public HComponentVisProxy { DECLARE_HIT_PROXY(); HMyComponentVisProxy(const UActorComponent* InComponent, int32 InPointIndex) : HComponentVisProxy(InComponent, HPP_Wireframe) , PointIndex(InPointIndex) { } int32 PointIndex; };
PointIndex変数は点の制御のために用意しています。
コンストラクタはPointIndexを代入できるように拡張しています。
.cppファイルの頭にIMPLEMENT_HIT_PROXYマクロを記述します。(おまじないだと思ってください。)
IMPLEMENT_HIT_PROXY(HMyComponentVisProxy, HComponentVisProxy);
次にDrawVisualization関数に描画の処理を記述します。
点の描画の前後にSetHitProxyを記述します。これだけで入力を受け取れるようになります。
線や別の図形を操作したい場合も同じようにSetHitProxyで挟めば入力を受け取れるようになります。
void FMyComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) { if (const UMyActorComponent* myComp = Cast<const UMyActorComponent>(Component)) { const int32 n = myComp->Points.Num(); for (int32 i = 0; i < n; i++) { // 線の描画 if (i < n-1) { PDI->DrawLine(myComp->Points[i], myComp->Points[i+1], FColor::Cyan, SDPG_World, 2.f, 0.f, true); } // 点の描画 PDI->SetHitProxy(new HMyComponentVisProxy(Component, i)); PDI->DrawPoint(myComp->Points[i], FColor::Red, 15.f, SDPG_World); PDI->SetHitProxy(NULL); } } }
入力の受信
まずはFMyComponentVisualizerに変数を用意します。また、各関数をオーバーライドしておきます。
class FMyComponentVisualizer : public FComponentVisualizer { public: //~ Begin FComponentVisualizer Interface virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override; virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override; virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override; virtual bool HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale) override; virtual UActorComponent* GetEditedComponent() const override; //~ End FComponentVisualizer Interface TObjectPtr<UMyActorComponent> CurrentComponent; // 追加 int32 SelectPointIndex; // 追加 };
オーバーライドしたVisProxyHandleClick関数に点をクリックした時の処理を記述します。
クリックした際にコンポーネントと選択中の点のインデックス番号を保存しておきます。
bool FMyComponentVisualizer::VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) { if (VisProxy && VisProxy->Component.IsValid()) { const HMyComponentVisProxy* Proxy = (HMyComponentVisProxy*)VisProxy; CurrentComponent = (UMyActorComponent*)Proxy->Component.Get(); // コンポーネントの保存 SelectPointIndex = Proxy->PointIndex; // 現在選択中のインデックス番号の保存 return true; } return false; }
保存したコンポーネントは、GetEditedComponent関数で渡してあげます。
UActorComponent* FMyComponentVisualizer::GetEditedComponent() const { return CurrentComponent; }
マニピュレータの描画
GetWidgetLocation関数で引数のOutLocationに選択中の点の座標を代入します。
bool FMyComponentVisualizer::GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const { if (CurrentComponent) { OutLocation = CurrentComponent->Points[SelectPointIndex]; return true; } return false; }
これでクリックした際にマニピュレータが表示されるようになりました。但しマニピュレータを操作しても図形は変化しません。
最後にHandleInputDelta関数で操作した時の差分をPoints変数に加算してあげます。
bool FMyComponentVisualizer::HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltalRotate, FVector& DeltaScale) { if (CurrentComponent) { if (!DeltaTranslate.IsZero()) { CurrentComponent->Points[SelectPointIndex] += DeltaTranslate; } return true; } return false; }
マニピュレータで点を操作できるようになりました。
Tips: Handle系関数でイベントの受信
今回はマニピュレータでの操作だったのでHandleInputDelta関数で処理しましたが、他にもHandle系関数はいくつか用意されており、様々なタイミングでイベントを受け取ることができます。
例えば、下記のようにHandleInputKey関数を用いるとキー入力時に処理をすることができます。
bool FMyComponentVisualizer::HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) { if (Key == EKeys::Delete) { // 削除処理 } }
7. コンテキストメニューの追加
Component Visualizerではコンテキストメニューの仕組みも用意されています。
点を右クリックした時にPoints変数に要素を追加するメニューを追加したいと思います。
コマンド用クラスの用意
.cppファイルの頭でコマンド用クラスを定義します。
#define LOCTEXT_NAMESPACE "FMyVisualizerCommands" class FMyVisualizerCommands : public TCommands<FMyVisualizerCommands> { public: FMyVisualizerCommands() : TCommands<FMyVisualizerCommands> ( "MyVisualizerCommands", LOCTEXT("MyVisualizerCommands", "My Component Visualizer"), NAME_None, FAppStyle::GetAppStyleSetName() ){} virtual void RegisterCommands() override { UI_COMMAND(AddPointCommand, "Add Point", "Add the point.", EUserInterfaceActionType::Button, FInputGesture()); } TSharedPtr<FUICommandInfo> AddPointCommand; }; #undef LOCTEXT_NAMESPACE
コマンドクラスはComponent Visualizer固有のクラスではなく、Slateを書いたことがある方は馴染みがあるかと思います。
クラスの定義のみですので特別なことはしていませんが、UI_COMMANDマクロの第四引数でEUserInterfaceActionTypeを変えることでチェックボックスやラジオボタンにすることができます。今回はベーシックなボタンにしておきます。
次にFMyComponentVisualizerにコンストラクタや関数を追加します。
class FMyComponentVisualizer : public FComponentVisualizer { public: FMyComponentVisualizer(); // 追加 virtual ~FMyComponentVisualizer(); // 追加 //~ Begin FComponentVisualizer Interface virtual void OnRegister() override; // 追加 virtual TSharedPtr<SWidget> GenerateContextMenu() const override; // 追加 virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override; virtual void DrawVisualizationHUD(const UActorComponent* Component, const FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) override; virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override; virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override; virtual bool HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale) override; virtual UActorComponent* GetEditedComponent() const override; //~ End FComponentVisualizer Interface void OnAddPoint(); // 追加:ボタン押下時に実行される関数 TSharedPtr<FUICommandList> MyActions; // 追加 TObjectPtr<UMyActorComponent> CurrentComponent; int32 SelectPointIndex; };
初期化処理やコマンドの登録などを行います。
FMyComponentVisualizer::FMyComponentVisualizer() : FComponentVisualizer() , SelectPointIndex(INDEX_NONE) { FMyVisualizerCommands::Register(); MyActions = MakeShareable(new FUICommandList); } FMyComponentVisualizer::~FMyComponentVisualizer() { FMyVisualizerCommands::Unregister(); } void FMyComponentVisualizer::OnRegister() { const auto& Commands = FMyVisualizerCommands::Get(); MyActions->MapAction( Commands.AddPointCommand, FExecuteAction::CreateSP(this, &FMyComponentVisualizer::OnAddPoint), FCanExecuteAction()); }
コンテキストメニューからポイントの追加
GenerateContextMenu関数でメニューの登録を行い、OnAddPoint関数では選択中の点の後に要素を追加するようにしました。
TSharedPtr<SWidget> FMyComponentVisualizer::GenerateContextMenu() const { FMenuBuilder builder(true, MyActions); { builder.BeginSection("My Actions"); { builder.AddMenuEntry(FMyVisualizerCommands::Get().AddPointCommand); } builder.EndSection(); } return builder.MakeWidget(); } void FMyComponentVisualizer::OnAddPoint() { // 選択中の点の後に要素を追加 CurrentComponent->Points.Insert(FVector::Zero(), SelectPointIndex+1); }
右クリックのコンテキストメニューから要素を追加できるようになりました。
まとめ
いかがでしたでしょうか?
Component VisualizerはC++での実装が必須で場合によってはSlateも触るため実装難易度は高いかと思いますが、使いこなせれば強力なツールとなりうると思います。
最後に本記事で使用したコード全体を記述しておきます。
#pragma once #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "ComponentVisualizer.h" #include "MyActorComponent.generated.h" class UMyActorComponent; struct HMyComponentVisProxy : public HComponentVisProxy { DECLARE_HIT_PROXY(); HMyComponentVisProxy(const UActorComponent* InComponent, int32 InPointIndex) : HComponentVisProxy(InComponent, HPP_Wireframe) , PointIndex(InPointIndex) { } int32 PointIndex; }; class FMyComponentVisualizer : public FComponentVisualizer { public: FMyComponentVisualizer(); virtual ~FMyComponentVisualizer(); //~ Begin FComponentVisualizer Interface virtual bool ShouldShowForSelectedSubcomponents(const UActorComponent* Component) override; virtual void OnRegister() override; virtual TSharedPtr<SWidget> GenerateContextMenu() const override; virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override; virtual void DrawVisualizationHUD(const UActorComponent* Component, const FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) override; virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override; virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override; virtual bool HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale) override; virtual UActorComponent* GetEditedComponent() const override; //~ End FComponentVisualizer Interface void OnAddPoint(); TSharedPtr<FUICommandList> MyActions; TObjectPtr<UMyActorComponent> CurrentComponent; int32 SelectPointIndex; }; UCLASS(ClassGroup = Custom, meta = (BlueprintSpawnableComponent)) class MYCOMPONENTVISUALIZER_API UMyActorComponent : public UActorComponent { GENERATED_BODY() public: UMyActorComponent(); protected: virtual void BeginPlay() override; public: virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; public: UPROPERTY(EditInstanceOnly) TArray<FVector> Points; };
#include "MyActorComponent.h" #include "CanvasTypes.h" IMPLEMENT_HIT_PROXY(HMyComponentVisProxy, HComponentVisProxy); #define LOCTEXT_NAMESPACE "FMyVisualizerCommands" class FMyVisualizerCommands : public TCommands<FMyVisualizerCommands> { public: FMyVisualizerCommands() : TCommands<FMyVisualizerCommands> ( "MyVisualizerCommands", LOCTEXT("MyVisualizerCommands", "My Component Visualizer"), NAME_None, FAppStyle::GetAppStyleSetName() ){} virtual void RegisterCommands() override { UI_COMMAND(AddPointCommand, "Add Point", "Add the point.", EUserInterfaceActionType::Button, FInputGesture()); } TSharedPtr<FUICommandInfo> AddPointCommand; }; #undef LOCTEXT_NAMESPACE FMyComponentVisualizer::FMyComponentVisualizer() : FComponentVisualizer() , SelectPointIndex(INDEX_NONE) { FMyVisualizerCommands::Register(); MyActions = MakeShareable(new FUICommandList); } FMyComponentVisualizer::~FMyComponentVisualizer() { FMyVisualizerCommands::Unregister(); } bool FMyComponentVisualizer::ShouldShowForSelectedSubcomponents(const UActorComponent* Component) { return false; } void FMyComponentVisualizer::OnRegister() { const auto& Commands = FMyVisualizerCommands::Get(); MyActions->MapAction( Commands.AddPointCommand, FExecuteAction::CreateSP(this, &FMyComponentVisualizer::OnAddPoint), FCanExecuteAction()); } TSharedPtr<SWidget> FMyComponentVisualizer::GenerateContextMenu() const { FMenuBuilder builder(true, MyActions); { builder.BeginSection("My Actions"); { builder.AddMenuEntry(FMyVisualizerCommands::Get().AddPointCommand); } builder.EndSection(); } return builder.MakeWidget(); } void FMyComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) { if (const UMyActorComponent* myComp = Cast<const UMyActorComponent>(Component)) { const int32 n = myComp->Points.Num(); for (int32 i = 0; i < n; i++) { // 線の描画 if (i < n-1) { PDI->DrawLine(myComp->Points[i], myComp->Points[i+1], FColor::Cyan, SDPG_World, 2.f, 0.f, true); } // 点の描画 PDI->SetHitProxy(new HMyComponentVisProxy(Component, i)); PDI->DrawPoint(myComp->Points[i], FColor::Red, 15.f, SDPG_World); PDI->SetHitProxy(NULL); } } } void FMyComponentVisualizer::DrawVisualizationHUD(const UActorComponent* Component, const FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) { if (const UMyActorComponent* myComp = Cast<const UMyActorComponent>(Component)) { // スクリーン位置の計算 const FIntRect rect = Canvas->GetViewRect(); const float halfX = rect.Width() / 2.f; const float halfY = rect.Height() / 2.f; const float offsetY = 12.f; for (int32 i = 0; i < myComp->Points.Num(); i++) { // ワールド座標からスクリーン座標に変換 const FVector pos = FVector(View->Project(myComp->Points[i])); const float drawX = FMath::FloorToFloat(halfX + pos.X * halfX); const float drawY = FMath::FloorToFloat(halfY + -1.f * pos.Y * halfY); // 描画 Canvas->DrawShadowedString(drawX, (drawY + offsetY), *FString::Printf(TEXT("Points[%d]"), i), GEngine->GetLargeFont(), FColor::White, FColor::Black); } } } bool FMyComponentVisualizer::VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) { if (VisProxy && VisProxy->Component.IsValid()) { const HMyComponentVisProxy* Proxy = (HMyComponentVisProxy*)VisProxy; CurrentComponent = (UMyActorComponent*)Proxy->Component.Get(); // コンポーネントの保存 SelectPointIndex = Proxy->PointIndex; // 現在選択中のインデックス番号の保存 return true; } return false; } bool FMyComponentVisualizer::GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const { if (CurrentComponent) { OutLocation = CurrentComponent->Points[SelectPointIndex]; return true; } return false; } bool FMyComponentVisualizer::HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltalRotate, FVector& DeltaScale) { if (CurrentComponent) { if (!DeltaTranslate.IsZero()) { CurrentComponent->Points[SelectPointIndex] += DeltaTranslate; } return true; } return false; } UActorComponent* FMyComponentVisualizer::GetEditedComponent() const { return CurrentComponent; } void FMyComponentVisualizer::OnAddPoint() { // 選択中の点の後に要素を追加 CurrentComponent->Points.Insert(FVector::Zero(), SelectPointIndex+1); } UMyActorComponent::UMyActorComponent() { PrimaryComponentTick.bCanEverTick = true; } void UMyActorComponent::BeginPlay() { Super::BeginPlay(); } void UMyActorComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); }