#include "PlayerControllerBase.h" #include "Common.h" #include "RootCanvas.h" #include "Tangible.h" #include "TangibleManager.h" #include "Blueprint/UserWidget.h" #include "Blueprint/WidgetTree.h" #include "Components/InputComponent.h" #include "Engine/GameInstance.h" #include "Engine/GameViewportClient.h" #include "Engine/LevelScriptActor.h" #include "Engine/LocalPlayer.h" #include "Framework/Application/SlateApplication.h" #include "Framework/Application/SlateUser.h" #include "Kismet/GameplayStatics.h" #include "Widgets/SViewport.h" AlxPlayerControllerBase *AlxPlayerControllerBase::FromContext(const UObject *Context) { APlayerController *PC = Context->GetWorld()->GetFirstPlayerController(); AlxPlayerControllerBase *Result = Cast(PC); if (Result == nullptr) { UE_LOG(LogLuprexIntegration, Fatal, TEXT("Not currently using a Luprex Player Controller.")); } return Result; } const FHitResult &AlxPlayerControllerBase::GetLookAt(const UObject *Context) { return FromContext(Context)->CurrentLookAt; } const AActor *AlxPlayerControllerBase::GetLookAtActor(const UObject *Context) { return FromContext(Context)->CurrentLookAt.GetActor(); } void AlxPlayerControllerBase::SetLookAt(const UObject *Context, const FHitResult &HitResult) { AlxPlayerControllerBase *PC = FromContext(Context); if (PC->CurrentLookAt.HitObjectHandle != HitResult.HitObjectHandle) { PC->MustCallLookAtChanged = true; } PC->CurrentLookAt = HitResult; } void AlxPlayerControllerBase::SetLookAtChanged(const UObject *Context) { AlxPlayerControllerBase *PC = FromContext(Context); PC->MustCallLookAtChanged = true; } FVector2D AlxPlayerControllerBase::GetLookAtPixel(const UObject *Context) { AlxPlayerControllerBase *PC = FromContext(Context); FVector2D ScreenPosition; if (!UGameplayStatics::ProjectWorldToScreen(PC, PC->CurrentLookAt.Location, ScreenPosition, false)) { return FVector2D(); } return ScreenPosition; } void AlxPlayerControllerBase::BeginPlay() { // Build the root UMG stack BEFORE Super::BeginPlay. Super calls // ReceiveBeginPlay, which fires the Blueprint Event BeginPlay; // BP code there may immediately try to add widgets to RootCanvas, // so the canvas must already exist. RootWidget = CreateWidget(this); RootCanvas = RootWidget->WidgetTree->ConstructWidget(); RootWidget->WidgetTree->RootWidget = RootCanvas; RootWidget->AddToViewport(0); Super::BeginPlay(); if (FSlateApplication::IsInitialized()) { FocusChangingHandle = FSlateApplication::Get().OnFocusChanging().AddUObject( this, &AlxPlayerControllerBase::HandleFocusChanging); } } void AlxPlayerControllerBase::EndPlay(const EEndPlayReason::Type EndPlayReason) { if (FocusChangingHandle.IsValid() && FSlateApplication::IsInitialized()) { FSlateApplication::Get().OnFocusChanging().Remove(FocusChangingHandle); FocusChangingHandle.Reset(); } if (IsValid(RootWidget)) { RootWidget->RemoveFromParent(); } RootWidget = nullptr; RootCanvas = nullptr; Super::EndPlay(EndPlayReason); } void AlxPlayerControllerBase::HandleFocusChanging( const FFocusEvent &FocusEvent, const FWeakWidgetPath &OldPath, const TSharedPtr &OldFocusedWidget, const FWidgetPath &NewPath, const TSharedPtr &NewFocusedWidget) { UE_LOG(LogLuprexIntegration, Display, TEXT("Focus changing: '%s' -> '%s' (cause: %s)"), OldFocusedWidget.IsValid() ? *OldFocusedWidget->GetTypeAsString() : TEXT(""), NewFocusedWidget.IsValid() ? *NewFocusedWidget->GetTypeAsString() : TEXT(""), *UEnum::GetValueAsString(FocusEvent.GetCause())); } UInputComponent* AlxPlayerControllerBase::GetWidgetInputComponent(UUserWidget *Widget) { if (!IsValid(Widget)) return nullptr; // Cache the FProperty on first call. FProperties are owned by // the native UUserWidget UClass, which lives for the process // lifetime, so the pointer stays valid without us needing to // root anything against GC. Static local init is thread-safe. static FObjectProperty *InputComponentProp = FindFProperty( UUserWidget::StaticClass(), TEXT("InputComponent")); check(InputComponentProp); UObject *Value = InputComponentProp->GetObjectPropertyValue_InContainer(Widget); return Cast(Value); } void AlxPlayerControllerBase::RestoreFocusToFrontWidget(const UObject *Context) { // This will trigger UpdateInputMode to shift focus back to // the front window, if the front window wants focus. FromContext(Context)->RootCanvas->LastWidgetGrantedFocus = nullptr; } void AlxPlayerControllerBase::AddWidgetToRoot(UUserWidget *Widget) { if (!IsValid(Widget)) { UE_LOG(LogLuprexIntegration, Error, TEXT("AddWidgetToRoot called with an invalid widget.")); return; } APlayerController *OwningPC = Widget->GetOwningPlayer(); AlxPlayerControllerBase *PC = Cast(OwningPC); if (PC == nullptr) { UE_LOG(LogLuprexIntegration, Error, TEXT("AddWidgetToRoot: widget '%s' owning player is not an AlxPlayerControllerBase (got %s)."), *Widget->GetName(), *GetNameSafe(OwningPC)); return; } if (PC->RootCanvas == nullptr) { UE_LOG(LogLuprexIntegration, Error, TEXT("AddWidgetToRoot: root canvas is not initialized, this is probably an initialization order issue")); return; } if (Widget->GetParent() == PC->RootCanvas) return; PC->RootCanvas->AddChildToRootCanvas(Widget); } void AlxPlayerControllerBase::BuildInputStack(TArray& InputStack) { // Controlled pawn gets last dibs on the input stack APawn* ControlledPawn = GetPawnOrSpectator(); if (ControlledPawn) { if (ControlledPawn->InputEnabled()) { // Get the explicit input component that is created upon Pawn possession. This one gets last dibs. if (ControlledPawn->InputComponent) { InputStack.Push(ControlledPawn->InputComponent); } // See if there is another InputComponent that was added to the Pawn's components array (possibly by script). for (UActorComponent* ActorComponent : ControlledPawn->GetComponents()) { UInputComponent* PawnInputComponent = Cast(ActorComponent); if (PawnInputComponent && PawnInputComponent != ControlledPawn->InputComponent) { InputStack.Push(PawnInputComponent); } } } } // LevelScriptActors are put on the stack next for (ULevel* Level : GetWorld()->GetLevels()) { ALevelScriptActor* ScriptActor = Level->GetLevelScriptActor(); if (ScriptActor) { if (ScriptActor->InputEnabled() && ScriptActor->InputComponent) { InputStack.Push(ScriptActor->InputComponent); } } } if (InputEnabled()) { InputStack.Push(InputComponent); } // Get the widget slots. TArray WidgetSlots = RootCanvas->GetSortedUserWidgets(); // Generate a set of input components that are being managed by the WidgetSlots. TSet WidgetManagedComponents; for (UlxRootCanvasSlot *Slot : WidgetSlots) { UUserWidget *Widget = Cast(Slot->GetContent()); UInputComponent *IC = GetWidgetInputComponent(Widget); if (IC) WidgetManagedComponents.Add(IC); } // Add components in the CurrentInputStack, *unless* they are being managed // by widgets. If they're being managed by widgets, they get added later. for (int32 Idx=0; IdxEnableEnhancedInput) { UUserWidget *Widget = Cast(Slot->GetContent()); UInputComponent *IC = GetWidgetInputComponent(Widget); if (IC) { IC->bBlockInput = Slot->BlockInput; InputStack.Push(IC); } } } } void AlxPlayerControllerBase::UpdateInputMode() { // Get all the various objects we need to be able to manipulate // the input mode. UGameViewportClient *GameViewportClient = GetWorld()->GetGameViewport(); ULocalPlayer *LocalPlayer = Cast(Player); if (GameViewportClient == nullptr || LocalPlayer == nullptr) return; TSharedPtr ViewportWidget = GameViewportClient->GetGameViewportWidget(); if (!ViewportWidget.IsValid()) return; TSharedRef ViewportWidgetRef = ViewportWidget.ToSharedRef(); FReply &SlateOperations = LocalPlayer->GetSlateOperations(); TSharedPtr SlateUser = LocalPlayer->GetSlateUser(); if (!SlateUser.IsValid()) return; RootCanvas->UpdateZOrders(); // Get the desired configuration from the top widget. UUserWidget *Widget = nullptr; UWidget *Focus = nullptr; bool ShowPointer = false; if (UlxRootCanvasSlot *Top = RootCanvas->GetTopWidget()) { Widget = Cast(Top->GetContent()); Focus = Widget->GetDesiredFocusWidget(); ShowPointer = Top->ShowPointer; } SetShowMouseCursor(ShowPointer); if (ShowPointer) { // Only release capture if the viewport is currently holding it // (e.g. we just came from GameOnly). A blanket ReleaseMouseCapture // every tick would yank capture away from widgets mid-gesture // (scrollbar drags, slider drags, etc.). if (SlateUser->DoesWidgetHaveAnyCapture(ViewportWidget)) { SlateOperations.ReleaseMouseCapture(); } SlateOperations.ReleaseMouseLock(); GameViewportClient->SetMouseLockMode(EMouseLockMode::DoNotLock); GameViewportClient->SetHideCursorDuringCapture(false); GameViewportClient->SetMouseCaptureMode(EMouseCaptureMode::CaptureDuringMouseDown); } else { // Also captures the mouse to the viewport. SlateOperations.UseHighPrecisionMouseMovement(ViewportWidgetRef); SlateOperations.LockMouseToWidget(ViewportWidgetRef); GameViewportClient->SetMouseLockMode(EMouseLockMode::LockOnCapture); GameViewportClient->SetMouseCaptureMode(EMouseCaptureMode::CapturePermanently); } GameViewportClient->SetIgnoreInput(false); // How we handle focus depends on whether we're showing the pointer. // In pointer mode, we set focus to the desired state just once, // and then we let the pointer control it from there on. In // no-pointer mode, we set focus to the desired state and // keep putting it back forever. // // If the user clicks the mouse on a focusable widget, the // viewport client notifies us of that fact. We then focus the // widget if possible. // if ((!ShowPointer) || (RootCanvas->LastWidgetGrantedFocus != Focus)) { if (Focus) { if (TSharedPtr SlateFocus = Focus->GetCachedWidget()) { SlateOperations.SetUserFocus(SlateFocus.ToSharedRef()); RootCanvas->LastWidgetGrantedFocus = Focus; } } else { SlateOperations.SetUserFocus(ViewportWidgetRef); RootCanvas->LastWidgetGrantedFocus = nullptr; } } } void AlxPlayerControllerBase::UpdateLookAt() { UlxTangibleManager *TM = GetGameInstance()->GetSubsystem(); if (TM == nullptr) return; UlxTangible *Possessed = TM->GetPossessedTangible(); if (Possessed == nullptr) return; APawn *Pawn = GetPawn(); if (Pawn == nullptr) return; if (Possessed->GetActor() != Cast(Pawn)) return; if (PlayerCameraManager == nullptr) return; CalculateLookAt(); if (MustCallLookAtChanged) { MustCallLookAtChanged = false; LookAtChanged(); } }