// Copyright Epic Games, Inc. All Rights Reserved. #include "LyraPawnComponent_CharacterParts.h" #include "GameFramework/Character.h" #include "Components/SkeletalMeshComponent.h" #include "Components/ChildActorComponent.h" #include "Net/UnrealNetwork.h" #include "GameplayTagAssetInterface.h" ////////////////////////////////////////////////////////////////////// FString FLyraAppliedCharacterPartEntry::GetDebugString() const { return FString::Printf(TEXT("(PartClass: %s, Socket: %s, Instance: %s)"), *GetPathNameSafe(Part.PartClass), *Part.SocketName.ToString(), *GetPathNameSafe(SpawnedComponent)); } ////////////////////////////////////////////////////////////////////// void FLyraCharacterPartList::PreReplicatedRemove(const TArrayView RemovedIndices, int32 FinalSize) { bool bDestroyedAnyActors = false; for (int32 Index : RemovedIndices) { FLyraAppliedCharacterPartEntry& Entry = Entries[Index]; bDestroyedAnyActors |= DestroyActorForEntry(Entry); } if (bDestroyedAnyActors) { OwnerComponent->BroadcastChanged(); } } void FLyraCharacterPartList::PostReplicatedAdd(const TArrayView AddedIndices, int32 FinalSize) { bool bCreatedAnyActors = false; for (int32 Index : AddedIndices) { FLyraAppliedCharacterPartEntry& Entry = Entries[Index]; bCreatedAnyActors |= SpawnActorForEntry(Entry); } if (bCreatedAnyActors) { OwnerComponent->BroadcastChanged(); } } void FLyraCharacterPartList::PostReplicatedChange(const TArrayView ChangedIndices, int32 FinalSize) { bool bChangedAnyActors = false; // We don't support dealing with propagating changes, just destroy and recreate for (int32 Index : ChangedIndices) { FLyraAppliedCharacterPartEntry& Entry = Entries[Index]; bChangedAnyActors |= DestroyActorForEntry(Entry); bChangedAnyActors |= SpawnActorForEntry(Entry); } if (bChangedAnyActors) { OwnerComponent->BroadcastChanged(); } } FLyraCharacterPartHandle FLyraCharacterPartList::AddEntry(FLyraCharacterPart NewPart) { FLyraCharacterPartHandle Result; Result.PartHandle = PartHandleCounter++; if (ensure(OwnerComponent && OwnerComponent->GetOwner() && OwnerComponent->GetOwner()->HasAuthority())) { FLyraAppliedCharacterPartEntry& NewEntry = Entries.AddDefaulted_GetRef(); NewEntry.Part = NewPart; NewEntry.PartHandle = Result.PartHandle; if (SpawnActorForEntry(NewEntry)) { OwnerComponent->BroadcastChanged(); } MarkItemDirty(NewEntry); } return Result; } void FLyraCharacterPartList::RemoveEntry(FLyraCharacterPartHandle Handle) { for (auto EntryIt = Entries.CreateIterator(); EntryIt; ++EntryIt) { FLyraAppliedCharacterPartEntry& Entry = *EntryIt; if (Entry.PartHandle == Handle.PartHandle) { const bool bDestroyedActor = DestroyActorForEntry(Entry); EntryIt.RemoveCurrent(); MarkArrayDirty(); if (bDestroyedActor) { OwnerComponent->BroadcastChanged(); } break; } } } void FLyraCharacterPartList::ClearAllEntries(bool bBroadcastChangeDelegate) { bool bDestroyedAnyActors = false; for (FLyraAppliedCharacterPartEntry& Entry : Entries) { bDestroyedAnyActors |= DestroyActorForEntry(Entry); } Entries.Reset(); MarkArrayDirty(); if (bDestroyedAnyActors && bBroadcastChangeDelegate) { OwnerComponent->BroadcastChanged(); } } FGameplayTagContainer FLyraCharacterPartList::CollectCombinedTags() const { FGameplayTagContainer Result; for (const FLyraAppliedCharacterPartEntry& Entry : Entries) { if (Entry.SpawnedComponent != nullptr) { if (IGameplayTagAssetInterface* TagInterface = Cast(Entry.SpawnedComponent->GetChildActor())) { TagInterface->GetOwnedGameplayTags(/*inout*/ Result); } } } return Result; } bool FLyraCharacterPartList::SpawnActorForEntry(FLyraAppliedCharacterPartEntry& Entry) { bool bCreatedAnyActors = false; if (!OwnerComponent->IsNetMode(NM_DedicatedServer)) { if (Entry.Part.PartClass != nullptr) { UWorld* World = OwnerComponent->GetWorld(); if (USceneComponent* ComponentToAttachTo = OwnerComponent->GetSceneComponentToAttachTo()) { const FTransform SpawnTransform = ComponentToAttachTo->GetSocketTransform(Entry.Part.SocketName); UChildActorComponent* PartComponent = NewObject(OwnerComponent->GetOwner()); PartComponent->SetupAttachment(ComponentToAttachTo, Entry.Part.SocketName); PartComponent->SetChildActorClass(Entry.Part.PartClass); PartComponent->RegisterComponent(); if (AActor* SpawnedActor = PartComponent->GetChildActor()) { switch (Entry.Part.CollisionMode) { case ECharacterCustomizationCollisionMode::UseCollisionFromCharacterPart: // Do nothing break; case ECharacterCustomizationCollisionMode::NoCollision: SpawnedActor->SetActorEnableCollision(false); break; } // Set up a direct tick dependency to work around the child actor component not providing one if (USceneComponent* SpawnedRootComponent = SpawnedActor->GetRootComponent()) { SpawnedRootComponent->AddTickPrerequisiteComponent(ComponentToAttachTo); } } Entry.SpawnedComponent = PartComponent; bCreatedAnyActors = true; } } } return bCreatedAnyActors; } bool FLyraCharacterPartList::DestroyActorForEntry(FLyraAppliedCharacterPartEntry& Entry) { bool bDestroyedAnyActors = false; if (Entry.SpawnedComponent != nullptr) { Entry.SpawnedComponent->DestroyComponent(); Entry.SpawnedComponent = nullptr; bDestroyedAnyActors = true; } return bDestroyedAnyActors; } ////////////////////////////////////////////////////////////////////// ULyraPawnComponent_CharacterParts::ULyraPawnComponent_CharacterParts(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , CharacterPartList(this) { SetIsReplicatedByDefault(true); } void ULyraPawnComponent_CharacterParts::GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME(ThisClass, CharacterPartList); } FLyraCharacterPartHandle ULyraPawnComponent_CharacterParts::AddCharacterPart(const FLyraCharacterPart& NewPart) { return CharacterPartList.AddEntry(NewPart); } void ULyraPawnComponent_CharacterParts::RemoveCharacterPart(FLyraCharacterPartHandle Handle) { CharacterPartList.RemoveEntry(Handle); } void ULyraPawnComponent_CharacterParts::RemoveAllCharacterParts() { CharacterPartList.ClearAllEntries(/*bBroadcastChangeDelegate=*/ true); } void ULyraPawnComponent_CharacterParts::BeginPlay() { Super::BeginPlay(); } void ULyraPawnComponent_CharacterParts::EndPlay(const EEndPlayReason::Type EndPlayReason) { CharacterPartList.ClearAllEntries(/*bBroadcastChangeDelegate=*/ false); Super::EndPlay(EndPlayReason); } TArray ULyraPawnComponent_CharacterParts::GetCharacterPartActors() const { TArray Result; Result.Reserve(CharacterPartList.Entries.Num()); for (const FLyraAppliedCharacterPartEntry& Entry : CharacterPartList.Entries) { if (UChildActorComponent* PartComponent = Entry.SpawnedComponent) { if (AActor* SpawnedActor = PartComponent->GetChildActor()) { Result.Add(SpawnedActor); } } } return Result; } USkeletalMeshComponent* ULyraPawnComponent_CharacterParts::GetParentMeshComponent() const { if (AActor* OwnerActor = GetOwner()) { if (ACharacter* OwningCharacter = Cast(OwnerActor)) { if (USkeletalMeshComponent* MeshComponent = OwningCharacter->GetMesh()) { return MeshComponent; } } } return nullptr; } USceneComponent* ULyraPawnComponent_CharacterParts::GetSceneComponentToAttachTo() const { if (USkeletalMeshComponent* MeshComponent = GetParentMeshComponent()) { return MeshComponent; } else if (AActor* OwnerActor = GetOwner()) { return OwnerActor->GetRootComponent(); } else { return nullptr; } } FGameplayTagContainer ULyraPawnComponent_CharacterParts::GetCombinedTags(FGameplayTag RequiredPrefix) const { FGameplayTagContainer Result = CharacterPartList.CollectCombinedTags(); if (RequiredPrefix.IsValid()) { return Result.Filter(FGameplayTagContainer(RequiredPrefix)); } else { return Result; } } void ULyraPawnComponent_CharacterParts::BroadcastChanged() { const bool bReinitPose = true; // Check to see if the body type has changed if (USkeletalMeshComponent* MeshComponent = GetParentMeshComponent()) { // Determine the mesh to use based on cosmetic part tags const FGameplayTagContainer MergedTags = GetCombinedTags(FGameplayTag()); USkeletalMesh* DesiredMesh = BodyMeshes.SelectBestBodyStyle(MergedTags); // Apply the desired mesh (this call is a no-op if the mesh hasn't changed) MeshComponent->SetSkeletalMesh(DesiredMesh, /*bReinitPose=*/ bReinitPose); // Apply the desired physics asset if there's a forced override independent of the one from the mesh if (UPhysicsAsset* PhysicsAsset = BodyMeshes.ForcedPhysicsAsset) { MeshComponent->SetPhysicsAsset(PhysicsAsset, /*bForceReInit=*/ bReinitPose); } } // Let observers know, e.g., if they need to apply team coloring or similar OnCharacterPartsChanged.Broadcast(this); }