#pragma region CPL License /* Nuclex Unreal Module Copyright (C) 2014-2021 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License #include "UI/DialogPawn.h" #include #include #include #include "ErrorHandlingHelper.h" // --------------------------------------------------------------------------------------------- // ADialogPawn::ADialogPawn() : HmdOffsetCompensation(nullptr), ObserverCamera(nullptr), WidgetInteraction(nullptr), leftMouseButtonKey(TEXT("LeftMouseButton")) { // Use our own defaults suitable for a dialog interaction pawn Super::BaseEyeHeight = 0.0f; Super::AutoPossessPlayer = EAutoReceiveInput::Player0; Super::AutoPossessAI = EAutoPossessAI::Disabled; Super::AIControllerClass = nullptr; // An empty pawn should always have a transform component as its root, // but just so we don't crash, we'll try to manage if there is none (since Epic's // code also cluelessly stumbles around with this, so maybe that *can* happen). if(this->RootComponent == nullptr) { RootComponent = CreateDefaultSubobject( TEXT("SceneTransform") ); RootComponent->CreationMethod = EComponentCreationMethod::Native; } // Set up an intermediary transform component to compensate for the player's neutral // head position (it counters the player's resting position, no matter what VR setup, // so the player's eyes ultimately end up matching the dialog pawn's transform. this->HmdOffsetCompensation = CreateDefaultSubobject( TEXT("HmdOffsetCompensation") ); this->HmdOffsetCompensation->CreationMethod = EComponentCreationMethod::Native; this->HmdOffsetCompensation->SetupAttachment(RootComponent); // Finally, the camera component through which the player observes the scene this->ObserverCamera = CreateDefaultSubobject( TEXT("ObserverCamera") ); this->ObserverCamera->CreationMethod = EComponentCreationMethod::Native; this->ObserverCamera->SetupAttachment(this->HmdOffsetCompensation); // Also, put a widget interaction component on the camera for aim-clicking. This is // useful to trace where the player is looking (either because you want to control // UI without VR controllers or to check what the player is focusing on) this->WidgetInteraction = CreateDefaultSubobject( TEXT("WidgetInteraction") ); this->WidgetInteraction->CreationMethod = EComponentCreationMethod::Native; this->WidgetInteraction->InteractionDistance = 1024.0f; this->WidgetInteraction->TraceChannel = ECollisionChannel::ECC_Visibility; this->WidgetInteraction->SetupAttachment( this->ObserverCamera, FName(TEXT("WidgetInteraction")) ); // Turning this on locks the camera to the pawn position, allowing a Vive or // newer HMD to be instantly converted into a rotation-only Oculus DevKit 1. //this->ObserverCamera->bUsePawnControlRotation = true; // Turning this off causes the camera to be /lazily/ updated, // which is most useful in making VR players puke on their keyboards. //this->ObserverCamera->bLockToHmd = false; // Not needed. Is it ever? //this->observerCamera->AutoActivateForPlayer = EAutoReceiveInput::Player0; } // --------------------------------------------------------------------------------------------- // void ADialogPawn::RecenterHmd() { FTransform hmdTransform = this->ObserverCamera->GetRelativeTransform(); this->HmdOffsetCompensation->SetRelativeTransform(hmdTransform.Inverse()); } // --------------------------------------------------------------------------------------------- // void ADialogPawn::ClearHmdCenter() { this->HmdOffsetCompensation->SetRelativeTransform(FTransform::Identity); } // --------------------------------------------------------------------------------------------- // void ADialogPawn::BeginPlay() { Super::BeginPlay(); // This allows the eyes to end up at the exact location of the camera. // Not strictly necessary, but keeps the counter-transform free of head height adjustments. // // Some HMDs have their origin at the players floor (good for room scale games), others // default to the calibrated eye center position (good for flight sims and such). UHeadMountedDisplayFunctionLibrary::SetTrackingOrigin(EHMDTrackingOrigin::Eye); // We want to control VR UI by looking at it, rather than requiring dedicated VR controllers. // Setting this in the constructor crashes the editor on launch, so we only do it at runtime. if(UHeadMountedDisplayFunctionLibrary::IsHeadMountedDisplayEnabled()) { this->WidgetInteraction->InteractionSource = EWidgetInteractionSource::World; } else { this->WidgetInteraction->InteractionSource = EWidgetInteractionSource::Mouse; } } // --------------------------------------------------------------------------------------------- // #if defined(WANT_TO_HANDLE_INPUT_OURSELVES) void ADialogPawn::DestroyPlayerInputComponent() { // Attempt to unregister the key bindings we set up UInputComponent *playerInputComponent = this->InputComponent; if(IsValid(playerInputComponent)) { UE_LOG( LogNuclex, Display, TEXT("%s - INFO: %s"), TEXT("ADialogPawn::DestroyPlayerInputComponent()"), TEXT("Dialog pawn ejected, unbinding mouse input buttons") ); // Bind mouse input bypassing any actions - this is only used for UI stuff such as // dragging sliders & scrollbars. TArray &keyBindings = playerInputComponent->KeyBindings; int32 keyBindingCount = keyBindings.Num(); if(keyBindingCount > 0) { for(int32 index = keyBindingCount - 1; index >= 0; --index) { // Figure out if the current key binding is one we set up in SetupPlayerInputComponent() // so that we don't remove key bindings that don't belong to us. bool wasRegisteredByUs; { const FInputKeyBinding &keyBinding = keyBindings[index]; wasRegisteredByUs = ( (keyBinding.KeyEvent == EInputEvent::IE_Pressed) || (keyBinding.KeyEvent == EInputEvent::IE_Released) ); wasRegisteredByUs &= keyBinding.KeyDelegate.IsBoundToObject(this); wasRegisteredByUs &= keyBinding.Chord.Key == this->leftMouseButtonKey; } // If we are indeed responsible for this binding, remove it if(wasRegisteredByUs) { keyBindings.RemoveAt(index); UE_LOG( LogNuclex, Display, TEXT("%s - INFO: %s"), TEXT("ADialogPawn::DestroyPlayerInputComponent()"), TEXT("Key binding removed") ); } } } } // Give our base class a chance to clean up its stuff as well. // (APawn actually destroys the input component in the current UE version, // so all of the above was for naught, but we don't want to make assumptions!) Super::DestroyPlayerInputComponent(); } #endif // defined(WANT_TO_HANDLE_INPUT_OURSELVES) // --------------------------------------------------------------------------------------------- // #if defined(WANT_TO_HANDLE_INPUT_OURSELVES) void ADialogPawn::SetupPlayerInputComponent(UInputComponent *playerInputComponent) { Super::SetupPlayerInputComponent(playerInputComponent); if(IsValid(playerInputComponent)) { UE_LOG( LogNuclex, Display, TEXT("%s - INFO: %s"), TEXT("ADialogPawn::SetupPlayerInputComponent()"), TEXT("Dialog pawn possessed, registering mouse buttons for WidgetInteractionComponent") ); // Bind mouse input bypassing any actions - this is only used for UI stuff such as // dragging sliders & scrollbars. playerInputComponent->BindKey( this->leftMouseButtonKey, EInputEvent::IE_Pressed, this, &ADialogPawn::pointerPressed ); playerInputComponent->BindKey( this->leftMouseButtonKey, EInputEvent::IE_Released, this, &ADialogPawn::pointerReleased ); } } #endif // defined(WANT_TO_HANDLE_INPUT_OURSELVES) // --------------------------------------------------------------------------------------------- // #if defined(WANT_TO_HANDLE_INPUT_OURSELVES) void ADialogPawn::pointerPressed() { if(IsValid(this->WidgetInteraction)) { UE_LOG( LogNuclex, Display, TEXT("%s - INFO: %s"), TEXT("ADialogPawn::pointerPressed()"), TEXT("Sending left mouse button pressed notification") ); this->WidgetInteraction->PressPointerKey(this->leftMouseButtonKey); } } #endif // defined(WANT_TO_HANDLE_INPUT_OURSELVES) // --------------------------------------------------------------------------------------------- // #if defined(WANT_TO_HANDLE_INPUT_OURSELVES) void ADialogPawn::pointerReleased() { if(IsValid(this->WidgetInteraction)) { UE_LOG( LogNuclex, Display, TEXT("%s - INFO: %s"), TEXT("ADialogPawn::pointerPressed()"), TEXT("Sending left mouse button released notification") ); this->WidgetInteraction->ReleasePointerKey(this->leftMouseButtonKey); } } #endif // defined(WANT_TO_HANDLE_INPUT_OURSELVES) // --------------------------------------------------------------------------------------------- //