visualize and modify an array of transforms

I have a struct

USTRUCT(BlueprintType)
struct FspecialPoints{
    // technically only care about the location as offset (could be world space but right now offset), and rotation as absolute world position
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FTransform offsetPoint;
    // other members for the point
};

right now this is being held in an array by an Actor component so it could just be thrown on any actor (for example these could be Spawn Points for NPC(s)/player(s), or target points of a moving platform, or AI-trigger-Points

I am able to use the points in code but currently I have to manually modify them in the details window, but where I am using these as offsets right now I have to create a default cube then move it around in the editor grab the transform values, then get the offset of the transform to plug that into the transform of the struct (I don’t want to ask a level designer to go through these steps)

what I would like is some way to say when the Component is selected in the editor, and one of these structs is un-collapsed to show like an arrow/gizmo for that transform, so that when the transform is modified the arrow/gizmo moves around and rotates.
if All the transforms have to be visible when the component is selected so be it.

would making this a SceneComponent, or an Actor make this easier?

after doing some digging getting the gizmo to effect the transform is a lot more work (and might require source edits)

Ok so I think I got something, but falling down a little

If I declare/define a Visualizer that knows of my struct (though it does require hard casting which I am not happy with (sure put in a separate module but then because we need member variables give it a hard link to a core code “thanks”)

maybe the softer approach would be having an Interface to get to the Transform, or the Array, but the Visualizer still needs to know about the Struct, because C++ structs support inheritance you could have the interface return the “parent struct” that only has the stuff for the visualization, but then just inherit the struct with the Transform.

struct FSpawnPoint : public FspecialPoints and yes struct inheritance is supported by USTRUCT use that as you will.

#include "ComponentVisualizer.h"
class FTransformVisualizer : public FComponentVisualizer
{
public:
	virtual void DrawVisualization(
		const UActorComponent* Component,
		const FSceneView* View,
		FPrimitiveDrawInterface* PDI) override
	{
		if ( const UMyActorComponent* component= Cast<const UMyActorComponent>(Component) )
		{
			for ( const FspecialPoints point : component->SpecialPoints )
			{
               // not completely necessary if the Transform.Location is in pure worldspace
				const FTransform OwnerTransform = component->GetOwner()->GetActorTransform();
				const FVector Start = OwnerTransform.TransformPosition(point.transform.GetLocation());
				const FVector End = Start + point.transform.GetRotation().Vector() * 100.0f;
               // there is no PDI->DrawArrow, but we can make do" could even steal values off the Transform.Scale for things like DrawPoint::Scale or even the colors
				PDI->DrawLine(Start, End, FLinearColor::Red, SDPG_World);

				PDI->DrawPoint(End, FLinearColor::Blue, 2.0f, SDPG_World);
			}
		}
	}
};

the thing is I don’t want this being drawn all the time.

is there some way to figure out if say the Component is currently select in the Actor’s Hierarchy? would Love to call this there instead of whenever the Component exists in the editor.
is there some way to figure out what indexes of a given array is open in the Details window?

You can find out which component is currently selected like this:

TArray<UObject*> Buffer;
GEditor->GetSelectedComponents()->GetSelectedObjects(UActorComponent::StaticClass(), Buffer);

But tracking the moment of component selection is not so trivial…

The SSceneOutliner::GetOnItemSelectionChanged() - is delegate you need.

But you need to find the right instance of this widget (the one created for the Outliner tab).

Most likely it happens somewhere in the depths of FLevelEditorModule. But I can’t tell you more specifically (I don’t know myself).

However, you can use a not very good, but nevertheless working method - to check each tick the state of the currently selected components. At your discretion.

well this is a case of figure something out, getting 80% satisfied with your clunky implementation then finding a guide that someone wrote years ago that does basically everything you were asking for and a bit more

https://unrealcommunity.wiki/component-visualizers-xaa1qsng

(video is maybe the worst way to give programming tutorials, but engagement time is a thing, even a text book you don’t need to worry if they are zoomed enough so you can read it)
I will be working through this link a bit more to see if it does everything I need.

1 Like

was able to get ahold of the element index through a HComponentVisProxy click event, but now I am both confused and frustrated at this guide (there are black magic non-existant assignments, assigning both a pointer and null to the same delegate, misplacing their own variable names), and some of it just doesn’t work
I was able to get the selection to work, but I am having trouble just drawing a number to the screen inside the visualization (supposedly FPrimitiveDrawInterface::DrawText() existed at some point so many of the LLMs won’t shut up about it, or gasslighting me when I tell it the function doesn’t exist)
so many methods to draw text to the screen require a WorldContext which needs at the very least PIE, or to break out Slate, and converting world-space to screen-space in slate sounds like a nightmare.

  • how can I draw either FText, or an FString to the screen from an FComponentVisualizer outside of WorldContext.

right now I write a message to the log when the array index is acquired, and have a different color for the one currently selected.

where this is a requested feature multiple times on the Forum with no real answer I will include the steps and my implementation

for the purpose of this instruction the base project name is MyGame, so any time you see MyGame or MYGAME you can replace it with your [ProjectName]

first we need the struct we are going to be dealing with and a UActorComponent (this also has no reason not to works with any of the children) that will be holding it for me these both reside in MyGameEntitySpawner.h

USTRUCT(BlueprintType)
struct FMyGameTransformStruct
{
	GENERATED_BODY()
public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	FTransform transform;
	// if these will always be offsets
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	bool bIsOffset = true;
};

USTRUCT(BlueprintType)
struct FMyGameSpawnPoint : public FMyGameTransformStruct
{
	GENERATED_BODY()
public:
	// other stuff for a spawnPoint
	// ...
};

//...

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class MYGAME_API UMyGameEntitySpawner : public UActorComponent
{
	GENERATED_BODY()
public:
	// should be in #ifdef WITH_EDITORONLY_DATA but even when I put that it complains about needing what is already there...
	UPROPERTY(VisibleAnywhere)
	int32 VisualizerSelectedIndex = INDEX_NONE;
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TArray<FMyGameSpawnPoint> SpawnPoints;
	// ...
};

These next two steps can be done in either order, but the name must be exact, and a json database is easier to edit then deleting, and adding another module
(Visual Studio as of 17.11.0 update has added the ability to create a module and other “common” unreal Engine items inside visual studio for modules it means you don’t have to blindly copy/make files and hope you named things consistently)
we need to create a Module.
Module Name = [ProjectName]GameEditor;
Module Type = Editor
Module Loading Phase = PostEngineInit
IDE will probably still ask you to Reload (at least we didn’t need to open the editor “YAY”)
need to Edit the “MyGame.uproject” (this file is effectively a json database so names, orders and spelling are specific) This might be generated by Visual Studio, but I am a mad person and did this in reverse.
find

"Modules": [
	{
		// this is your gamestuff
	},
	{
		"Name": "MyGameEditor",
		"Type": "Editor",
		"LoadingPhase": "PostEngineInit"
	}
]

if you are having trouble with your module and Unreal says you can’t launch your project this is the part you would Remove (JSon does not support comments, and there is validation done, so you need to remove it)

your project is currently not in a run-able state before we take some extra steps.

go to “MyGameEditor.Target.cs” ([ProjectName]Editor.Targets.cs)
find the line

// before
ExtraModuleNames.AddRange( new string[] { "MyGame" } );
// after
ExtraModuleNames.AddRange( new string[] { "MyGame", "MyGameEditor" } );

“MyGameEditor.Build.cs” and change it to the following

        PublicDependencyModuleNames.AddRange(new string[] {"Core", "CoreUObject", "Engine", "MyGame" });

        PrivateDependencyModuleNames.AddRange(new string[] { "UnrealEd", "ComponentVisualizers" });

now we deal with the actual implementation that as of writing Visual studio only got half way there, and maybe named things differently then guides would have (Visual studio is hard enforcing “predominant class name should match file name excluding the prefix” so it has the word “Module” at the end of the cpp and h) I will be proceeding with the names “MyGameEditorModule” for the file names of the cpp and h of the module executable

the “MyGameEditorModule.h” should look like this

#pragma once

#include "CoreMinimal.h"
#include "Modules//ModuleInterface.h"
#include "Modules/ModuleManager.h"
#include "Modules/ModuleInterface.h"
#include "UnrealEd.h"

DECLARE_LOG_CATEGORY_EXTERN(MyGameEditor, All, All)

class FMyGameEditorModule : public IModuleInterface
{
public:
	// don't believe the guides saying we wont need these
	virtual void StartupModule() override;
	virtual void ShutdownModule() override;
};

then “MyGameEditorModule.cpp” should look like this

#include "MyGameEditorModule.h"

IMPLEMENT_MODULE(FMyGameEditorModule, MyGameEditor);

void FMyGameEditorModule::StartupModule() { }

void FMyGameEditorModule::ShutdownModule() { }

at this point you want to hit build (Ctrl+B) with the Module in focus, and if it doesn’t have errors it should be safe to launch the editor (if you are concerned about crashes then attach the debugger)

if the Engine launches normally, then one the top bar “Tools”->“Debug”->“Modules”, and in the search box type your [ProjectName] (“MyGame”)in my case) and you should see the game itself, and then [ProjectName]Editor.

we now have an editor module, you can do the extra step of hitting play to see if anything breaks, (if you are doing all of this by hand without the template at this time go ahead “Tools”->“Refresh Visual Studio files”) , but we are off the implement our visualizer.

we now want to add the Visualizer to the module. you can either do this through the Add C++ because we are in the editor (be sure to put it in the EditorModule we are making), or we can use the Visual Studio template add (right click on the module in the solution browser “Add”->“Unreal item”)

with the editor closed

explicit implementation directly follows, Interface implementation will be after (MyGameEditorModule.h will be the same as above, and MyGameEditorModule.cpp will need specific class FNames so it will not be different for the interface)

MyGameEditorModule.cpp

#include "MyGameEditorModule.h"
#include "MyGameTransformVisualizer.h"
// the headers of the class(es) that we are actually going to be working with
#include "MyGameEntitySpawner.h"


IMPLEMENT_MODULE(FMyGameEditorModule, MyGameGameEditor);

void FMyGameEditorModule::StartupModule()
{
	if ( GUnrealEd )
	{
		TSharedPtr<FMyGameTransformVisualizer> MyGameTransformVisualizer = MakeShareable(new FMyGameTransformVisualizer());
		if ( MyGameTransformVisualizer.IsValid() )
		{
			// each class type the Visualizer class will be used with because these are FName of the class we cannot use an interface here (I wish we could)
			GUnrealEd->RegisterComponentVisualizer(UMyGameEntitySpawner::StaticClass()->GetFName(), MyGameTransformVisualizer);
			// add other classes that the visualizer will be working with
			MyGameTransformVisualizer->OnRegister();
		}
	}
}

void FMyGameEditorModule::ShutdownModule()
{
	if ( GUnrealEd )
	{
		// what we register we need to unregister
		GUnrealEd->UnregisterComponentVisualizer(UMyGameEntitySpawner::StaticClass()->GetFName());
	}
}

for this example “MyGameTransformVisualizer”
MyGameTransformVisualizer.h

#pragma once

#include "CoreMinimal.h"
// this is needed to draw the visualization in the first place
#include "ComponentVisualizer.h"
// we could put the headers of the class(es) we intend to use the visualizer for, but we only need a pointer to them,
// so we will forward declare the pointer

/**Base class for clickable targeting editing proxies*/
struct HMyGameTransformVisProxy : public HComponentVisProxy
{
    DECLARE_HIT_PROXY();

    HMyGameTransformVisProxy (const UActorComponent* InComponent, int32 inElementIndex)
    : HComponentVisProxy(InComponent, HPP_Wireframe), ElementIndex(inElementIndex)
    {}
    int32 ElementIndex;
};

/**
 * 
 */
class MYGAMEEDITOR_API FMyGameTransformVisualizer : public FComponentVisualizer
{
public:
    FMyGameTransformVisualizer();
    ~FMyGameTransformVisualizer();

    // Begin FComponentVisualizer interface

    // the things being drawn to the screen
    virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override;
    // the click events on the object
    virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override;
    // cleanup when we are no longer focused on the actor
    virtual void EndEditing() override;
    // allows us to place the widget where we would like;
    // widget strangely returns to Actors <0,0,0> after edit but "livable" I guess
    virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override;
    // applying modifications onto the transform
    virtual bool HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale) override;
    // End FComponentVisualizer interface
    
    /** the target component we are currently editing an unimplemented Getter is even less useful */
    class UMyGameEntitySpawner* EditedComponent = nullptr;

    // for an interface implementation I am leaving this here, because this and the line above will be the only difference
    // technically the U variant would make sense for safety checks but we need the interface functions, and we are told 
    // not to put anything in the U
//  class IMyGameTransformStructInterface* HeldComponent = nullptr;

private:
    /**Index of target in selected component*/
    int32 CurrentlySelectedIndex;
};

MyGameTransformVisualizer.cpp

#include "MyGameTransformVisualizer.h"

// the class(es) we are implementing into the visualizer
// note that without interfaces you will either need a separate visualizer class for each Object type with duplicated code
// or have the drawVisualizer have a lot of attempts to cast to the different object types which will add more overhead to
// the editor and still a lot of duplicated code
#include "MyGameEntitySpawner.h"

IMPLEMENT_HIT_PROXY(HMyGameTransformVisProxy, HComponentVisProxy)
FMyGameTransformVisualizer::FMyGameTransformVisualizer() { }

FMyGameTransformVisualizer::~FMyGameTransformVisualizer() { }

void FMyGameTransformVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
	if( const UMyGameEntitySpawner* spawner = Cast<const UMyGameEntitySpawner>(Component) )
	{
		// only need the index twice, and range based for loops are more "sane"
		int32 ii = 0;
		for ( FMyGameSpawnPoint point : (spawner->SpawnPoints) )
		{
			// sorry colorblind people
			FLinearColor Color = (ii == CurrentlySelectedIndex) ? FLinearColor::Green : FLinearColor::Red;

			FVector Start = point.transform.GetLocation();
			if ( point.bIsOffset )
			{
				Start += spawner->GetOwner()->GetActorLocation();
			}
			// writing text to the display outside of World-Context is hard, at least they don't crash
//			if ( ii == CurrentlySelectedIndex )
//			{
//				DrawDebugString(EditedComponent->GetWorld(), Start, FString::FromInt(ii),
//					nullptr, FColor::Magenta, 0.0f, true, 20.0f);
//				GEngine->AddOnScreenDebugMessage(INDEX_NONE, 1.0f, FColor::Magenta, FString::FromInt(ii));
//			}
			
			// only 1 of these endpoints per iteration, and won't change
			const FVector End = Start + point.transform.GetRotation().Vector() * 100.0f;
			// the callback for the index
			PDI->SetHitProxy(new HMyGameTransformVisProxy(Component, ii));
			// drawing the line
			PDI->DrawLine(Start, End, Color, SDPG_World);
			// putting a dot on the End of the line to simulate an arrow (so I don't have to do arrow barbs)
			PDI->DrawPoint(End, FLinearColor::Blue, 5.0f, SDPG_World);
			// we need accurate ii (about the same performance as a full for-loop)
			ii++;
		}
	}
}

bool FMyGameTransformVisualizer::VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click)
{
	bool bEditing = false;
	if ( VisProxy && VisProxy->Component.IsValid() )
	{
		bEditing = true;
		if ( VisProxy->IsA(HMyGameTransformVisProxy::StaticGetType()) )
		{
			HMyGameTransformVisProxy* Proxy = (HMyGameTransformVisProxy*)VisProxy;
			// don't think to hard about this it is fine. I hate having to right this more then you hate me for writing it.
			EditedComponent = const_cast<UMyGameEntitySpawner*>(Cast<const UMyGameEntitySpawner>(Proxy->Component));
			UE_LOG(LogTemp, Error, TEXT("editedComponent Transform %i belonging to %s")
				,Proxy->ElementIndex, *EditedComponent->GetOwner()->GetName());
			CurrentlySelectedIndex = Proxy->ElementIndex;
			EditedComponent->VisualizerSelectedIndex = Proxy->ElementIndex;
		}
	}
	else
	{
		if ( EditedComponent != nullptr && IsValid(EditedComponent) ) { EditedComponent->VisualizerSelectedIndex = INDEX_NONE; }
		CurrentlySelectedIndex = INDEX_NONE;
	}
	return bEditing;
}

void FMyGameTransformVisualizer::EndEditing()
{
	CurrentlySelectedIndex = INDEX_NONE;
	EditedComponent = nullptr;
}

bool FMyGameTransformVisualizer::GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const
{
	// should we even be working on this (null check because IsValid does not check for null on raw pointers and can crash)
	if ( EditedComponent != nullptr && IsValid(EditedComponent) && CurrentlySelectedIndex != INDEX_NONE )
	{
		// could local var the struct, but it would be for 3 accesses
		// we need this part regardless and works out the "same" barring FloatingPointApproximation
		OutLocation = EditedComponent->SpawnPoints[CurrentlySelectedIndex].transform.GetLocation();

		if ( EditedComponent->SpawnPoints[CurrentlySelectedIndex].bIsOffset )
		{
			// the other part of the offset.
			OutLocation += EditedComponent->GetOwner()->GetActorLocation();
		}
		return true;
	}
	return false;
}

bool FMyGameTransformVisualizer::HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale)
{
	bool bHandled = false;
	if ( EditedComponent != nullptr && IsValid(EditedComponent) && CurrentlySelectedIndex != INDEX_NONE )
	{
		// by getting and working on the pointer to the FTransform we save a reassignment through either
		// the copy constructor, or a 3 arg call to FTransform::constructor.
		FTransform* trans = &(EditedComponent->SpawnPoints[CurrentlySelectedIndex].transform);
		FVector loc = trans->GetLocation();
		loc += DeltaTranslate;
		trans->SetLocation(loc);
		
		// FQuat will always be a magic thing.
		FRotator rot = trans->GetRotation().Rotator();
		rot += DeltaRotate;
		trans->SetRotation(FQuat(rot));

		loc = trans->GetScale3D();
		loc += DeltaScale;
		trans->SetScale3D(loc);
		bHandled = true;
	}
	return bHandled;
}

at this point you should be able to build the module (hopefully I have documented all the steps). do a double check by building your game, and as long as you don’t have any errors you should be albe to launch the editor, select an actor with the designated component, and for each Transform in the array get there will be a red-line with a blue-dot that when clicked will turn green. you can move them around with either the widget that appears when they are selected or you modify the Transform in the details window
it will also behave with the editors rotation mode in the viewport. Scale is currently not modifying anything but the Widget mode does work.

these “features” with the explicit might be because I am working with an UActorComponent not a USceneComponent

  • after finishing any Widget interaction the widget will return to the Actors <0,0,0>, and modify the actor. but clicking the green line again will put the widget back
  • the visualizations only show up when the Actor is selected in the Actor’s Outliner, selecting the Componenent these Visualizations are visualizing, or any other Component of the Actor will make the Visualizations disappear.
  • I would like to have the CurrentlySelectedIndex show when one is selected, but writing text to the screen outside of World-Context or SLATE is apparently non trivial if someone can figure this out, or provide the needed “SLATE” operations I would really appreciate it.
  • because of the previous the is an Error message in the Log when an index is selected (this is not an actual error, but will stand out against Display messages)
1 Like

The interface implementation only needs 3 files of refactoring (+2 for each additional interface implementer basically if you are going to derive from the TransformStruct you will need to have a separate Interface implementation and therefore registering that to the MyGameEditorModule::StartupModule and implementing the interface itself)
we need to create an interface for any class that we want our visualizer to work with. the Visual Studio Template supports Interfaces (they are under the class submenu) I will be naming this MyGameTransformStructInterface
MyGameTransformStructInterface.h

#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"

#include "MyGameTransformStructInterface.generated.h"

// I moved this here because the interface needs it, and struct inheritance is "fine"
USTRUCT(BlueprintType)
struct FMyGameTransformStruct
{
	GENERATED_BODY()
public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	bool bIsOffset = true;
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	FTransform transform;
};

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UMyGameTransformStructInterface : public UInterface
{
	GENERATED_BODY()
};

/**
 * 
 */
class MYGAME_API IMyGameTransformStructInterface
{
	GENERATED_BODY()

	// Add interface functions to this class. This is the class that will be inherited to implement this interface.

    // if you are going to wrap the functions in the implementers as #ifdef WITH_EDITOR then these should be as well "because interfaces" (just remember to wrap EACH implementation of the functions that way)
public:
//#ifdef WITH_EDITOR
	// if the component will only have 1 of the struct then implementation could given an array of 1 item.
	// outside of Unreal this could be an Array<FMyGameTransformStruct*> 
	virtual const TArray<FMyGameTransformStruct> GetVisualizationArray() const = 0;
	
	// if the component will only have 1 of the struct then it could be returned through this with index 0
	virtual FMyGameTransformStruct* GetTransformStructIndex(int32 index) = 0;

	// if we are going to be working with and assigning things then we might as well be able to assign it
	virtual void SetTransformStructIndex(int32 index, FTransform& inTransform) = 0;
	
	// it says Get but we are 
	virtual void GetVisualizerCurrentlySelectedIndex(int32 inIndex) = 0;

	//really dumb pass through functions technically in the Visualizer this will be an interface Instance not an actor component.
	virtual UWorld* GetTheWorld() const = 0;

	virtual AActor* GetOwningActor() const = 0;
// #endif WITH_EDITOR
};

now wee need to have our Component implement it, so over to out
MyGameEntitySpawner.h

#include "MyGameTransformStructInterface.h"

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class MYGAME_API UMyGameEntitySpawner : public UActorComponent, public IMyGameTransformStructInterface
{
// the interface can't be inherited as private to then mark the Visualizer as friend (because we are not allowed to private
// inherit ANYTHING from reflection) and puting per-processor conditions WITH_EDITOR around the interface methods which can cause
// packaging nightmare if someone uses one in development (just keep UFUNCTION away from them)
	GENERATED_BODY()

public:	
	// should be in #ifdef WITH_EDITORONLY_DATA but even when I put that it complains about needing what is already there...
	UPROPERTY(VisibleAnywhere)
	int32 VisualizerSelectedIndex = INDEX_NONE;
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TArray<FMyGameSpawnPoint> SpawnPoints;

// ...
//#ifdef WITH_EDITOR
	// Start TransformVisualizationInterface
	// everything about this interface could/should probably be wrapped in #ifdef WITH_EDITOR
	// DO NOT access in production code
	virtual const TArray<FMyGameTransformStruct> GetVisualizationArray() const override
	{
		TArray<FMyGameTransformStruct> result;
		// because inheritance
		for ( FMyGameSpawnPoint point : SpawnPoints ) { result.Add(point); }
		return result;
	}
	// DO NOT access in production code
	virtual FMyGameTransformStruct* GetTransformStructIndex(int32 index) override
	{
		if ( index >= 0 && index < SpawnPoints.Num() ) { return &SpawnPoints[index]; }
		return nullptr;
	}
	// DO NOT access in production code
	virtual void SetTransformStructIndex(int32 index, FTransform& inTransform) override
	{
		if ( index >= 0 && index < SpawnPoints.Num() ) { SpawnPoints[index].transform = inTransform; }
	}
	// DO NOT access in production code
	virtual void GetVisualizerCurrentlySelectedIndex(int32 inIndex) { VisualizerSelectedIndex = inIndex; }
	// DO NOT access in production code
	virtual UWorld* GetTheWorld() const override { return GetWorld(); }
	// DO NOT access in production code
	virtual AActor* GetOwningActor() const override { return GetOwner(); }
	// End TransformVisualizationInterface
//endif WITH_EDITOR
//...
};

now back over to the Visualizer.cpp as that is really what has changed “because interfaces”

#include "MyGameTransformVisualizer.h"
// the interface we will be working with in this visualizer.
#include "MyGameTransformStructInterface.h"

IMPLEMENT_HIT_PROXY(HMyGameTransformVisProxy, HComponentVisProxy)
FMyGameTransformVisualizer::FMyGameTransformVisualizer() { }

FMyGameTransformVisualizer::~FMyGameTransformVisualizer() { }


void FMyGameTransformVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
	if ( Component->Implements<UMyGameTransformStructInterface>() )
	{
		if ( const IMyGameTransformStructInterface* InterfaceComponent = Cast<const IMyGameTransformStructInterface>(Component) )
		{
			// we only have 1 Actor Transform used for offsets
			const FVector OwnerLoc = InterfaceComponent->GetOwningActor()->GetActorLocation();

			const TArray<FMyGameTransformStruct> structArray = InterfaceComponent->GetVisualizationArray();

			int32 jj = 0;
			for ( FMyGameTransformStruct point : structArray )
			{
				// sorry colorblind people
				FLinearColor Color = (jj == CurrentlySelectedIndex) ? FLinearColor::Green : FLinearColor::Red;

				FVector Start = point.transform.GetLocation();
				if ( point.bIsOffset )
				{
					Start += OwnerLoc;
				}
				
				// writing text to the display outside of World-Context is hard, at least they don't crash
//				if ( jj == CurrentlySelectedIndex )
//				{
//					DrawDebugString(HeldComponent->GetTheWorld(), Start, FString::FromInt(jj),
//						nullptr, FColor::Magenta, 0.0f, true, 20.0f);
//					GEngine->AddOnScreenDebugMessage(INDEX_NONE, 1.0f, FColor::Magenta, FString::FromInt(jj));
//				}
				// only 1 of these endpoints per iteration, and won't change
				const FVector End = Start + point.transform.GetRotation().Vector() * 100.0f;
				// the callback for the index
				PDI->SetHitProxy(new HMyGameTransformVisProxy(Component, jj));
				// drawing the line
				PDI->DrawLine(Start, End, Color, SDPG_World);
				// putting a dot on the End of the line to simulate an arrow (so I don't have to do arrow barbs)
				PDI->DrawPoint(End, FLinearColor::Blue, 5.0f, SDPG_World);
				// we need accurate ii (about the same performance as a full for-loop)
				jj++;
			}
		}
	}
}

bool FMyGameTransformVisualizer::VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click)
{
	bool bEditing = false;

	if ( VisProxy && VisProxy->Component.IsValid() )
	{
		bEditing = true;
		if ( VisProxy->IsA(HMyGameTransformVisProxy::StaticGetType()) )
		{
			HMyGameTransformVisProxy* Proxy = (HMyGameTransformVisProxy*)VisProxy;
			// don't think to hard about this it is fine. I hate having to right this more then you hate me for writing it.
			HeldComponent = const_cast<IMyGameTransformStructInterface*>(Cast<const IMyGameTransformStructInterface>(Proxy->Component));
			UE_LOG(LogTemp, Error, TEXT("HeldComponent Transform %i belonging to %s")
				,Proxy->ElementIndex, *HeldComponent->GetOwningActor()->GetName());
			CurrentlySelectedIndex = Proxy->ElementIndex;
			HeldComponent->GetVisualizerCurrentlySelectedIndex(Proxy->ElementIndex);
		}
	}
	else
	{
		if ( HeldComponent != nullptr ) { HeldComponent->GetVisualizerCurrentlySelectedIndex(INDEX_NONE); }
		CurrentlySelectedIndex = INDEX_NONE;
	}

	return bEditing;
}

void FMyGameTransformVisualizer::EndEditing()
{
	CurrentlySelectedIndex = INDEX_NONE;
	HeldComponent = nullptr;
}

bool FMyGameTransformVisualizer::GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const
{
	if ( HeldComponent != nullptr && CurrentlySelectedIndex != INDEX_NONE )
	{
		if ( FMyGameTransformStruct* form = HeldComponent->GetTransformStructIndex(CurrentlySelectedIndex) )
		{
			OutLocation = form->transform.GetLocation();
			if ( form->bIsOffset )
			{
				OutLocation += HeldComponent->GetOwningActor()->GetActorLocation();
			}
			return true;
		}
	}
	return false;
}

bool FMyGameTransformVisualizer::HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale)
{
	bool bHandled = false;
	if ( HeldComponent != nullptr && HeldComponent && CurrentlySelectedIndex != INDEX_NONE )
	{
		if ( FMyGameTransformStruct* form = HeldComponent->GetTransformStructIndex(CurrentlySelectedIndex) )
		{
			// by getting and working on the pointer to the FTransform we save a reassignment through either
			// the copy constructor, or a 3 arg call to constructor.
			FTransform* trans = &form->transform;
			FVector loc = trans->GetLocation();
			loc += DeltaTranslate;
			trans->SetLocation(loc);
		
			// FQuat will always be a magic thing.
			FRotator rot = trans->GetRotation().Rotator();
			rot += DeltaRotate;
			trans->SetRotation(FQuat(rot));

			loc = trans->GetScale3D();
			loc += DeltaScale;
			trans->SetScale3D(loc);
			bHandled = true;
		}
	}
	return bHandled;
}

at this point you should be able to build your project and run. selecting an actor component that is registered in the MyGameEditorModule::StartupModule should have the red lines with blue dots when a red line is selected it should turn green, with widgets in the same modes as the editor (as long as a SceneComponent is selected (see below)


Special “features” of the module for both explicit and interface implementation(s)

  • I would like to have the CurrentlySelectedIndex show when one is selected, but writing text to the screen outside of World-Context or SLATE is apparently non trivial if someone can figure this out, or provide the needed “SLATE” operations I would really appreciate it. (if this thread is archived then Link back to one of my posts in this thread and @ me)
  • because of the previous there is an Error message in the Log when an index is selected (this is not an actual error, but will stand out among Display messages) The VisualizerSelectedIndex could also be read from the Component’s Details being visibleAnywhere (less helpful for UActorComponent but something)

special “features” of the Interface implementation When dealing with a UActorComponent implementing the interface

  • selecting anything that does not inherit from at least USceneComponent (including the Component the Visualization is being drawn for) the Widgets will not work/display.
  • if the root actor/component is selected the then after the widget has been released (completed move/rotate/scale) the widget is now desynced from the transform meaning modifications will not be applied.
  • if any other USceneComponent is selected in the Actor’s Outliner the widget and the Transform will stay synced (I don’t know why this is a thing)

special “features” of the Interface implementation when dealing with a USceneComponent implementing the interface (Thank you @gardian206 for the notes and testing the USceneComponent)

  • the widget will no longer become desynced from the transform while the Actor or Root Component of the Actor’s outliner are selected
  • the Widget will be visible and interactable while the Component being visualized is selected.

thanks to: Unreal Engine Component Visualizers: Unleashing the Power of Editor Debug Visualization | Quod Soler had a lot of the details on the steps to setup clearly details (even showing code not just a video)

and to: Component Visualizers | Unreal Engine Community Wiki for some of the implementation like the VisProxy stuff, but…
*please fix the inconsistencies of variable names, and actually assign your pointer it is probably like 5 line changes away from being functional, you also use the dataType Target when I have to guess these are probably FVector, also I don’t know where your LaserCannon is implemented in the wiki, but we only care about like 2 things about it (that it is an ActorComponent and this targets array.

1 Like

was trying this out a bit with a SceneComponent

#include "CoreMinimal.h"
#include "Components/SceneComponent.h"
#include "MyGameTransformStructInterface.h"
#include "MyGameTargetPointsComponent.generated.h"


/*
* generic class for a SceneComponent that implements the interface required for Visualizing Array of Transforms
* if the TransformStruct needs to be inherited then create a separate class that implements the interface, and
* register that to the module
*/
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class MYGAME_API UMyGameTargetPointsComponent : public USceneComponent, public IMyGameTransformStructInterface
{
	GENERATED_BODY()

public:	

	// Sets default values for this component's properties
	UMyGameTargetPointsComponent();

	void PostInit();

	UPROPERTY(VisibleAnywhere)
	int32 VisualizerSelectedIndex = INDEX_NONE;
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TArray<FTransformStruct> Targets;
	
	// Start TransformVisualizationInterface
	// everything about this interface could/should probably be wrapped in #ifdef WITH_EDITOR
	// DO NOT access in production code
	virtual const TArray<FTransformStruct> GetVisualizationArray() const override { return Targets; }
	// DO NOT access in production code
	virtual FTransformStruct* GetTransformStructIndex(int32 index) override
	{
		if ( index >= 0 && index < Targets.Num() ) { return &Targets[index]; }
		return nullptr;
	}
	// DO NOT access in production code
	virtual void SetTransformStructIndex(int32 index, FTransform& inTransform) override
	{
		if ( index >= 0 && index < Targets.Num() ) { Targets[index].transform = inTransform; }
	}
	// DO NOT access in production code
	virtual void GetVisualizerCurrentlySelectedIndex(int32 inIndex) { VisualizerSelectedIndex = inIndex; }
	// DO NOT access in production code
	virtual UWorld* GetTheWorld() const override { return GetWorld(); }
	// DO NOT access in production code
	virtual AActor* GetOwningActor() const override { return GetOwner(); }
	// End TransformVisualizationInterface

protected:
	// Called when the game starts
	virtual void BeginPlay() override;
	
};

it seams to behave a lot better with a SceneComponent.

  • the widgets appear even if the Visualized-Component is selected
  • and there doesn’t appear to be any of the “desync” you were reporting even with the root component selected.

some details on your “features” (at least the Interface implementation) the “DeSync” you were reporting only looks to happen if the root of the Actor is selected, and I am getting the Lines to appear for all the components, I just can’t get the Widgets to appear if I select anything that derives directly from ActorComponent (maybe this is internal logic where ‘if the object does not natively have a transform then there is no reason to be rendering the transform widget(s)’ so there is not widget to re-position, or it is being re-positions but just is non-interactable)

I am getting the lines to show up for every component in an actor that has a UActorComponent registered to the module (so for your example if I give your UMyGameEntitySpawner to a random actor that has a bunch of other SceneComponents the lines render in world space no matter which of the Components in the Actor’s Outliner, I can even select the lines with the with another ActorComponent Selected (just no Widgets).

so I guess the work around for your Desync

for ActorComponents registered to the module be sure to have any SceneComponent besides the root selected when editing the transforms through the visualization.

I am on the fence about your choice to use an Error in the log, but I get your intent (No I don’t know how to render text.

the only major complaint I could have about this at all is like with most 3D stuff a single line expresses forward, where in 3D space we kind of need 3 points of reference to orient ourselves IRL we do get a little spoiled where gravity tells us where “up/down” is.

I would also note to be careful which classes you register to the module as changing any header that is included in a module looks to drastically increase build times
where my Ryzen R9 3900X goes from taking under a couple seconds to finish building, to by adding a single class header to the includes of the module any time that header is touch take almost a minute to spin up the build (or anything it includes)

1 Like

for 3D space you do “need” 3 points of reference to determine direction, but remember that a line is already 2 points of reference, so for the directional context of 3D you would only “need” 2 lines with the same origin.
most 3D software give you 3 vectors (sharing the same origin) for orientation, because they double as other convenient mental short hand through color for “forward, left/right, and up” with color coding for the given axis, and then they double as convenient ways to modify the local/world values of that given axis.
if you would like your level designers to have an “easier time” regarding roll (that is technically the one that will not be captured with a single line)


you can modify the DrawVisualization() as follows

			for ( FMyGameTransformStruct point : structArray )
			{
				// sorry colorblind people
				const FLinearColor FwdColor = (jj == CurrentlySelectedIndex) ? FLinearColor::Green : FLinearColor::Red;
				const FLinearColor UpColor = (jj == CurrentlySelectedIndex) ? FLinearColor::Yellow : FLinearColor::Blue;
				// it is more convenient to have this non-const because it could be an offset
				FVector Start = point.transform.GetLocation();
				if ( point.bIsOffset )
				{
					Start += OwnerLoc;
				}
				
				// writing text to the display outside of World-Context is hard, at least they don't crash

				// only need these endpoints once per iteration, and won't change
				const FVector FwdEnd = Start + point.transform.GetRotation().Vector() * 100.0f;
				const FVector UpEnd = Start + point.transform.GetRotation().Rotator().RotateVector(FVector::UpVector) * 100.0f;
				// the callback for the index
				PDI->SetHitProxy(new HTransformVisProxy(Component, jj));
				// drawing the lines
				PDI->DrawLine(Start, FwdEnd, FwdColor, SDPG_World);
				PDI->DrawLine(Start, UpEnd, UpColor, SDPG_World);
				// putting a dot on the End of the line to simulate an arrow (so I don't have to do arrow barbs)
				PDI->DrawPoint(FwdEnd, FLinearColor::Blue, 5.0f, SDPG_World);
				PDI->DrawPoint(UpEnd, FLinearColor::Green, 5.0f, SDPG_World);
				// incrementing jj makes this technically a for-loop with extra steps, but we need the value
				jj++;
			}

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.