Files
KingshearthLegacy/Source/KingshearthLegacy/Interaction/InteractionComponent.h
T
Dolobarsch 8099d69a23 Neues Interaktionssystem implementiert
Einführung eines modularen Interaktionssystems:
- Hinzufügen der `IInteractable`- und `IInteractionLookupProvider`-Schnittstellen.
- Implementierung der `UInteractionComponent` zur Steuerung von Interaktionen (Tippen, Halten, Kontextmenü).
- Integration der `InteractionComponent` in `KingshearthLegacyCharacter`.
- Hinzufügen des `InteractionContextMenuWidget` für UI-Interaktionen.
- Änderungen an `PlayerHUDWidget`, um Interaktions-Widgets zu unterstützen.
- Neue Assets und Konfigurationsänderungen für Kollisionen und Redirects.
- Verbesserte Modularität und Benutzerfreundlichkeit durch klare Trennung von Logik und Darstellung.
2026-09-05 03:04:12 +02:00

145 lines
6.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "InteractionTypes.h"
#include "InteractionComponent.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnFocusedInteractableChanged, AActor*, FocusedActor, const TArray<FInteractionOption>&, FocusedOptions);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnContextMenuOpened, const TArray<FInteractionOption>&, Options, int32, InitialHighlightedIndex);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnContextMenuHighlightChanged, int32, NewHighlightedIndex);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnContextMenuClosed);
/**
* Drives the whole "hold E" contextual interaction system for its owning Actor:
* every tick it look-traces for an IInteractable, distinguishes tap/hold on the
* currently-focused one, and either executes an interaction directly (1-2 options)
* or drives a Context Menu (3+ options). Interactable objects need to know nothing
* about any of this - they just implement IInteractable and return their own
* FInteractionOption data.
*
* Detection is a single line trace on the "Interactable" trace channel (see
* DefaultEngine.ini - ECC_GameTraceChannel1, default response Block), not a
* proximity sweep. That channel defaults to blocking everything, exactly like
* Visibility, so ordinary geometry occludes it correctly out of the box; to make
* only part of an actor count as its "interactable hit box", set that specific
* component's Interactable trace response and turn the rest of the mesh's off
* (Ignore) in its Collision settings - no C++ changes needed per-actor.
*/
UCLASS(ClassGroup = (Interaction), Blueprintable, meta = (BlueprintSpawnableComponent))
class KINGSHEARTHLEGACY_API UInteractionComponent : public UActorComponent
{
GENERATED_BODY()
public:
UInteractionComponent();
/** Fired every time the look-traced focus changes, including to/from nothing - drives an ever-present "you can interact" prompt. */
UPROPERTY(BlueprintAssignable, Category = "Interaction")
FOnFocusedInteractableChanged OnFocusedInteractableChanged;
/** Fired when 3+ options are available and the hold threshold is reached. */
UPROPERTY(BlueprintAssignable, Category = "Interaction")
FOnContextMenuOpened OnContextMenuOpened;
/** Fired whenever CycleContextMenuOptions moves the highlighted entry. */
UPROPERTY(BlueprintAssignable, Category = "Interaction")
FOnContextMenuHighlightChanged OnContextMenuHighlightChanged;
/** Fired when the Context Menu closes, whether by confirming a choice or losing its target. */
UPROPERTY(BlueprintAssignable, Category = "Interaction")
FOnContextMenuClosed OnContextMenuClosed;
/** Call from the Interact input's Started/pressed event. */
UFUNCTION(BlueprintCallable, Category = "Interaction")
void TryBeginInteract();
/** Call from the Interact input's Completed/released event. */
UFUNCTION(BlueprintCallable, Category = "Interaction")
void TryEndInteract();
/** Call while the Context Menu is open (e.g. from a mouse wheel axis) to move the highlighted entry. No-op otherwise. */
UFUNCTION(BlueprintCallable, Category = "Interaction")
void CycleContextMenuOptions(float Direction);
UFUNCTION(BlueprintPure, Category = "Interaction")
bool IsContextMenuOpen() const { return bContextMenuOpen; }
/** The actor currently under the look-trace, or nullptr if none. */
UFUNCTION(BlueprintPure, Category = "Interaction")
AActor* GetFocusedActor() const { return FocusedActor.Get(); }
protected:
virtual void BeginPlay() override;
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
/** Max distance of the look trace. */
UPROPERTY(EditAnywhere, Category = "Interaction")
float InteractionRange = 300.0f;
/** Trace channel the look trace queries - see the "Interactable" channel set up in DefaultEngine.ini. */
UPROPERTY(EditAnywhere, Category = "Interaction")
TEnumAsByte<ECollisionChannel> InteractionTraceChannel = ECC_GameTraceChannel1;
/** Seconds the Interact input must be held before it counts as a hold rather than a tap. */
UPROPERTY(EditAnywhere, Category = "Interaction")
float HoldThreshold = 0.4f;
/** Draws the look trace every tick (green = hit an IInteractable, red = blocked/missed) and logs focus changes. */
UPROPERTY(EditAnywhere, Category = "Interaction|Debug")
bool bShowDebugTraces = false;
/**
* Finds the look-trace target. If the owning Actor implements IInteractionLookupProvider,
* defers to its InteractionLookupLoop first - implement that interface directly on your
* Character/Actor to fully replace how a target is found (VR hand ray, a cone check, ...)
* without touching the tap/hold/menu logic below. Falls back to a line trace from the
* controller's view point along its view direction, InteractionRange long, on
* InteractionTraceChannel, whenever the interface isn't implemented or returns false.
*/
bool PerformInteractionTrace(FHitResult& OutHit) const;
/** Re-runs PerformInteractionTrace, updates FocusedActor/FocusedOptions, and broadcasts OnFocusedInteractableChanged if either changed. */
void UpdateFocus();
/** Bound to the hold timer; only ever runs when CurrentOptions.Num() >= 2. */
void HandleHoldThresholdReached();
void OpenContextMenu();
void CloseContextMenu();
/** Executes CurrentOptions[Index] against CurrentTarget, if both are still valid. */
void ExecuteOption(int32 Index);
void ClearCachedTarget();
/** Actor currently under the look trace and implementing IInteractable, updated every tick by UpdateFocus. */
UPROPERTY()
TWeakObjectPtr<AActor> FocusedActor;
UPROPERTY()
TArray<FInteractionOption> FocusedOptions;
/** Snapshot of FocusedActor/FocusedOptions taken when Interact was pressed - what TryEndInteract actually acts on. */
UPROPERTY()
TWeakObjectPtr<AActor> CurrentTarget;
UPROPERTY()
TArray<FInteractionOption> CurrentOptions;
UPROPERTY()
int32 HighlightedIndex = 0;
UPROPERTY()
bool bContextMenuOpen = false;
/** Set the moment the hold timer fires, so TryEndInteract knows not to also run the tap path. */
bool bHoldConsumed = false;
FTimerHandle HoldTimerHandle;
};