Files
UE5-Modular-Game-Framework/Source/Framework/Private/Input/SS_EnhancedInputManager.cpp

388 lines
10 KiB
C++

// 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(LogFrameworkInput, Log, All);
USS_EnhancedInputManager::USS_EnhancedInputManager()
{
}
// ============================================================================
// Lifecycle
// ============================================================================
void USS_EnhancedInputManager::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
UE_LOG(LogFrameworkInput, 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(LogFrameworkInput, 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(LogFrameworkInput, 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(LogFrameworkInput, 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(LogFrameworkInput, Log, TEXT("PopContext — '%s'"), *Popped.GetTagName().ToString());
RebuildContextStack();
OnContextPopped.Broadcast(Popped, Prio);
return;
}
}
UE_LOG(LogFrameworkInput, 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(LogFrameworkInput, Log, TEXT("PopContextByTag — '%s'"), *ContextTag.GetTagName().ToString());
PopContext(ContextStack[i].Context);
return;
}
}
UE_LOG(LogFrameworkInput, 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(LogFrameworkInput, 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(LogFrameworkInput, 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(LogFrameworkInput, 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(LogFrameworkInput, 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(LogFrameworkInput, 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)
{
// UE 5.7+: ResetPlayerMappedKeys was removed. Use UEnhancedInputUserSettings or iterate context entries.
// Subsystem->RequestRebuildPlayerMappedKeys();
UE_LOG(LogFrameworkInput, 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(LogFrameworkInput, 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);
});
// UE 5.7+: RemoveAllMappingContexts was removed. Remove individually.
for (const FInputContextEntry& Existing : ContextStack)
{
if (Existing.Context)
{
Subsystem->RemoveMappingContext(Existing.Context);
}
}
for (const FInputContextEntry& Entry : ContextStack)
{
if (Entry.Context)
{
Subsystem->AddMappingContext(Entry.Context, static_cast<int32>(Entry.Priority));
}
}
UE_LOG(LogFrameworkInput, 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>();
}