Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a900208273 | ||
|
|
c857293eb4 | ||
|
|
90de51b1a0 | ||
|
|
a5eb673c6e | ||
|
|
e4fb702a1c | ||
|
|
a36eaf27bc | ||
|
|
0b34a9742a | ||
|
|
fd08fe4804 | ||
|
|
8099d69a23 |
@@ -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,65 @@ AppliedTargetedHardwareClass=Desktop
|
|||||||
DefaultGraphicsPerformance=Maximum
|
DefaultGraphicsPerformance=Maximum
|
||||||
AppliedDefaultGraphicsPerformance=Maximum
|
AppliedDefaultGraphicsPerformance=Maximum
|
||||||
|
|
||||||
|
[/Script/Engine.CollisionProfile]
|
||||||
|
-Profiles=(Name="NoCollision",CollisionEnabled=NoCollision,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="No collision",bCanModify=False)
|
||||||
|
-Profiles=(Name="BlockAll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=,HelpMessage="WorldStatic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False)
|
||||||
|
-Profiles=(Name="OverlapAll",CollisionEnabled=QueryOnly,ObjectTypeName="WorldStatic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False)
|
||||||
|
-Profiles=(Name="BlockAllDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=,HelpMessage="WorldDynamic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False)
|
||||||
|
-Profiles=(Name="OverlapAllDynamic",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False)
|
||||||
|
-Profiles=(Name="IgnoreOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that ignores Pawn and Vehicle. All other channels will be set to default.",bCanModify=False)
|
||||||
|
-Profiles=(Name="OverlapOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that overlaps Pawn, Camera, and Vehicle. All other channels will be set to default. ",bCanModify=False)
|
||||||
|
-Profiles=(Name="Pawn",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Pawn",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object. Can be used for capsule of any playerable character or AI. ",bCanModify=False)
|
||||||
|
-Profiles=(Name="Spectator",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="WorldStatic",Response=ECR_Block),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore)),HelpMessage="Pawn object that ignores all other actors except WorldStatic.",bCanModify=False)
|
||||||
|
-Profiles=(Name="CharacterMesh",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object that is used for Character Mesh. All other channels will be set to default.",bCanModify=False)
|
||||||
|
-Profiles=(Name="PhysicsActor",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=,HelpMessage="Simulating actors",bCanModify=False)
|
||||||
|
-Profiles=(Name="Destructible",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Destructible",CustomResponses=,HelpMessage="Destructible actors",bCanModify=False)
|
||||||
|
-Profiles=(Name="InvisibleWall",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldStatic object that is invisible.",bCanModify=False)
|
||||||
|
-Profiles=(Name="InvisibleWallDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that is invisible.",bCanModify=False)
|
||||||
|
-Profiles=(Name="Trigger",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that is used for trigger. All other channels will be set to default.",bCanModify=False)
|
||||||
|
-Profiles=(Name="Ragdoll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Simulating Skeletal Mesh Component. All other channels will be set to default.",bCanModify=False)
|
||||||
|
-Profiles=(Name="Vehicle",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Vehicle",CustomResponses=,HelpMessage="Vehicle object that blocks Vehicle, WorldStatic, and WorldDynamic. All other channels will be set to default.",bCanModify=False)
|
||||||
|
-Profiles=(Name="UI",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Block),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False)
|
||||||
|
-Profiles=(Name="WaterBodyCollision",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="",CustomResponses=((Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="Default Water Collision Profile (Created by Water Plugin)")
|
||||||
|
+Profiles=(Name="NoCollision",CollisionEnabled=NoCollision,bCanModify=False,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="No collision")
|
||||||
|
+Profiles=(Name="BlockAll",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="WorldStatic",CustomResponses=,HelpMessage="WorldStatic object that blocks all actors by default. All new custom channels will use its own default response. ")
|
||||||
|
+Profiles=(Name="OverlapAll",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldStatic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ")
|
||||||
|
+Profiles=(Name="BlockAllDynamic",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=,HelpMessage="WorldDynamic object that blocks all actors by default. All new custom channels will use its own default response. ")
|
||||||
|
+Profiles=(Name="OverlapAllDynamic",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that overlaps all actors by default. All new custom channels will use its own default response. ")
|
||||||
|
+Profiles=(Name="IgnoreOnlyPawn",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that ignores Pawn and Vehicle. All other channels will be set to default.")
|
||||||
|
+Profiles=(Name="OverlapOnlyPawn",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that overlaps Pawn, Camera, and Vehicle. All other channels will be set to default. ")
|
||||||
|
+Profiles=(Name="Pawn",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="Pawn",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object. Can be used for capsule of any playerable character or AI. ")
|
||||||
|
+Profiles=(Name="Spectator",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="Pawn",CustomResponses=((Channel="WorldStatic"),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore)),HelpMessage="Pawn object that ignores all other actors except WorldStatic.")
|
||||||
|
+Profiles=(Name="CharacterMesh",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="Pawn",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object that is used for Character Mesh. All other channels will be set to default.")
|
||||||
|
+Profiles=(Name="PhysicsActor",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="PhysicsBody",CustomResponses=,HelpMessage="Simulating actors")
|
||||||
|
+Profiles=(Name="Destructible",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="Destructible",CustomResponses=,HelpMessage="Destructible actors")
|
||||||
|
+Profiles=(Name="InvisibleWall",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldStatic object that is invisible.")
|
||||||
|
+Profiles=(Name="InvisibleWallDynamic",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that is invisible.")
|
||||||
|
+Profiles=(Name="Trigger",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that is used for trigger. All other channels will be set to default.")
|
||||||
|
+Profiles=(Name="Ragdoll",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="PhysicsBody",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Simulating Skeletal Mesh Component. All other channels will be set to default.")
|
||||||
|
+Profiles=(Name="Vehicle",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="Vehicle",CustomResponses=,HelpMessage="Vehicle object that blocks Vehicle, WorldStatic, and WorldDynamic. All other channels will be set to default.")
|
||||||
|
+Profiles=(Name="UI",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility"),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ")
|
||||||
|
+Profiles=(Name="WaterBodyCollision",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="",CustomResponses=((Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="Default Water Collision Profile (Created by Water Plugin)")
|
||||||
|
+DefaultChannelResponses=(Channel=ECC_GameTraceChannel1,DefaultResponse=ECR_Block,bTraceType=True,bStaticObject=False,Name="Interactable")
|
||||||
|
-ProfileRedirects=(OldName="BlockingVolume",NewName="InvisibleWall")
|
||||||
|
-ProfileRedirects=(OldName="InterpActor",NewName="IgnoreOnlyPawn")
|
||||||
|
-ProfileRedirects=(OldName="StaticMeshComponent",NewName="BlockAllDynamic")
|
||||||
|
-ProfileRedirects=(OldName="SkeletalMeshActor",NewName="PhysicsActor")
|
||||||
|
-ProfileRedirects=(OldName="InvisibleActor",NewName="InvisibleWallDynamic")
|
||||||
|
+ProfileRedirects=(OldName="BlockingVolume",NewName="InvisibleWall")
|
||||||
|
+ProfileRedirects=(OldName="InterpActor",NewName="IgnoreOnlyPawn")
|
||||||
|
+ProfileRedirects=(OldName="StaticMeshComponent",NewName="BlockAllDynamic")
|
||||||
|
+ProfileRedirects=(OldName="SkeletalMeshActor",NewName="PhysicsActor")
|
||||||
|
+ProfileRedirects=(OldName="InvisibleActor",NewName="InvisibleWallDynamic")
|
||||||
|
-CollisionChannelRedirects=(OldName="Static",NewName="WorldStatic")
|
||||||
|
-CollisionChannelRedirects=(OldName="Dynamic",NewName="WorldDynamic")
|
||||||
|
-CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle")
|
||||||
|
-CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn")
|
||||||
|
+CollisionChannelRedirects=(OldName="Static",NewName="WorldStatic")
|
||||||
|
+CollisionChannelRedirects=(OldName="Dynamic",NewName="WorldDynamic")
|
||||||
|
+CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle")
|
||||||
|
+CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn")
|
||||||
|
|
||||||
[/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")
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
[/Script/EngineSettings.GeneralProjectSettings]
|
[/Script/EngineSettings.GeneralProjectSettings]
|
||||||
ProjectID=CE954E444D2A872317AAC38182169D03
|
ProjectID=CE954E444D2A872317AAC38182169D03
|
||||||
ProjectName=Third Person Game Template
|
ProjectName=Kingsharth Legacy
|
||||||
|
|
||||||
[ConsoleVariables]
|
[ConsoleVariables]
|
||||||
CommonUI.CheckKeyboardFocusAndParentage=1
|
CommonUI.CheckKeyboardFocusAndParentage=1
|
||||||
CommonUI.DisallowUserFocusedWidgetForPendingFocusRecipient=1
|
CommonUI.DisallowUserFocusedWidgetForPendingFocusRecipient=1
|
||||||
CommonUI.FallbackToDesiredOnAutoRestoreFailure=1
|
CommonUI.FallbackToDesiredOnAutoRestoreFailure=1
|
||||||
|
|
||||||
|
|||||||
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.
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,41 @@
|
|||||||
|
// 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);
|
||||||
|
|
||||||
|
/** Friendly object name for prompts, e.g. "Berry Bush", "Wooden Chest", "Workbench". Optional - an empty default just drops the "with X" part of the prompt. */
|
||||||
|
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interaction")
|
||||||
|
FText GetInteractableDisplayName();
|
||||||
|
|
||||||
|
/** 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,258 @@
|
|||||||
|
// 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();
|
||||||
|
UpdateHoldProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UInteractionComponent::PerformInteractionTrace(FHitResult& OutHit) const
|
||||||
|
{
|
||||||
|
AActor* Owner = GetOwner();
|
||||||
|
UWorld* World = GetWorld();
|
||||||
|
if (!Owner || !World)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Owner->Implements<UInteractionLookupProvider>())
|
||||||
|
{
|
||||||
|
// Authoritative once implemented - a miss here means "nothing found", full stop.
|
||||||
|
// Don't silently fall through to the default trace below just because this one
|
||||||
|
// came back empty; that would use a completely different (un-offset) trace and
|
||||||
|
// could contradict what the override deliberately decided.
|
||||||
|
return IInteractionLookupProvider::Execute_InteractionLookupLoop(Owner, OutHit);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
const bool bImplementsInteractable = bHit && Hit.GetActor() && Hit.GetActor()->Implements<UInteractable>();
|
||||||
|
AActor* NewFocusedActor = bImplementsInteractable ? Hit.GetActor() : nullptr;
|
||||||
|
|
||||||
|
#if ENABLE_DRAW_DEBUG
|
||||||
|
if (bShowDebugTraces)
|
||||||
|
{
|
||||||
|
UE_LOG(LogKingshearthLegacy, Log, TEXT("[Interaction] Tick: bHit=%s hitActor='%s' implementsInteractable=%s currentFocused='%s'"),
|
||||||
|
bHit ? TEXT("true") : TEXT("false"),
|
||||||
|
*GetNameSafe(bHit ? Hit.GetActor() : nullptr),
|
||||||
|
bImplementsInteractable ? TEXT("true") : TEXT("false"),
|
||||||
|
*GetNameSafe(FocusedActor.Get()));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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::UpdateHoldProgress()
|
||||||
|
{
|
||||||
|
const bool bTimerActive = GetWorld()->GetTimerManager().IsTimerActive(HoldTimerHandle);
|
||||||
|
const float Progress = bTimerActive && HoldThreshold > 0.0f
|
||||||
|
? FMath::Clamp(GetWorld()->GetTimerManager().GetTimerElapsed(HoldTimerHandle) / HoldThreshold, 0.0f, 1.0f)
|
||||||
|
: 0.0f;
|
||||||
|
|
||||||
|
if (FMath::IsNearlyEqual(Progress, LastBroadcastHoldProgress, KINDA_SMALL_NUMBER))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LastBroadcastHoldProgress = Progress;
|
||||||
|
OnInteractHoldProgressChanged.Broadcast(Progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
RefreshFocusedOptions();
|
||||||
|
CloseContextMenu();
|
||||||
|
}
|
||||||
|
else if (!bHoldConsumed && CurrentOptions.Num() >= 1)
|
||||||
|
{
|
||||||
|
ExecuteOption(0);
|
||||||
|
RefreshFocusedOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
ClearCachedTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionComponent::CycleContextMenuOptions(float Direction)
|
||||||
|
{
|
||||||
|
if (!bContextMenuOpen || CurrentOptions.Num() == 0 || FMath::IsNearlyZero(Direction))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scrolling up (positive Direction) moves the highlight toward the top of the list (index 0),
|
||||||
|
// matching how the mouse wheel scrolls content elsewhere - hence the flipped sign here.
|
||||||
|
const int32 Step = Direction > 0.0f ? -1 : 1;
|
||||||
|
const int32 NewHighlightedIndex = FMath::Clamp(HighlightedIndex + Step, 0, CurrentOptions.Num() - 1);
|
||||||
|
|
||||||
|
if (NewHighlightedIndex == HighlightedIndex)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HighlightedIndex = NewHighlightedIndex;
|
||||||
|
OnContextMenuHighlightChanged.Broadcast(HighlightedIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionComponent::HandleHoldThresholdReached()
|
||||||
|
{
|
||||||
|
bHoldConsumed = true;
|
||||||
|
|
||||||
|
if (CurrentOptions.Num() == 2)
|
||||||
|
{
|
||||||
|
ExecuteOption(1);
|
||||||
|
RefreshFocusedOptions();
|
||||||
|
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::RefreshFocusedOptions()
|
||||||
|
{
|
||||||
|
AActor* Actor = FocusedActor.Get();
|
||||||
|
if (!Actor)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FocusedOptions = IInteractable::Execute_GetInteractionOptions(Actor, GetOwner());
|
||||||
|
OnFocusedInteractableChanged.Broadcast(Actor, FocusedOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionComponent::ClearCachedTarget()
|
||||||
|
{
|
||||||
|
CurrentTarget = nullptr;
|
||||||
|
CurrentOptions.Reset();
|
||||||
|
HighlightedIndex = 0;
|
||||||
|
bHoldConsumed = false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
// 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);
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnInteractHoldProgressChanged, float, Progress);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired every tick while the hold timer is running, with the 0-1 fraction of
|
||||||
|
* HoldThreshold elapsed so far - drives a fill bar for the "hold E" prompt.
|
||||||
|
* Fires once more with 0 the instant the hold ends, however it ends (release,
|
||||||
|
* secondary fired, Context Menu opened), so a bound bar always snaps back
|
||||||
|
* empty. Never fires at all for a 1-option target, since no hold timer is
|
||||||
|
* started for it.
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Interaction")
|
||||||
|
FOnInteractHoldProgressChanged OnInteractHoldProgressChanged;
|
||||||
|
|
||||||
|
/** 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();
|
||||||
|
|
||||||
|
/** Re-derives hold progress from HoldTimerHandle every tick and broadcasts OnInteractHoldProgressChanged when it changes. */
|
||||||
|
void UpdateHoldProgress();
|
||||||
|
|
||||||
|
/** 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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-pulls GetInteractionOptions() for the still-focused actor and re-broadcasts
|
||||||
|
* OnFocusedInteractableChanged. Call right after ExecuteOption - the interaction just run
|
||||||
|
* may have changed what that same actor now offers (e.g. a chest that's now empty), and
|
||||||
|
* UpdateFocus alone would never notice since it only re-pulls options when the focused
|
||||||
|
* actor's identity changes, not when its offered options do. No-op if nothing is focused.
|
||||||
|
*/
|
||||||
|
void RefreshFocusedOptions();
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/** Last value passed to OnInteractHoldProgressChanged, so UpdateHoldProgress only broadcasts on actual change. */
|
||||||
|
float LastBroadcastHoldProgress = 0.0f;
|
||||||
|
};
|
||||||
@@ -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,58 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "InteractionTypes.generated.h"
|
||||||
|
|
||||||
|
class UTexture2D;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/** Optional - shown by UInteractionOptionEntryWidget's Icon image when set, hidden otherwise. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||||
|
TObjectPtr<UTexture2D> Icon = nullptr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional - when this is the primary (index 0) option and this is set, the ever-present
|
||||||
|
* prompt's first line uses this verbatim instead of building "{DisplayName} with
|
||||||
|
* {ObjectName}" - e.g. a chest can set this to "Open Chest" instead of ending up as
|
||||||
|
* "Open with Chest". The "Press E" / "Hold E for ..." lines are unaffected.
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||||
|
FText PromptOverride;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional per-option override for the Context Menu row's text color (both normal and
|
||||||
|
* highlighted state use this same color, so the indication doesn't disappear on hover) -
|
||||||
|
* e.g. red for an action you currently can't perform, gold for a special one. Overrides
|
||||||
|
* UInteractionOptionEntryWidget's NormalTextColor/HighlightedTextColor for this option
|
||||||
|
* only. Alpha 0 (the default) means "use the entry widget's own colors".
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||||
|
FLinearColor TextColorOverride = FLinearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional per-option override for the Context Menu row's highlight tint - overrides
|
||||||
|
* UInteractionOptionEntryWidget's HighlightColor for this option only. Alpha 0 (the
|
||||||
|
* default) means "use the entry widget's own color".
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||||
|
FLinearColor HighlightColorOverride = FLinearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
};
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "InventoryTypes.generated.h"
|
||||||
|
|
||||||
|
class UTexture2D;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where an equipped item is displayed. Purely a display label for WBP_EquipmentSlot - there is
|
||||||
|
* no attachment-point/socket hierarchy and no validation that an item is appropriate for the
|
||||||
|
* slot it's put in. Multiple slots can share the same type (e.g. two Back slots, two Belt
|
||||||
|
* slots) - disambiguate those via FEquipmentSlotData::SlotDisplayNameOverride.
|
||||||
|
*/
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class EEquipmentSlotType : uint8
|
||||||
|
{
|
||||||
|
Head,
|
||||||
|
Body,
|
||||||
|
Hands,
|
||||||
|
Legs,
|
||||||
|
Feet,
|
||||||
|
Back,
|
||||||
|
Belt
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single inventory/equipment/quick-slot item as far as the UI is concerned. Mass and Volume
|
||||||
|
* describe the item's physical footprint for weight/capacity tracking - they never affect how
|
||||||
|
* large the item appears on screen, since every item always renders at the same fixed 2x2 UI
|
||||||
|
* size (see UInventoryItemWidget). An ItemID of NAME_None means the slot is empty.
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FItemData
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
|
||||||
|
FName ItemID;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
|
||||||
|
FText DisplayName;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
|
||||||
|
TObjectPtr<UTexture2D> Icon = nullptr;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
|
||||||
|
int32 Quantity = 1;
|
||||||
|
|
||||||
|
/** Physical mass in kg, used for carry-weight totals - not UI size. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
|
||||||
|
float Mass = 0.0f;
|
||||||
|
|
||||||
|
/** Physical volume in liters, used for carry-capacity totals - not UI size. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
|
||||||
|
float Volume = 0.0f;
|
||||||
|
|
||||||
|
/** Optional basic state label shown alongside the item (e.g. "Broken", "New", "Equipped"). Empty means no state to show. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
|
||||||
|
FText StateText;
|
||||||
|
|
||||||
|
bool IsEmpty() const { return ItemID.IsNone(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One equipment slot's contents. SlotType is a display label only - WBP_EquipmentSlot shows
|
||||||
|
* whatever it's given here and does not validate that EquippedItem actually belongs in SlotType.
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FEquipmentSlotData
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Equipment")
|
||||||
|
EEquipmentSlotType SlotType = EEquipmentSlotType::Head;
|
||||||
|
|
||||||
|
/** Optional override shown instead of SlotType's enum name - use this to tell apart slots that share a type (e.g. "Back 1"/"Back 2", "Belt 1"/"Belt 2"). Empty falls back to the SlotType display name. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Equipment")
|
||||||
|
FText SlotDisplayNameOverride;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Equipment")
|
||||||
|
FItemData EquippedItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One inventory container's contents (the player's own bag, a chest being looted, ...). Items
|
||||||
|
* are a flat, ordered list - there is no physical/spatial grid position per item, only UI-list
|
||||||
|
* ordering (no Tetris-style placement).
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FInventoryContainerInfo
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
|
||||||
|
FName ContainerID;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
|
||||||
|
FText DisplayName;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
|
||||||
|
TArray<FItemData> Items;
|
||||||
|
};
|
||||||
@@ -19,6 +19,7 @@ public class KingshearthLegacy : ModuleRules
|
|||||||
"GameplayStateTreeModule",
|
"GameplayStateTreeModule",
|
||||||
"UMG",
|
"UMG",
|
||||||
"Slate",
|
"Slate",
|
||||||
|
"SlateCore",
|
||||||
"GameplayAbilities",
|
"GameplayAbilities",
|
||||||
"GameplayTags",
|
"GameplayTags",
|
||||||
"GameplayTasks"
|
"GameplayTasks"
|
||||||
@@ -30,7 +31,10 @@ public class KingshearthLegacy : ModuleRules
|
|||||||
"KingshearthLegacy",
|
"KingshearthLegacy",
|
||||||
"KingshearthLegacy/AttributeSets",
|
"KingshearthLegacy/AttributeSets",
|
||||||
"KingshearthLegacy/GameplayEffects",
|
"KingshearthLegacy/GameplayEffects",
|
||||||
|
"KingshearthLegacy/Interaction",
|
||||||
|
"KingshearthLegacy/Inventory",
|
||||||
"KingshearthLegacy/UI/Menu",
|
"KingshearthLegacy/UI/Menu",
|
||||||
|
"KingshearthLegacy/UI/Inventory",
|
||||||
"KingshearthLegacy/Variant_Platforming",
|
"KingshearthLegacy/Variant_Platforming",
|
||||||
"KingshearthLegacy/Variant_Platforming/Animation",
|
"KingshearthLegacy/Variant_Platforming/Animation",
|
||||||
"KingshearthLegacy/Variant_Combat",
|
"KingshearthLegacy/Variant_Combat",
|
||||||
|
|||||||
@@ -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 **/
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "UI/PlayerHUDWidget.h"
|
#include "UI/PlayerHUDWidget.h"
|
||||||
#include "UI/Menu/InGameMenuWidget.h"
|
#include "UI/Menu/InGameMenuWidget.h"
|
||||||
#include "UI/Menu/MenuManagerSubsystem.h"
|
#include "UI/Menu/MenuManagerSubsystem.h"
|
||||||
|
#include "UI/Inventory/EquipmentInventoryTabWidget.h"
|
||||||
#include "KingshearthLegacyCharacter.h"
|
#include "KingshearthLegacyCharacter.h"
|
||||||
|
|
||||||
void AKingshearthLegacyPlayerController::BeginPlay()
|
void AKingshearthLegacyPlayerController::BeginPlay()
|
||||||
@@ -70,6 +71,11 @@ void AKingshearthLegacyPlayerController::SetupInputComponent()
|
|||||||
{
|
{
|
||||||
EnhancedInputComponent->BindAction(MenuAction, ETriggerEvent::Started, this, &AKingshearthLegacyPlayerController::ToggleInGameMenu);
|
EnhancedInputComponent->BindAction(MenuAction, ETriggerEvent::Started, this, &AKingshearthLegacyPlayerController::ToggleInGameMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (InventoryAction)
|
||||||
|
{
|
||||||
|
EnhancedInputComponent->BindAction(InventoryAction, ETriggerEvent::Started, this, &AKingshearthLegacyPlayerController::ToggleInventory);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,8 +108,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,3 +143,27 @@ void AKingshearthLegacyPlayerController::ToggleInGameMenu()
|
|||||||
MenuManager->PushScreen(InGameMenuClass);
|
MenuManager->PushScreen(InGameMenuClass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AKingshearthLegacyPlayerController::ToggleInventory()
|
||||||
|
{
|
||||||
|
if (!IsLocalPlayerController() || !InventoryScreenClass)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UGameInstance* GI = GetGameInstance();
|
||||||
|
UMenuManagerSubsystem* MenuManager = GI ? GI->GetSubsystem<UMenuManagerSubsystem>() : nullptr;
|
||||||
|
if (!MenuManager)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MenuManager->IsAnyScreenOpen())
|
||||||
|
{
|
||||||
|
MenuManager->PopScreen();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MenuManager->PushScreen(InventoryScreenClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class UInputAction;
|
|||||||
class UUserWidget;
|
class UUserWidget;
|
||||||
class UPlayerHUDWidget;
|
class UPlayerHUDWidget;
|
||||||
class UInGameMenuWidget;
|
class UInGameMenuWidget;
|
||||||
|
class UEquipmentInventoryTabWidget;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic PlayerController class for a third person game
|
* Basic PlayerController class for a third person game
|
||||||
@@ -59,6 +60,14 @@ protected:
|
|||||||
UPROPERTY(EditAnywhere, Category = "Input|Input Mappings")
|
UPROPERTY(EditAnywhere, Category = "Input|Input Mappings")
|
||||||
TObjectPtr<UInputAction> MenuAction;
|
TObjectPtr<UInputAction> MenuAction;
|
||||||
|
|
||||||
|
/** Inventory/equipment screen, pushed/popped through the menu manager by ToggleInventory */
|
||||||
|
UPROPERTY(EditAnywhere, Category = "UI")
|
||||||
|
TSubclassOf<UEquipmentInventoryTabWidget> InventoryScreenClass;
|
||||||
|
|
||||||
|
/** Input action that opens the inventory screen, or closes the top menu screen if one is already open */
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Input|Input Mappings")
|
||||||
|
TObjectPtr<UInputAction> InventoryAction;
|
||||||
|
|
||||||
/** Gameplay initialization */
|
/** Gameplay initialization */
|
||||||
virtual void BeginPlay() override;
|
virtual void BeginPlay() override;
|
||||||
|
|
||||||
@@ -75,4 +84,8 @@ protected:
|
|||||||
UFUNCTION(BlueprintCallable, Category = "UI")
|
UFUNCTION(BlueprintCallable, Category = "UI")
|
||||||
void ToggleInGameMenu();
|
void ToggleInGameMenu();
|
||||||
|
|
||||||
|
/** Opens InventoryScreenClass, or closes the top menu screen if one is already open */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "UI")
|
||||||
|
void ToggleInventory();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "InteractionContextMenuWidget.h"
|
||||||
|
#include "InteractionOptionListItem.h"
|
||||||
|
#include "InteractionPromptWidget.h"
|
||||||
|
#include "Interaction/InteractionComponent.h"
|
||||||
|
#include "Components/ListView.h"
|
||||||
|
#include "KingshearthLegacy.h"
|
||||||
|
|
||||||
|
void UInteractionContextMenuWidget::NativeConstruct()
|
||||||
|
{
|
||||||
|
Super::NativeConstruct();
|
||||||
|
|
||||||
|
if (OptionsList)
|
||||||
|
{
|
||||||
|
OptionsList->SetVisibility(ESlateVisibility::Collapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionContextMenuWidget::InitializeInteraction(UInteractionComponent* InInteractionComponent)
|
||||||
|
{
|
||||||
|
if (InteractionComponent)
|
||||||
|
{
|
||||||
|
InteractionComponent->OnContextMenuOpened.RemoveDynamic(this, &UInteractionContextMenuWidget::HandleContextMenuOpened);
|
||||||
|
InteractionComponent->OnContextMenuHighlightChanged.RemoveDynamic(this, &UInteractionContextMenuWidget::HandleContextMenuHighlightChanged);
|
||||||
|
InteractionComponent->OnContextMenuClosed.RemoveDynamic(this, &UInteractionContextMenuWidget::HandleContextMenuClosed);
|
||||||
|
}
|
||||||
|
|
||||||
|
InteractionComponent = InInteractionComponent;
|
||||||
|
|
||||||
|
if (InteractionComponent)
|
||||||
|
{
|
||||||
|
InteractionComponent->OnContextMenuOpened.AddDynamic(this, &UInteractionContextMenuWidget::HandleContextMenuOpened);
|
||||||
|
InteractionComponent->OnContextMenuHighlightChanged.AddDynamic(this, &UInteractionContextMenuWidget::HandleContextMenuHighlightChanged);
|
||||||
|
InteractionComponent->OnContextMenuClosed.AddDynamic(this, &UInteractionContextMenuWidget::HandleContextMenuClosed);
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogKingshearthLegacy, Log, TEXT("[Interaction] ContextMenu widget InitializeInteraction: OptionsList bound: %s, WBP_InteractionQuick bound: %s"),
|
||||||
|
OptionsList ? TEXT("yes") : TEXT("NO"),
|
||||||
|
WBP_InteractionQuick ? TEXT("yes") : TEXT("NO - check its Name in the Designer tree matches the native property, and that it's reparented to UInteractionPromptWidget"));
|
||||||
|
|
||||||
|
if (WBP_InteractionQuick)
|
||||||
|
{
|
||||||
|
WBP_InteractionQuick->InitializeInteraction(InInteractionComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionContextMenuWidget::PopulateOptionsList(const TArray<FInteractionOption>& Options)
|
||||||
|
{
|
||||||
|
if (!OptionsList)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OptionsList->ClearListItems();
|
||||||
|
ListItems.Reset(Options.Num());
|
||||||
|
|
||||||
|
TArray<UObject*> ItemObjects;
|
||||||
|
ItemObjects.Reserve(Options.Num());
|
||||||
|
|
||||||
|
for (const FInteractionOption& OptionEntry : Options)
|
||||||
|
{
|
||||||
|
UInteractionOptionListItem* Item = NewObject<UInteractionOptionListItem>(this);
|
||||||
|
Item->Option = OptionEntry;
|
||||||
|
ListItems.Add(Item);
|
||||||
|
ItemObjects.Add(Item);
|
||||||
|
}
|
||||||
|
|
||||||
|
OptionsList->SetListItems(ItemObjects);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionContextMenuWidget::SelectHighlighted(int32 Index)
|
||||||
|
{
|
||||||
|
if (!OptionsList)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ListItems.IsValidIndex(Index))
|
||||||
|
{
|
||||||
|
OptionsList->SetSelectedItem(ListItems[Index]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OptionsList->ClearSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionContextMenuWidget::HandleContextMenuOpened(const TArray<FInteractionOption>& Options, int32 InitialHighlightedIndex)
|
||||||
|
{
|
||||||
|
UE_LOG(LogKingshearthLegacy, Log, TEXT("[Interaction] Widget received OnContextMenuOpened: %d option(s), OptionsList bound: %s"),
|
||||||
|
Options.Num(), OptionsList ? TEXT("yes") : TEXT("NO - add a ListView named exactly \"OptionsList\" in this widget's Designer tree"));
|
||||||
|
|
||||||
|
CachedOptions = Options;
|
||||||
|
PopulateOptionsList(CachedOptions);
|
||||||
|
SelectHighlighted(InitialHighlightedIndex);
|
||||||
|
|
||||||
|
if (OptionsList)
|
||||||
|
{
|
||||||
|
OptionsList->SetVisibility(ESlateVisibility::Visible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionContextMenuWidget::HandleContextMenuHighlightChanged(int32 NewHighlightedIndex)
|
||||||
|
{
|
||||||
|
SelectHighlighted(NewHighlightedIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionContextMenuWidget::HandleContextMenuClosed()
|
||||||
|
{
|
||||||
|
CachedOptions.Reset();
|
||||||
|
ListItems.Reset();
|
||||||
|
|
||||||
|
if (OptionsList)
|
||||||
|
{
|
||||||
|
OptionsList->ClearListItems();
|
||||||
|
OptionsList->SetVisibility(ESlateVisibility::Collapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
// 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 UInteractionOptionListItem;
|
||||||
|
class UInteractionPromptWidget;
|
||||||
|
class UListView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Context Menu shown while holding Interact on a 3+-option IInteractable
|
||||||
|
* (OptionsList, driven by OnContextMenuOpened/HighlightChanged/Closed). The
|
||||||
|
* ever-present "you're looking at something" prompt lives in a separate
|
||||||
|
* nested UInteractionPromptWidget instance (e.g. WBP_InteractionQuick) -
|
||||||
|
* BindWidget can only see direct nodes of this widget's own tree, not reach
|
||||||
|
* inside another UserWidget's tree, so that prompt/slider logic can't live
|
||||||
|
* here; InitializeInteraction forwards to it instead, so the HUD only needs
|
||||||
|
* to call this one entry point.
|
||||||
|
*/
|
||||||
|
UCLASS(abstract)
|
||||||
|
class UInteractionContextMenuWidget : public UUserWidget
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Call once, right after creation (see UPlayerHUDWidget::InitializeHUD), to bind to InInteractionComponent's delegates, including forwarding to the nested WBP_InteractionQuick prompt widget. */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Interaction")
|
||||||
|
void InitializeInteraction(UInteractionComponent* InInteractionComponent);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
virtual void NativeConstruct() override;
|
||||||
|
|
||||||
|
/** Rebuilds OptionsList's items from Options; ListItems is kept alive here so the ListView's UObjects don't get GC'd. */
|
||||||
|
void PopulateOptionsList(const TArray<FInteractionOption>& Options);
|
||||||
|
|
||||||
|
/** Selects ListItems[Index] on OptionsList, which drives each row's IUserObjectListEntry::NativeOnItemSelectionChanged. */
|
||||||
|
void SelectHighlighted(int32 Index);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void HandleContextMenuOpened(const TArray<FInteractionOption>& Options, int32 InitialHighlightedIndex);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void HandleContextMenuHighlightChanged(int32 NewHighlightedIndex);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void HandleContextMenuClosed();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Context Menu's option list. Optional, place it in the WBP subclass's Designer
|
||||||
|
* tree as a ListView, single-selection, with its Entry Widget Class set to a WBP
|
||||||
|
* subclass of UInteractionOptionEntryWidget - that class receives each row's data
|
||||||
|
* and highlighted state natively, no BP event graph logic needed.
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UListView> OptionsList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ever-present prompt + hold-progress bar, nested as a WBP_InteractionQuick
|
||||||
|
* instance (a UInteractionPromptWidget subclass) named exactly "WBP_InteractionQuick"
|
||||||
|
* in the Designer tree. Optional - InitializeInteraction just skips forwarding to it
|
||||||
|
* when unbound.
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UInteractionPromptWidget> WBP_InteractionQuick;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<FInteractionOption> CachedOptions;
|
||||||
|
|
||||||
|
/** UObject wrappers backing OptionsList - one per CachedOptions entry, rebuilt in PopulateOptionsList. */
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<TObjectPtr<UInteractionOptionListItem>> ListItems;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UInteractionComponent> InteractionComponent;
|
||||||
|
};
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "InteractionOptionEntryWidget.h"
|
||||||
|
#include "InteractionOptionListItem.h"
|
||||||
|
#include "Components/Border.h"
|
||||||
|
#include "Components/Image.h"
|
||||||
|
#include "Components/TextBlock.h"
|
||||||
|
#include "Components/Widget.h"
|
||||||
|
|
||||||
|
void UInteractionOptionEntryWidget::NativeConstruct()
|
||||||
|
{
|
||||||
|
Super::NativeConstruct();
|
||||||
|
|
||||||
|
// Capture Background's Designer-authored color before ApplyOptionVisuals ever runs, so
|
||||||
|
// "no override" can fall back to it later instead of an arbitrary hardcoded default.
|
||||||
|
if (UImage* BackgroundImage = Cast<UImage>(Background.Get()))
|
||||||
|
{
|
||||||
|
DefaultBackgroundColor = BackgroundImage->GetColorAndOpacity();
|
||||||
|
}
|
||||||
|
else if (UBorder* BackgroundBorder = Cast<UBorder>(Background.Get()))
|
||||||
|
{
|
||||||
|
DefaultBackgroundColor = BackgroundBorder->GetBrushColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The ListView only calls NativeOnItemSelectionChanged when a row's selection state
|
||||||
|
// actually changes, so a freshly-spawned row that has never been (de)selected would
|
||||||
|
// otherwise keep whatever visibility Highlight has in the Designer - force it into
|
||||||
|
// the correct "not highlighted" state right away instead.
|
||||||
|
ApplyHighlightVisuals();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionOptionEntryWidget::NativeOnListItemObjectSet(UObject* ListItemObject)
|
||||||
|
{
|
||||||
|
if (const UInteractionOptionListItem* ListItem = Cast<UInteractionOptionListItem>(ListItemObject))
|
||||||
|
{
|
||||||
|
Option = ListItem->Option;
|
||||||
|
ApplyOptionVisuals();
|
||||||
|
|
||||||
|
// SListView recycles row widgets between items; a row reassigned from a previously-
|
||||||
|
// selected item to a new one doesn't necessarily get a fresh NativeOnItemSelectionChanged
|
||||||
|
// call in the same pass, so re-derive the real selection state here rather than trusting
|
||||||
|
// whatever bHighlighted this (possibly recycled) widget already had.
|
||||||
|
bHighlighted = IsListItemSelected();
|
||||||
|
ApplyHighlightVisuals();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionOptionEntryWidget::NativeOnItemSelectionChanged(bool bIsSelected)
|
||||||
|
{
|
||||||
|
bHighlighted = bIsSelected;
|
||||||
|
ApplyHighlightVisuals();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionOptionEntryWidget::ApplyOptionVisuals()
|
||||||
|
{
|
||||||
|
if (InteractionText)
|
||||||
|
{
|
||||||
|
InteractionText->SetText(Option.DisplayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Icon)
|
||||||
|
{
|
||||||
|
if (Option.Icon)
|
||||||
|
{
|
||||||
|
Icon->SetBrushFromTexture(Option.Icon);
|
||||||
|
Icon->SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Icon->SetVisibility(ESlateVisibility::Collapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Background)
|
||||||
|
{
|
||||||
|
const FLinearColor ResolvedBackgroundColor = Option.HighlightColorOverride.A > 0.0f ? Option.HighlightColorOverride : DefaultBackgroundColor;
|
||||||
|
|
||||||
|
if (UImage* BackgroundImage = Cast<UImage>(Background.Get()))
|
||||||
|
{
|
||||||
|
BackgroundImage->SetColorAndOpacity(ResolvedBackgroundColor);
|
||||||
|
}
|
||||||
|
else if (UBorder* BackgroundBorder = Cast<UBorder>(Background.Get()))
|
||||||
|
{
|
||||||
|
BackgroundBorder->SetBrushColor(ResolvedBackgroundColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionOptionEntryWidget::ApplyHighlightVisuals()
|
||||||
|
{
|
||||||
|
// Alpha 0 (the struct default) means the option didn't request an override - fall back
|
||||||
|
// to this widget's own Style colors.
|
||||||
|
const FLinearColor ResolvedHighlightColor = Option.HighlightColorOverride.A > 0.0f ? Option.HighlightColorOverride : HighlightColor;
|
||||||
|
const FLinearColor ResolvedTextColor = Option.TextColorOverride.A > 0.0f
|
||||||
|
? Option.TextColorOverride
|
||||||
|
: (bHighlighted ? HighlightedTextColor : NormalTextColor);
|
||||||
|
|
||||||
|
if (Highlight)
|
||||||
|
{
|
||||||
|
Highlight->SetVisibility(bHighlighted ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Hidden);
|
||||||
|
|
||||||
|
if (UImage* HighlightImage = Cast<UImage>(Highlight.Get()))
|
||||||
|
{
|
||||||
|
HighlightImage->SetColorAndOpacity(ResolvedHighlightColor);
|
||||||
|
}
|
||||||
|
else if (UBorder* HighlightBorder = Cast<UBorder>(Highlight.Get()))
|
||||||
|
{
|
||||||
|
HighlightBorder->SetBrushColor(ResolvedHighlightColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (InteractionText)
|
||||||
|
{
|
||||||
|
InteractionText->SetColorAndOpacity(ResolvedTextColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Blueprint/UserWidget.h"
|
||||||
|
#include "Blueprint/IUserObjectListEntry.h"
|
||||||
|
#include "Interaction/InteractionTypes.h"
|
||||||
|
#include "InteractionOptionEntryWidget.generated.h"
|
||||||
|
|
||||||
|
class UImage;
|
||||||
|
class UTextBlock;
|
||||||
|
class UWidget;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One row of the Context Menu's OptionsList (a UListView). Assign a WBP
|
||||||
|
* subclass of this as that ListView's Entry Widget Class in the Designer -
|
||||||
|
* name your widgets exactly "InteractionText" (Text), "Icon" (Image) and
|
||||||
|
* "Highlight" (a Border, or an Image - either works, tinted with
|
||||||
|
* HighlightColor) and all population/highlight styling is handled here in
|
||||||
|
* C++, driven by the ListView itself via IUserObjectListEntry; no BP event
|
||||||
|
* graph logic needed.
|
||||||
|
*/
|
||||||
|
UCLASS(abstract)
|
||||||
|
class KINGSHEARTHLEGACY_API UInteractionOptionEntryWidget : public UUserWidget, public IUserObjectListEntry
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Interaction")
|
||||||
|
const FInteractionOption& GetOption() const { return Option; }
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Interaction")
|
||||||
|
bool IsHighlighted() const { return bHighlighted; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
virtual void NativeConstruct() override;
|
||||||
|
|
||||||
|
/** IUserObjectListEntry - called by the owning ListView when this row is assigned an UInteractionOptionListItem. */
|
||||||
|
virtual void NativeOnListItemObjectSet(UObject* ListItemObject) override;
|
||||||
|
|
||||||
|
/** IUserObjectListEntry - called by the owning ListView when this row's selected state changes (drives the Context Menu highlight). */
|
||||||
|
virtual void NativeOnItemSelectionChanged(bool bIsSelected) override;
|
||||||
|
|
||||||
|
void ApplyOptionVisuals();
|
||||||
|
|
||||||
|
/** Applies bHighlighted to Highlight's visibility/tint and InteractionText's color. Called on construct (so freshly-spawned rows never default to "selected") and on every selection change. */
|
||||||
|
void ApplyHighlightVisuals();
|
||||||
|
|
||||||
|
/** Row label - bind a TextBlock named exactly "InteractionText" in the Designer. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UTextBlock> InteractionText;
|
||||||
|
|
||||||
|
/** Option icon - bind an Image named exactly "Icon" in the Designer. Hidden automatically when the option has no Icon set. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UImage> Icon;
|
||||||
|
|
||||||
|
/** Highlight background - bind a Border or an Image named exactly "Highlight" in the Designer. Shown and tinted HighlightColor only while this row is selected/highlighted. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UWidget> Highlight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional persistent row background - bind a Border or an Image named exactly
|
||||||
|
* "Background" in the Designer. Unlike Highlight, this isn't gated on selection - it's
|
||||||
|
* tinted with the option's HighlightColorOverride (from FInteractionOption) whenever one
|
||||||
|
* is set, so a "can't do this" or "special" row stays visually marked regardless of
|
||||||
|
* whether it's currently highlighted. Left at its Designer-authored appearance when the
|
||||||
|
* option has no override.
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UWidget> Background;
|
||||||
|
|
||||||
|
/** Tint applied to Highlight while this row is highlighted. */
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Interaction|Style")
|
||||||
|
FLinearColor HighlightColor = FLinearColor(1.0f, 0.85f, 0.3f, 1.0f);
|
||||||
|
|
||||||
|
/** InteractionText color while this row is NOT highlighted. */
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Interaction|Style")
|
||||||
|
FLinearColor NormalTextColor = FLinearColor::White;
|
||||||
|
|
||||||
|
/** InteractionText color while this row IS highlighted. */
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Interaction|Style")
|
||||||
|
FLinearColor HighlightedTextColor = FLinearColor::Black;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||||
|
FInteractionOption Option;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||||
|
bool bHighlighted = false;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Background's Designer-authored color, captured once in NativeConstruct before any
|
||||||
|
* option is ever assigned. Rows are recycled by the ListView, so a row previously tinted
|
||||||
|
* by one option's HighlightColorOverride needs an explicit color to fall back to when
|
||||||
|
* reassigned to a different, unconfigured option - rather than just leaving whatever
|
||||||
|
* tint the recycled widget last had.
|
||||||
|
*/
|
||||||
|
FLinearColor DefaultBackgroundColor = FLinearColor::White;
|
||||||
|
};
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "UObject/Object.h"
|
||||||
|
#include "Interaction/InteractionTypes.h"
|
||||||
|
#include "InteractionOptionListItem.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plain UObject wrapper around one FInteractionOption - UListView items must be
|
||||||
|
* UObjects, not structs, so UInteractionContextMenuWidget wraps each option in
|
||||||
|
* one of these before calling OptionsList->SetListItems.
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class KINGSHEARTHLEGACY_API UInteractionOptionListItem : public UObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||||
|
FInteractionOption Option;
|
||||||
|
};
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "InteractionPromptWidget.h"
|
||||||
|
#include "Interaction/InteractionComponent.h"
|
||||||
|
#include "Interaction/Interactable.h"
|
||||||
|
#include "Components/TextBlock.h"
|
||||||
|
#include "Components/Widget.h"
|
||||||
|
#include "SegmentedBarWidget.h"
|
||||||
|
#include "KingshearthLegacy.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
/** Toggles TextContainer when bound (so a background living alongside PromptText hides with it), else PromptText itself. */
|
||||||
|
void SetPromptVisible(UWidget* TextContainer, UTextBlock* PromptText, bool bVisible)
|
||||||
|
{
|
||||||
|
const ESlateVisibility Visibility = bVisible ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Collapsed;
|
||||||
|
|
||||||
|
if (TextContainer)
|
||||||
|
{
|
||||||
|
TextContainer->SetVisibility(Visibility);
|
||||||
|
}
|
||||||
|
else if (PromptText)
|
||||||
|
{
|
||||||
|
PromptText->SetVisibility(Visibility);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionPromptWidget::NativeConstruct()
|
||||||
|
{
|
||||||
|
Super::NativeConstruct();
|
||||||
|
SetPromptVisible(TextContainer, PromptText, false);
|
||||||
|
if (InfoText)
|
||||||
|
{
|
||||||
|
InfoText->SetVisibility(ESlateVisibility::Collapsed);
|
||||||
|
}
|
||||||
|
if (WBP_InteractionsSlider)
|
||||||
|
{
|
||||||
|
WBP_InteractionsSlider->SetVisibility(ESlateVisibility::Collapsed);
|
||||||
|
// USegmentedBarWidget defaults to full (it's shared with the health/stamina bars), but
|
||||||
|
// the hold bar should start empty and only fill as OnInteractHoldProgressChanged reports.
|
||||||
|
WBP_InteractionsSlider->SetSegments(0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionPromptWidget::InitializeInteraction(UInteractionComponent* InInteractionComponent)
|
||||||
|
{
|
||||||
|
UE_LOG(LogKingshearthLegacy, Log, TEXT("[Interaction] Prompt widget InitializeInteraction: component=%s, PromptText bound: %s, InfoText bound: %s, TextContainer bound: %s, Slider bound: %s"),
|
||||||
|
*GetNameSafe(InInteractionComponent),
|
||||||
|
PromptText ? TEXT("yes") : TEXT("NO"),
|
||||||
|
InfoText ? TEXT("yes") : TEXT("NO"),
|
||||||
|
TextContainer ? TEXT("yes") : TEXT("NO"),
|
||||||
|
WBP_InteractionsSlider ? TEXT("yes") : TEXT("NO"));
|
||||||
|
|
||||||
|
if (InteractionComponent)
|
||||||
|
{
|
||||||
|
InteractionComponent->OnFocusedInteractableChanged.RemoveDynamic(this, &UInteractionPromptWidget::HandleFocusedInteractableChanged);
|
||||||
|
InteractionComponent->OnContextMenuOpened.RemoveDynamic(this, &UInteractionPromptWidget::HandleContextMenuOpened);
|
||||||
|
InteractionComponent->OnContextMenuClosed.RemoveDynamic(this, &UInteractionPromptWidget::HandleContextMenuClosed);
|
||||||
|
InteractionComponent->OnInteractHoldProgressChanged.RemoveDynamic(this, &UInteractionPromptWidget::HandleHoldProgressChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
InteractionComponent = InInteractionComponent;
|
||||||
|
|
||||||
|
if (InteractionComponent)
|
||||||
|
{
|
||||||
|
InteractionComponent->OnFocusedInteractableChanged.AddDynamic(this, &UInteractionPromptWidget::HandleFocusedInteractableChanged);
|
||||||
|
InteractionComponent->OnContextMenuOpened.AddDynamic(this, &UInteractionPromptWidget::HandleContextMenuOpened);
|
||||||
|
InteractionComponent->OnContextMenuClosed.AddDynamic(this, &UInteractionPromptWidget::HandleContextMenuClosed);
|
||||||
|
InteractionComponent->OnInteractHoldProgressChanged.AddDynamic(this, &UInteractionPromptWidget::HandleHoldProgressChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionPromptWidget::HandleHoldProgressChanged(float Progress)
|
||||||
|
{
|
||||||
|
if (WBP_InteractionsSlider)
|
||||||
|
{
|
||||||
|
WBP_InteractionsSlider->SetSegments(Progress, 0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionPromptWidget::HandleFocusedInteractableChanged(AActor* FocusedActor, const TArray<FInteractionOption>& Options)
|
||||||
|
{
|
||||||
|
UE_LOG(LogKingshearthLegacy, Log, TEXT("[Interaction] Prompt widget HandleFocusedInteractableChanged: actor='%s' options=%d"),
|
||||||
|
*GetNameSafe(FocusedActor), Options.Num());
|
||||||
|
|
||||||
|
LastFocusedActor = FocusedActor;
|
||||||
|
LastFocusedOptions = Options;
|
||||||
|
UpdatePromptVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionPromptWidget::HandleContextMenuOpened(const TArray<FInteractionOption>& Options, int32 InitialHighlightedIndex)
|
||||||
|
{
|
||||||
|
UpdatePromptVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionPromptWidget::HandleContextMenuClosed()
|
||||||
|
{
|
||||||
|
UpdatePromptVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInteractionPromptWidget::UpdatePromptVisibility()
|
||||||
|
{
|
||||||
|
if (!PromptText)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto HideAll = [this]()
|
||||||
|
{
|
||||||
|
SetPromptVisible(TextContainer, PromptText, false);
|
||||||
|
if (InfoText)
|
||||||
|
{
|
||||||
|
InfoText->SetVisibility(ESlateVisibility::Collapsed);
|
||||||
|
}
|
||||||
|
if (WBP_InteractionsSlider)
|
||||||
|
{
|
||||||
|
WBP_InteractionsSlider->SetVisibility(ESlateVisibility::Collapsed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// The Context Menu owns the screen while it's open - keep the ever-present prompt
|
||||||
|
// out of its way until OnContextMenuClosed brings us back here.
|
||||||
|
if (InteractionComponent && InteractionComponent->IsContextMenuOpen())
|
||||||
|
{
|
||||||
|
HideAll();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AActor* FocusedActor = LastFocusedActor.Get();
|
||||||
|
if (!FocusedActor || LastFocusedOptions.Num() == 0)
|
||||||
|
{
|
||||||
|
HideAll();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Primary line: "{Verb} with {ObjectName}" (or PromptOverride verbatim, if the primary
|
||||||
|
// option set one). Hint line: "Press E" always, plus a hint for the hold behaviour only
|
||||||
|
// when there is one (2 options = secondary, 3+ = Context Menu).
|
||||||
|
const FInteractionOption& PrimaryOption = LastFocusedOptions[0];
|
||||||
|
FString Prompt;
|
||||||
|
if (!PrimaryOption.PromptOverride.IsEmpty())
|
||||||
|
{
|
||||||
|
Prompt = PrimaryOption.PromptOverride.ToString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const FText ObjectName = IInteractable::Execute_GetInteractableDisplayName(FocusedActor);
|
||||||
|
Prompt = ObjectName.IsEmpty()
|
||||||
|
? PrimaryOption.DisplayName.ToString()
|
||||||
|
: FString::Printf(TEXT("%s with %s"), *PrimaryOption.DisplayName.ToString(), *ObjectName.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
FString Hint = TEXT("Press E");
|
||||||
|
if (LastFocusedOptions.Num() == 2)
|
||||||
|
{
|
||||||
|
Hint += FString::Printf(TEXT("\nHold E for %s"), *LastFocusedOptions[1].DisplayName.ToString());
|
||||||
|
}
|
||||||
|
else if (LastFocusedOptions.Num() >= 3)
|
||||||
|
{
|
||||||
|
Hint += TEXT("\nHold E for Context Menu");
|
||||||
|
}
|
||||||
|
|
||||||
|
PromptText->SetText(FText::FromString(Prompt));
|
||||||
|
SetPromptVisible(TextContainer, PromptText, true);
|
||||||
|
|
||||||
|
if (InfoText)
|
||||||
|
{
|
||||||
|
InfoText->SetText(FText::FromString(Hint));
|
||||||
|
InfoText->SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WBP_InteractionsSlider)
|
||||||
|
{
|
||||||
|
WBP_InteractionsSlider->SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||||
|
WBP_InteractionsSlider->SetSegments(0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Blueprint/UserWidget.h"
|
||||||
|
#include "Interaction/InteractionTypes.h"
|
||||||
|
#include "InteractionPromptWidget.generated.h"
|
||||||
|
|
||||||
|
class UInteractionComponent;
|
||||||
|
class USegmentedBarWidget;
|
||||||
|
class UTextBlock;
|
||||||
|
class UWidget;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The 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) plus an optional hold-to-interact fill bar. Kept as its own
|
||||||
|
* UserWidget (e.g. WBP_InteractionQuick) rather than folded into
|
||||||
|
* UInteractionContextMenuWidget so it can be laid out and nested independently
|
||||||
|
* of the Context Menu's option list - see UInteractionContextMenuWidget's
|
||||||
|
* WBP_InteractionQuick binding, which forwards InitializeInteraction here.
|
||||||
|
*/
|
||||||
|
UCLASS(abstract)
|
||||||
|
class UInteractionPromptWidget : public UUserWidget
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Call once, right after creation, to bind to InInteractionComponent's delegates. Safe to call again to rebind to a different component. */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Interaction")
|
||||||
|
void InitializeInteraction(UInteractionComponent* InInteractionComponent);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
virtual void NativeConstruct() override;
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void HandleFocusedInteractableChanged(AActor* FocusedActor, const TArray<FInteractionOption>& Options);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void HandleContextMenuOpened(const TArray<FInteractionOption>& Options, int32 InitialHighlightedIndex);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void HandleContextMenuClosed();
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void HandleHoldProgressChanged(float Progress);
|
||||||
|
|
||||||
|
/** Ever-present prompt's primary line - "{Verb} with {ObjectName}" (or PromptOverride verbatim). Named exactly "PromptText" in the Designer. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UTextBlock> PromptText;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional wrapper around PromptText (e.g. a panel/border providing its background, or the
|
||||||
|
* Overlay it shares with WBP_InteractionsSlider) named exactly "TextContainer" in the
|
||||||
|
* Designer. When bound, its visibility is toggled instead of PromptText's own - so a
|
||||||
|
* background/slider living alongside PromptText, not inside it, actually hides too. Falls
|
||||||
|
* back to toggling PromptText directly when this isn't bound.
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UWidget> TextContainer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional second line - "Press E" plus a hint for the hold behaviour (2 options =
|
||||||
|
* secondary, 3+ = Context Menu). Named exactly "InfoText" in the Designer. Kept separate
|
||||||
|
* from PromptText so it can sit outside TextContainer (e.g. below the slider bar rather
|
||||||
|
* than layered on top of it). Toggles visibility on its own, in lockstep with PromptText.
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UTextBlock> InfoText;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional hold-to-interact fill bar - drop a WBP_SegmentedBar instance into the
|
||||||
|
* Designer tree named exactly "WBP_InteractionsSlider" and it fills 0 to 1 while
|
||||||
|
* holding E on a 2+-option target, driven by InteractionComponent's
|
||||||
|
* OnInteractHoldProgressChanged. Left unbound, holding just does nothing visually.
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<USegmentedBarWidget> WBP_InteractionsSlider;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UInteractionComponent> InteractionComponent;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/** Rebuilds PromptText from LastFocused*, collapsing it while the Context Menu is open. */
|
||||||
|
void UpdatePromptVisibility();
|
||||||
|
|
||||||
|
TWeakObjectPtr<AActor> LastFocusedActor;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<FInteractionOption> LastFocusedOptions;
|
||||||
|
};
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "EquipmentInventoryTabWidget.h"
|
||||||
|
#include "UI/Inventory/EquipmentWidget.h"
|
||||||
|
#include "UI/Inventory/QuickSlotListWidget.h"
|
||||||
|
#include "UI/Inventory/InventoryItemListWidget.h"
|
||||||
|
#include "UI/Inventory/InventoryGridWidget.h"
|
||||||
|
#include "KingshearthLegacy.h"
|
||||||
|
#include "InputCoreTypes.h"
|
||||||
|
|
||||||
|
UEquipmentInventoryTabWidget::UEquipmentInventoryTabWidget(const FObjectInitializer& ObjectInitializer)
|
||||||
|
: Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
// Needed so the menu manager's SetWidgetToFocus (see UMenuManagerSubsystem::RefreshInputMode)
|
||||||
|
// can actually give this screen keyboard focus - otherwise NativeOnPreviewKeyDown below never fires.
|
||||||
|
// Only takes effect set here, at construction - UUserWidget::SetIsFocusable asserts if called later.
|
||||||
|
SetIsFocusable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
FReply UEquipmentInventoryTabWidget::NativeOnPreviewKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent)
|
||||||
|
{
|
||||||
|
if (InKeyEvent.GetKey() == EKeys::Tab)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Super::NativeOnPreviewKeyDown(InGeometry, InKeyEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UEquipmentInventoryTabWidget::InitializeInventory(const TArray<FEquipmentSlotData>& InEquipmentSlots, const TArray<FItemData>& InBigQuickSlots, const TArray<FItemData>& InSmallQuickSlots, const TArray<FItemData>& InInventoryItems)
|
||||||
|
{
|
||||||
|
UE_LOG(LogKingshearthLegacy, Log, TEXT("[Inventory] EquipmentInventoryTab InitializeInventory: WBP_Equipment bound: %s, WBP_QuickSlotList bound: %s, WBP_ItemList bound: %s, WBP_InventoryGrid bound: %s"),
|
||||||
|
WBP_Equipment ? TEXT("yes") : TEXT("NO - check its Name in the Designer tree matches the native property, and that it's reparented to UEquipmentWidget"),
|
||||||
|
WBP_QuickSlotList ? TEXT("yes") : TEXT("NO - check its Name in the Designer tree matches the native property, and that it's reparented to UQuickSlotListWidget"),
|
||||||
|
WBP_ItemList ? TEXT("yes") : TEXT("NO - check its Name in the Designer tree matches the native property, and that it's reparented to UInventoryItemListWidget"),
|
||||||
|
WBP_InventoryGrid ? TEXT("yes") : TEXT("NO - check its Name in the Designer tree matches the native property, and that it's reparented to UInventoryGridWidget"));
|
||||||
|
|
||||||
|
if (WBP_Equipment)
|
||||||
|
{
|
||||||
|
WBP_Equipment->SetEquipmentSlots(InEquipmentSlots);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WBP_QuickSlotList)
|
||||||
|
{
|
||||||
|
WBP_QuickSlotList->SetQuickSlots(InBigQuickSlots, InSmallQuickSlots);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WBP_ItemList)
|
||||||
|
{
|
||||||
|
WBP_ItemList->SetItems(InInventoryItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WBP_InventoryGrid)
|
||||||
|
{
|
||||||
|
WBP_InventoryGrid->SetItems(InInventoryItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShowInventoryGridView();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UEquipmentInventoryTabWidget::ShowItemListView()
|
||||||
|
{
|
||||||
|
if (WBP_ItemList)
|
||||||
|
{
|
||||||
|
WBP_ItemList->SetVisibility(ESlateVisibility::Visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WBP_InventoryGrid)
|
||||||
|
{
|
||||||
|
WBP_InventoryGrid->SetVisibility(ESlateVisibility::Collapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UEquipmentInventoryTabWidget::ShowInventoryGridView()
|
||||||
|
{
|
||||||
|
if (WBP_ItemList)
|
||||||
|
{
|
||||||
|
WBP_ItemList->SetVisibility(ESlateVisibility::Collapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WBP_InventoryGrid)
|
||||||
|
{
|
||||||
|
WBP_InventoryGrid->SetVisibility(ESlateVisibility::Visible);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "UI/Menu/MenuScreenWidget.h"
|
||||||
|
#include "Inventory/InventoryTypes.h"
|
||||||
|
#include "EquipmentInventoryTabWidget.generated.h"
|
||||||
|
|
||||||
|
class UEquipmentWidget;
|
||||||
|
class UQuickSlotListWidget;
|
||||||
|
class UInventoryItemListWidget;
|
||||||
|
class UInventoryGridWidget;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main inventory screen - a shallow wrapper coordinating its child widgets. Pushed/popped
|
||||||
|
* through the menu manager like any other UMenuScreenWidget (see
|
||||||
|
* AKingshearthLegacyPlayerController::ToggleInventory), so opening/closing it gets the usual UI
|
||||||
|
* input mode + cursor handling for free, and an optional "BackButton" (inherited from
|
||||||
|
* UMenuScreenWidget) closes it the same way any other menu screen closes:
|
||||||
|
*
|
||||||
|
* WBP_EquipmentInventoryTAB
|
||||||
|
* |-- WBP_Equipment
|
||||||
|
* | `-- WBP_EquipmentSlot
|
||||||
|
* |-- WBP_QuickSlotList
|
||||||
|
* `-- Inventory View
|
||||||
|
* |-- WBP_ItemList
|
||||||
|
* `-- WBP_InventoryGrid
|
||||||
|
*
|
||||||
|
* Owns no inventory logic itself - it only forwards data supplied by the inventory/equipment
|
||||||
|
* system to its children, and switches which of WBP_ItemList / WBP_InventoryGrid is visible for
|
||||||
|
* the "Inventory View" section (both are kept populated with the same items; only one is shown
|
||||||
|
* at a time).
|
||||||
|
*
|
||||||
|
* Nest instances named exactly "WBP_Equipment", "WBP_QuickSlotList", "WBP_ItemList" and
|
||||||
|
* "WBP_InventoryGrid" in the Designer tree, reparented to their respective C++ base classes.
|
||||||
|
*/
|
||||||
|
UCLASS(abstract)
|
||||||
|
class UEquipmentInventoryTabWidget : public UMenuScreenWidget
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
UEquipmentInventoryTabWidget(const FObjectInitializer& ObjectInitializer);
|
||||||
|
|
||||||
|
/** Populates every child widget. Call once the inventory/equipment system has data ready (e.g. right after this screen is pushed). */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Inventory")
|
||||||
|
void InitializeInventory(const TArray<FEquipmentSlotData>& InEquipmentSlots, const TArray<FItemData>& InBigQuickSlots, const TArray<FItemData>& InSmallQuickSlots, const TArray<FItemData>& InInventoryItems);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Inventory")
|
||||||
|
void ShowItemListView();
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Inventory")
|
||||||
|
void ShowInventoryGridView();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* While this screen is open, the menu manager puts input in UI-only mode - the PlayerController
|
||||||
|
* stops receiving Tab presses entirely, so re-pressing Tab can only be caught here, at the
|
||||||
|
* focused widget itself. Requires bIsFocusable (set true in the constructor) so the menu
|
||||||
|
* manager's SetWidgetToFocus call actually succeeds.
|
||||||
|
*
|
||||||
|
* Uses the Preview (tunneling) pass rather than NativeOnKeyDown (bubbling) - Tab is Slate's
|
||||||
|
* default "focus next widget" navigation key, and that default navigation runs as a fallback
|
||||||
|
* once the bubbling pass goes unhandled, which pre-empts NativeOnKeyDown ever getting a
|
||||||
|
* chance to close the screen. Intercepting in Preview, which runs before that fallback, avoids it.
|
||||||
|
*/
|
||||||
|
virtual FReply NativeOnPreviewKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent) override;
|
||||||
|
|
||||||
|
/** Nested WBP_Equipment instance - named exactly "WBP_Equipment" in the Designer tree. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UEquipmentWidget> WBP_Equipment;
|
||||||
|
|
||||||
|
/** Nested WBP_QuickSlotList instance - named exactly "WBP_QuickSlotList" in the Designer tree. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UQuickSlotListWidget> WBP_QuickSlotList;
|
||||||
|
|
||||||
|
/** Nested WBP_ItemList instance - named exactly "WBP_ItemList" in the Designer tree, part of the Inventory View toggle. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UInventoryItemListWidget> WBP_ItemList;
|
||||||
|
|
||||||
|
/** Nested WBP_InventoryGrid instance - named exactly "WBP_InventoryGrid" in the Designer tree, part of the Inventory View toggle. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UInventoryGridWidget> WBP_InventoryGrid;
|
||||||
|
};
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "UObject/Object.h"
|
||||||
|
#include "Inventory/InventoryTypes.h"
|
||||||
|
#include "EquipmentSlotListEntry.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plain UObject wrapper around one FEquipmentSlotData - UListView items must be UObjects, not
|
||||||
|
* structs. Used by WBP_Equipment before calling SetListItems, so each row's UEquipmentSlotWidget
|
||||||
|
* receives one of these via IUserObjectListEntry.
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class UEquipmentSlotListEntry : public UObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Equipment")
|
||||||
|
FEquipmentSlotData Slot;
|
||||||
|
};
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "EquipmentSlotWidget.h"
|
||||||
|
#include "UI/Inventory/EquipmentSlotListEntry.h"
|
||||||
|
#include "Components/Image.h"
|
||||||
|
#include "Components/TextBlock.h"
|
||||||
|
|
||||||
|
void UEquipmentSlotWidget::SetSlotData(const FEquipmentSlotData& InSlotData)
|
||||||
|
{
|
||||||
|
SlotData = InSlotData;
|
||||||
|
ApplySlotVisuals();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UEquipmentSlotWidget::NativeOnListItemObjectSet(UObject* ListItemObject)
|
||||||
|
{
|
||||||
|
if (const UEquipmentSlotListEntry* ListItem = Cast<UEquipmentSlotListEntry>(ListItemObject))
|
||||||
|
{
|
||||||
|
SetSlotData(ListItem->Slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UEquipmentSlotWidget::ApplySlotVisuals()
|
||||||
|
{
|
||||||
|
const bool bHasItem = !SlotData.EquippedItem.IsEmpty();
|
||||||
|
|
||||||
|
if (Icon)
|
||||||
|
{
|
||||||
|
if (bHasItem && SlotData.EquippedItem.Icon)
|
||||||
|
{
|
||||||
|
Icon->SetBrushFromTexture(SlotData.EquippedItem.Icon);
|
||||||
|
Icon->SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Icon->SetVisibility(ESlateVisibility::Collapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QuantityText)
|
||||||
|
{
|
||||||
|
if (bHasItem && SlotData.EquippedItem.Quantity > 1)
|
||||||
|
{
|
||||||
|
QuantityText->SetText(FText::AsNumber(SlotData.EquippedItem.Quantity));
|
||||||
|
QuantityText->SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QuantityText->SetVisibility(ESlateVisibility::Collapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SlotLabel)
|
||||||
|
{
|
||||||
|
SlotLabel->SetText(!SlotData.SlotDisplayNameOverride.IsEmpty()
|
||||||
|
? SlotData.SlotDisplayNameOverride
|
||||||
|
: UEnum::GetDisplayValueAsText(SlotData.SlotType));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Blueprint/UserWidget.h"
|
||||||
|
#include "Blueprint/IUserObjectListEntry.h"
|
||||||
|
#include "Inventory/InventoryTypes.h"
|
||||||
|
#include "EquipmentSlotWidget.generated.h"
|
||||||
|
|
||||||
|
class UImage;
|
||||||
|
class UTextBlock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single equipment slot's visuals. This is a display slot only - it shows whatever
|
||||||
|
* FEquipmentSlotData it is given and does not validate that the item is appropriate for the
|
||||||
|
* slot, and does not represent an attachment point/socket on the character mesh.
|
||||||
|
*
|
||||||
|
* Name your widgets exactly "Icon" (Image), "QuantityText" (TextBlock) and "SlotLabel"
|
||||||
|
* (TextBlock) in the Designer and population is handled here in C++; no BP event graph logic
|
||||||
|
* needed.
|
||||||
|
*/
|
||||||
|
UCLASS(abstract)
|
||||||
|
class UEquipmentSlotWidget : public UUserWidget, public IUserObjectListEntry
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Equipment")
|
||||||
|
void SetSlotData(const FEquipmentSlotData& InSlotData);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Equipment")
|
||||||
|
const FEquipmentSlotData& GetSlotData() const { return SlotData; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
/** IUserObjectListEntry - called by the owning ListView (WBP_Equipment) when this row is assigned an UEquipmentSlotListEntry. */
|
||||||
|
virtual void NativeOnListItemObjectSet(UObject* ListItemObject) override;
|
||||||
|
|
||||||
|
void ApplySlotVisuals();
|
||||||
|
|
||||||
|
/** Equipped item's icon - bind an Image named exactly "Icon" in the Designer. Hidden automatically when the slot is empty. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UImage> Icon;
|
||||||
|
|
||||||
|
/** Equipped item's stack count - bind a TextBlock named exactly "QuantityText" in the Designer. Hidden automatically when Quantity <= 1. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UTextBlock> QuantityText;
|
||||||
|
|
||||||
|
/** Slot type label (e.g. "Head", "Back 1") - bind a TextBlock named exactly "SlotLabel" in the Designer. Prefers SlotData.SlotDisplayNameOverride when set, falling back to the SlotType enum name. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UTextBlock> SlotLabel;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Equipment")
|
||||||
|
FEquipmentSlotData SlotData;
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "EquipmentWidget.h"
|
||||||
|
#include "UI/Inventory/EquipmentSlotListEntry.h"
|
||||||
|
#include "Components/ListView.h"
|
||||||
|
|
||||||
|
void UEquipmentWidget::SetEquipmentSlots(const TArray<FEquipmentSlotData>& InSlots)
|
||||||
|
{
|
||||||
|
if (!SlotsList)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SlotsList->ClearListItems();
|
||||||
|
ListItems.Reset(InSlots.Num());
|
||||||
|
|
||||||
|
TArray<UObject*> SlotObjects;
|
||||||
|
SlotObjects.Reserve(InSlots.Num());
|
||||||
|
|
||||||
|
for (const FEquipmentSlotData& SlotData : InSlots)
|
||||||
|
{
|
||||||
|
UEquipmentSlotListEntry* Entry = NewObject<UEquipmentSlotListEntry>(this);
|
||||||
|
Entry->Slot = SlotData;
|
||||||
|
ListItems.Add(Entry);
|
||||||
|
SlotObjects.Add(Entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
SlotsList->SetListItems(SlotObjects);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Blueprint/UserWidget.h"
|
||||||
|
#include "Inventory/InventoryTypes.h"
|
||||||
|
#include "EquipmentWidget.generated.h"
|
||||||
|
|
||||||
|
class UEquipmentSlotListEntry;
|
||||||
|
class UListView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The character's full set of equipment slots.
|
||||||
|
*
|
||||||
|
* Name a ListView exactly "SlotsList" in the Designer, with its Entry Widget Class set to a WBP
|
||||||
|
* subclass of UEquipmentSlotWidget, and population is handled here in C++.
|
||||||
|
*/
|
||||||
|
UCLASS(abstract)
|
||||||
|
class UEquipmentWidget : public UUserWidget
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Equipment")
|
||||||
|
void SetEquipmentSlots(const TArray<FEquipmentSlotData>& InSlots);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
/** Named exactly "SlotsList" in the Designer, Entry Widget Class set to a WBP subclass of UEquipmentSlotWidget. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UListView> SlotsList;
|
||||||
|
|
||||||
|
/** UObject wrappers backing SlotsList - kept alive here so the ListView's UObjects don't get GC'd. */
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<TObjectPtr<UEquipmentSlotListEntry>> ListItems;
|
||||||
|
};
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "InventoryContainerListWidget.h"
|
||||||
|
#include "UI/Inventory/InventoryGridWidget.h"
|
||||||
|
#include "Components/TextBlock.h"
|
||||||
|
|
||||||
|
void UInventoryContainerListWidget::NativeConstruct()
|
||||||
|
{
|
||||||
|
Super::NativeConstruct();
|
||||||
|
|
||||||
|
SetExpanded(bExpanded);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInventoryContainerListWidget::SetContainerData(const FInventoryContainerInfo& InContainerData)
|
||||||
|
{
|
||||||
|
ContainerData = InContainerData;
|
||||||
|
|
||||||
|
if (ContainerLabel)
|
||||||
|
{
|
||||||
|
ContainerLabel->SetText(ContainerData.DisplayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WBP_InventoryGrid)
|
||||||
|
{
|
||||||
|
WBP_InventoryGrid->SetItems(ContainerData.Items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInventoryContainerListWidget::SetExpanded(bool bInExpanded)
|
||||||
|
{
|
||||||
|
bExpanded = bInExpanded;
|
||||||
|
|
||||||
|
if (WBP_InventoryGrid)
|
||||||
|
{
|
||||||
|
WBP_InventoryGrid->SetVisibility(bExpanded ? ESlateVisibility::Visible : ESlateVisibility::Collapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInventoryContainerListWidget::ToggleExpanded()
|
||||||
|
{
|
||||||
|
SetExpanded(!bExpanded);
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Blueprint/UserWidget.h"
|
||||||
|
#include "Inventory/InventoryTypes.h"
|
||||||
|
#include "InventoryContainerListWidget.generated.h"
|
||||||
|
|
||||||
|
class UInventoryGridWidget;
|
||||||
|
class UTextBlock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A labeled, collapsible view of one inventory container (the player's own bag, a chest being
|
||||||
|
* looted, ...):
|
||||||
|
*
|
||||||
|
* WBP_InventoryContainerList
|
||||||
|
* |-- Container Label
|
||||||
|
* `-- Collapsible Inventory Grid
|
||||||
|
* `-- WBP_InventoryGrid
|
||||||
|
* `-- WBP_Item
|
||||||
|
*
|
||||||
|
* Name a TextBlock exactly "ContainerLabel" in the Designer, and nest a WBP subclass of
|
||||||
|
* UInventoryGridWidget named exactly "WBP_InventoryGrid". SetContainerData populates the label
|
||||||
|
* and forwards the container's items to the nested grid; SetExpanded/ToggleExpanded show or
|
||||||
|
* collapse it. Wire an expand/collapse button to ToggleExpanded in the Designer/event graph.
|
||||||
|
*/
|
||||||
|
UCLASS(abstract)
|
||||||
|
class UInventoryContainerListWidget : public UUserWidget
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Inventory")
|
||||||
|
void SetContainerData(const FInventoryContainerInfo& InContainerData);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Inventory")
|
||||||
|
const FInventoryContainerInfo& GetContainerData() const { return ContainerData; }
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Inventory")
|
||||||
|
void SetExpanded(bool bInExpanded);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Inventory")
|
||||||
|
void ToggleExpanded();
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Inventory")
|
||||||
|
bool IsExpanded() const { return bExpanded; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
virtual void NativeConstruct() override;
|
||||||
|
|
||||||
|
/** Named exactly "ContainerLabel" in the Designer. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UTextBlock> ContainerLabel;
|
||||||
|
|
||||||
|
/** Nested WBP_InventoryGrid instance - named exactly "WBP_InventoryGrid" in the Designer tree, reparented to a UInventoryGridWidget subclass. Collapsed/shown by SetExpanded. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UInventoryGridWidget> WBP_InventoryGrid;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Inventory")
|
||||||
|
FInventoryContainerInfo ContainerData;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Inventory")
|
||||||
|
bool bExpanded = true;
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "InventoryGridWidget.h"
|
||||||
|
#include "UI/Inventory/InventoryItemListEntry.h"
|
||||||
|
#include "Components/TileView.h"
|
||||||
|
|
||||||
|
void UInventoryGridWidget::SetItems(const TArray<FItemData>& InItems)
|
||||||
|
{
|
||||||
|
Items = InItems;
|
||||||
|
PopulateGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInventoryGridWidget::PopulateGrid()
|
||||||
|
{
|
||||||
|
if (!ItemsGrid)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemsGrid->ClearListItems();
|
||||||
|
ListItems.Reset(Items.Num());
|
||||||
|
|
||||||
|
TArray<UObject*> ItemObjects;
|
||||||
|
ItemObjects.Reserve(Items.Num());
|
||||||
|
|
||||||
|
for (const FItemData& Item : Items)
|
||||||
|
{
|
||||||
|
UInventoryItemListEntry* Entry = NewObject<UInventoryItemListEntry>(this);
|
||||||
|
Entry->Item = Item;
|
||||||
|
ListItems.Add(Entry);
|
||||||
|
ItemObjects.Add(Entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemsGrid->SetListItems(ItemObjects);
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Blueprint/UserWidget.h"
|
||||||
|
#include "Inventory/InventoryTypes.h"
|
||||||
|
#include "InventoryGridWidget.generated.h"
|
||||||
|
|
||||||
|
class UInventoryItemListEntry;
|
||||||
|
class UTileView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A UI grid of items. This is purely a tiled layout - every item occupies the same fixed-size
|
||||||
|
* 2x2 tile no matter its Mass/Volume, and items have no spatial position of their own (no
|
||||||
|
* physical/Tetris-style grid placement). Carries no container/label concept of its own - that's
|
||||||
|
* WBP_InventoryContainerList's job when a labeled, collapsible section is needed.
|
||||||
|
*
|
||||||
|
* Name a TileView exactly "ItemsGrid" in the Designer, with its Entry Widget Class set to a WBP
|
||||||
|
* subclass of UInventoryItemWidget, and population is handled here in C++.
|
||||||
|
*/
|
||||||
|
UCLASS(abstract)
|
||||||
|
class UInventoryGridWidget : public UUserWidget
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Inventory")
|
||||||
|
void SetItems(const TArray<FItemData>& InItems);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Inventory")
|
||||||
|
const TArray<FItemData>& GetItems() const { return Items; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
void PopulateGrid();
|
||||||
|
|
||||||
|
/** Named exactly "ItemsGrid" in the Designer, Entry Widget Class set to a WBP subclass of UInventoryItemWidget. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UTileView> ItemsGrid;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Inventory")
|
||||||
|
TArray<FItemData> Items;
|
||||||
|
|
||||||
|
/** UObject wrappers backing ItemsGrid - kept alive here so the TileView's UObjects don't get GC'd. */
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<TObjectPtr<UInventoryItemListEntry>> ListItems;
|
||||||
|
};
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "UObject/Object.h"
|
||||||
|
#include "Inventory/InventoryTypes.h"
|
||||||
|
#include "InventoryItemListEntry.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plain UObject wrapper around one FItemData - UListView/UTileView items must be UObjects, not
|
||||||
|
* structs. Used by WBP_ItemList, WBP_InventoryGrid and WBP_QuickSlotList before calling
|
||||||
|
* SetListItems, so each row's UInventoryItemWidget receives one of these via
|
||||||
|
* IUserObjectListEntry.
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class UInventoryItemListEntry : public UObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Item")
|
||||||
|
FItemData Item;
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "InventoryItemListWidget.h"
|
||||||
|
#include "UI/Inventory/InventoryItemListEntry.h"
|
||||||
|
#include "Components/ListView.h"
|
||||||
|
|
||||||
|
void UInventoryItemListWidget::SetItems(const TArray<FItemData>& InItems)
|
||||||
|
{
|
||||||
|
if (!ItemsList)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemsList->ClearListItems();
|
||||||
|
ListItems.Reset(InItems.Num());
|
||||||
|
|
||||||
|
TArray<UObject*> ItemObjects;
|
||||||
|
ItemObjects.Reserve(InItems.Num());
|
||||||
|
|
||||||
|
for (const FItemData& Item : InItems)
|
||||||
|
{
|
||||||
|
UInventoryItemListEntry* Entry = NewObject<UInventoryItemListEntry>(this);
|
||||||
|
Entry->Item = Item;
|
||||||
|
ListItems.Add(Entry);
|
||||||
|
ItemObjects.Add(Entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemsList->SetListItems(ItemObjects);
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Blueprint/UserWidget.h"
|
||||||
|
#include "Inventory/InventoryTypes.h"
|
||||||
|
#include "InventoryItemListWidget.generated.h"
|
||||||
|
|
||||||
|
class UInventoryItemListEntry;
|
||||||
|
class UListView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple vertical list of items - not a physical/spatial grid, just an ordered list. Used as
|
||||||
|
* the list-view alternative to UInventoryGridWidget inside WBP_EquipmentInventoryTAB's Inventory
|
||||||
|
* View (see UEquipmentInventoryTabWidget::ShowItemListView), and reusable standalone anywhere
|
||||||
|
* else that only needs a plain item list (a crafting recipe's ingredients, a shop's stock, ...).
|
||||||
|
*
|
||||||
|
* Name a ListView exactly "ItemsList" in the Designer, with its Entry Widget Class set to a WBP
|
||||||
|
* subclass of UInventoryItemWidget, and population is handled here in C++.
|
||||||
|
*/
|
||||||
|
UCLASS(abstract)
|
||||||
|
class UInventoryItemListWidget : public UUserWidget
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Item List")
|
||||||
|
void SetItems(const TArray<FItemData>& InItems);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
/** Named exactly "ItemsList" in the Designer, Entry Widget Class set to a WBP subclass of UInventoryItemWidget. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UListView> ItemsList;
|
||||||
|
|
||||||
|
/** UObject wrappers backing ItemsList - kept alive here so the ListView's UObjects don't get GC'd. */
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<TObjectPtr<UInventoryItemListEntry>> ListItems;
|
||||||
|
};
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "InventoryItemWidget.h"
|
||||||
|
#include "UI/Inventory/InventoryItemListEntry.h"
|
||||||
|
#include "Components/Image.h"
|
||||||
|
#include "Components/TextBlock.h"
|
||||||
|
|
||||||
|
void UInventoryItemWidget::SetItemData(const FItemData& InItemData)
|
||||||
|
{
|
||||||
|
ItemData = InItemData;
|
||||||
|
ApplyItemVisuals();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInventoryItemWidget::NativeOnListItemObjectSet(UObject* ListItemObject)
|
||||||
|
{
|
||||||
|
if (const UInventoryItemListEntry* ListItem = Cast<UInventoryItemListEntry>(ListItemObject))
|
||||||
|
{
|
||||||
|
SetItemData(ListItem->Item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UInventoryItemWidget::ApplyItemVisuals()
|
||||||
|
{
|
||||||
|
if (Icon)
|
||||||
|
{
|
||||||
|
if (!ItemData.IsEmpty() && ItemData.Icon)
|
||||||
|
{
|
||||||
|
Icon->SetBrushFromTexture(ItemData.Icon);
|
||||||
|
Icon->SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Icon->SetVisibility(ESlateVisibility::Collapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ItemNameText)
|
||||||
|
{
|
||||||
|
if (!ItemData.IsEmpty())
|
||||||
|
{
|
||||||
|
ItemNameText->SetText(ItemData.DisplayName);
|
||||||
|
ItemNameText->SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ItemNameText->SetVisibility(ESlateVisibility::Collapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QuantityText)
|
||||||
|
{
|
||||||
|
if (!ItemData.IsEmpty() && ItemData.Quantity > 1)
|
||||||
|
{
|
||||||
|
QuantityText->SetText(FText::AsNumber(ItemData.Quantity));
|
||||||
|
QuantityText->SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QuantityText->SetVisibility(ESlateVisibility::Collapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StateText)
|
||||||
|
{
|
||||||
|
if (!ItemData.IsEmpty() && !ItemData.StateText.IsEmpty())
|
||||||
|
{
|
||||||
|
StateText->SetText(ItemData.StateText);
|
||||||
|
StateText->SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StateText->SetVisibility(ESlateVisibility::Collapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Blueprint/UserWidget.h"
|
||||||
|
#include "Blueprint/IUserObjectListEntry.h"
|
||||||
|
#include "Inventory/InventoryTypes.h"
|
||||||
|
#include "InventoryItemWidget.generated.h"
|
||||||
|
|
||||||
|
class UImage;
|
||||||
|
class UTextBlock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single inventory/equipment/quick-slot item. Always renders at the same fixed UI size
|
||||||
|
* regardless of the item's Mass/Volume - physical footprint is inventory-property data, not a
|
||||||
|
* UI dimension. Reused as the entry widget class for WBP_ItemList, WBP_InventoryGrid and
|
||||||
|
* WBP_QuickSlotList (via IUserObjectListEntry), and can also be placed directly and driven with
|
||||||
|
* SetItemData.
|
||||||
|
*
|
||||||
|
* Name your widgets exactly "Icon" (Image), "ItemNameText" (TextBlock), "QuantityText"
|
||||||
|
* (TextBlock) and "StateText" (TextBlock) in the Designer and population is handled here in
|
||||||
|
* C++; no BP event graph logic needed.
|
||||||
|
*/
|
||||||
|
UCLASS(abstract)
|
||||||
|
class UInventoryItemWidget : public UUserWidget, public IUserObjectListEntry
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Item")
|
||||||
|
void SetItemData(const FItemData& InItemData);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Item")
|
||||||
|
const FItemData& GetItemData() const { return ItemData; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
/** IUserObjectListEntry - called by the owning ListView/TileView when this row is assigned an UInventoryItemListEntry. */
|
||||||
|
virtual void NativeOnListItemObjectSet(UObject* ListItemObject) override;
|
||||||
|
|
||||||
|
void ApplyItemVisuals();
|
||||||
|
|
||||||
|
/** Item icon - bind an Image named exactly "Icon" in the Designer. Hidden automatically when the item slot is empty. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UImage> Icon;
|
||||||
|
|
||||||
|
/** Item name - bind a TextBlock named exactly "ItemNameText" in the Designer. Hidden automatically when the item slot is empty. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UTextBlock> ItemNameText;
|
||||||
|
|
||||||
|
/** Stack count - bind a TextBlock named exactly "QuantityText" in the Designer. Hidden automatically when Quantity <= 1. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UTextBlock> QuantityText;
|
||||||
|
|
||||||
|
/** Basic item state (e.g. "Broken", "New") - bind a TextBlock named exactly "StateText" in the Designer. Hidden automatically when the item has no StateText set. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UTextBlock> StateText;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Item")
|
||||||
|
FItemData ItemData;
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "QuickSlotListWidget.h"
|
||||||
|
#include "UI/Inventory/InventoryItemListEntry.h"
|
||||||
|
#include "Components/TileView.h"
|
||||||
|
|
||||||
|
void UQuickSlotListWidget::SetQuickSlots(const TArray<FItemData>& InBigQuickSlots, const TArray<FItemData>& InSmallQuickSlots)
|
||||||
|
{
|
||||||
|
PopulateSlots(BigQuickSlotsView, InBigQuickSlots, BigListItems);
|
||||||
|
PopulateSlots(SmallQuickSlotsView, InSmallQuickSlots, SmallListItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UQuickSlotListWidget::PopulateSlots(UTileView* TargetView, const TArray<FItemData>& InSlots, TArray<TObjectPtr<UInventoryItemListEntry>>& OutListItems)
|
||||||
|
{
|
||||||
|
if (!TargetView)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TargetView->ClearListItems();
|
||||||
|
OutListItems.Reset(InSlots.Num());
|
||||||
|
|
||||||
|
TArray<UObject*> ItemObjects;
|
||||||
|
ItemObjects.Reserve(InSlots.Num());
|
||||||
|
|
||||||
|
for (const FItemData& Item : InSlots)
|
||||||
|
{
|
||||||
|
UInventoryItemListEntry* Entry = NewObject<UInventoryItemListEntry>(this);
|
||||||
|
Entry->Item = Item;
|
||||||
|
OutListItems.Add(Entry);
|
||||||
|
ItemObjects.Add(Entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
TargetView->SetListItems(ItemObjects);
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Blueprint/UserWidget.h"
|
||||||
|
#include "Inventory/InventoryTypes.h"
|
||||||
|
#include "QuickSlotListWidget.generated.h"
|
||||||
|
|
||||||
|
class UInventoryItemListEntry;
|
||||||
|
class UTileView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The player's quick-access hotbar: a row of "big" slots (primary, keybound actions) and a row
|
||||||
|
* of "small" slots (secondary/utility). Both still render items at the standard fixed 2x2 size -
|
||||||
|
* "big"/"small" describes the slot's role and on-screen footprint, not item icon scaling. Pass
|
||||||
|
* an entry with an empty FItemData (see FItemData::IsEmpty) for slots that have nothing
|
||||||
|
* assigned. The slot arrays may include slots granted by equipped items (e.g. a backpack) -
|
||||||
|
* that's the inventory/equipment system's decision, this widget just displays whatever it's
|
||||||
|
* given.
|
||||||
|
*
|
||||||
|
* Name two TileViews exactly "BigQuickSlotsView" and "SmallQuickSlotsView" in the Designer, both
|
||||||
|
* with their Entry Widget Class set to a WBP subclass of UInventoryItemWidget (the same class
|
||||||
|
* used by WBP_ItemList/WBP_InventoryGrid), and population is handled here in C++.
|
||||||
|
*/
|
||||||
|
UCLASS(abstract)
|
||||||
|
class UQuickSlotListWidget : public UUserWidget
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Quick Slots")
|
||||||
|
void SetQuickSlots(const TArray<FItemData>& InBigQuickSlots, const TArray<FItemData>& InSmallQuickSlots);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
void PopulateSlots(UTileView* TargetView, const TArray<FItemData>& InSlots, TArray<TObjectPtr<UInventoryItemListEntry>>& OutListItems);
|
||||||
|
|
||||||
|
/** Named exactly "BigQuickSlotsView" in the Designer, Entry Widget Class set to a WBP subclass of UInventoryItemWidget. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UTileView> BigQuickSlotsView;
|
||||||
|
|
||||||
|
/** Named exactly "SmallQuickSlotsView" in the Designer, Entry Widget Class set to a WBP subclass of UInventoryItemWidget. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
TObjectPtr<UTileView> SmallQuickSlotsView;
|
||||||
|
|
||||||
|
/** UObject wrappers backing BigQuickSlotsView - kept alive here so the TileView's UObjects don't get GC'd. */
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<TObjectPtr<UInventoryItemListEntry>> BigListItems;
|
||||||
|
|
||||||
|
/** UObject wrappers backing SmallQuickSlotsView - kept alive here so the TileView's UObjects don't get GC'd. */
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<TObjectPtr<UInventoryItemListEntry>> SmallListItems;
|
||||||
|
};
|
||||||
@@ -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