369 lines
11 KiB
C++
369 lines
11 KiB
C++
#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<AlxPlayerControllerBase>(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<UlxRootWidget>(this);
|
|
RootCanvas = RootWidget->WidgetTree->ConstructWidget<UlxRootCanvasPanel>();
|
|
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<SWidget> &OldFocusedWidget,
|
|
const FWidgetPath &NewPath,
|
|
const TSharedPtr<SWidget> &NewFocusedWidget)
|
|
{
|
|
UE_LOG(LogLuprexIntegration, Display,
|
|
TEXT("Focus changing: '%s' -> '%s' (cause: %s)"),
|
|
OldFocusedWidget.IsValid() ? *OldFocusedWidget->GetTypeAsString() : TEXT("<none>"),
|
|
NewFocusedWidget.IsValid() ? *NewFocusedWidget->GetTypeAsString() : TEXT("<none>"),
|
|
*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<FObjectProperty>(
|
|
UUserWidget::StaticClass(), TEXT("InputComponent"));
|
|
check(InputComponentProp);
|
|
|
|
UObject *Value = InputComponentProp->GetObjectPropertyValue_InContainer(Widget);
|
|
return Cast<UInputComponent>(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)->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<AlxPlayerControllerBase>(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<UInputComponent*>& 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<UInputComponent>(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<UlxRootCanvasSlot*> WidgetSlots = RootCanvas->GetSortedUserWidgets();
|
|
|
|
// Generate a set of input components that are being managed by the WidgetSlots.
|
|
TSet<UInputComponent*> WidgetManagedComponents;
|
|
for (UlxRootCanvasSlot *Slot : WidgetSlots)
|
|
{
|
|
UUserWidget *Widget = Cast<UUserWidget>(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; Idx<CurrentInputStack.Num(); ++Idx)
|
|
{
|
|
UInputComponent* IC = CurrentInputStack[Idx].Get();
|
|
if (IsValid(IC))
|
|
{
|
|
if (!WidgetManagedComponents.Contains(IC)) InputStack.Push(IC);
|
|
}
|
|
else
|
|
{
|
|
CurrentInputStack.RemoveAt(Idx--);
|
|
}
|
|
}
|
|
|
|
// Now add the widget-managed input components.
|
|
for (UlxRootCanvasSlot *Slot : WidgetSlots)
|
|
{
|
|
if (Slot->EnableEnhancedInput)
|
|
{
|
|
UUserWidget *Widget = Cast<UUserWidget>(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<ULocalPlayer>(Player);
|
|
if (GameViewportClient == nullptr || LocalPlayer == nullptr) return;
|
|
TSharedPtr<SViewport> ViewportWidget = GameViewportClient->GetGameViewportWidget();
|
|
if (!ViewportWidget.IsValid()) return;
|
|
TSharedRef<SViewport> ViewportWidgetRef = ViewportWidget.ToSharedRef();
|
|
FReply &SlateOperations = LocalPlayer->GetSlateOperations();
|
|
TSharedPtr<FSlateUser> 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<UUserWidget>(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) || (LastWidgetGrantedFocus != Focus))
|
|
{
|
|
if (Focus)
|
|
{
|
|
if (TSharedPtr<SWidget> SlateFocus = Focus->GetCachedWidget())
|
|
{
|
|
SlateOperations.SetUserFocus(SlateFocus.ToSharedRef());
|
|
LastWidgetGrantedFocus = Focus;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SlateOperations.SetUserFocus(ViewportWidgetRef);
|
|
LastWidgetGrantedFocus = nullptr;
|
|
}
|
|
}
|
|
else if (TSharedPtr<SWidget> ClickedWidget = ClickToFocusTarget.Pin())
|
|
{
|
|
SlateOperations.SetUserFocus(ClickedWidget.ToSharedRef());
|
|
}
|
|
ClickToFocusTarget.Reset();
|
|
}
|
|
|
|
void AlxPlayerControllerBase::ClickToFocus(TSharedRef<SWidget> Widget)
|
|
{
|
|
ClickToFocusTarget = Widget.ToWeakPtr();
|
|
}
|
|
|
|
void AlxPlayerControllerBase::UpdateLookAt()
|
|
{
|
|
UlxTangibleManager *TM = GetGameInstance()->GetSubsystem<UlxTangibleManager>();
|
|
if (TM == nullptr) return;
|
|
UlxTangible *Possessed = TM->GetPossessedTangible();
|
|
if (Possessed == nullptr) return;
|
|
APawn *Pawn = GetPawn();
|
|
if (Pawn == nullptr) return;
|
|
if (Possessed->GetActor() != Cast<AActor>(Pawn)) return;
|
|
if (PlayerCameraManager == nullptr) return;
|
|
|
|
CalculateLookAt();
|
|
|
|
if (MustCallLookAtChanged)
|
|
{
|
|
MustCallLookAtChanged = false;
|
|
LookAtChanged();
|
|
}
|
|
}
|