Refactor clothing visuals
This commit is contained in:
@@ -81,37 +81,6 @@ float UClothingManager::GetHeelHeight()
|
||||
return Footwear->GetClothingItemDefinition()->ShoesOffset;
|
||||
}
|
||||
|
||||
USkeletalMeshComponent* UClothingManager::GetMeshComponent(const EClothingSlotType SlotType) const
|
||||
{
|
||||
const ANakedDesireCharacter* Player = Cast<ANakedDesireCharacter>(GetOwner());
|
||||
if (!Player)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
switch (SlotType)
|
||||
{
|
||||
case EClothingSlotType::Nipples: return Player->NipplesMeshComponent;
|
||||
case EClothingSlotType::Anal: return Player->AnalMeshComponent;
|
||||
case EClothingSlotType::Vagina: return Player->VaginaMeshComponent;
|
||||
case EClothingSlotType::Head: return Player->HeadMeshComponent;
|
||||
case EClothingSlotType::Neck: return Player->NeckMeshComponent;
|
||||
case EClothingSlotType::Face: return Player->FaceMeshComponent;
|
||||
case EClothingSlotType::Eyes: return Player->EyesMeshComponent;
|
||||
case EClothingSlotType::Bodysuit: return Player->BodySuitMeshComponent;
|
||||
case EClothingSlotType::Top: return Player->TopMeshComponent;
|
||||
case EClothingSlotType::Bottom: return Player->BottomMeshComponent;
|
||||
case EClothingSlotType::UnderwearTop: return Player->UnderwearTopMeshComponent;
|
||||
case EClothingSlotType::UnderwearBottom:return Player->UnderwearBottomMeshComponent;
|
||||
case EClothingSlotType::Socks: return Player->SocksMeshComponent;
|
||||
case EClothingSlotType::Footwear: return Player->FootwearMeshComponent;
|
||||
case EClothingSlotType::Outerwear: return Player->OuterwearMeshComponent;
|
||||
case EClothingSlotType::WristRestraint: return Player->WristRestraintMeshComponent;
|
||||
case EClothingSlotType::AnkleRestraint: return Player->AnkleRestraintMeshComponent;
|
||||
case EClothingSlotType::NeckRestraint: return Player->NeckRestraintMeshComponent;
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void UClothingManager::SpawnClothingPickup(UClothingItemInstance* ItemInstance)
|
||||
{
|
||||
if (!ItemPickupActor)
|
||||
@@ -139,22 +108,8 @@ void UClothingManager::PutOnClothing(UClothingItemInstance* ClothingItemInstance
|
||||
return;
|
||||
|
||||
const EClothingSlotType ClothingSlotType = ClothingItemInstance->GetClothingItemDefinition()->SlotType;
|
||||
|
||||
USkeletalMeshComponent* MeshComponent = GetMeshComponent(ClothingSlotType);
|
||||
MeshComponent->SetSkeletalMesh(ClothingItemInstance->GetClothingItemDefinition()->SkeletalMesh);
|
||||
if (!ClothingItemInstance->GetClothingItemDefinition()->Materials.IsEmpty())
|
||||
{
|
||||
for (const TPair<FName, UMaterialInstance*>& Material : ClothingItemInstance->GetClothingItemDefinition()->Materials)
|
||||
{
|
||||
MeshComponent->SetMaterialByName(Material.Key, Material.Value);
|
||||
}
|
||||
}
|
||||
|
||||
SetClothingSlotItem(ClothingSlotType, ClothingItemInstance);
|
||||
if (ClothingItemInstance->GetClothingItemDefinition()->UseLeaderPose)
|
||||
{
|
||||
MeshComponent->SetLeaderPoseComponent(Cast<ACharacter>(GetOwner())->GetMesh());
|
||||
}
|
||||
|
||||
const UClothingItemDefinition* ClothingItem = ClothingItemInstance->GetClothingItemDefinition();
|
||||
if (ClothingItem->SlotType == EClothingSlotType::Bodysuit)
|
||||
@@ -200,14 +155,6 @@ UClothingItemInstance* UClothingManager::RemoveClothing(const EClothingSlotType
|
||||
|
||||
SetClothingSlotItem(ClothingSlotType, nullptr);
|
||||
|
||||
USkeletalMeshComponent* MeshComponent = GetMeshComponent(ClothingSlotType);
|
||||
MeshComponent->SetSkeletalMesh(nullptr);
|
||||
|
||||
if (ExistingItem->GetClothingItemDefinition()->UseLeaderPose)
|
||||
{
|
||||
MeshComponent->SetLeaderPoseComponent(nullptr);
|
||||
}
|
||||
|
||||
OnClothingUnequip.Broadcast(ExistingItem);
|
||||
|
||||
USaveSubsystem* SaveSubsystem = UGameplayStatics::GetGameInstance(GetWorld())->GetSubsystem<USaveSubsystem>();
|
||||
|
||||
@@ -53,7 +53,6 @@ public:
|
||||
float GetHeelHeight();
|
||||
|
||||
private:
|
||||
USkeletalMeshComponent* GetMeshComponent(EClothingSlotType SlotType) const;
|
||||
void SpawnClothingPickup(UClothingItemInstance* ItemInstance);
|
||||
|
||||
UPROPERTY()
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "ClothingVisualsComponent.h"
|
||||
|
||||
#include "ClothingItemDefinition.h"
|
||||
#include "ClothingItemInstance.h"
|
||||
#include "ClothingManager.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "Materials/MaterialInstance.h"
|
||||
|
||||
UClothingVisualsComponent::UClothingVisualsComponent()
|
||||
{
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
}
|
||||
|
||||
void UClothingVisualsComponent::Initialize(USkeletalMeshComponent* InBodyMesh, UClothingManager* InClothingManager)
|
||||
{
|
||||
BodyMesh = InBodyMesh;
|
||||
ClothingManager = InClothingManager;
|
||||
|
||||
if (!ClothingManager)
|
||||
return;
|
||||
|
||||
ClothingManager->OnClothingEquip.AddUniqueDynamic(this, &UClothingVisualsComponent::HandleClothingEquip);
|
||||
ClothingManager->OnClothingUnequip.AddUniqueDynamic(this, &UClothingVisualsComponent::HandleClothingUnequip);
|
||||
|
||||
for (const UClothingItemInstance* Equipped : ClothingManager->GetEquippedClothing())
|
||||
{
|
||||
Apply(Equipped);
|
||||
}
|
||||
}
|
||||
|
||||
void UClothingVisualsComponent::Apply(const UClothingItemInstance* ClothingItemInstance)
|
||||
{
|
||||
if (!ClothingItemInstance)
|
||||
return;
|
||||
|
||||
const UClothingItemDefinition* Definition = ClothingItemInstance->GetClothingItemDefinition();
|
||||
if (!Definition)
|
||||
return;
|
||||
|
||||
USkeletalMeshComponent* SlotMesh = GetOrCreateSlotMesh(Definition->SlotType);
|
||||
if (!SlotMesh)
|
||||
return;
|
||||
|
||||
SlotMesh->SetSkeletalMesh(Definition->SkeletalMesh);
|
||||
|
||||
if (Definition->UseLeaderPose)
|
||||
{
|
||||
SlotMesh->SetLeaderPoseComponent(BodyMesh);
|
||||
}
|
||||
|
||||
for (const TPair<FName, UMaterialInstance*>& Material : Definition->Materials)
|
||||
{
|
||||
SlotMesh->SetMaterialByName(Material.Key, Material.Value);
|
||||
}
|
||||
}
|
||||
|
||||
void UClothingVisualsComponent::ClearSlot(EClothingSlotType SlotType)
|
||||
{
|
||||
if (const TObjectPtr<USkeletalMeshComponent>* Found = SlotMeshes.Find(SlotType))
|
||||
{
|
||||
(*Found)->SetLeaderPoseComponent(nullptr);
|
||||
(*Found)->SetSkeletalMesh(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void UClothingVisualsComponent::HandleClothingEquip(UClothingItemInstance* ClothingItemInstance)
|
||||
{
|
||||
Apply(ClothingItemInstance);
|
||||
}
|
||||
|
||||
void UClothingVisualsComponent::HandleClothingUnequip(UClothingItemInstance* ClothingItemInstance)
|
||||
{
|
||||
if (ClothingItemInstance && ClothingItemInstance->GetClothingItemDefinition())
|
||||
{
|
||||
ClearSlot(ClothingItemInstance->GetClothingItemDefinition()->SlotType);
|
||||
}
|
||||
}
|
||||
|
||||
USkeletalMeshComponent* UClothingVisualsComponent::GetOrCreateSlotMesh(EClothingSlotType SlotType)
|
||||
{
|
||||
if (const TObjectPtr<USkeletalMeshComponent>* Found = SlotMeshes.Find(SlotType))
|
||||
{
|
||||
return *Found;
|
||||
}
|
||||
|
||||
if (!BodyMesh)
|
||||
return nullptr;
|
||||
|
||||
const FName ComponentName = *FString::Printf(TEXT("ClothingSlotMesh_%d"), static_cast<int32>(SlotType));
|
||||
USkeletalMeshComponent* SlotMesh = NewObject<USkeletalMeshComponent>(GetOwner(), ComponentName);
|
||||
SlotMesh->SetupAttachment(BodyMesh);
|
||||
SlotMesh->RegisterComponent();
|
||||
SlotMeshes.Add(SlotType, SlotMesh);
|
||||
return SlotMesh;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "ClothingSlotType.h"
|
||||
#include "ClothingVisualsComponent.generated.h"
|
||||
|
||||
class UClothingManager;
|
||||
class UClothingItemInstance;
|
||||
class USkeletalMeshComponent;
|
||||
|
||||
/**
|
||||
* Renders equipped clothing by lazily spawning one skeletal mesh component per
|
||||
* occupied slot, parented to a target body mesh. Reacts to a UClothingManager's
|
||||
* equip/unequip events, so the character, mirror impostor, and cinematic double
|
||||
* share one implementation instead of hand-maintained per-slot components.
|
||||
*/
|
||||
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
|
||||
class NAKEDDESIRE_API UClothingVisualsComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UClothingVisualsComponent();
|
||||
|
||||
/**
|
||||
* Bind to a clothing manager and a body mesh (the leader-pose source and
|
||||
* attach parent for spawned slot meshes), then apply the current equipped set.
|
||||
* Call after the manager has hydrated (e.g. owning actor's BeginPlay).
|
||||
*/
|
||||
void Initialize(USkeletalMeshComponent* InBodyMesh, UClothingManager* InClothingManager);
|
||||
|
||||
void Apply(const UClothingItemInstance* ClothingItemInstance);
|
||||
void ClearSlot(EClothingSlotType SlotType);
|
||||
|
||||
private:
|
||||
UFUNCTION()
|
||||
void HandleClothingEquip(UClothingItemInstance* ClothingItemInstance);
|
||||
|
||||
UFUNCTION()
|
||||
void HandleClothingUnequip(UClothingItemInstance* ClothingItemInstance);
|
||||
|
||||
/** Returns the slot's mesh component, creating + attaching it on first use. */
|
||||
USkeletalMeshComponent* GetOrCreateSlotMesh(EClothingSlotType SlotType);
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<USkeletalMeshComponent> BodyMesh;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UClothingManager> ClothingManager;
|
||||
|
||||
UPROPERTY()
|
||||
TMap<EClothingSlotType, TObjectPtr<USkeletalMeshComponent>> SlotMeshes;
|
||||
};
|
||||
Reference in New Issue
Block a user