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.
145 lines
6.1 KiB
C++
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;
|
|
};
|