Building An Interactive Viewport Tool For Making And Viewing Connections Between Actors

Goal: create an Editor Scriptable Tool that allows the user to edit variable-defined connections between Actors in a level by click-dragging on them in the editor viewport, and visualizes these connections.

Requires Unreal Engine 5.2 or newer.

66ca8f15-00e1-4305-8818-79bfcb57ec64

Visualizing arbitrary connections between actors in editor mode has not been a simple task in Unreal, especially if you don’t want to write C++ code. Scriptable Tool system provides a way to do this in a modal way, while being completely separate from gameplay code and exposed to Blueprint.

1. Enable Plugins

Plugins Scriptable Tools Framework, Scriptable Tools Editor Mode and Geometry Script (we’ll need this one to generate temporary collision geometry for object selection) have to be enabled in the project.

2. Create Actor Blueprint

For this example, I created a simple Actor Blueprint Class called BP_Waypoint. It has one Instance Editable variable called NextWaypoint of type BP_Waypoint Object Reference.

7fc4ac37-51b8-4f81-80df-d2779f3105fd

We are going to create a viewpoort tool for visualizing and editing connections defined by this variable.

Drop a few instances of this Actor into a level.

3. Create Scriptable Tool Blueprint

Next, I created an Editor Utility Blueprint (under Editor Utilities group in the asset creation menu) called BP_WaypointScriptableTool. It extends EditorScriptableClickDragTool, which fires events when user clicks and drags the cursor over viewport while using the tool.

f9ff91d4-c4ef-4245-97a4-0c98cb77502e

65606500-5d30-427a-afce-896d059f8a5b

In its Class Defaults I filled out some basic information about the tool that will be displayed in the toolbar.

fdcb6435-9846-4a47-908c-6ded47a35e72

4. Tool Setup Event

The tool functionality is implemented by overriding base class’ functions and events. When the tool is activated, the OnScriptSetup event is fired.

In my override for this event, I am getting a list of all BP_Waypoint actors in the level and storing it in a variable called WaypointList. I also call a custom function to build a “selection mesh”, but we will talk about it a bit later.

OnScriptSetup implementation

5. Visualizing connections

The function that handles in-world visualization is called OnScriptRender. Here I iterate through WaypointList, and if a Waypoint has a defined NextWaypoint, I draw a line between the two actors.

OnScriptRender implementation

Since the connection is directional, I also made a function DrawChevronPath that draws chevrons along the line.

DrawChevronPath implementation

My OnScriptRender override has a second part - it draws a chevron path from an actor referenced in EditedWaypoint variable (if valid) to a location vector stored in DragTempTarget variable. This is drawn when the user is in the middle of creating a new connection and is dragging the mouse cursor from one waypoint to another. These variables will be defined in other events.

Compile the blueprint. At this point, if you manually define some NextWaypoint references on the waypoints in your level, you can go to Scriptable Tools Mode (Shift+F9) in the main editor window, selected your tool on the left, and see the connections visualized in the viewport!

27fd3c67-7492-46ca-a29b-ae93223785ad

66ca8f15-00e1-4305-8818-79bfcb57ec64

6. Selecting Actors

The events in EditorScriptableClickDragTool provide us with a world-space ray that we can use to get objects the user clicked on. If the actor that you want to edit has collision geometry, you can get it by just doing a line trace against this collision geo, like you would normally do in gameplay code.

But our BP_Waypoint actors have no collision - and the Scriptable Tools do not expose editor’s normal viewport mouse selection logic, so we need to use a workaround.

One simple option is to just add an editor-only sphere collision component to BP_Waypoint. This will result in unnecessary components that are only intended for editor use, which might cause unintended behaviour in play mode if you’re not careful. Additionally, if you wish to later extend the tool to support more types of objects, you will need to add such collision components to each of them.

Another option that keeps the selection logic completely inside our tool, is to create a temporary mesh using Geometry Script and cast the ray against that.

I made a function for building this mesh, and a macro for casting a ray against it and getting the associated Waypoint from the list. I chose a macro instead of a function to have a second exec output to handle the case when no Waypoint was found.

  • The function BuildSelectionMesh constructs a new DynamicMesh object, creates a sphere on each Waypoint position, stores the Waypoint’s index in the list as a Poly Group ID, and builds a BVH for the resulting mesh. The mesh object itself and the BVH are stored in the variables called SelectionMesh and SelectionMeshBVH. This is the function that is called in theOnScriptSetup event.
  • The macro GetWaypointFromSelectionMeshRayCast takes a ray as an input, casts it against the mesh using BVH, reads the Poly Group ID, and gets the Waypoint from the array. It also exposes the location of the hit.

BuildSelectionMesh implementation

GetWaypointFromSelectionMeshRayCast implementation

7. Set NextWaypoint Property Transaction

This is the function that will be called to actually edit the property on the waypoint Actor. I use the Transaction system to make the action undo-able.

SetNextWaypointPropertyTransaction implementation

8. Mouse Interaction

Now we just need to overide the events responsible for mouse interactions.

  • TestIfCanBeginClickDrag makes a cast against our selection mesh and reports if a hit was detected.

TestIfCanBeginClickDrag implementation

  • OnDragBegin makes a cast against the selection mesh and stores the reference to the clicked Waypoint in the EditedWaypoint variable. It also resets DragTempTarget to the location of the detected hit. If no hit was detected, it resets EditedWaypoint to Null.

OnDragBegin implementation

  • OnDragUpdatePosition projects a given ray into the world at the same distance as the EditedWaypoint is from the origin point, and stores the resulting location in the DragTempTarget variable. This is used to provide visual feedback for dragging the cursor around the viewport in OnScriptRender.

OnDragUpdatePosition implementation

  • OnDragEnd makes a cast against the selection mesh, and calls the SetNextWaypointTransaction function, setting the found Waypoint as the EditedWaypoint’s NextWaypoint. If no hit was detected, NextWaypoint is set to Null instead. Then we finalize the action by resetting EditedWaypoint to Null.

OnDragEnd implementation

And that’s it!

This can obviously be further improved, for example:

  • Handle creation, removal and movement of Waypoint Actors in the level while the tool is active to rebuild the WaypointList and selection geometry.
  • Highlight unconnected Waypoints in the level when the tool is active.
  • Allow the user to customize Waypoint and path colors.
2 Likes