Cannot save level after dynamically assigning the materials in C++

Hello,

in my project based on procedurally generated environments I have to use the UMaterialInstanceDynamic along with the SetMaterial function of the UProceduralMeshComponent, because I want the materials to be loaded at the runtime of the editor from the specified asset-names.
Every material is declared in the class UMaterialHolder as

UPROPERTY(EditAnywhere, Category = Materials)
	UMaterialInstanceDynamic * floor5;
UPROPERTY(EditAnywhere, Category = Materials)
	UMaterial * floor5Inst;

I assign it in the constructor as:

ConstructorHelpers::FObjectFinder<UMaterial> afloor5(_T("Material'/Game/Textures/floor1.floor1'"));
if (afloor5.Succeeded())
{
	floor5Inst = (UMaterial*)afloor5.Object;
}

At the runtime I simply call when necessary

if (floor5Inst)
	floor5 = UMaterialInstanceDynamic::Create(floor5Inst, this);

All of it works. But the problem is that I am unable to save the generated level. The error I get is

    Can't save D:/UEProject_full/UEIfcGen/Content/Maps/textured_model2.umap: Graph is linked to private object(s) in an external package.
External Object(s):
MaterialHolder_0
/Engine/Transient
MaterialInstanceDynamic_32
MaterialInstanceDynamic_33
MaterialInstanceDynamic_31
...
  
Try to find the chain of references to that object (may take some time)?

After clicking ok I am presented with this message:

Warning Can't save D:/UEProject_full/UEIfcGen/Content/Maps/textured_model2.umap: Graph is linked to external private object MaterialInstanceDynamic /Engine/Transient.MaterialHolder_0:MaterialInstanceDynamic_37 (floor5)

Is there any way to save the level that contains materials assigned dynamically in C++?

I am able to reduce the error messages to the MaterialHolder being unaccessible “external private object” if I use UMaterial instead of UMaterialInstanceDynamic. But the problem remains - I cannot save the level, unless I also initialize the MaterialHolder in the actor’s constructor, which I am trying to avoid, because I need the material loading to be happening during the runtime.

Hello Mtimofeev, did you by any chance found out the solution for this problem? I’m currently struggling with the same issue - I have an UMaterialInstanceDynamic* that is a private variable in an UCLASS. It’s not an UPROPERTY though.

In other threads/questions about this issue people say that making the variable an UPROPERTY(Transient) helps, but it does not solve the issue in my case.

Anyone found a solution for this? I also ran into this problem

I had a similar problem, though it was for something meant to be editor-only that had a shader property dynamically updated every frame, so it didn’t exactly need to be saved. I couldn’t use a UMaterialInstance for this because every time it updated the shader property it would try to re-bake the material, which isn’t what I needed.

Based on that behavior, I suspect UMaterialInstanceDynamic cannot be saved under any circumstance, so your options are to either use a UMaterialInstance, or you save some other information used to regenerate the UMaterialInstanceDynamic at runtime after construction. A hybrid approach is to have a UMaterialInstance assigned at construction and then replace it with a duplicated/modified instance later (which is a common pattern from UE3 that has continued into UE4). There are a number of variations of that pattern depending on your needs.

In my particular case I was making an editor-only Actor. I was assigning a MID (Material Instance Dynamic) to a Billboard via the Actor’s constructor. Once I placed it in the level, I got this error when trying to save it. I solved it by marking both the MID and the Billboard as Transient since I was okay with both being regenerated every time I open the editor. The problem with solely marking the MID as Transient is that it was being assigned to a value in the Billboard that wasn’t marked as transient, so when trying to save the scene it descended into the Billboard elements and found something it couldn’t save.

Hope this helps.

UMaterialInstanceDynamic* UMaterialInstanceDynamic::Create(UMaterialInterface* ParentMaterial, UObject* InOuter)
{
LLM_SCOPE(ELLMTag::MaterialInstance);
UObject* Outer = InOuter ? InOuter : GetTransientPackage();
UMaterialInstanceDynamic* MID = NewObject(Outer);
MID->SetParentInternal(ParentMaterial, false);
return MID;
}

Base on this function, if you don’t provide an outer it will crate a transient package, and transient object are not saved.