388 lines
10 KiB
C++
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>();
|
|
}
|