Why does Actor::AttachToComponent not work and result in Template Mismatch during attachment?

Hi,

I’d like to attach an AActor to a Socket of a Character Mesh (actually nothing special). This leads to the followingI have the following Ensure condition failed:

LogOutputDevice: Error: Template Mismatch during attachment. Attaching instanced component to template component. Parent 'CharacterMesh0' (Owner 'Default__BP_ThirdPersonCharacter_C') Self 'StaticMesh' (Owner 'BP_Gun_C_UAID_7085C272D161757801_1718746471').

These are the relationships:

  • Has a:
    ATheThirdPersonGameCharacter
    UInventoryComponent
    USocketStorage
    → Reference to UInventoryComponent

  • Calls: UInventoryComponent::Add(...)
    UInventoryComponent::AttachToCharacter(..)
    ATheThirdPersonGameCharacter::AttachToSocket(...)
    AActor::AttachToComponent(...)

Any suggestions why this could happen and how I can resolve the issue?

tl;dr

ATheThirdPersonGameCharacter

class ATheThirdPersonGameCharacter : public ACharacter
{
private
	UPROPERTY(..., meta = (AllowPrivateAccess = "true"))
	TObjectPtr<UInventoryComponent> InventoryComponent;
}
void ATheThirdPersonGameCharacter::AttachToSocket(AActor* Actor, FName SocketName) const
{
	const FAttachmentTransformRules AttachmentTransformRules(
		EAttachmentRule::SnapToTarget,
		EAttachmentRule::SnapToTarget,
		EAttachmentRule::KeepWorld,
		false);
	Actor->AttachToComponent(GetMesh(), AttachmentTransformRules, SocketName);
}

UInventoryComponent

class ..._API UInventoryComponent : public UActorComponent
{
private:	
	UPROPERTY() TObjectPtr<USocketStorage> Melee1;
}
void UInventoryComponent::AttachToCharacter(APickup* Pickup, EStorageName StorageName)
{
	const FName SocketName = *StorageSocketMap.Find(StorageName);
	CharacterOwner->AttachToSocket(Pickup, SocketName);
}

USocketStorage : UStorage

class ..._API UStorage : public UObject
{
	UPROPERTY()
	TObjectPtr<UInventoryComponent> InventoryComponent;
void USocketStorage::Add(APickup* Pickup, FStorageInfo& StorageInfo)
{
	InventoryComponent->AttachToCharacter(Pickup, StorageInfo.StorageName);
}

Inside ATheThirdPersonGameCharacter ::BeginPlay()

  • IsTemplate()` returns false: instanced

Inside UInventoryComponent::AttachToCharacter(...):

  • UInventoryComponent::IsTemplate() returns false: instanced
  • ATheThirdPersonGameCharacter ::IsTemplate() returns true: template
  • The Character’s USkeletalMeshComponent::IsTemplate() returns true: template
  • APickup::IsTemplate() returns false: instanced
  • The Actor’s UStaticMeshComponent::IsTemplate() returns false: instanced

UObjectBaseUtility.h

/**
* Determines whether this object is a template object
*
* @return	true if this object is a template object (owned by a UClass)
*/
bool IsTemplate( EObjectFlags TemplateTypes = RF_ArchetypeObject|RF_ClassDefaultObject ) const;

The reference to ATheThirdPersonGameCharacter is a template object. It is the Class Default Object (CDO) that you get from (see https://1danielcoelho.github.io/unreal-engine-basics-base-classes):

  UClass* Class = Object->GetClass();
  UMyObject* CDO = Class->GetDefaultObject<UMyObject>();

But why does the following reference a CDO and not the instanced object?

class ..._API UInventoryComponent : public UActorComponent
{
private:
	UPROPERTY(Transient, DuplicateTransient)
	TObjectPtr<ATheThirdPersonGameCharacter> CharacterOwner;
}
void UInventoryComponent::PostLoad()
{
	Super::PostLoad();
	CharacterOwner = Cast<ATheThirdPersonGameCharacter>(GetOwner());
}

I initialized the CharacterOwner in PostLoad(), because it is done in a similar way in UCharacterMovementComponent::PostLoad().

I should have initialized the CharacterOwner in BeginPlay(), because initialized in:

  • PostLoad() CharacterOwner->IsTemplate() returns true: template
  • BeginPlay() CharacterOwner->IsTemplate() returns false: instanced
void UInventoryComponent::BeginPlay()
{
	Super::BeginPlay();	
	CharacterOwner = Cast<ATheThirdPersonGameCharacter>(GetOwner());

	UE_LOG(LogTemp, Warning, TEXT("In %s, IsTemplate() = %s"),  
      *FString(__FUNCTION__),
      CharacterOwner->IsTemplate() ? TEXT("true") : TEXT("false"));
}