What I’m trying to accomplish:
Load a static mesh and set certain material slots to material instances (without changing the base mesh materials, because that would change any previous actors who had been spawned in with that mesh).
Example - A bow that was crafted from Osage Orange wood is now being spawned. I need to take the base mesh and apply my Osage Orange MI to the “core” material slot on that mesh.
My problem:
My code is slow. It takes anywhere from 10-60 milliseconds for the below code to execute (on a dell XPS laptop with 10th gen i9). Occasionally I’ve seen it shoot up to ~140 ms, but that’s rare. 40-50 is very common, however. Which means my player only needs to come across ~30 items and that’s a full second of frozen computer.
I’ve added some comments to the code to help give some context, but the project is our game client which connects to an external server.
I’m sure I’m doing things horribly wrong, please don’t be gentle.
UStaticMesh* AGameData::LoadStaticMeshFromEntityId(int64 EntityId, bool CouldHaveSkeletal)
{
if (LoadedItems.Contains(EntityId)) {
//"Item" is custom class that does not inherit from UObject, it is just a subclass of this class and is just used to hold information about game items(equipment/consumables/etc).
Item* I = LoadedItems.Find(EntityId);
//making a custom key based off of information about the item from the server. Item->PathKey is a code in string format that comes from the server to identify the item.
FString SerializedMaterialSlots = GetSerializedItemMaterialSlots(EntityId, true);
if (InstancedStaticMeshMap.Contains(I->GetPathKey() + SerializedMaterialSlots)) {
return *InstancedStaticMeshMap.Find(I->GetPathKey() + SerializedMaterialSlots);
}
FString Path = PathLoader::LoadPathFromPathKey(I->GetPathKey());
LastPath = Path;
if (CouldHaveSkeletal) {
FString RelativePath = PathLoader::LoadRelativeFilePathFromPathKey(I->GetPathKey());
RelativePath = RelativePath.Replace(*FString(".uasset"), *FString("_SM.uasset"));
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
if (PlatformFile.FileExists(*RelativePath)) {
//found a specific static mesh (this means the normal path would lead us to a skeletal mesh).
Path += "_SM";
}
}
UStaticMesh* StaticMesh = LoadObject<UStaticMesh>(nullptr, *Path);
if (nullptr != StaticMesh) {
//"I->MaterialSlots" (TMap<FString, FString>) == <unreal material slot name, material instance pathkey>
if (I->GetMaterialSlots()->Num() > 0) {
FName name = *(I->GetPathKey() + SerializedMaterialSlots);
//creating duplicate meshes so I can change materials on the new mesh without affecting the original
UStaticMesh* DuplicateMesh = DuplicateObject(StaticMesh, StaticMesh->GetOuter(), name);
InstancedStaticMeshMap.Add(I->GetPathKey() + SerializedMaterialSlots, DuplicateMesh);
for (const TPair<FString, FString>& pair : *I->GetMaterialSlots()) {
int32 Index = StaticMesh->GetMaterialIndex(*pair.Key);
if (Index == INDEX_NONE) {
continue;
}
//if (I->UsesCustomMaterial) then we have to find the material and blend our albedo for the material of the item. (example - we have a sword made of bronze that relies on the material instance for it's 3-d appearance, so we load that material instance and add our bronze colored albedo to it). If the item doesn't require a custom material, then for that bronze sword example, we would simply set the blade material slot to our standard bronze material instance.
if (I->GetUsesCustomMaterial()) {
//because the material is item-specific, we use the item's pathkey to load the material.
Path = PathLoader::LoadPathForCustomItemMaterialFromPathKey(I->GetPathKey());
UMaterialInterface* Material = LoadObject<UMaterialInterface>(nullptr, *Path);
if (nullptr == Material) { continue; }
Path = PathLoader::LoadPathForMaterialAlbedo(pair.Value);
UMaterialInstanceDynamic* DynamicMatInstance = UMaterialInstanceDynamic::Create(Material, DuplicateMesh);
UTexture* Albedo = LoadObject<UTexture>(nullptr, *Path);
if (nullptr != Albedo) {
DynamicMatInstance->SetTextureParameterValue("AlbedoMod", Albedo);
DynamicMatInstance->SetScalarParameterValue("Metallic", 1.15f);
}
DuplicateMesh->SetMaterial(Index, DynamicMatInstance);
}
else {
Path = PathLoader::LoadPathFromPathKey(pair.Value);
UMaterialInterface* Material = LoadObject<UMaterialInterface>(nullptr, *Path);
if (nullptr == Material) { continue; }
DuplicateMesh->SetMaterial(Index, Material);
}
}
return DuplicateMesh;
}
return StaticMesh;
}
else if (HasEntity(EntityId) && GetEntity(EntityId)->GetPathKey() != "") {
LastPath = PathLoader::LoadPathFromPathKey(GetEntity(EntityId)->GetPathKey());
return LoadObject<UStaticMesh>(nullptr, *LastPath);
}
}
Print("Attempting to get mesh by EntityId for an entity that isn't loaded or does not have a PathKey saved.");
return nullptr;
}