Actor factory drag and drop custom class in 5.7 vs 4.27

Hi,

We’ve been porting our game from 4.27 to 5.7.4 and one issue that has come up is with our drag and drop from content browser behavior. What we had previously was a setup where in GetDefaultActor we would find the static mesh like this:

  if(AssetData.GetClass())
  {
    UStaticMesh* staticMesh = Cast<UStaticMesh>(StaticLoadObject(AssetData.GetClass(), NULL, *AssetData.GetObjectPathString()));

After that check the static mesh for asset user data that was added to the mesh and from that we could determine which kind of actor to place in the level, then change some settings stored in the user data later in PostSpawnActor. This would make sure designers could place meshes from the content browser without worrying about the type of actor to spawn.

This doesn’t seem to work anymore, AssetData doesn’t have any information anymore in GetDefaultActor (looking at UEditorEngine::FindActorFactoryForActorClass it just calls it with an empty FAssetData). Could this be done in some other way now in 5.7?

Thanks,

Oliver

[Attachment Removed]

Hi Oliver,

If I understand correctly, you’d like the Editor to spawn different Actor types when a Static Mesh asset is dragged from the Content Browser into the Level Editor Viewport (instead of always a StaticMeshActor), depending on the Asset User Data associated with the dragged asset. Is that correct?

I have investigated ways to achieve this in both 4.27 and 5.7, and I’m afraid it may not be possible in the current engine version without editing the source code.

In UE 4.27, all existing factories were discovered and registered during engine initialization inside UEditorEngine::InitEditor(). Afterwards, they were also sorted by their property “MenuPriority”, so that factories with higher priority ended up before those with lower priority in the list. Later, when an asset was dragged to the Level Editor Viewport, function FActorFactoryAssetProxy::AddActorForAsset() was called to decide what factory to use for that asset. To do that, it iterated over all registered factories and selected the first one to return true on CanCreateActorFrom(AssetData).

The process above meant that a custom factory for UStaticMesh assets would have to be assigned a very high priority. The base UActorFactory has a priority of 10 by default, and UActorFactoryStaticMesh has 30 (as configured in file <ENGINE_PATH>\Engine\Config\BaseEditor.ini). A custom factory would be considered before the built-in ones if it had a priority above 30. Was that something you had set up in 4.27?

Now, in UE 5.7, all the process above still exists, but it has been superceded by the new UPlacementSubsystem, which has its own list of “AssetFactories”. The old process is only used if UPlacementSubsystem does not return a valid factory. The problem is that this new factory list is not sorted at any time, so its ordering is not reliable, which seems like an oversight of the new implementation. Moreover, the placement subsystem does not offer a way to add factories to the beginning of the list, only to the end.

Let me know if I understood your intent correctly and if the information above is helpful. I am still investigating this further and searching for a workaround. Let me know also if you are building the engine from source.

Best regards,

Vitor

[Attachment Removed]

Hi, yep you’ve understood correctly! And yes we set MenuPriority to 100 for our Actor Factories in 4.27 to achieve this.

Ah, I see. That makes sense, we do build engine from source and making a few changes to sort the list in the Placement Subsystem according to their MenuPriority I could get the old behavior to work, let me know if these look sensible!

==== .../Engine/Source/Editor/EditorFramework/Private/Subsystems/PlacementSubsystem.cpp#2 (text) ====
 
[Content removed]9 @@ (RegisterPlacementFactories)
                        }
                }
        }
+
+       // Sort the asset factories according to their MenuPriority
+       AssetFactories.Sort([](const TScriptInterface<IAssetFactoryInterface>& A, const TScriptInterface<IAssetFactoryInterface>& B) { return A->GetMenuPriority() > B->GetMenuPriority(); });
 
        PlacementFactoriesRegistered.Broadcast();
 }
 
==== .../Engine/Source/Editor/EditorFramework/Public/Factories/AssetFactoryInterface.h#2 (text) ====
 
[Content removed]8 @@
         * For example, for instanced static mesh placement, we'd rebuild the parent component's tree here.
         */
         virtual void EndPlacement(TArrayView<const FTypedElementHandle> InPlacedElements, const FPlacementOptions& InPlacementOptions) = 0;
+
+        virtual int GetMenuPriority() const { return 0; }
 
         /**
          * Returns the settings object which this factory will use to customize placement settings, based on the given placement information.
 
==== .../Engine/Source/Editor/UnrealEd/Classes/ActorFactories/ActorFactory.h#2 (text) ====
 
[Content removed]8 @@
        UNREALED_API virtual void BeginPlacement(const FPlacementOptions& InPlacementOptions) override;
        UNREALED_API virtual void EndPlacement(TArrayView<const FTypedElementHandle> InPlacedElements, const FPlacementOptions& InPlacementOptions) override;
        UNREALED_API virtual UInstancedPlacemenClientSettings* FactorySettingsObjectForPlacement(const FAssetData& InAssetData, const FPlacementOptions& InPlacementOptions) override;
+
+       UNREALED_API virtual int GetMenuPriority() const override;
        // End IAssetFactoryInterface Interface
 
        static UNREALED_API void CreateBrushForVolumeActor(AVolume* NewActor, UBrushBuilder* BrushBuilder);
 
==== .../Engine/Source/Editor/UnrealEd/Private/Factories/ActorFactory.cpp#2 (text) ====
 
[Content removed]11 @@
        return nullptr;
 }
 
+int UActorFactory::GetMenuPriority() const
+{
+       return MenuPriority;
+}
+
 AActor* UActorFactory::CreateActor(UObject* InAsset, ULevel* InLevel, const FTransform& InTransform, const FActorSpawnParameters& InSpawnParams)
 {
        AActor* NewActor = nullptr;

[Attachment Removed]

Yes, looks sensible. If you want to sort exactly like the other system, here’s how it does it:

	// From UEditorEngine::InitEditor() [EditorEngine.cpp]
	struct FCompareUActorFactoryByMenuPriority
	{
		FORCEINLINE bool operator()(const UActorFactory& A, const UActorFactory& B) const
		{
			if (B.MenuPriority == A.MenuPriority)
			{
				if (A.GetClass() != UActorFactory::StaticClass() && B.IsA(A.GetClass()))
				{
					return false;
				}
				else if (B.GetClass() != UActorFactory::StaticClass() && A.IsA(B.GetClass()))
				{
					return true;
				}
				else
				{
					return A.GetClass()->GetName() < B.GetClass()->GetName();
				}
			}
			else
			{
				return B.MenuPriority < A.MenuPriority;
			}
		}
	};
	// Sort by menu priority.
	ActorFactories.Sort(FCompareUActorFactoryByMenuPriority());

Also note that, if you register your factory on a callback bound to FCoreDelegates::OnPostEngineInit, it is likely that this happens after UPlacementSubsystem::RegisterPlacementFactories() has run. Ideally the sort should happen again, maybe on request, or maybe called from FEngineLoop::Init() after modules are loaded after this line:

if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostEngineInit))

[Attachment Removed]

By the way, I filed an internal bug report for this issue. Here’s the tracking number: UE-371946. This link should become accessible once the devs mark it as public.

All the best,

Vitor

[Attachment Removed]

Perfect, thank you - I will give it a go!

[Attachment Removed]