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.
This commit is contained in:
@@ -3,7 +3,8 @@
|
|||||||
"allow": [
|
"allow": [
|
||||||
"mcp__unreal-mcp__describe_toolset",
|
"mcp__unreal-mcp__describe_toolset",
|
||||||
"mcp__unreal-mcp__call_tool",
|
"mcp__unreal-mcp__call_tool",
|
||||||
"Bash(python *)"
|
"Bash(python *)",
|
||||||
|
"mcp__unreal-mcp__list_toolsets"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"enabledMcpjsonServers": [
|
"enabledMcpjsonServers": [
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ AppliedTargetedHardwareClass=Desktop
|
|||||||
DefaultGraphicsPerformance=Maximum
|
DefaultGraphicsPerformance=Maximum
|
||||||
AppliedDefaultGraphicsPerformance=Maximum
|
AppliedDefaultGraphicsPerformance=Maximum
|
||||||
|
|
||||||
|
[/Script/Engine.CollisionProfile]
|
||||||
|
+DefaultChannelResponses=(Channel=ECC_GameTraceChannel1,Name="Interactable",DefaultResponse=ECR_Block,bTraceType=True,bStaticObject=False)
|
||||||
|
|
||||||
[/Script/Engine.Engine]
|
[/Script/Engine.Engine]
|
||||||
+ActiveGameNameRedirects=(OldGameName="TP_ThirdPerson",NewGameName="/Script/KingshearthLegacy")
|
+ActiveGameNameRedirects=(OldGameName="TP_ThirdPerson",NewGameName="/Script/KingshearthLegacy")
|
||||||
+ActiveGameNameRedirects=(OldGameName="/Script/TP_ThirdPerson",NewGameName="/Script/KingshearthLegacy")
|
+ActiveGameNameRedirects=(OldGameName="/Script/TP_ThirdPerson",NewGameName="/Script/KingshearthLegacy")
|
||||||
|
|||||||
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.
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "UObject/Interface.h"
|
||||||
|
#include "InteractionTypes.h"
|
||||||
|
#include "Interactable.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic interaction interface. Blueprintable so pure-Blueprint actors
|
||||||
|
* (BP_DoorFrame, BP_JumpPad, ...) can implement it without any C++.
|
||||||
|
*/
|
||||||
|
UINTERFACE(MinimalAPI, Blueprintable)
|
||||||
|
class UInteractable : public UInterface
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implement this and return interaction data - the InteractionComponent decides
|
||||||
|
* on its own whether that means a direct tap, a hold, or a Context Menu.
|
||||||
|
*/
|
||||||
|
class IInteractable
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Returns the interactions currently available to Interactor, in priority order (index 0 = primary/tap). */
|
||||||
|
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interaction")
|
||||||
|
TArray<FInteractionOption> GetInteractionOptions(AActor* Interactor);
|
||||||
|
|
||||||
|
/** Runs the interaction identified by ActionID (one of the ActionIDs previously returned by GetInteractionOptions). */
|
||||||
|
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interaction")
|
||||||
|
void ExecuteInteraction(AActor* Interactor, FName ActionID);
|
||||||
|
};
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "InteractionComponent.h"
|
||||||
|
#include "Interactable.h"
|
||||||
|
#include "InteractionLookupProvider.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "TimerManager.h"
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "GameFramework/Pawn.h"
|
||||||
|
#include "GameFramework/Controller.h"
|
||||||
|
#include "DrawDebugHelpers.h"
|
||||||
|
#include "KingshearthLegacy.h"
|
||||||
|
|
||||||
|
UInteractionComponent::UInteractionComponent()
|
||||||
|
{
|
||||||
|
PrimaryComponentTick.bCanEverTick = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionComponent::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
UpdateFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||||
|
{
|
||||||
|
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||||
|
UpdateFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UInteractionComponent::PerformInteractionTrace(FHitResult& OutHit) const
|
||||||
|
{
|
||||||
|
AActor* Owner = GetOwner();
|
||||||
|
UWorld* World = GetWorld();
|
||||||
|
if (!Owner || !World)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Owner->Implements<UInteractionLookupProvider>())
|
||||||
|
{
|
||||||
|
if (IInteractionLookupProvider::Execute_InteractionLookupLoop(Owner, OutHit))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector TraceStart = Owner->GetActorLocation();
|
||||||
|
FRotator TraceRotation = Owner->GetActorRotation();
|
||||||
|
|
||||||
|
if (const APawn* OwnerPawn = Cast<APawn>(Owner))
|
||||||
|
{
|
||||||
|
if (const AController* Controller = OwnerPawn->GetController())
|
||||||
|
{
|
||||||
|
Controller->GetPlayerViewPoint(TraceStart, TraceRotation);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TraceStart = OwnerPawn->GetPawnViewLocation();
|
||||||
|
TraceRotation = OwnerPawn->GetViewRotation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FVector TraceEnd = TraceStart + TraceRotation.Vector() * InteractionRange;
|
||||||
|
|
||||||
|
FCollisionQueryParams QueryParams;
|
||||||
|
QueryParams.AddIgnoredActor(Owner);
|
||||||
|
|
||||||
|
const bool bHit = World->LineTraceSingleByChannel(OutHit, TraceStart, TraceEnd, InteractionTraceChannel, QueryParams);
|
||||||
|
|
||||||
|
#if ENABLE_DRAW_DEBUG
|
||||||
|
if (bShowDebugTraces)
|
||||||
|
{
|
||||||
|
const bool bHitInteractable = bHit && OutHit.GetActor() && OutHit.GetActor()->Implements<UInteractable>();
|
||||||
|
DrawDebugLine(World, TraceStart, bHit ? OutHit.Location : TraceEnd, bHitInteractable ? FColor::Green : FColor::Red, false, 0.0f, 0, 1.5f);
|
||||||
|
if (bHit)
|
||||||
|
{
|
||||||
|
DrawDebugSphere(World, OutHit.Location, 8.0f, 12, bHitInteractable ? FColor::Green : FColor::Red, false, 0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return bHit;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionComponent::UpdateFocus()
|
||||||
|
{
|
||||||
|
FHitResult Hit;
|
||||||
|
const bool bHit = PerformInteractionTrace(Hit);
|
||||||
|
|
||||||
|
AActor* NewFocusedActor = (bHit && Hit.GetActor() && Hit.GetActor()->Implements<UInteractable>()) ? Hit.GetActor() : nullptr;
|
||||||
|
|
||||||
|
if (NewFocusedActor == FocusedActor.Get())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FocusedActor = NewFocusedActor;
|
||||||
|
FocusedOptions = NewFocusedActor ? IInteractable::Execute_GetInteractionOptions(NewFocusedActor, GetOwner()) : TArray<FInteractionOption>();
|
||||||
|
|
||||||
|
#if ENABLE_DRAW_DEBUG
|
||||||
|
if (bShowDebugTraces)
|
||||||
|
{
|
||||||
|
UE_LOG(LogKingshearthLegacy, Log, TEXT("[Interaction] Focus changed to '%s' (%d option(s))"), *GetNameSafe(NewFocusedActor), FocusedOptions.Num());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
OnFocusedInteractableChanged.Broadcast(NewFocusedActor, FocusedOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionComponent::TryBeginInteract()
|
||||||
|
{
|
||||||
|
UpdateFocus();
|
||||||
|
|
||||||
|
if (!FocusedActor.IsValid() || FocusedOptions.Num() == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentTarget = FocusedActor;
|
||||||
|
CurrentOptions = FocusedOptions;
|
||||||
|
HighlightedIndex = 0;
|
||||||
|
bHoldConsumed = false;
|
||||||
|
|
||||||
|
if (CurrentOptions.Num() >= 2)
|
||||||
|
{
|
||||||
|
GetWorld()->GetTimerManager().SetTimer(HoldTimerHandle, this, &UInteractionComponent::HandleHoldThresholdReached, HoldThreshold, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionComponent::TryEndInteract()
|
||||||
|
{
|
||||||
|
GetWorld()->GetTimerManager().ClearTimer(HoldTimerHandle);
|
||||||
|
|
||||||
|
if (bContextMenuOpen)
|
||||||
|
{
|
||||||
|
ExecuteOption(HighlightedIndex);
|
||||||
|
CloseContextMenu();
|
||||||
|
}
|
||||||
|
else if (!bHoldConsumed && CurrentOptions.Num() >= 1)
|
||||||
|
{
|
||||||
|
ExecuteOption(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClearCachedTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionComponent::CycleContextMenuOptions(float Direction)
|
||||||
|
{
|
||||||
|
if (!bContextMenuOpen || CurrentOptions.Num() == 0 || FMath::IsNearlyZero(Direction))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 Step = Direction > 0.0f ? 1 : -1;
|
||||||
|
HighlightedIndex = (HighlightedIndex + Step + CurrentOptions.Num()) % CurrentOptions.Num();
|
||||||
|
OnContextMenuHighlightChanged.Broadcast(HighlightedIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionComponent::HandleHoldThresholdReached()
|
||||||
|
{
|
||||||
|
bHoldConsumed = true;
|
||||||
|
|
||||||
|
if (CurrentOptions.Num() == 2)
|
||||||
|
{
|
||||||
|
ExecuteOption(1);
|
||||||
|
ClearCachedTarget();
|
||||||
|
}
|
||||||
|
else if (CurrentOptions.Num() >= 3)
|
||||||
|
{
|
||||||
|
OpenContextMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionComponent::OpenContextMenu()
|
||||||
|
{
|
||||||
|
bContextMenuOpen = true;
|
||||||
|
HighlightedIndex = 0;
|
||||||
|
OnContextMenuOpened.Broadcast(CurrentOptions, HighlightedIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionComponent::CloseContextMenu()
|
||||||
|
{
|
||||||
|
bContextMenuOpen = false;
|
||||||
|
OnContextMenuClosed.Broadcast();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionComponent::ExecuteOption(int32 Index)
|
||||||
|
{
|
||||||
|
AActor* Target = CurrentTarget.Get();
|
||||||
|
if (!Target || !CurrentOptions.IsValidIndex(Index))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IInteractable::Execute_ExecuteInteraction(Target, GetOwner(), CurrentOptions[Index].ActionID);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionComponent::ClearCachedTarget()
|
||||||
|
{
|
||||||
|
CurrentTarget = nullptr;
|
||||||
|
CurrentOptions.Reset();
|
||||||
|
HighlightedIndex = 0;
|
||||||
|
bHoldConsumed = false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
// 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;
|
||||||
|
};
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "UObject/Interface.h"
|
||||||
|
#include "InteractionLookupProvider.generated.h"
|
||||||
|
|
||||||
|
UINTERFACE(MinimalAPI, Blueprintable)
|
||||||
|
class UInteractionLookupProvider : public UInterface
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional interface for the Actor a UInteractionComponent is attached to (e.g.
|
||||||
|
* BP_ThirdPersonCharacter). Implement it there - Class Settings > Implement Interface,
|
||||||
|
* just like IInteractable on an interactable object - and override InteractionLookupLoop
|
||||||
|
* in that Actor's own Event Graph to replace how its "look at" target is found (a
|
||||||
|
* different origin, a cone check, a VR hand-ray, ...). No separate component subclass
|
||||||
|
* or class-swapping needed. If not implemented, or the implementation returns false,
|
||||||
|
* UInteractionComponent falls back to its own default camera-forward line trace.
|
||||||
|
*/
|
||||||
|
class IInteractionLookupProvider
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Return true and fill OutHit with what was found; return false to fall back to the component's default trace. */
|
||||||
|
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interaction")
|
||||||
|
bool InteractionLookupLoop(FHitResult& OutHit);
|
||||||
|
};
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "InteractionTypes.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single interaction an IInteractable currently offers. Order matters:
|
||||||
|
* index 0 is always the primary (tap) action, index 1 (if present) is the
|
||||||
|
* secondary (hold) action, and 3+ entries populate the Context Menu in order.
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FInteractionOption
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/** Shown to the player (Context Menu entry, interaction prompt, ...) */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||||
|
FText DisplayName;
|
||||||
|
|
||||||
|
/** Opaque id passed back to IInteractable::ExecuteInteraction - the object interprets it itself */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||||
|
FName ActionID;
|
||||||
|
};
|
||||||
@@ -30,6 +30,7 @@ public class KingshearthLegacy : ModuleRules
|
|||||||
"KingshearthLegacy",
|
"KingshearthLegacy",
|
||||||
"KingshearthLegacy/AttributeSets",
|
"KingshearthLegacy/AttributeSets",
|
||||||
"KingshearthLegacy/GameplayEffects",
|
"KingshearthLegacy/GameplayEffects",
|
||||||
|
"KingshearthLegacy/Interaction",
|
||||||
"KingshearthLegacy/UI/Menu",
|
"KingshearthLegacy/UI/Menu",
|
||||||
"KingshearthLegacy/Variant_Platforming",
|
"KingshearthLegacy/Variant_Platforming",
|
||||||
"KingshearthLegacy/Variant_Platforming/Animation",
|
"KingshearthLegacy/Variant_Platforming/Animation",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include "GE_HealthDebuff.h"
|
#include "GE_HealthDebuff.h"
|
||||||
#include "GE_StaminaFatiguePenalty.h"
|
#include "GE_StaminaFatiguePenalty.h"
|
||||||
#include "KingshearthLegacyGameplayTags.h"
|
#include "KingshearthLegacyGameplayTags.h"
|
||||||
|
#include "Interaction/InteractionComponent.h"
|
||||||
|
|
||||||
AKingshearthLegacyCharacter::AKingshearthLegacyCharacter()
|
AKingshearthLegacyCharacter::AKingshearthLegacyCharacter()
|
||||||
{
|
{
|
||||||
@@ -64,6 +65,8 @@ AKingshearthLegacyCharacter::AKingshearthLegacyCharacter()
|
|||||||
|
|
||||||
StaminaAttributeSet = CreateDefaultSubobject<UStaminaAttributeSet>(TEXT("StaminaAttributeSet"));
|
StaminaAttributeSet = CreateDefaultSubobject<UStaminaAttributeSet>(TEXT("StaminaAttributeSet"));
|
||||||
AbilitySystemComponent->AddAttributeSetSubobject(StaminaAttributeSet.Get());
|
AbilitySystemComponent->AddAttributeSetSubobject(StaminaAttributeSet.Get());
|
||||||
|
|
||||||
|
InteractionComponent = CreateDefaultSubobject<UInteractionComponent>(TEXT("InteractionComponent"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AKingshearthLegacyCharacter::BeginPlay()
|
void AKingshearthLegacyCharacter::BeginPlay()
|
||||||
@@ -276,6 +279,11 @@ void AKingshearthLegacyCharacter::SetupPlayerInputComponent(UInputComponent* Pla
|
|||||||
// Sprinting
|
// Sprinting
|
||||||
EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Started, this, &AKingshearthLegacyCharacter::StartSprint);
|
EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Started, this, &AKingshearthLegacyCharacter::StartSprint);
|
||||||
EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Completed, this, &AKingshearthLegacyCharacter::StopSprint);
|
EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Completed, this, &AKingshearthLegacyCharacter::StopSprint);
|
||||||
|
|
||||||
|
// Interacting
|
||||||
|
EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Started, this, &AKingshearthLegacyCharacter::DoInteractStart);
|
||||||
|
EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Completed, this, &AKingshearthLegacyCharacter::DoInteractEnd);
|
||||||
|
EnhancedInputComponent->BindAction(InteractCycleAction, ETriggerEvent::Triggered, this, &AKingshearthLegacyCharacter::InteractCycle);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -343,6 +351,35 @@ void AKingshearthLegacyCharacter::DoJumpEnd()
|
|||||||
StopJumping();
|
StopJumping();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AKingshearthLegacyCharacter::DoInteractStart()
|
||||||
|
{
|
||||||
|
if (InteractionComponent)
|
||||||
|
{
|
||||||
|
InteractionComponent->TryBeginInteract();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AKingshearthLegacyCharacter::DoInteractEnd()
|
||||||
|
{
|
||||||
|
if (InteractionComponent)
|
||||||
|
{
|
||||||
|
InteractionComponent->TryEndInteract();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AKingshearthLegacyCharacter::DoInteractCycle(float Direction)
|
||||||
|
{
|
||||||
|
if (InteractionComponent)
|
||||||
|
{
|
||||||
|
InteractionComponent->CycleContextMenuOptions(Direction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AKingshearthLegacyCharacter::InteractCycle(const FInputActionValue& Value)
|
||||||
|
{
|
||||||
|
DoInteractCycle(Value.Get<float>());
|
||||||
|
}
|
||||||
|
|
||||||
void AKingshearthLegacyCharacter::Jump()
|
void AKingshearthLegacyCharacter::Jump()
|
||||||
{
|
{
|
||||||
const bool bCouldJump = CanJump();
|
const bool bCouldJump = CanJump();
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class UInputAction;
|
|||||||
class UAbilitySystemComponent;
|
class UAbilitySystemComponent;
|
||||||
class UHealthAttributeSet;
|
class UHealthAttributeSet;
|
||||||
class UStaminaAttributeSet;
|
class UStaminaAttributeSet;
|
||||||
|
class UInteractionComponent;
|
||||||
struct FInputActionValue;
|
struct FInputActionValue;
|
||||||
|
|
||||||
DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All);
|
DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All);
|
||||||
@@ -58,6 +59,14 @@ protected:
|
|||||||
UPROPERTY(EditAnywhere, Category="Input")
|
UPROPERTY(EditAnywhere, Category="Input")
|
||||||
UInputAction* SprintAction;
|
UInputAction* SprintAction;
|
||||||
|
|
||||||
|
/** Interact Input Action - Started begins a tap/hold, Completed resolves it */
|
||||||
|
UPROPERTY(EditAnywhere, Category="Input")
|
||||||
|
UInputAction* InteractAction;
|
||||||
|
|
||||||
|
/** Cycles the Context Menu's highlighted option while it is open (e.g. mouse wheel axis) */
|
||||||
|
UPROPERTY(EditAnywhere, Category="Input")
|
||||||
|
UInputAction* InteractCycleAction;
|
||||||
|
|
||||||
/** Walk speed is multiplied by this while sprinting */
|
/** Walk speed is multiplied by this while sprinting */
|
||||||
UPROPERTY(EditAnywhere, Category="Sprint")
|
UPROPERTY(EditAnywhere, Category="Sprint")
|
||||||
float SprintSpeedMultiplier = 1.6f;
|
float SprintSpeedMultiplier = 1.6f;
|
||||||
@@ -120,6 +129,9 @@ protected:
|
|||||||
/** Called when the sprint input is released */
|
/** Called when the sprint input is released */
|
||||||
void StopSprint();
|
void StopSprint();
|
||||||
|
|
||||||
|
/** Called for Context Menu cycling input (e.g. mouse wheel axis) */
|
||||||
|
void InteractCycle(const FInputActionValue& Value);
|
||||||
|
|
||||||
/** Advances the regen delay/ramp curve and applies Stamina/Fatigue regen for this frame. */
|
/** Advances the regen delay/ramp curve and applies Stamina/Fatigue regen for this frame. */
|
||||||
void TickStaminaRegen(float DeltaSeconds);
|
void TickStaminaRegen(float DeltaSeconds);
|
||||||
|
|
||||||
@@ -144,6 +156,10 @@ protected:
|
|||||||
/** Currently-applied GE_HealthDebuff instance, if any - reapplied/removed wholesale by SetHealthDebuff so it behaves like the old "set to X" API instead of stacking. */
|
/** Currently-applied GE_HealthDebuff instance, if any - reapplied/removed wholesale by SetHealthDebuff so it behaves like the old "set to X" API instead of stacking. */
|
||||||
FActiveGameplayEffectHandle HealthDebuffEffectHandle;
|
FActiveGameplayEffectHandle HealthDebuffEffectHandle;
|
||||||
|
|
||||||
|
/** Drives the contextual "hold E" interaction system - see UInteractionComponent. */
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Interaction", meta = (AllowPrivateAccess = "true"))
|
||||||
|
TObjectPtr<UInteractionComponent> InteractionComponent;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
//~ Begin IAbilitySystemInterface
|
//~ Begin IAbilitySystemInterface
|
||||||
@@ -219,6 +235,21 @@ public:
|
|||||||
UFUNCTION(BlueprintCallable, Category="Input")
|
UFUNCTION(BlueprintCallable, Category="Input")
|
||||||
virtual void DoJumpEnd();
|
virtual void DoJumpEnd();
|
||||||
|
|
||||||
|
/** Handles interact pressed inputs from either controls or UI interfaces - begins a tap/hold */
|
||||||
|
UFUNCTION(BlueprintCallable, Category="Input")
|
||||||
|
virtual void DoInteractStart();
|
||||||
|
|
||||||
|
/** Handles interact released inputs from either controls or UI interfaces - resolves the tap/hold */
|
||||||
|
UFUNCTION(BlueprintCallable, Category="Input")
|
||||||
|
virtual void DoInteractEnd();
|
||||||
|
|
||||||
|
/** Handles Context Menu cycling inputs (e.g. mouse wheel) from either controls or UI interfaces */
|
||||||
|
UFUNCTION(BlueprintCallable, Category="Input")
|
||||||
|
virtual void DoInteractCycle(float Direction);
|
||||||
|
|
||||||
|
/** Returns InteractionComponent subobject **/
|
||||||
|
FORCEINLINE UInteractionComponent* GetInteractionComponent() const { return InteractionComponent; }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/** Returns CameraBoom subobject **/
|
/** Returns CameraBoom subobject **/
|
||||||
|
|||||||
@@ -102,8 +102,9 @@ void AKingshearthLegacyPlayerController::OnPossess(APawn* InPawn)
|
|||||||
const AKingshearthLegacyCharacter* PossessedCharacter = Cast<AKingshearthLegacyCharacter>(InPawn);
|
const AKingshearthLegacyCharacter* PossessedCharacter = Cast<AKingshearthLegacyCharacter>(InPawn);
|
||||||
UHealthAttributeSet* PossessedHealthAttributeSet = PossessedCharacter ? PossessedCharacter->GetHealthAttributeSet() : nullptr;
|
UHealthAttributeSet* PossessedHealthAttributeSet = PossessedCharacter ? PossessedCharacter->GetHealthAttributeSet() : nullptr;
|
||||||
UStaminaAttributeSet* PossessedStaminaAttributeSet = PossessedCharacter ? PossessedCharacter->GetStaminaAttributeSet() : nullptr;
|
UStaminaAttributeSet* PossessedStaminaAttributeSet = PossessedCharacter ? PossessedCharacter->GetStaminaAttributeSet() : nullptr;
|
||||||
|
UInteractionComponent* PossessedInteractionComponent = PossessedCharacter ? PossessedCharacter->GetInteractionComponent() : nullptr;
|
||||||
|
|
||||||
PlayerHUD->InitializeHUD(PossessedHealthAttributeSet, PossessedStaminaAttributeSet);
|
PlayerHUD->InitializeHUD(PossessedHealthAttributeSet, PossessedStaminaAttributeSet, PossessedInteractionComponent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "InteractionContextMenuWidget.h"
|
||||||
|
#include "Interaction/InteractionComponent.h"
|
||||||
|
#include "Components/VerticalBox.h"
|
||||||
|
#include "Components/TextBlock.h"
|
||||||
|
#include "Blueprint/WidgetTree.h"
|
||||||
|
#include "KingshearthLegacy.h"
|
||||||
|
|
||||||
|
void UInteractionContextMenuWidget::NativeConstruct()
|
||||||
|
{
|
||||||
|
Super::NativeConstruct();
|
||||||
|
|
||||||
|
if (PromptText)
|
||||||
|
{
|
||||||
|
PromptText->SetVisibility(ESlateVisibility::Collapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OptionsBox)
|
||||||
|
{
|
||||||
|
OptionsBox->SetVisibility(ESlateVisibility::Collapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionContextMenuWidget::InitializeInteraction(UInteractionComponent* InInteractionComponent)
|
||||||
|
{
|
||||||
|
if (InteractionComponent)
|
||||||
|
{
|
||||||
|
InteractionComponent->OnFocusedInteractableChanged.RemoveDynamic(this, &UInteractionContextMenuWidget::HandleFocusedInteractableChanged);
|
||||||
|
InteractionComponent->OnContextMenuOpened.RemoveDynamic(this, &UInteractionContextMenuWidget::HandleContextMenuOpened);
|
||||||
|
InteractionComponent->OnContextMenuHighlightChanged.RemoveDynamic(this, &UInteractionContextMenuWidget::HandleContextMenuHighlightChanged);
|
||||||
|
InteractionComponent->OnContextMenuClosed.RemoveDynamic(this, &UInteractionContextMenuWidget::HandleContextMenuClosed);
|
||||||
|
}
|
||||||
|
|
||||||
|
InteractionComponent = InInteractionComponent;
|
||||||
|
|
||||||
|
if (InteractionComponent)
|
||||||
|
{
|
||||||
|
InteractionComponent->OnFocusedInteractableChanged.AddDynamic(this, &UInteractionContextMenuWidget::HandleFocusedInteractableChanged);
|
||||||
|
InteractionComponent->OnContextMenuOpened.AddDynamic(this, &UInteractionContextMenuWidget::HandleContextMenuOpened);
|
||||||
|
InteractionComponent->OnContextMenuHighlightChanged.AddDynamic(this, &UInteractionContextMenuWidget::HandleContextMenuHighlightChanged);
|
||||||
|
InteractionComponent->OnContextMenuClosed.AddDynamic(this, &UInteractionContextMenuWidget::HandleContextMenuClosed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionContextMenuWidget::HandleFocusedInteractableChanged(AActor* FocusedActor, const TArray<FInteractionOption>& Options)
|
||||||
|
{
|
||||||
|
if (!PromptText)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FocusedActor && Options.Num() > 0)
|
||||||
|
{
|
||||||
|
PromptText->SetText(Options[0].DisplayName);
|
||||||
|
PromptText->SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PromptText->SetVisibility(ESlateVisibility::Collapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionContextMenuWidget::RefreshOptions(const TArray<FInteractionOption>& Options, int32 HighlightedIndex)
|
||||||
|
{
|
||||||
|
if (!OptionsBox)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OptionsBox->ClearChildren();
|
||||||
|
|
||||||
|
for (int32 Index = 0; Index < Options.Num(); ++Index)
|
||||||
|
{
|
||||||
|
UTextBlock* Row = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass());
|
||||||
|
Row->SetText(Options[Index].DisplayName);
|
||||||
|
Row->SetColorAndOpacity(FSlateColor(Index == HighlightedIndex ? HighlightedColor : NormalColor));
|
||||||
|
OptionsBox->AddChildToVerticalBox(Row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionContextMenuWidget::HandleContextMenuOpened(const TArray<FInteractionOption>& Options, int32 InitialHighlightedIndex)
|
||||||
|
{
|
||||||
|
UE_LOG(LogKingshearthLegacy, Log, TEXT("[Interaction] Widget received OnContextMenuOpened: %d option(s), OptionsBox bound: %s"),
|
||||||
|
Options.Num(), OptionsBox ? TEXT("yes") : TEXT("NO - add a VerticalBox named exactly \"OptionsBox\" in this widget's Designer tree"));
|
||||||
|
|
||||||
|
CachedOptions = Options;
|
||||||
|
RefreshOptions(CachedOptions, InitialHighlightedIndex);
|
||||||
|
|
||||||
|
if (OptionsBox)
|
||||||
|
{
|
||||||
|
OptionsBox->SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionContextMenuWidget::HandleContextMenuHighlightChanged(int32 NewHighlightedIndex)
|
||||||
|
{
|
||||||
|
RefreshOptions(CachedOptions, NewHighlightedIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionContextMenuWidget::HandleContextMenuClosed()
|
||||||
|
{
|
||||||
|
CachedOptions.Reset();
|
||||||
|
|
||||||
|
if (OptionsBox)
|
||||||
|
{
|
||||||
|
OptionsBox->ClearChildren();
|
||||||
|
OptionsBox->SetVisibility(ESlateVisibility::Collapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Blueprint/UserWidget.h"
|
||||||
|
#include "Interaction/InteractionTypes.h"
|
||||||
|
#include "InteractionContextMenuWidget.generated.h"
|
||||||
|
|
||||||
|
class UInteractionComponent;
|
||||||
|
class UVerticalBox;
|
||||||
|
class UTextBlock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One widget covering both interaction UI needs so you only wire up a single thing
|
||||||
|
* in WBP_PlayerHUD: an ever-present "you're looking at something" prompt (PromptText,
|
||||||
|
* driven by OnFocusedInteractableChanged - shows any time the look trace is on an
|
||||||
|
* IInteractable, regardless of option count or whether Interact is even pressed) and
|
||||||
|
* the Context Menu shown while holding Interact on a 3+-option IInteractable
|
||||||
|
* (OptionsBox, driven by OnContextMenuOpened/HighlightChanged/Closed). Both bound
|
||||||
|
* widgets are optional and toggle their own visibility independently - add whichever
|
||||||
|
* you want in the Designer, named exactly as below; all population/highlight/show-hide
|
||||||
|
* logic lives here in C++.
|
||||||
|
*/
|
||||||
|
UCLASS(abstract)
|
||||||
|
class UInteractionContextMenuWidget : public UUserWidget
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Call once, right after creation (see UPlayerHUDWidget::InitializeHUD), to bind to InInteractionComponent's delegates. */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Interaction")
|
||||||
|
void InitializeInteraction(UInteractionComponent* InInteractionComponent);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
virtual void NativeConstruct() override;
|
||||||
|
|
||||||
|
/** Rebuilds OptionsBox's rows from Options, styling HighlightedIndex differently from the rest. */
|
||||||
|
void RefreshOptions(const TArray<FInteractionOption>& Options, int32 HighlightedIndex);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void HandleFocusedInteractableChanged(AActor* FocusedActor, const TArray<FInteractionOption>& Options);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void HandleContextMenuOpened(const TArray<FInteractionOption>& Options, int32 InitialHighlightedIndex);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void HandleContextMenuHighlightChanged(int32 NewHighlightedIndex);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void HandleContextMenuClosed();
|
||||||
|
|
||||||
|
/** Text color used for the highlighted row. */
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Interaction|Style")
|
||||||
|
FLinearColor HighlightedColor = FLinearColor(1.0f, 0.85f, 0.3f, 1.0f);
|
||||||
|
|
||||||
|
/** Text color used for every other row. */
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Interaction|Style")
|
||||||
|
FLinearColor NormalColor = FLinearColor::White;
|
||||||
|
|
||||||
|
/** Ever-present prompt - shows the primary option's DisplayName any time the look trace is on an IInteractable. Optional, place it in the WBP subclass's Designer tree. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UTextBlock> PromptText;
|
||||||
|
|
||||||
|
/** Container the Context Menu's option rows are built into. Optional, place it in the WBP subclass's Designer tree. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UVerticalBox> OptionsBox;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<FInteractionOption> CachedOptions;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UInteractionComponent> InteractionComponent;
|
||||||
|
};
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
#include "PlayerHUDWidget.h"
|
#include "PlayerHUDWidget.h"
|
||||||
#include "UI/HealthBarWidget.h"
|
#include "UI/HealthBarWidget.h"
|
||||||
#include "UI/StaminaBarWidget.h"
|
#include "UI/StaminaBarWidget.h"
|
||||||
|
#include "UI/InteractionContextMenuWidget.h"
|
||||||
|
#include "KingshearthLegacy.h"
|
||||||
|
|
||||||
void UPlayerHUDWidget::InitializeHUD(UHealthAttributeSet* InHealthAttributeSet, UStaminaAttributeSet* InStaminaAttributeSet)
|
void UPlayerHUDWidget::InitializeHUD(UHealthAttributeSet* InHealthAttributeSet, UStaminaAttributeSet* InStaminaAttributeSet, UInteractionComponent* InInteractionComponent)
|
||||||
{
|
{
|
||||||
if (HealthBar)
|
if (HealthBar)
|
||||||
{
|
{
|
||||||
@@ -13,4 +15,11 @@ void UPlayerHUDWidget::InitializeHUD(UHealthAttributeSet* InHealthAttributeSet,
|
|||||||
{
|
{
|
||||||
StaminaBar->InitializeStaminaBar(InStaminaAttributeSet);
|
StaminaBar->InitializeStaminaBar(InStaminaAttributeSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogKingshearthLegacy, Log, TEXT("[Interaction] PlayerHUD ContextMenuWidget bound: %s"), ContextMenuWidget ? TEXT("yes") : TEXT("NO - check the widget in ContextMenuWidget is named exactly \"ContextMenuWidget\" in WBP_PlayerHUD's Designer tree"));
|
||||||
|
|
||||||
|
if (ContextMenuWidget)
|
||||||
|
{
|
||||||
|
ContextMenuWidget->InitializeInteraction(InInteractionComponent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ class UHealthBarWidget;
|
|||||||
class UStaminaBarWidget;
|
class UStaminaBarWidget;
|
||||||
class UHealthAttributeSet;
|
class UHealthAttributeSet;
|
||||||
class UStaminaAttributeSet;
|
class UStaminaAttributeSet;
|
||||||
|
class UInteractionComponent;
|
||||||
|
class UInteractionContextMenuWidget;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Top-level player HUD. Owns a health bar and a stamina bar and forwards
|
* Top-level player HUD. Owns a health bar and a stamina bar and forwards
|
||||||
@@ -21,7 +23,7 @@ class UPlayerHUDWidget : public UUserWidget
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
UFUNCTION(BlueprintCallable, Category = "HUD")
|
UFUNCTION(BlueprintCallable, Category = "HUD")
|
||||||
void InitializeHUD(UHealthAttributeSet* InHealthAttributeSet, UStaminaAttributeSet* InStaminaAttributeSet);
|
void InitializeHUD(UHealthAttributeSet* InHealthAttributeSet, UStaminaAttributeSet* InStaminaAttributeSet, UInteractionComponent* InInteractionComponent);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
@@ -29,4 +31,7 @@ protected:
|
|||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
TObjectPtr<UStaminaBarWidget> StaminaBar;
|
TObjectPtr<UStaminaBarWidget> StaminaBar;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UInteractionContextMenuWidget> ContextMenuWidget;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user