Rework interaction system

This commit is contained in:
2026-05-28 21:53:34 +03:00
parent 891a0744a0
commit 6f0aa5274c
26 changed files with 496 additions and 216 deletions
+2 -2
View File
@@ -141,6 +141,6 @@ bSupportsTouch=False
bSupportsGamepad=True
DefaultGamepadName=Generic
bCanChangeGamepadType=True
+ControllerData=/Game/Input/GamepadControllerData.GamepadControllerData_C
+ControllerData=/Game/Input/KeyboardControllerData.KeyboardControllerData_C
+ControllerData=/Game/Input/CommonUI/KeyboardControllerData.KeyboardControllerData_C
+ControllerData=/Game/Input/CommonUI/GamepadControllerData.GamepadControllerData_C
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -6,6 +6,7 @@
#include "ClothingItemInstance.h"
#include "GameFramework/Character.h"
#include "Kismet/GameplayStatics.h"
#include "NakedDesire/Interactables/ItemPickup.h"
#include "NakedDesire/Player/NakedDesireCharacter.h"
#include "NakedDesire/SaveGame/GlobalSaveGameData.h"
#include "NakedDesire/SaveGame/ItemSaveRecord.h"
@@ -108,6 +109,24 @@ USkeletalMeshComponent* UClothingManager::GetMeshComponent(const EClothingSlotTy
}
}
void UClothingManager::SpawnClothingPickup(UClothingItemInstance* ItemInstance)
{
if (!ItemPickupActor)
{
UE_LOG(LogTemp, Warning, TEXT("UClothingManager::SpawnClothingPickup ItemPickupActor is not set"));
return;
}
AItemPickup* NewItemPickup = GetWorld()->SpawnActor<AItemPickup>(ItemPickupActor, GetOwner()->GetActorTransform());
if (!NewItemPickup)
{
UE_LOG(LogTemp, Warning, TEXT("UClothingManager::SpawnClothingPickup NewItemPickup == nullptr"));
return;
}
NewItemPickup->SetItem(ItemInstance);
}
void UClothingManager::PutOnClothing(UClothingItemInstance* ClothingItemInstance)
{
if (!ClothingItemInstance)
@@ -220,6 +239,8 @@ void UClothingManager::DropClothing(const EClothingSlotType ClothingType)
FItemSaveRecord ItemSaveRecord;
ItemSaveRecord.Init(ClothingItemInstance);
SaveSubsystem->GetCurrentSave()->DroppedItems.Push(ItemSaveRecord);
SpawnClothingPickup(ClothingItemInstance);
OnClothingDropped.Broadcast(ClothingItemInstance);
}
@@ -8,6 +8,7 @@
#include "Components/ActorComponent.h"
#include "ClothingManager.generated.h"
class AItemPickup;
class UGlobalSaveGameData;
class AClothingPickup;
class UClothingItemInstance;
@@ -53,7 +54,11 @@ public:
private:
USkeletalMeshComponent* GetMeshComponent(EClothingSlotType SlotType) const;
void SpawnClothingPickup(UClothingItemInstance* ItemInstance);
UPROPERTY()
TMap<EClothingSlotType, TObjectPtr<UClothingItemInstance>> EquippedClothing;
UPROPERTY(EditDefaultsOnly, Category = "Clothing")
TSubclassOf<AItemPickup> ItemPickupActor;
};
@@ -1,55 +0,0 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "InteractableBase.h"
#include "Components/WidgetComponent.h"
#include "Kismet/GameplayStatics.h"
#include "NakedDesire/InteractionSystem/InteractionManager.h"
#include "NakedDesire/Player/NakedDesireCharacter.h"
AInteractableBase::AInteractableBase()
{
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.TickInterval = 0.25f;
RootSceneComponent = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
RootComponent = RootSceneComponent;
WidgetAnchor = CreateDefaultSubobject<USceneComponent>(TEXT("Widget Anchor"));
WidgetAnchor->SetupAttachment(RootComponent);
InteractionTrigger = CreateDefaultSubobject<UWidgetComponent>(TEXT("Interaction Trigger"));
InteractionTrigger->SetupAttachment(WidgetAnchor);
InteractionTrigger->SetDrawSize(FVector2D(35, 35));
InteractionTrigger->SetWidgetSpace(EWidgetSpace::Screen);
InteractionTrigger->SetWindowFocusable(false);
}
void AInteractableBase::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
if (!Player)
{
if (ACharacter* PlayerCharacter = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0))
{
Player = Cast<ANakedDesireCharacter>(PlayerCharacter);
}
}
if (Player)
{
const TScriptInterface<IInteractionTarget> NearestInteractionTarget = Player->InteractionManager->GetNearestInteractionTarget();
if (NearestInteractionTarget.GetObject() == this)
{
InteractionTrigger->SetVisibility(true);
}
else
{
InteractionTrigger->SetVisibility(false);
}
}
}
@@ -1,34 +0,0 @@
// © 2025 Naked People Team. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "NakedDesire/InteractionSystem/InteractionTarget.h"
#include "InteractableBase.generated.h"
class ANakedDesireCharacter;
class UWidgetComponent;
UCLASS()
class NAKEDDESIRE_API AInteractableBase : public AActor, public IInteractionTarget
{
GENERATED_BODY()
UPROPERTY(EditDefaultsOnly)
USceneComponent* RootSceneComponent = nullptr;
UPROPERTY(EditDefaultsOnly)
USceneComponent* WidgetAnchor = nullptr;
UPROPERTY(EditDefaultsOnly)
UWidgetComponent* InteractionTrigger = nullptr;
UPROPERTY()
ANakedDesireCharacter* Player = nullptr;
public:
AInteractableBase();
virtual void Tick(float DeltaSeconds) override;
};
@@ -0,0 +1,76 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "ItemPickup.h"
#include "Components/BoxComponent.h"
#include "Components/WidgetComponent.h"
#include "NakedDesire/Clothing/ClothingItem.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Clothing/ClothingManager.h"
#include "NakedDesire/Player/NakedDesireCharacter.h"
AItemPickup::AItemPickup()
{
Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
SetRootComponent(Mesh);
Mesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
Collider = CreateDefaultSubobject<UBoxComponent>(TEXT("Collider"));
Collider->SetupAttachment(RootComponent);
Collider->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
InteractionHint = CreateDefaultSubobject<UWidgetComponent>(TEXT("Interaction Hint"));
InteractionHint->SetupAttachment(RootComponent);
}
void AItemPickup::Interact_Implementation(ANakedDesireCharacter* Player)
{
if (ClothingItemInstance)
{
Player->ClothingManager->TakeClothing(ClothingItemInstance);
Destroy();
}
}
bool AItemPickup::CanInteract_Implementation(ANakedDesireCharacter* Player) const
{
return ClothingItemInstance != nullptr;
}
void AItemPickup::HideInteractionHint_Implementation()
{
ApplyOutline(Mesh, false, 0);
InteractionHint->SetVisibility(false);
}
void AItemPickup::ShowInteractionFocusHint_Implementation()
{
ApplyOutline(Mesh, true, 2);
InteractionHint->SetVisibility(true);
}
void AItemPickup::ShowInteractionProximityHint_Implementation()
{
ApplyOutline(Mesh, true, 1);
InteractionHint->SetVisibility(false);
}
void AItemPickup::BeginPlay()
{
Super::BeginPlay();
InteractionHint->SetVisibility(false);
}
void AItemPickup::SetItem(UClothingItemInstance* InItem)
{
ClothingItemInstance = InItem;
Mesh->SetStaticMesh(InItem->GetClothingItem()->StaticMesh);
}
void AItemPickup::ApplyOutline(UStaticMeshComponent* InMesh, bool bEnabled, int32 StencilValue)
{
InMesh->SetRenderCustomDepth(bEnabled);
InMesh->SetCustomDepthStencilValue(StencilValue);
}
@@ -0,0 +1,49 @@
// © 2025 Naked People Team. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "NakedDesire/Interaction/Interactable.h"
#include "ItemPickup.generated.h"
class UWidgetComponent;
class UBoxComponent;
class UClothingItemInstance;
UCLASS(Abstract)
class NAKEDDESIRE_API AItemPickup : public AActor, public IInteractable
{
GENERATED_BODY()
AItemPickup();
public:
virtual void Interact_Implementation(ANakedDesireCharacter* Player) override;
virtual bool CanInteract_Implementation(ANakedDesireCharacter* Player) const override;
virtual void HideInteractionHint_Implementation() override;
virtual void ShowInteractionFocusHint_Implementation() override;
virtual void ShowInteractionProximityHint_Implementation() override;
void SetItem(UClothingItemInstance* InItem);
UClothingItemInstance* GetItem() const { return ClothingItemInstance; }
protected:
virtual void BeginPlay() override;
private:
UPROPERTY()
TObjectPtr<UClothingItemInstance> ClothingItemInstance;
UPROPERTY(EditDefaultsOnly)
TObjectPtr<UStaticMeshComponent> Mesh;
UPROPERTY(EditDefaultsOnly)
TObjectPtr<UBoxComponent> Collider;
UPROPERTY(EditDefaultsOnly)
TObjectPtr<UWidgetComponent> InteractionHint;
void ApplyOutline(UStaticMeshComponent* InMesh, bool bEnabled, int32 StencilValue);
};
@@ -2,7 +2,6 @@
#include "Wardrobe.h"
#include "Kismet/GameplayStatics.h"
#include "NakedDesire/SaveGame/GlobalSaveGameData.h"
#include "NakedDesire/SaveGame/ItemSaveRecord.h"
+2 -2
View File
@@ -3,14 +3,13 @@
#pragma once
#include "CoreMinimal.h"
#include "InteractableBase.h"
#include "GameFramework/Actor.h"
#include "Wardrobe.generated.h"
class UClothingItemInstance;
UCLASS(Blueprintable)
class NAKEDDESIRE_API AWardrobe : public AInteractableBase
class NAKEDDESIRE_API AWardrobe : public AActor
{
GENERATED_BODY()
@@ -21,5 +20,6 @@ public:
void AddItem(UClothingItemInstance* ClothingItemInstance);
void RemoveItem(UClothingItemInstance* ClothingItemInstance) const;
protected:
virtual void BeginPlay() override;
};
@@ -0,0 +1,43 @@
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "Interactable.generated.h"
class ANakedDesireCharacter;
UINTERFACE(MinimalAPI, BlueprintType)
class UInteractable : public UInterface
{
GENERATED_BODY()
};
class NAKEDDESIRE_API IInteractable
{
GENERATED_BODY()
public:
// Returns true if this actor can currently be interacted with by the given player
UFUNCTION(BlueprintNativeEvent)
bool CanInteract(ANakedDesireCharacter* Player) const;
// Executes the interaction (called server-side)
UFUNCTION(BlueprintNativeEvent)
void Interact(ANakedDesireCharacter* Player);
// Text shown in the interaction prompt UI
UFUNCTION(BlueprintNativeEvent)
FText GetInteractionPrompt() const;
// Called (client-side) when this actor enters the player's proximity radius
UFUNCTION(BlueprintNativeEvent)
void ShowInteractionProximityHint();
// Called (client-side) when this actor becomes the focused interaction target
UFUNCTION(BlueprintNativeEvent)
void ShowInteractionFocusHint();
// Called (client-side) when this actor loses proximity or focus
UFUNCTION(BlueprintNativeEvent)
void HideInteractionHint();
};
@@ -0,0 +1,208 @@
#include "InteractionComponent.h"
#include "Interactable.h"
#include "Engine/OverlapResult.h"
#include "CollisionQueryParams.h"
#include "NakedDesire/Player/NakedDesireCharacter.h"
UInteractionComponent::UInteractionComponent()
{
PrimaryComponentTick.bCanEverTick = false;
}
void UInteractionComponent::BeginPlay()
{
Super::BeginPlay();
// Only run on the locally controlled pawn — outlines and focus are per-client visuals
const APawn* OwnerPawn = Cast<APawn>(GetOwner());
if (!OwnerPawn)
return;
GetWorld()->GetTimerManager().SetTimer(
InteractionTimerHandle,
this,
&UInteractionComponent::UpdateInteraction,
0.1f,
true
);
}
void UInteractionComponent::UpdateInteraction()
{
UpdateNearbyInteractables();
UpdateFocusedInteractable();
}
void UInteractionComponent::Interact()
{
AActor* Target = FocusedInteractable.Get();
if (!Target)
return;
if (!IInteractable::Execute_CanInteract(Target, Cast<ANakedDesireCharacter>(GetOwner())))
return;
IInteractable::Execute_Interact(Target, Cast<ANakedDesireCharacter>(GetOwner()));
}
void UInteractionComponent::UpdateNearbyInteractables()
{
AActor* Owner = GetOwner();
if (!Owner)
return;
// Sphere overlap to find all nearby actors
TArray<FOverlapResult> Overlaps;
FCollisionObjectQueryParams ObjectParams;
ObjectParams.AddObjectTypesToQuery(ECC_WorldDynamic);
ObjectParams.AddObjectTypesToQuery(ECC_WorldStatic);
GetWorld()->OverlapMultiByObjectType(
Overlaps,
Owner->GetActorLocation(),
FQuat::Identity,
ObjectParams,
FCollisionShape::MakeSphere(ProximityRadius)
);
// Build the new set from overlap results, filtering to IInteractable actors
TSet<TWeakObjectPtr<AActor>> NewNearby;
for (const FOverlapResult& Overlap : Overlaps)
{
AActor* Actor = Overlap.GetActor();
if (Actor && Actor != Owner && Actor->Implements<UInteractable>())
{
NewNearby.Add(Actor);
}
}
// Actors that left proximity: hide their hints
for (const TWeakObjectPtr<AActor>& OldActor : NearbyInteractables)
{
if (OldActor.IsValid() && !NewNearby.Contains(OldActor))
{
if (FocusedInteractable == OldActor)
{
FocusedInteractable = nullptr;
OnFocusedInteractableChanged.Broadcast(nullptr);
}
IInteractable::Execute_HideInteractionHint(OldActor.Get());
}
}
NearbyInteractables = MoveTemp(NewNearby);
// Recompute the proximity hint for every nearby actor based on current LOS.
// Runs every tick so actors that enter/leave line-of-sight mid-session update
// immediately — e.g. a wall sliding into place hides the hint without the
// actor ever leaving the proximity radius.
// The focused actor is skipped: UpdateFocusedInteractable owns its hint state.
for (const TWeakObjectPtr<AActor>& WeakActor : NearbyInteractables)
{
AActor* Actor = WeakActor.Get();
if (!Actor || FocusedInteractable == Actor)
continue;
if (HasLineOfSightFromPawn(Actor))
IInteractable::Execute_ShowInteractionProximityHint(Actor);
else
IInteractable::Execute_HideInteractionHint(Actor);
}
}
void UInteractionComponent::UpdateFocusedInteractable()
{
FVector ViewLocation;
FRotator ViewRotation;
if (!GetOwnerViewPoint(ViewLocation, ViewRotation))
return;
const FVector ViewForward = ViewRotation.Vector();
ANakedDesireCharacter* Player = Cast<ANakedDesireCharacter>(GetOwner());
// Among nearby interactables, find the one most centered in the player's view
// that is also within interaction distance and passes CanInteract
AActor* BestActor = nullptr;
float BestDot = -1.f;
for (const TWeakObjectPtr<AActor>& WeakActor : NearbyInteractables)
{
AActor* Actor = WeakActor.Get();
if (!Actor)
continue;
if (FVector::DistSquared(Player->GetActorLocation(), Actor->GetActorLocation()) > FMath::Square(InteractionDistance))
continue;
if (!IInteractable::Execute_CanInteract(Actor, Player))
continue;
// Look-cone check — cheap filter before the LOS trace
const FVector ToActor = (Actor->GetActorLocation() - ViewLocation).GetSafeNormal();
const float Dot = FVector::DotProduct(ViewForward, ToActor);
if (Dot < LookDotThreshold || Dot <= BestDot)
continue;
// LOS from the pawn's eye position — not the camera, so looking over a
// wall with the third-person camera does not grant interaction range
if (!HasLineOfSightFromPawn(Actor))
continue;
BestDot = Dot;
BestActor = Actor;
}
if (FocusedInteractable.Get() == BestActor)
return;
// Restore the previously focused actor — only show a proximity hint if it is
// still nearby AND in line-of-sight; hide it otherwise
if (AActor* OldFocused = FocusedInteractable.Get())
{
if (NearbyInteractables.Contains(OldFocused) && HasLineOfSightFromPawn(OldFocused))
IInteractable::Execute_ShowInteractionProximityHint(OldFocused);
else
IInteractable::Execute_HideInteractionHint(OldFocused);
}
FocusedInteractable = BestActor;
if (BestActor)
IInteractable::Execute_ShowInteractionFocusHint(BestActor);
OnFocusedInteractableChanged.Broadcast(BestActor);
}
bool UInteractionComponent::GetOwnerViewPoint(FVector& OutLocation, FRotator& OutRotation) const
{
const APawn* OwnerPawn = Cast<APawn>(GetOwner());
if (!OwnerPawn)
return false;
AController* Controller = OwnerPawn->GetController();
if (!Controller)
return false;
Controller->GetPlayerViewPoint(OutLocation, OutRotation);
return true;
}
bool UInteractionComponent::HasLineOfSightFromPawn(AActor* Target) const
{
const APawn* OwnerPawn = Cast<APawn>(GetOwner());
if (!OwnerPawn || !Target)
return false;
// Start from the pawn's own eye position, NOT the spring-arm camera.
// This means a player who tilts the camera above a wall still cannot
// reach objects on the other side — the trace originates from the character mesh.
const FVector Start = OwnerPawn->GetPawnViewLocation();
const FVector End = Target->GetActorLocation();
FCollisionQueryParams Params(SCENE_QUERY_STAT(InteractionLOS), false);
Params.AddIgnoredActor(GetOwner());
Params.AddIgnoredActor(Target);
FHitResult Hit;
const bool bBlocked = GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Visibility, Params);
return !bBlocked;
}
@@ -0,0 +1,64 @@
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "InteractionComponent.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnFocusedInteractableChanged, AActor*, NewFocused);
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class NAKEDDESIRE_API UInteractionComponent : public UActorComponent
{
GENERATED_BODY()
public:
UInteractionComponent();
virtual void BeginPlay() override;
// Call from interact input — sends server RPC if not authority
void Interact();
UFUNCTION(BlueprintPure)
AActor* GetFocusedInteractable() const { return FocusedInteractable.Get(); }
// Broadcasts whenever the focused (interactable) actor changes; nullptr = no target
UPROPERTY(BlueprintAssignable)
FOnFocusedInteractableChanged OnFocusedInteractableChanged;
protected:
// Actors within this radius receive a proximity outline
UPROPERTY(EditDefaultsOnly, Category="Interaction")
float ProximityRadius = 350.f;
// Player must be within this distance to focus an interactable (must be <= ProximityRadius)
UPROPERTY(EditDefaultsOnly, Category="Interaction")
float InteractionDistance = 150.f;
// Minimum dot product between look direction and direction to target.
// cos(20°) ≈ 0.94 — lower values allow a wider look cone.
UPROPERTY(EditDefaultsOnly, Category="Interaction")
float LookDotThreshold = 0.9f;
private:
FTimerHandle InteractionTimerHandle;
void UpdateInteraction();
TSet<TWeakObjectPtr<AActor>> NearbyInteractables;
TWeakObjectPtr<AActor> FocusedInteractable;
// Physics scan → update NearbyInteractables set → manage proximity outlines
void UpdateNearbyInteractables();
// Among nearby actors, find the one best matching look + distance conditions
void UpdateFocusedInteractable();
bool GetOwnerViewPoint(FVector& OutLocation, FRotator& OutRotation) const;
// Traces from the pawn's eye position (NOT the camera) to the target.
// Returns true only if nothing blocks the line — prevents exploiting the
// third-person camera to interact through walls by looking over them.
bool HasLineOfSightFromPawn(AActor* Target) const;
};
@@ -13,16 +13,16 @@ void ULocationRestriction::Init(ANakedDesireCharacter* PlayerCharacter)
{
Super::Init(PlayerCharacter);
AreaEnterHandle = PlayerCharacter->OnAreaEnter.AddUObject(this, &ULocationRestriction::OnAreaEnter);
AreaExitHandle = PlayerCharacter->OnAreaExit.AddUObject(this, &ULocationRestriction::OnAreaExit);
// AreaEnterHandle = PlayerCharacter->OnAreaEnter.AddUObject(this, &ULocationRestriction::OnAreaEnter);
// AreaExitHandle = PlayerCharacter->OnAreaExit.AddUObject(this, &ULocationRestriction::OnAreaExit);
}
void ULocationRestriction::Stop()
{
Super::Stop();
Player->OnAreaEnter.Remove(AreaEnterHandle);
Player->OnAreaExit.Remove(AreaExitHandle);
// Player->OnAreaEnter.Remove(AreaEnterHandle);
// Player->OnAreaExit.Remove(AreaExitHandle);
}
FText ULocationRestriction::GetDescription() const
@@ -1,12 +1,8 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "NakedDesireCharacter.h"
#include "NakedDesire/Locations/LocationTrigger.h"
#include "NakedDesire/Clothing/ClothingManager.h"
#include "Components/CapsuleComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "NakedDesire/InteractionSystem/InteractionManager.h"
#include "NakedDesire/InteractionSystem/InteractionTarget.h"
#include "NakedDesire/MissionBuilder/MissionsManager.h"
#include "NakedDesire/Stats/StatsManager.h"
#include "EnhancedInputComponent.h"
@@ -15,22 +11,17 @@
#include "Internationalization/Text.h"
#include "NakedDesire/Clothing/ClothingItem.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Clothing/ClothingSlotsData.h"
#include "NakedDesire/Global/Constants.h"
#include "NakedDesire/Global/NakedDesireHUD.h"
#include "NakedDesire/Global/NakedDesireUserSettings.h"
#include "NakedDesire/Interaction/InteractionComponent.h"
#include "NakedDesire/UI/GameLayoutWidget.h"
#include "NakedDesire/UI/HUDWidget.h"
#include "NakedDesire/UI/RadialMenu/RadialMenuController.h"
#include "Perception/AIPerceptionStimuliSourceComponent.h"
#include "Perception/AISense_Sight.h"
ANakedDesireCharacter::ANakedDesireCharacter()
{
GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;
GetCapsuleComponent()->OnComponentBeginOverlap.AddUniqueDynamic(this, &ANakedDesireCharacter::OnBeginOverlap);
GetCapsuleComponent()->OnComponentEndOverlap.AddUniqueDynamic(this, &ANakedDesireCharacter::OnEndOverlap);
bUseControllerRotationYaw = false;
@@ -40,8 +31,6 @@ ANakedDesireCharacter::ANakedDesireCharacter()
ClothingManager = CreateDefaultSubobject<UClothingManager>("Clothing Manager");
StatsManager = CreateDefaultSubobject<UStatsManager>("Stats Manager");
MissionsManager = CreateDefaultSubobject<UMissionsManager>("Missions Manager");
InteractionManager = CreateDefaultSubobject<UInteractionManager>("Interaction Manager");
RadialMenuController = CreateDefaultSubobject<URadialMenuController>(TEXT("Radial Menu Controller"));
NipplesMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Nipples");
NipplesMeshComponent->SetupAttachment(GetMesh());
@@ -90,6 +79,7 @@ ANakedDesireCharacter::ANakedDesireCharacter()
AnalCensorship->SetupAttachment(GetMesh(), FName(TEXT("pelvis")));
StimuliSourceComponent = CreateDefaultSubobject<UAIPerceptionStimuliSourceComponent>(TEXT("Stimuli Source Component"));
InteractionComponent = CreateDefaultSubobject<UInteractionComponent>(TEXT("Interaction Component"));
}
EGait ANakedDesireCharacter::GetGait() const
@@ -97,11 +87,6 @@ EGait ANakedDesireCharacter::GetGait() const
return Gait;
}
EStance ANakedDesireCharacter::GetStance() const
{
return Stance;
}
void ANakedDesireCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
@@ -138,6 +123,7 @@ void ANakedDesireCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInp
EnhancedInputComponent->BindAction(RunAction, ETriggerEvent::Completed, this, &ANakedDesireCharacter::OnRunRelease);
EnhancedInputComponent->BindAction(CrouchAction, ETriggerEvent::Completed, this, &ANakedDesireCharacter::OnCrouchToggle);
EnhancedInputComponent->BindAction(EquipmentAction, ETriggerEvent::Started, this, &ANakedDesireCharacter::OnEquipmentPress);
EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Started, this, &ANakedDesireCharacter::OnInteractPress);
}
}
@@ -233,36 +219,6 @@ bool ANakedDesireCharacter::CheckSight(const FVector& StartLocation, const FVect
return bHit;
}
void ANakedDesireCharacter::OnBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (OtherActor->Implements<UInteractionTarget>())
{
InteractionManager->OnTargetEnter(OtherActor);
}
if (const ALocationTrigger* AreaTrigger = Cast<ALocationTrigger>(OtherActor))
{
CurrentArea = AreaTrigger->GetLocationData();
OnAreaEnter.Broadcast(CurrentArea);
}
}
void ANakedDesireCharacter::OnEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
if (OtherActor->Implements<UInteractionTarget>())
{
InteractionManager->OnTargetExit(OtherActor);
}
if (const ALocationTrigger* AreaTrigger = Cast<ALocationTrigger>(OtherActor))
{
CurrentArea = nullptr;
OnAreaExit.Broadcast(AreaTrigger->GetLocationData());
}
}
void ANakedDesireCharacter::OnClothingEquip(UClothingItemInstance* ClothingItemInstance)
{
if (ClothingItemInstance->GetClothingItem()->HiddenBodyParts.Contains(EBodyPart::Ass))
@@ -374,28 +330,9 @@ void ANakedDesireCharacter::OnEquipmentPress(const FInputActionValue& Value)
HUD->GetGameLayoutWidget()->OpenInventory();
}
void ANakedDesireCharacter::BuildRadialMenuEntries()
void ANakedDesireCharacter::OnInteractPress(const FInputActionValue& Value)
{
if (!SlotsData)
{
UE_LOG(LogTemp, Warning, TEXT("ANakedDesireCharacter::BuildRadialMenuEntries SlotsData not defined"));
return;
}
TArray<FRadialMenuEntry> Entries;
for (const auto& [Key, Value] : SlotsData->Slots)
{
FRadialMenuEntry Entry;
const UClothingItemInstance* EquippedItem = ClothingManager->GetSlotClothing(Key);
Entry.bEnabled = true;
Entry.DisplayName = Value.Name;
Entry.Icon = EquippedItem ? EquippedItem->GetClothingItem()->Icon : Value.Icon;
Entry.ItemId = FName(Value.Name.ToString());
Entries.Push(Entry);
}
RadialMenuController->Entries = Entries;
InteractionComponent->Interact();
}
void ANakedDesireCharacter::NotifyNoticed(ANPCAIController* NPCController)
@@ -9,17 +9,15 @@
#include "NakedDesire/Global/Gait.h"
#include "NakedDesire/Global/NakedDesireUserSettings.h"
#include "NakedDesire/Global/Stance.h"
#include "NakedDesire/UI/RadialMenu/RadialMenuController.h"
#include "Perception/AISightTargetInterface.h"
#include "NakedDesireCharacter.generated.h"
class UInteractionComponent;
class ANakedDesireHUD;
class UClothingItem;
class UClothingItemInstance;
class UClothingSlotsData;
class URadialMenuController;
class UAIPerceptionStimuliSourceComponent;
class UInteractionManager;
class UClothingManager;
class UStatsManager;
class UMissionsManager;
@@ -27,7 +25,6 @@ class ANPCAIController;
class ULocationData;
DECLARE_MULTICAST_DELEGATE_OneParam(FOnNoticedSignature, ANPCAIController*);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnAreaChangeSignature, ULocationData*);
UCLASS(config=Game)
class ANakedDesireCharacter : public ACharacter, public IAISightTargetInterface
@@ -55,6 +52,9 @@ public:
UPROPERTY(EditDefaultsOnly, Category = "Input")
UInputAction* EquipmentAction;
UPROPERTY(EditDefaultsOnly, Category = "Input")
UInputAction* InteractAction;
// Clothing
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Clothing")
@@ -136,14 +136,11 @@ public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
UMissionsManager* MissionsManager;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
UInteractionManager* InteractionManager;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
UAIPerceptionStimuliSourceComponent* StimuliSourceComponent;
UPROPERTY(EditDefaultsOnly)
TObjectPtr<URadialMenuController> RadialMenuController;
UPROPERTY(EditDefaultsOnly, Category = "Interaction")
TObjectPtr<UInteractionComponent> InteractionComponent;
// Variables
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
@@ -159,18 +156,12 @@ public:
FOnNoticedSignature OnNoticed;
FOnAreaChangeSignature OnAreaEnter;
FOnAreaChangeSignature OnAreaExit;
void NotifyNoticed(ANPCAIController* NPCController);
void SetIsRunning(bool Value);
UFUNCTION(BlueprintPure)
EGait GetGait() const;
UFUNCTION(BlueprintPure)
EStance GetStance() const;
virtual void Tick(float DeltaTime) override;
virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
virtual void NotifyControllerChanged() override;
@@ -180,14 +171,6 @@ public:
private:
EGait Gait = EGait::Walk;
EStance Stance = EStance::Stand;
UFUNCTION()
void OnBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
void OnEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex);
UFUNCTION()
void OnClothingEquip(UClothingItemInstance* ClothingItemInstance);
@@ -204,8 +187,7 @@ private:
void OnRunRelease(const FInputActionValue& Value);
void OnCrouchToggle(const FInputActionValue& Value);
void OnEquipmentPress(const FInputActionValue& Value);
void BuildRadialMenuEntries();
void OnInteractPress(const FInputActionValue& Value);
bool CheckSight(const FVector& StartLocation, const FVector& EndLocation, FHitResult& HitResult, const AActor* IgnoreActor);