I was trying to find a more optimal way to add a StaticMesh to my Player SkeletalMeshComponents socket at runtime, but I coudn’t seem to find a shorter way to do so.
void APlayableCharacter::OnMeleeWeaponUpdated(IMeleeWeapon* MeleeWeapon)
{
if (!MeleeWeapon) return;
if (MeleeWeapon->GetStaticMesh().IsNull()) return;
// Create a weak pointer to the MeleeWeapon
TWeakObjectPtr<UObject> WeakMeleeWeapon = Cast<UObject>(MeleeWeapon);
// Get the StreamableManager instance
FStreamableManager& StreamableManager = UAssetManager::GetStreamableManager();
// Asynchronously load the asset
StreamableManager.RequestAsyncLoad(
MeleeWeapon->GetStaticMesh().ToSoftObjectPath(),
FStreamableDelegate::CreateUObject(this, &APlayableCharacter::OnMeleeWeaponStaticMeshLoaded, WeakMeleeWeapon)
);
}
void APlayableCharacter::OnMeleeWeaponStaticMeshLoaded(TWeakObjectPtr<UObject> WeakMeleeWeapon)
{
if (!WeakMeleeWeapon.IsValid()) return;
IMeleeWeapon* MeleeWeapon = Cast<IMeleeWeapon>(WeakMeleeWeapon.Get());
if (!MeleeWeapon) return;
// Get the static mesh
UStaticMesh* StaticMesh = MeleeWeapon->GetStaticMesh().Get();
if (!StaticMesh) return;
// Create the static mesh component and set its owner to the character
UStaticMeshComponent* StaticMeshComponent = NewObject<UStaticMeshComponent>(this);
StaticMeshComponent->SetStaticMesh(StaticMesh);
// Attach to the character's skeletal mesh
const FAttachmentTransformRules TransformRules(EAttachmentRule::SnapToTarget, true);
StaticMeshComponent->AttachToComponent(GetMesh(), TransformRules, MeleeWeapon->GetUnequippedSocketName());
// Register the component and set necessary flags
if (!StaticMeshComponent->IsRegistered()) StaticMeshComponent->RegisterComponent();
StaticMeshComponent->SetSimulatePhysics(false);
}
So my question would be, is this the only way to do this, or there are more optimal soloutions that I’m unuware of?
void UAssetLoader::CreateStaticMeshComponentAsync(USkeletalMeshComponent* Owner, TSoftObjectPtr<UStaticMesh> StaticMesh, FName SocketName, FOnStaticMeshComponentCreated OnStaticMeshComponentCreated)
{
// Ensure the owner is valid
if (!Owner) return;
// Use a weak pointer to safely access the owner later
TWeakObjectPtr<USkeletalMeshComponent> WeakOwner = Owner;
// Get the StreamableManager instance
FStreamableManager& StreamableManager = UAssetManager::GetStreamableManager();
// Asynchronously load the static mesh
StreamableManager.RequestAsyncLoad(
StaticMesh.ToSoftObjectPath(),
FStreamableDelegate::CreateStatic(&UAssetLoader::OnStaticMeshLoaded, WeakOwner, StaticMesh, SocketName, OnStaticMeshComponentCreated)
);
}
void UAssetLoader::OnStaticMeshLoaded(TWeakObjectPtr<USkeletalMeshComponent> OwnerComponent, TSoftObjectPtr<UStaticMesh> SoftStaticMesh, FName SocketName, FOnStaticMeshComponentCreated OnStaticMeshComponentCreated)
{
// Check if the owner component is still valid
if (!OwnerComponent.IsValid()) return;
// Get the static mesh
UStaticMesh* StaticMesh = SoftStaticMesh.Get();
if (!StaticMesh) return;
// Create the static mesh component and set its owner to the character
UStaticMeshComponent* StaticMeshComponent = NewObject<UStaticMeshComponent>(OwnerComponent->GetOwner());
StaticMeshComponent->SetStaticMesh(StaticMesh);
// Attach to the character's skeletal mesh
const FAttachmentTransformRules TransformRules(EAttachmentRule::SnapToTarget, true);
StaticMeshComponent->AttachToComponent(OwnerComponent.Get(), TransformRules, SocketName);
// Register the component and set necessary flags
StaticMeshComponent->RegisterComponent();
StaticMeshComponent->SetSimulatePhysics(false);
StaticMeshComponent->SetVisibility(true);
// Trigger the delegate and pass the created static mesh component
OnStaticMeshComponentCreated.ExecuteIfBound(StaticMeshComponent);
}
As far as I am aware, there is no other way to async load soft pointers in c++. I have seen some different approaches, but they all boiled down to being wrappers that call FStreamableManager::RequestAsyncLoad() internally.
One thing you could do in case you plan on doing more async loads, which you should in any non-trivial project, is to make your UAssetLoader more generic. As long as the caller code is C++ only, you should be able to pass any arguments through the callback delegate. So your function would turn into something like this:
That way you can avoid having to create specific functions and callback delegates for each of your loads. So you end up with three functions (one for soft objects, soft classes and soft paths) through which you can load anything and handle the post-load logic in the clients’ callback functions.
I see where you’re coming from, and the idea of generalizing the solution is definitely a good one. However, the issue is that your solution essentially just wraps StreamableManager.RequestAsyncLoad in a different function (UAssetLoader::LoadAsync), which results in the same functionality but under a different name. My main goal was to simplify the process further, so I don’t have to keep recreating async loading and things like USkeletalMeshComponent locally every time. In this case, it doesn’t really address the redundancy I was trying to avoid, though I appreciate the thought behind it. But that info that there is practically nothing much to do, is really good to know! So thank you for taking the time to share!
I might have misunderstood the question then. I was under the impression that you were asking whether there is another, simpler way to load the soft pointer. Hence my answer that simplifies it to passing in what you want to load and a callback that is executed once the pointer is loaded. And since it does not require you to declare any additional delegates or other data structures, you cannot really simplify it further in my opinion.
As you said, my approach also just wraps the engine call but that’s the entire point of creating abstractions. You create a higher level API that simplifies using a lower level API that actually does things. For example, if your concern is with just having to write less code to execute the async load, you can extend the logic with checks that verify whether the soft pointer is valid or already loaded and react accordingly. That way, you can remove this logic from the client code, making it shorter/simpler.
Perhaps you could explicitly mark which lines of code you would like to simplify/remove? Of course, you cannot get rid of creating and attaching the component since you can only load the mesh itself and that’s as far as the async load is concerned, what you want to do with it after that is for you to implement. You can separate the logic into reusable functions when applicable like you did, but in the end the logic has to be written somewhere.
You’re right, maybe I stated wrong in my original question of what I was looking for.
By simplify, I meant “shorter”, or perhaps cleaner solution than what I shared, but keeping the current behaviour if it’s plausible.
The way I abstracted it is exactly what you said, but with additional functionality, so I can not just hide the async loading code, but also build on top of it like a Facade pattern.
As you just pointed out, there are no better options to do this, so I take it as a Solution from now on!