Files
integration/Source/Integration/PlayerControllerBase.cpp

371 lines
12 KiB
C++

#include "PlayerControllerBase.h"
#include "Common.h"
#include "Tangible.h"
#include "TangibleManager.h"
#include "Blueprint/UserWidget.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()
{
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();
}
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::WidgetRequestInputMode(UUserWidget *Widget, bool ShowPointer, bool BlockInput, UWidget *Focus, bool EnableInputComponent)
{
if (!IsValid(Widget))
{
UE_LOG(LogLuprexIntegration, Error, TEXT("WidgetRequestInputMode called with an invalid widget."));
return;
}
APlayerController *OwningPC = Widget->GetOwningPlayer();
AlxPlayerControllerBase *PC = Cast<AlxPlayerControllerBase>(OwningPC);
if (PC == nullptr)
{
UE_LOG(LogLuprexIntegration, Error,
TEXT("WidgetRequestInputMode: widget '%s' owning player is not an AlxPlayerControllerBase (got %s)."),
*Widget->GetName(), *GetNameSafe(OwningPC));
return;
}
FlxInputModeRequest Req;
Req.Widget = Widget;
Req.Focus = Focus;
Req.ShowPointer = ShowPointer;
Req.BlockInput = BlockInput;
Req.EnableInputComponent = EnableInputComponent;
PC->InputModeRequests.Request(Req);
}
void AlxPlayerControllerBase::PushInputComponent(UInputComponent* InInputComponent)
{
if (InInputComponent)
{
// Widgets don't go on the current input stack. Instead, they're
// tracked in InputModeRequests, where the PlayerController can arbitrate
// focus, pointer visibility, and input blocking across them.
if (UUserWidget *Widget = Cast<UUserWidget>(InInputComponent->GetOuter()))
{
InputModeRequests.SetEnableInputComponent(Widget, true);
return;
}
CurrentInputStack.RemoveSingle(InInputComponent);
CurrentInputStack.Add(InInputComponent);
}
}
bool AlxPlayerControllerBase::PopInputComponent(UInputComponent* InInputComponent)
{
if (InInputComponent)
{
if (UUserWidget *Widget = Cast<UUserWidget>(InInputComponent->GetOuter()))
{
InputModeRequests.SetEnableInputComponent(Widget, false);
InInputComponent->ClearBindingValues();
return true;
}
if (CurrentInputStack.RemoveSingle(InInputComponent) > 0)
{
InInputComponent->ClearBindingValues();
return true;
}
}
return false;
}
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);
}
// The current input stack is unlikely to have anything,
// given that we've moved widgets to their own mechanism.
// But, if there's anything here, sort it and push it.
if (!CurrentInputStack.IsEmpty())
{
TArray<UInputComponent*, TInlineAllocator<20>> Pushed;
for (int32 Idx = 0; Idx < CurrentInputStack.Num(); ++Idx)
{
UInputComponent* IC = CurrentInputStack[Idx].Get();
if (IsValid(IC)) Pushed.Add(IC);
else CurrentInputStack.RemoveAt(Idx--);
}
Pushed.StableSort([](const UInputComponent& A, const UInputComponent& B)
{
if (A.bBlockInput != B.bBlockInput) return !A.bBlockInput;
return A.Priority < B.Priority;
});
InputStack.Append(Pushed);
}
// Push the widget input components from InputModeRequests. Requests
// are ordered high-priority first; the input stack is processed
// from top (last index) down, so we push back-to-front: low
// priority first, high priority last (ending up on top).
const TArray<FlxInputModeRequest> &Requests = InputModeRequests.GetRequests();
for (int32 Idx = Requests.Num() - 1; Idx >= 0; --Idx)
{
const FlxInputModeRequest &Req = Requests[Idx];
if (!Req.EnableInputComponent) continue;
UUserWidget *Widget = Req.Widget;
if (!Widget->IsInViewport()) continue;
if (UInputComponent *IC = GetWidgetInputComponent(Widget))
{
IC->bBlockInput = Req.BlockInput;
InputStack.Push(IC);
}
}
}
void AlxPlayerControllerBase::UpdateInputMode()
{
InputModeRequests.GarbageCollect();
// 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;
// The first active entry in InputModeRequests dictates the
// pointer / capture / focus state. If there are no requests at all,
// fall back to a static default-constructed request.
static const FlxInputModeRequest EmptyRequest;
const FlxInputModeRequest *Top = &EmptyRequest;
for (const FlxInputModeRequest &Req : InputModeRequests.GetRequests())
{
if (Req.Widget->IsInViewport()) { Top = &Req; break; }
}
SetShowMouseCursor(Top->ShowPointer);
if (Top->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 ((!Top->ShowPointer) || (LastRequestGrantedFocus != Top->SequenceNumber))
{
if (Top->Focus)
{
if (TSharedPtr<SWidget> SlateFocus = Top->Focus->GetCachedWidget())
{
SlateOperations.SetUserFocus(SlateFocus.ToSharedRef());
LastRequestGrantedFocus = Top->SequenceNumber;
}
}
else
{
SlateOperations.SetUserFocus(ViewportWidgetRef);
LastRequestGrantedFocus = Top->SequenceNumber;
}
}
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();
}
}