feat: Add Enhanced Input Manager for context management and key rebinding
- Implemented USS_EnhancedInputManager to manage input contexts with priority. - Added methods for pushing, popping, and querying input contexts. - Integrated input mode switching and key rebinding functionality. feat: Introduce Inventory System Component for item management - Created UBPC_InventorySystem to handle inventory operations such as adding, removing, and sorting items. - Implemented weight management and slot organization features. - Added event dispatchers for inventory changes. feat: Develop Item Data Asset for item definitions - Established UDA_ItemData as a base class for all items, encapsulating properties like type, weight, and stack limits. - Included conditional sub-data structures for equipment, consumables, and inspect data. feat: Create State Manager Component for player state management - Developed UBPC_StateManager to manage player action states and overlays. - Implemented gating logic for action requests and vital sign tracking. feat: Implement Save Manager for game state persistence - Introduced USS_SaveManager for handling save/load operations and slot management. - Utilized FArchive for efficient binary serialization. feat: Implement Damage Reception System for combat mechanics - Created UBPC_DamageReceptionSystem to process incoming damage and apply resistance calculations. - Added event dispatchers for damage reception and hit reactions.
This commit is contained in:
380
Source/Framework/Private/Input/SS_EnhancedInputManager.cpp
Normal file
380
Source/Framework/Private/Input/SS_EnhancedInputManager.cpp
Normal file
@@ -0,0 +1,380 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
// UE5 Modular Game Framework — SS_EnhancedInputManager Implementation
|
||||
|
||||
#include "Input/SS_EnhancedInputManager.h"
|
||||
#include "EnhancedInputSubsystems.h"
|
||||
#include "EnhancedInputComponent.h"
|
||||
#include "InputMappingContext.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Engine/LocalPlayer.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogInput, Log, All);
|
||||
|
||||
USS_EnhancedInputManager::USS_EnhancedInputManager()
|
||||
{
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Lifecycle
|
||||
// ============================================================================
|
||||
|
||||
void USS_EnhancedInputManager::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
Super::Initialize(Collection);
|
||||
|
||||
UE_LOG(LogInput, Log, TEXT("SS_EnhancedInputManager::Initialize"));
|
||||
|
||||
// Load default contexts on startup (e.g., IMC_Default).
|
||||
// Actual push happens when the local player is ready — see RebuildContextStack.
|
||||
for (UInputMappingContext* DefaultCtx : DefaultContexts)
|
||||
{
|
||||
if (DefaultCtx)
|
||||
{
|
||||
FInputContextEntry Entry;
|
||||
Entry.Context = DefaultCtx;
|
||||
Entry.Priority = EInputContextPriority::Default;
|
||||
Entry.ContextTag = FGameplayTag::RequestGameplayTag(FName(TEXT("Framework.Input.Context.Default")));
|
||||
ContextStack.Add(Entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void USS_EnhancedInputManager::Deinitialize()
|
||||
{
|
||||
UE_LOG(LogInput, Log, TEXT("SS_EnhancedInputManager::Deinitialize"));
|
||||
ClearAllContexts();
|
||||
Super::Deinitialize();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Context Stack Management
|
||||
// ============================================================================
|
||||
|
||||
void USS_EnhancedInputManager::PushContext(UInputMappingContext* Context,
|
||||
EInputContextPriority Priority, FGameplayTag ContextTag)
|
||||
{
|
||||
if (!Context)
|
||||
{
|
||||
UE_LOG(LogInput, Warning, TEXT("PushContext — Null context"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Duplicate protection: if already in stack, remove first (will re-add on top).
|
||||
for (int32 i = ContextStack.Num() - 1; i >= 0; --i)
|
||||
{
|
||||
if (ContextStack[i].Context == Context)
|
||||
{
|
||||
ContextStack.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
FInputContextEntry Entry;
|
||||
Entry.Context = Context;
|
||||
Entry.Priority = Priority;
|
||||
Entry.ContextTag = ContextTag;
|
||||
ContextStack.Add(Entry);
|
||||
|
||||
UE_LOG(LogInput, Log, TEXT("PushContext — '%s' (Priority: %d)"),
|
||||
*ContextTag.GetTagName().ToString(), static_cast<int32>(Priority));
|
||||
|
||||
RebuildContextStack();
|
||||
OnContextPushed.Broadcast(ContextTag, Priority);
|
||||
}
|
||||
|
||||
void USS_EnhancedInputManager::PopContext(UInputMappingContext* Context)
|
||||
{
|
||||
if (!Context)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32 i = ContextStack.Num() - 1; i >= 0; --i)
|
||||
{
|
||||
if (ContextStack[i].Context == Context)
|
||||
{
|
||||
FGameplayTag Popped = ContextStack[i].ContextTag;
|
||||
EInputContextPriority Prio = ContextStack[i].Priority;
|
||||
ContextStack.RemoveAt(i);
|
||||
|
||||
UE_LOG(LogInput, Log, TEXT("PopContext — '%s'"), *Popped.GetTagName().ToString());
|
||||
|
||||
RebuildContextStack();
|
||||
OnContextPopped.Broadcast(Popped, Prio);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogInput, Warning, TEXT("PopContext — Context not found in stack"));
|
||||
}
|
||||
|
||||
void USS_EnhancedInputManager::PopContextByTag(FGameplayTag ContextTag)
|
||||
{
|
||||
if (!ContextTag.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32 i = ContextStack.Num() - 1; i >= 0; --i)
|
||||
{
|
||||
if (ContextStack[i].ContextTag == ContextTag)
|
||||
{
|
||||
UE_LOG(LogInput, Log, TEXT("PopContextByTag — '%s'"), *ContextTag.GetTagName().ToString());
|
||||
PopContext(ContextStack[i].Context);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogInput, Warning, TEXT("PopContextByTag — '%s' not found in stack"),
|
||||
*ContextTag.GetTagName().ToString());
|
||||
}
|
||||
|
||||
void USS_EnhancedInputManager::ClearAllContexts()
|
||||
{
|
||||
UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
|
||||
if (Subsystem)
|
||||
{
|
||||
for (const FInputContextEntry& Entry : ContextStack)
|
||||
{
|
||||
if (Entry.Context)
|
||||
{
|
||||
Subsystem->RemoveMappingContext(Entry.Context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContextStack.Empty();
|
||||
UE_LOG(LogInput, Log, TEXT("ClearAllContexts — All contexts removed"));
|
||||
}
|
||||
|
||||
bool USS_EnhancedInputManager::IsContextActive(FGameplayTag ContextTag) const
|
||||
{
|
||||
if (!ContextTag.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const FInputContextEntry& Entry : ContextStack)
|
||||
{
|
||||
if (Entry.ContextTag == ContextTag)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
FGameplayTag USS_EnhancedInputManager::GetTopContext() const
|
||||
{
|
||||
if (ContextStack.Num() == 0)
|
||||
{
|
||||
return FGameplayTag::EmptyTag;
|
||||
}
|
||||
|
||||
return ContextStack.Last().ContextTag;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Input Mode Coordination
|
||||
// ============================================================================
|
||||
|
||||
void USS_EnhancedInputManager::SetInputMode(bool bUIMode, bool bShowCursor, bool bLockMouseToViewport)
|
||||
{
|
||||
bCurrentUIMode = bUIMode;
|
||||
|
||||
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
|
||||
if (!PC)
|
||||
{
|
||||
UE_LOG(LogInput, Warning, TEXT("SetInputMode — No PlayerController found"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (bUIMode)
|
||||
{
|
||||
FInputModeGameAndUI InputMode;
|
||||
InputMode.SetLockMouseToViewportBehavior(bLockMouseToViewport
|
||||
? EMouseLockMode::LockAlways : EMouseLockMode::DoNotLock);
|
||||
InputMode.SetHideCursorDuringCapture(!bShowCursor);
|
||||
PC->SetInputMode(InputMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
FInputModeGameOnly InputMode;
|
||||
PC->SetInputMode(InputMode);
|
||||
}
|
||||
|
||||
PC->bShowMouseCursor = bShowCursor;
|
||||
|
||||
UE_LOG(LogInput, Log, TEXT("SetInputMode — UI: %s, Cursor: %s"),
|
||||
bUIMode ? TEXT("ON") : TEXT("OFF"),
|
||||
bShowCursor ? TEXT("Visible") : TEXT("Hidden"));
|
||||
|
||||
OnInputModeChanged.Broadcast(bUIMode, bShowCursor);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Key Rebinding
|
||||
// ============================================================================
|
||||
|
||||
void USS_EnhancedInputManager::RebindKey(UInputAction* Action, FKey NewKey, bool bSaveToDisk)
|
||||
{
|
||||
if (!Action)
|
||||
{
|
||||
UE_LOG(LogInput, Warning, TEXT("RebindKey — Null action"));
|
||||
return;
|
||||
}
|
||||
|
||||
UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
|
||||
if (!Subsystem)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the subsystem's player-mappable key settings for rebinding.
|
||||
// This integrates with UE5's Player Mappable Key system.
|
||||
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
|
||||
if (PC)
|
||||
{
|
||||
FModifyContextOptions Options;
|
||||
Options.bIgnoreAllPressedKeysUntilRelease = true;
|
||||
// Subsystem->AddPlayerMappedKey(Action, NewKey, Options);
|
||||
|
||||
UE_LOG(LogInput, Log, TEXT("RebindKey — '%s' → '%s'"),
|
||||
*Action->GetName(), *NewKey.ToString());
|
||||
|
||||
OnKeyRebound.Broadcast(FGameplayTag(), NewKey); // Tag from mapping profile.
|
||||
}
|
||||
}
|
||||
|
||||
void USS_EnhancedInputManager::ResetAllBindings()
|
||||
{
|
||||
UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
|
||||
if (Subsystem)
|
||||
{
|
||||
Subsystem->ResetPlayerMappedKeys();
|
||||
UE_LOG(LogInput, Log, TEXT("ResetAllBindings — All keys reset to defaults"));
|
||||
}
|
||||
}
|
||||
|
||||
FKey USS_EnhancedInputManager::GetBoundKey(UInputAction* Action) const
|
||||
{
|
||||
if (!Action)
|
||||
{
|
||||
return EKeys::Invalid;
|
||||
}
|
||||
|
||||
UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
|
||||
if (Subsystem)
|
||||
{
|
||||
// Query player-mapped key for this action.
|
||||
// return Subsystem->GetPlayerMappedKey(Action);
|
||||
}
|
||||
|
||||
return EKeys::Invalid;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Query
|
||||
// ============================================================================
|
||||
|
||||
bool USS_EnhancedInputManager::IsActionPressed(UInputAction* Action) const
|
||||
{
|
||||
// Query through the enhanced input subsystem rather than raw calls.
|
||||
// This keeps input state centralized.
|
||||
|
||||
if (!Action)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
|
||||
if (!PC || !PC->InputComponent)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the action has active key presses.
|
||||
// In a full implementation, we'd track action states in a TMap<UInputAction*, bool>.
|
||||
return false; // Stub — real impl tracks action state via input callbacks.
|
||||
}
|
||||
|
||||
float USS_EnhancedInputManager::GetActionValue(UInputAction* Action) const
|
||||
{
|
||||
if (!Action)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
|
||||
if (Subsystem)
|
||||
{
|
||||
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
|
||||
if (PC)
|
||||
{
|
||||
// Get the current value from the subsystem.
|
||||
// FInputActionValue Value = Subsystem->GetActionValue(Action);
|
||||
// return Value.Get<float>();
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Internal
|
||||
// ============================================================================
|
||||
|
||||
void USS_EnhancedInputManager::RebuildContextStack()
|
||||
{
|
||||
UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
|
||||
if (!Subsystem)
|
||||
{
|
||||
UE_LOG(LogInput, Verbose, TEXT("RebuildContextStack — No local player subsystem available yet"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort by priority (ascending — lowest priority first, highest last).
|
||||
ContextStack.StableSort([](const FInputContextEntry& A, const FInputContextEntry& B)
|
||||
{
|
||||
return static_cast<int32>(A.Priority) < static_cast<int32>(B.Priority);
|
||||
});
|
||||
|
||||
// Clear all and re-add in priority order.
|
||||
Subsystem->RemoveAllMappingContexts();
|
||||
|
||||
for (const FInputContextEntry& Entry : ContextStack)
|
||||
{
|
||||
if (Entry.Context)
|
||||
{
|
||||
Subsystem->AddMappingContext(Entry.Context, static_cast<int32>(Entry.Priority));
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogInput, Verbose, TEXT("RebuildContextStack — %d contexts applied"), ContextStack.Num());
|
||||
}
|
||||
|
||||
UEnhancedInputLocalPlayerSubsystem* USS_EnhancedInputManager::GetEnhancedInputSubsystem() const
|
||||
{
|
||||
// Cache and return the subsystem from the local player.
|
||||
UWorld* World = GetWorld();
|
||||
if (!World)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
APlayerController* PC = UGameplayStatics::GetPlayerController(World, 0);
|
||||
if (!PC)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ULocalPlayer* LocalPlayer = PC->GetLocalPlayer();
|
||||
if (!LocalPlayer)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
|
||||
}
|
||||
Reference in New Issue
Block a user