// 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(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. 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(); } } 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(A.Priority) < static_cast(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(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(); }