Skip to content

Instantly share code, notes, and snippets.

@Batname
Created May 15, 2018 12:41
Show Gist options
  • Save Batname/19006209eb1be4d5cd85c71c9cd15d5b to your computer and use it in GitHub Desktop.
Save Batname/19006209eb1be4d5cd85c71c9cd15d5b to your computer and use it in GitHub Desktop.
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "Engine/GameViewportClient.h"
#include "HAL/FileManager.h"
#include "Misc/CommandLine.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Misc/CoreDelegates.h"
#include "Misc/App.h"
#include "GameMapsSettings.h"
#include "EngineStats.h"
#include "RenderingThread.h"
#include "SceneView.h"
#include "LegacyScreenPercentageDriver.h"
#include "AI/Navigation/NavigationSystem.h"
#include "CanvasItem.h"
#include "Engine/Canvas.h"
#include "GameFramework/Volume.h"
#include "Components/SkeletalMeshComponent.h"
#include "UObject/UObjectIterator.h"
#include "UObject/Package.h"
#include "SceneManagement.h"
#include "Particles/ParticleSystemComponent.h"
#include "Engine/NetDriver.h"
#include "Engine/LocalPlayer.h"
#include "ContentStreaming.h"
#include "UnrealEngine.h"
#include "EngineUtils.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/SViewport.h"
#include "Engine/Console.h"
#include "GameFramework/HUD.h"
#include "FXSystem.h"
#include "SubtitleManager.h"
#include "ImageUtils.h"
#include "SceneViewExtension.h"
#include "IHeadMountedDisplay.h"
#include "IXRTrackingSystem.h"
#include "EngineModule.h"
#include "AudioDeviceManager.h"
#include "AudioDevice.h"
#include "Sound/SoundWave.h"
#include "HighResScreenshot.h"
#include "BufferVisualizationData.h"
#include "GameFramework/InputSettings.h"
#include "Components/LineBatchComponent.h"
#include "Debug/DebugDrawService.h"
#include "Components/BrushComponent.h"
#include "Engine/GameEngine.h"
#include "Logging/MessageLog.h"
#include "Blueprint/UserWidget.h"
#include "GameFramework/GameUserSettings.h"
#include "Engine/UserInterfaceSettings.h"
#include "Slate/SceneViewport.h"
#include "Slate/SGameLayerManager.h"
#include "ActorEditorUtils.h"
#include "ComponentRecreateRenderStateContext.h"
#include "Framework/Application/HardwareCursor.h"
#include "DynamicResolutionState.h"
#include "CsvProfiler.h"
#define LOCTEXT_NAMESPACE "GameViewport"
/** This variable allows forcing full screen of the first player controller viewport, even if there are multiple controllers plugged in and no cinematic playing. */
bool GForceFullscreen = false;
/** Whether to visualize the lightmap selected by the Debug Camera. */
extern ENGINE_API bool GShowDebugSelectedLightmap;
/** The currently selected component in the actor. */
extern ENGINE_API UPrimitiveComponent* GDebugSelectedComponent;
/** The lightmap used by the currently selected component, if it's a static mesh component. */
extern ENGINE_API class FLightMap2D* GDebugSelectedLightmap;
/** Delegate called at the end of the frame when a screenshot is captured */
FOnScreenshotCaptured UGameViewportClient::ScreenshotCapturedDelegate;
/** Delegate called when the game viewport is created. */
FSimpleMulticastDelegate UGameViewportClient::CreatedDelegate;
/** A list of all the stat names which are enabled for this viewport (static so they persist between runs) */
TArray<FString> UGameViewportClient::EnabledStats;
/** Those sound stat flags which are enabled on this viewport */
FViewportClient::ESoundShowFlags::Type UGameViewportClient::SoundShowFlags = FViewportClient::ESoundShowFlags::Disabled;
/**
* UI Stats
*/
DECLARE_CYCLE_STAT(TEXT("UI Drawing Time"),STAT_UIDrawingTime,STATGROUP_UI);
static TAutoConsoleVariable<int32> CVarSetBlackBordersEnabled(
TEXT("r.BlackBorders"),
0,
TEXT("To draw black borders around the rendered image\n")
TEXT("(prevents artifacts from post processing passes that read outside of the image e.g. PostProcessAA)\n")
TEXT("in pixels, 0:off"),
ECVF_Default);
static TAutoConsoleVariable<int32> CVarScreenshotDelegate(
TEXT("r.ScreenshotDelegate"),
1,
TEXT("ScreenshotDelegates prevent processing of incoming screenshot request and break some features. This allows to disable them.\n")
TEXT("Ideally we rework the delegate code to not make that needed.\n")
TEXT(" 0: off\n")
TEXT(" 1: delegates are on (default)"),
ECVF_Default);
static TAutoConsoleVariable<float> CVarSecondaryScreenPercentage( // TODO: make it a user settings instead?
TEXT("r.SecondaryScreenPercentage.GameViewport"),
0,
TEXT("Override secondary screen percentage for game viewport.\n")
TEXT(" 0: Compute secondary screen percentage = 100 / DPIScalefactor automaticaly (default);\n")
TEXT(" 1: override secondary screen percentage."),
ECVF_Default);
/**
* Draw debug info on a game scene view.
*/
class FGameViewDrawer : public FViewElementDrawer
{
public:
/**
* Draws debug info using the given draw interface.
*/
virtual void Draw(const FSceneView* View,FPrimitiveDrawInterface* PDI)
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
// Draw a wireframe sphere around the selected lightmap, if requested.
if ( GShowDebugSelectedLightmap && GDebugSelectedComponent && GDebugSelectedLightmap )
{
float Radius = GDebugSelectedComponent->Bounds.SphereRadius;
int32 Sides = FMath::Clamp<int32>( FMath::TruncToInt(Radius*Radius*4.0f*PI/(80.0f*80.0f)), 8, 200 );
DrawWireSphere( PDI, GDebugSelectedComponent->Bounds.Origin, FColor(255,130,0), GDebugSelectedComponent->Bounds.SphereRadius, Sides, SDPG_Foreground );
}
#endif
}
};
UGameViewportClient::UGameViewportClient(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, EngineShowFlags(ESFIM_Game)
, CurrentBufferVisualizationMode(NAME_None)
, HighResScreenshotDialog(NULL)
, bUseSoftwareCursorWidgets(true)
, bIgnoreInput(false)
, MouseCaptureMode(EMouseCaptureMode::CapturePermanently)
, bHideCursorDuringCapture(false)
, MouseLockMode(EMouseLockMode::LockOnCapture)
, AudioDeviceHandle(INDEX_NONE)
, bHasAudioFocus(false)
, bIsMouseOverClient(false)
{
TitleSafeZone.MaxPercentX = 0.9f;
TitleSafeZone.MaxPercentY = 0.9f;
TitleSafeZone.RecommendedPercentX = 0.8f;
TitleSafeZone.RecommendedPercentY = 0.8f;
bIsPlayInEditorViewport = false;
ViewModeIndex = VMI_Lit;
SplitscreenInfo.Init(FSplitscreenData(), ESplitScreenType::SplitTypeCount);
SplitscreenInfo[ESplitScreenType::None].PlayerData.Add(FPerPlayerSplitscreenData(1.0f, 1.0f, 0.0f, 0.0f));
SplitscreenInfo[ESplitScreenType::TwoPlayer_Horizontal].PlayerData.Add(FPerPlayerSplitscreenData(1.0f, 0.5f, 0.0f, 0.0f));
SplitscreenInfo[ESplitScreenType::TwoPlayer_Horizontal].PlayerData.Add(FPerPlayerSplitscreenData(1.0f, 0.5f, 0.0f, 0.5f));
SplitscreenInfo[ESplitScreenType::TwoPlayer_Vertical].PlayerData.Add(FPerPlayerSplitscreenData(0.5f, 1.0f, 0.0f, 0.0f));
SplitscreenInfo[ESplitScreenType::TwoPlayer_Vertical].PlayerData.Add(FPerPlayerSplitscreenData(0.5f, 1.0f, 0.5f, 0.0f));
SplitscreenInfo[ESplitScreenType::ThreePlayer_FavorTop].PlayerData.Add(FPerPlayerSplitscreenData(1.0f, 0.5f, 0.0f, 0.0f));
SplitscreenInfo[ESplitScreenType::ThreePlayer_FavorTop].PlayerData.Add(FPerPlayerSplitscreenData(0.5f, 0.5f, 0.0f, 0.5f));
SplitscreenInfo[ESplitScreenType::ThreePlayer_FavorTop].PlayerData.Add(FPerPlayerSplitscreenData(0.5f, 0.5f, 0.5f, 0.5f));
SplitscreenInfo[ESplitScreenType::ThreePlayer_FavorBottom].PlayerData.Add(FPerPlayerSplitscreenData(0.5f, 0.5f, 0.0f, 0.0f));
SplitscreenInfo[ESplitScreenType::ThreePlayer_FavorBottom].PlayerData.Add(FPerPlayerSplitscreenData(0.5f, 0.5f, 0.5f, 0.0f));
SplitscreenInfo[ESplitScreenType::ThreePlayer_FavorBottom].PlayerData.Add(FPerPlayerSplitscreenData(1.0f, 0.5f, 0.0f, 0.5f));
SplitscreenInfo[ESplitScreenType::FourPlayer].PlayerData.Add(FPerPlayerSplitscreenData(0.5f, 0.5f, 0.0f, 0.0f));
SplitscreenInfo[ESplitScreenType::FourPlayer].PlayerData.Add(FPerPlayerSplitscreenData(0.5f, 0.5f, 0.5f, 0.0f));
SplitscreenInfo[ESplitScreenType::FourPlayer].PlayerData.Add(FPerPlayerSplitscreenData(0.5f, 0.5f, 0.0f, 0.5f));
SplitscreenInfo[ESplitScreenType::FourPlayer].PlayerData.Add(FPerPlayerSplitscreenData(0.5f, 0.5f, 0.5f, 0.5f));
MaxSplitscreenPlayers = 4;
bSuppressTransitionMessage = true;
if (HasAnyFlags(RF_ClassDefaultObject) == false)
{
StatUnitData = new FStatUnitData();
StatHitchesData = new FStatHitchesData();
FCoreDelegates::StatCheckEnabled.AddUObject(this, &UGameViewportClient::HandleViewportStatCheckEnabled);
FCoreDelegates::StatEnabled.AddUObject(this, &UGameViewportClient::HandleViewportStatEnabled);
FCoreDelegates::StatDisabled.AddUObject(this, &UGameViewportClient::HandleViewportStatDisabled);
FCoreDelegates::StatDisableAll.AddUObject(this, &UGameViewportClient::HandleViewportStatDisableAll);
#if WITH_EDITOR
if (GIsEditor)
{
FSlateApplication::Get().OnWindowDPIScaleChanged().AddUObject(this, &UGameViewportClient::HandleWindowDPIScaleChanged);
}
#endif
}
}
UGameViewportClient::UGameViewportClient(FVTableHelper& Helper)
: Super(Helper)
, EngineShowFlags(ESFIM_Game)
, CurrentBufferVisualizationMode(NAME_None)
, HighResScreenshotDialog(NULL)
, bIgnoreInput(false)
, MouseCaptureMode(EMouseCaptureMode::CapturePermanently)
, bHideCursorDuringCapture(false)
, MouseLockMode(EMouseLockMode::LockOnCapture)
, AudioDeviceHandle(INDEX_NONE)
, bHasAudioFocus(false)
{
}
UGameViewportClient::~UGameViewportClient()
{
if (EngineShowFlags.Collision)
{
EngineShowFlags.SetCollision(false);
ToggleShowCollision();
}
FCoreDelegates::StatCheckEnabled.RemoveAll(this);
FCoreDelegates::StatEnabled.RemoveAll(this);
FCoreDelegates::StatDisabled.RemoveAll(this);
FCoreDelegates::StatDisableAll.RemoveAll(this);
#if WITH_EDITOR
if (GIsEditor && FSlateApplication::IsInitialized())
{
FSlateApplication::Get().OnWindowDPIScaleChanged().RemoveAll(this);
}
#endif
if (StatHitchesData)
{
delete StatHitchesData;
StatHitchesData = NULL;
}
if (StatUnitData)
{
delete StatUnitData;
StatUnitData = NULL;
}
}
void UGameViewportClient::PostInitProperties()
{
Super::PostInitProperties();
EngineShowFlags = FEngineShowFlags(ESFIM_Game);
}
void UGameViewportClient::BeginDestroy()
{
if (GEngine)
{
class FAudioDeviceManager* AudioDeviceManager = GEngine->GetAudioDeviceManager();
if (AudioDeviceManager)
{
AudioDeviceManager->ShutdownAudioDevice(AudioDeviceHandle);
}
}
RemoveAllViewportWidgets();
Super::BeginDestroy();
}
void UGameViewportClient::DetachViewportClient()
{
ViewportConsole = NULL;
ResetHardwareCursorStates();
RemoveAllViewportWidgets();
RemoveFromRoot();
}
FSceneViewport* UGameViewportClient::GetGameViewport()
{
return static_cast<FSceneViewport*>(Viewport);
}
const FSceneViewport* UGameViewportClient::GetGameViewport() const
{
return static_cast<FSceneViewport*>(Viewport);
}
TSharedPtr<class SViewport> UGameViewportClient::GetGameViewportWidget()
{
FSceneViewport* SceneViewport = GetGameViewport();
if (SceneViewport != nullptr)
{
TWeakPtr<SViewport> WeakViewportWidget = SceneViewport->GetViewportWidget();
TSharedPtr<SViewport> ViewportWidget = WeakViewportWidget.Pin();
return ViewportWidget;
}
return nullptr;
}
void UGameViewportClient::Tick( float DeltaTime )
{
TickDelegate.Broadcast(DeltaTime);
}
FString UGameViewportClient::ConsoleCommand( const FString& Command)
{
FString TruncatedCommand = Command.Left(1000);
FConsoleOutputDevice ConsoleOut(ViewportConsole);
Exec( GetWorld(), *TruncatedCommand,ConsoleOut);
return *ConsoleOut;
}
void UGameViewportClient::SetEnabledStats(const TArray<FString>& InEnabledStats)
{
if (FPlatformProcess::SupportsMultithreading())
{
EnabledStats = InEnabledStats;
}
else
{
UE_LOG(LogPlayerManagement, Warning, TEXT("WARNING: Stats disabled for non multi-threading platforms"));
}
#if !UE_BUILD_SHIPPING
if (UWorld* MyWorld = GetWorld())
{
if (FAudioDevice* AudioDevice = MyWorld->GetAudioDevice())
{
AudioDevice->ResolveDesiredStats(this);
}
}
#endif
}
void UGameViewportClient::Init(struct FWorldContext& WorldContext, UGameInstance* OwningGameInstance, bool bCreateNewAudioDevice)
{
// set reference to world context
WorldContext.AddRef(World);
// remember our game instance
GameInstance = OwningGameInstance;
// Set the projects default viewport mouse capture mode
MouseCaptureMode = GetDefault<UInputSettings>()->DefaultViewportMouseCaptureMode;
MouseLockMode = GetDefault<UInputSettings>()->DefaultViewportMouseLockMode;
// Create the cursor Widgets
UUserInterfaceSettings* UISettings = GetMutableDefault<UUserInterfaceSettings>(UUserInterfaceSettings::StaticClass());
if (GEngine)
{
FAudioDeviceManager* AudioDeviceManager = GEngine->GetAudioDeviceManager();
if (AudioDeviceManager)
{
FAudioDeviceManager::FCreateAudioDeviceResults NewDeviceResults;
if (AudioDeviceManager->CreateAudioDevice(bCreateNewAudioDevice, NewDeviceResults))
{
AudioDeviceHandle = NewDeviceResults.Handle;
#if !UE_BUILD_SHIPPING
if (NewDeviceResults.bNewDevice)
{
NewDeviceResults.AudioDevice->ResolveDesiredStats(this);
}
#endif // UE_BUILD_SHIPPING
// Set the base mix of the new device based on the world settings of the world
if (World)
{
NewDeviceResults.AudioDevice->SetDefaultBaseSoundMix(World->GetWorldSettings()->DefaultBaseSoundMix);
// Set the world's audio device handle to use so that sounds which play in that world will use the correct audio device
World->SetAudioDeviceHandle(AudioDeviceHandle);
}
// Set this audio device handle on the world context so future world's set onto the world context
// will pass the audio device handle to them and audio will play on the correct audio device
WorldContext.AudioDeviceHandle = AudioDeviceHandle;
}
}
}
// Set all the software cursors.
for ( auto& Entry : UISettings->SoftwareCursors )
{
AddSoftwareCursor(Entry.Key, Entry.Value);
}
// Set all the hardware cursors.
for ( auto& Entry : UISettings->HardwareCursors )
{
SetHardwareCursor(Entry.Key, Entry.Value.CursorPath, Entry.Value.HotSpot);
}
}
void UGameViewportClient::RebuildCursors()
{
UUserInterfaceSettings* UISettings = GetMutableDefault<UUserInterfaceSettings>(UUserInterfaceSettings::StaticClass());
// Set all the software cursors.
for (auto& Entry : UISettings->SoftwareCursors)
{
AddSoftwareCursor(Entry.Key, Entry.Value);
}
// Set all the hardware cursors.
for (auto& Entry : UISettings->HardwareCursors)
{
SetHardwareCursor(Entry.Key, Entry.Value.CursorPath, Entry.Value.HotSpot);
}
}
UWorld* UGameViewportClient::GetWorld() const
{
return World;
}
UGameInstance* UGameViewportClient::GetGameInstance() const
{
return GameInstance;
}
bool UGameViewportClient::InputKey(FViewport* InViewport, int32 ControllerId, FKey Key, EInputEvent EventType, float AmountDepressed, bool bGamepad)
{
if (IgnoreInput())
{
return ViewportConsole ? ViewportConsole->InputKey(ControllerId, Key, EventType, AmountDepressed, bGamepad) : false;
}
if ((Key == EKeys::Enter && EventType == EInputEvent::IE_Pressed && FSlateApplication::Get().GetModifierKeys().IsAltDown() && GetDefault<UInputSettings>()->bAltEnterTogglesFullscreen)
|| (IsRunningGame() && Key == EKeys::F11 && EventType == EInputEvent::IE_Pressed && GetDefault<UInputSettings>()->bF11TogglesFullscreen))
{
HandleToggleFullscreenCommand();
return true;
}
const int32 NumLocalPlayers = World ? World->GetGameInstance()->GetNumLocalPlayers() : 0;
if (NumLocalPlayers > 1 && Key.IsGamepadKey() && GetDefault<UGameMapsSettings>()->bOffsetPlayerGamepadIds)
{
++ControllerId;
}
else if (InViewport->IsPlayInEditorViewport() && Key.IsGamepadKey())
{
GEngine->RemapGamepadControllerIdForPIE(this, ControllerId);
}
#if WITH_EDITOR
// Give debugger commands a chance to process key binding
if (GameViewportInputKeyDelegate.IsBound())
{
if ( GameViewportInputKeyDelegate.Execute(Key, FSlateApplication::Get().GetModifierKeys(), EventType) )
{
return true;
}
}
#endif
// route to subsystems that care
bool bResult = ( ViewportConsole ? ViewportConsole->InputKey(ControllerId, Key, EventType, AmountDepressed, bGamepad) : false );
if (!bResult)
{
ULocalPlayer* const TargetPlayer = GEngine->GetLocalPlayerFromControllerId(this, ControllerId);
if (TargetPlayer && TargetPlayer->PlayerController)
{
bResult = TargetPlayer->PlayerController->InputKey(Key, EventType, AmountDepressed, bGamepad);
}
// A gameviewport is always considered to have responded to a mouse buttons to avoid throttling
if (!bResult && Key.IsMouseButton())
{
bResult = true;
}
}
// For PIE, let the next PIE window handle the input if none of our players did
// (this allows people to use multiple controllers to control each window)
if (!bResult && ControllerId > NumLocalPlayers - 1 && InViewport->IsPlayInEditorViewport())
{
UGameViewportClient *NextViewport = GEngine->GetNextPIEViewport(this);
if (NextViewport)
{
bResult = NextViewport->InputKey(InViewport, ControllerId - NumLocalPlayers, Key, EventType, AmountDepressed, bGamepad);
}
}
return bResult;
}
bool UGameViewportClient::InputAxis(FViewport* InViewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime, int32 NumSamples, bool bGamepad)
{
if (IgnoreInput())
{
return false;
}
const int32 NumLocalPlayers = World->GetGameInstance()->GetNumLocalPlayers();
if (NumLocalPlayers > 1 && Key.IsGamepadKey() && GetDefault<UGameMapsSettings>()->bOffsetPlayerGamepadIds)
{
++ControllerId;
}
else if (InViewport->IsPlayInEditorViewport() && Key.IsGamepadKey())
{
GEngine->RemapGamepadControllerIdForPIE(this, ControllerId);
}
bool bResult = false;
// Don't allow mouse/joystick input axes while in PIE and the console has forced the cursor to be visible. It's
// just distracting when moving the mouse causes mouse look while you are trying to move the cursor over a button
// in the editor!
if( !( InViewport->IsSlateViewport() && InViewport->IsPlayInEditorViewport() ) || ViewportConsole == NULL || !ViewportConsole->ConsoleActive() )
{
// route to subsystems that care
if (ViewportConsole != NULL)
{
bResult = ViewportConsole->InputAxis(ControllerId, Key, Delta, DeltaTime, NumSamples, bGamepad);
}
if (!bResult)
{
ULocalPlayer* const TargetPlayer = GEngine->GetLocalPlayerFromControllerId(this, ControllerId);
if (TargetPlayer && TargetPlayer->PlayerController)
{
bResult = TargetPlayer->PlayerController->InputAxis(Key, Delta, DeltaTime, NumSamples, bGamepad);
}
}
// For PIE, let the next PIE window handle the input if none of our players did
// (this allows people to use multiple controllers to control each window)
if (!bResult && ControllerId > NumLocalPlayers - 1 && InViewport->IsPlayInEditorViewport())
{
UGameViewportClient *NextViewport = GEngine->GetNextPIEViewport(this);
if (NextViewport)
{
bResult = NextViewport->InputAxis(InViewport, ControllerId - NumLocalPlayers, Key, Delta, DeltaTime, NumSamples, bGamepad);
}
}
if( InViewport->IsSlateViewport() && InViewport->IsPlayInEditorViewport() )
{
// Absorb all keys so game input events are not routed to the Slate editor frame
bResult = true;
}
}
return bResult;
}
bool UGameViewportClient::InputChar(FViewport* InViewport, int32 ControllerId, TCHAR Character)
{
// should probably just add a ctor to FString that takes a TCHAR
FString CharacterString;
CharacterString += Character;
//Always route to the console
bool bResult = (ViewportConsole ? ViewportConsole->InputChar(ControllerId, CharacterString) : false);
if (IgnoreInput())
{
return bResult;
}
// route to subsystems that care
if (!bResult && InViewport->IsSlateViewport() && InViewport->IsPlayInEditorViewport())
{
// Absorb all keys so game input events are not routed to the Slate editor frame
bResult = true;
}
return bResult;
}
bool UGameViewportClient::InputTouch(FViewport* InViewport, int32 ControllerId, uint32 Handle, ETouchType::Type Type, const FVector2D& TouchLocation, FDateTime DeviceTimestamp, uint32 TouchpadIndex)
{
if (IgnoreInput())
{
return false;
}
// route to subsystems that care
bool bResult = (ViewportConsole ? ViewportConsole->InputTouch(ControllerId, Handle, Type, TouchLocation, DeviceTimestamp, TouchpadIndex) : false);
if (!bResult)
{
ULocalPlayer* const TargetPlayer = GEngine->GetLocalPlayerFromControllerId(this, ControllerId);
if (TargetPlayer && TargetPlayer->PlayerController)
{
bResult = TargetPlayer->PlayerController->InputTouch(Handle, Type, TouchLocation, DeviceTimestamp, TouchpadIndex);
}
}
return bResult;
}
bool UGameViewportClient::InputMotion(FViewport* InViewport, int32 ControllerId, const FVector& Tilt, const FVector& RotationRate, const FVector& Gravity, const FVector& Acceleration)
{
if (IgnoreInput())
{
return false;
}
// route to subsystems that care
bool bResult = false;
ULocalPlayer* const TargetPlayer = GEngine->GetLocalPlayerFromControllerId(this, ControllerId);
if (TargetPlayer && TargetPlayer->PlayerController)
{
bResult = TargetPlayer->PlayerController->InputMotion(Tilt, RotationRate, Gravity, Acceleration);
}
return bResult;
}
void UGameViewportClient::SetIsSimulateInEditorViewport(bool bInIsSimulateInEditorViewport)
{
#if PLATFORM_DESKTOP || PLATFORM_HTML5
if (GetDefault<UInputSettings>()->bUseMouseForTouch)
{
FSlateApplication::Get().SetGameIsFakingTouchEvents(!bInIsSimulateInEditorViewport);
}
#endif
for (ULocalPlayer* LocalPlayer : GetOuterUEngine()->GetGamePlayers(this))
{
if (LocalPlayer->PlayerController)
{
if (bInIsSimulateInEditorViewport)
{
LocalPlayer->PlayerController->CleanupGameViewport();
}
else
{
LocalPlayer->PlayerController->CreateTouchInterface();
}
}
}
}
float UGameViewportClient::UpdateViewportClientWindowDPIScale() const
{
TSharedPtr<SWindow> PinnedWindow = Window.Pin();
float DPIScale = 1.0f;
if(PinnedWindow.IsValid() && PinnedWindow->GetNativeWindow().IsValid())
{
DPIScale = PinnedWindow->GetNativeWindow()->GetDPIScaleFactor();
}
return DPIScale;
}
void UGameViewportClient::MouseEnter(FViewport* InViewport, int32 x, int32 y)
{
Super::MouseEnter(InViewport, x, y);
#if PLATFORM_DESKTOP || PLATFORM_HTML5
if (GetDefault<UInputSettings>()->bUseMouseForTouch && !GetGameViewport()->GetPlayInEditorIsSimulate())
{
FSlateApplication::Get().SetGameIsFakingTouchEvents(true);
}
#endif
// Replace all the cursors.
TSharedPtr<ICursor> PlatformCursor = FSlateApplication::Get().GetPlatformCursor();
if ( ICursor* Cursor = PlatformCursor.Get() )
{
for ( auto& Entry : HardwareCursors )
{
Cursor->SetTypeShape(Entry.Key, Entry.Value->GetHandle());
}
}
bIsMouseOverClient = true;
}
void UGameViewportClient::MouseLeave(FViewport* InViewport)
{
Super::MouseLeave(InViewport);
if (InViewport && GetDefault<UInputSettings>()->bUseMouseForTouch)
{
// Only send the touch end event if we're not drag/dropping, as that will end the drag/drop operation.
if ( !FSlateApplication::Get().IsDragDropping() )
{
FIntPoint LastViewportCursorPos;
InViewport->GetMousePos(LastViewportCursorPos, false);
#if PLATFORM_DESKTOP || PLATFORM_HTML5
FVector2D CursorPos(LastViewportCursorPos.X, LastViewportCursorPos.Y);
FSlateApplication::Get().SetGameIsFakingTouchEvents(false, &CursorPos);
#endif
}
}
#if WITH_EDITOR
// NOTE: Only do this in editor builds where the editor is running.
// We don't care about bothering to clear them otherwise, and it may negatively impact
// things like drag/drop, since those would 'leave' the viewport.
if ( !FSlateApplication::Get().IsDragDropping() )
{
bIsMouseOverClient = false;
ResetHardwareCursorStates();
}
#endif
}
void UGameViewportClient::ResetHardwareCursorStates()
{
// clear all the overridden hardware cursors
TSharedPtr<ICursor> PlatformCursor = FSlateApplication::Get().GetPlatformCursor();
if (ICursor* Cursor = PlatformCursor.Get())
{
for (auto& Entry : HardwareCursors)
{
Cursor->SetTypeShape(Entry.Key, nullptr);
}
}
}
bool UGameViewportClient::GetMousePosition(FVector2D& MousePosition) const
{
bool bGotMousePosition = false;
if (Viewport && FSlateApplication::Get().IsMouseAttached())
{
FIntPoint MousePos;
Viewport->GetMousePos(MousePos);
if (MousePos.X >= 0 && MousePos.Y >= 0)
{
MousePosition = FVector2D(MousePos);
bGotMousePosition = true;
}
}
return bGotMousePosition;
}
bool UGameViewportClient::RequiresUncapturedAxisInput() const
{
bool bRequired = false;
if (Viewport != NULL && Viewport->HasFocus())
{
if (ViewportConsole && ViewportConsole->ConsoleActive())
{
bRequired = true;
}
else if (GameInstance && GameInstance->GetFirstLocalPlayerController())
{
bRequired = GameInstance->GetFirstLocalPlayerController()->ShouldShowMouseCursor();
}
}
return bRequired;
}
EMouseCursor::Type UGameViewportClient::GetCursor(FViewport* InViewport, int32 X, int32 Y)
{
// If the viewport isn't active or the console is active we don't want to override the cursor
if (!FSlateApplication::Get().IsActive() || (!InViewport->HasMouseCapture() && !InViewport->HasFocus()) || (ViewportConsole && ViewportConsole->ConsoleActive()))
{
return EMouseCursor::Default;
}
else if (GameInstance && GameInstance->GetFirstLocalPlayerController())
{
return GameInstance->GetFirstLocalPlayerController()->GetMouseCursor();
}
return FViewportClient::GetCursor(InViewport, X, Y);
}
void UGameViewportClient::SetVirtualCursorWidget(EMouseCursor::Type Cursor, UUserWidget* UserWidget)
{
if (ensure(UserWidget))
{
if (CursorWidgets.Contains(Cursor))
{
TSharedRef<SWidget>* Widget = CursorWidgets.Find(Cursor);
(*Widget) = UserWidget->TakeWidget();
}
else
{
CursorWidgets.Add(Cursor, UserWidget->TakeWidget());
}
}
}
void UGameViewportClient::AddSoftwareCursor(EMouseCursor::Type Cursor, const FSoftClassPath& CursorClass)
{
if (ensureMsgf(CursorClass.IsValid(), TEXT("UGameViewportClient::AddCusor: Cursor class is not valid!")))
{
UClass* Class = CursorClass.TryLoadClass<UUserWidget>();
if (Class)
{
UUserWidget* UserWidget = CreateWidget<UUserWidget>(GetGameInstance(), Class);
AddCursorWidget(Cursor, UserWidget);
}
else
{
UE_LOG(LogPlayerManagement, Warning, TEXT("UGameViewportClient::AddCursor: Could not load cursor class %s."), *CursorClass.GetAssetName());
}
}
}
void UGameViewportClient::AddCursorWidget(EMouseCursor::Type Cursor, class UUserWidget* CursorWidget)
{
if (ensure(CursorWidget))
{
CursorWidgets.Add(Cursor, CursorWidget->TakeWidget());
}
}
TOptional<TSharedRef<SWidget>> UGameViewportClient::MapCursor(FViewport* InViewport, const FCursorReply& CursorReply)
{
if (bUseSoftwareCursorWidgets)
{
if (CursorReply.GetCursorType() != EMouseCursor::None)
{
const TSharedRef<SWidget>* CursorWidgetPtr = CursorWidgets.Find(CursorReply.GetCursorType());
if (CursorWidgetPtr != nullptr)
{
return *CursorWidgetPtr;
}
else
{
UE_LOG(LogPlayerManagement, Warning, TEXT("UGameViewportClient::MapCursor: Could not find cursor to map to %d."),int32(CursorReply.GetCursorType()));
}
}
}
return TOptional<TSharedRef<SWidget>>();
}
void UGameViewportClient::SetDropDetail(float DeltaSeconds)
{
if (GEngine && GetWorld())
{
float FrameTime = 0.0f;
if (FPlatformProperties::SupportsWindowedMode() == false)
{
FrameTime = FPlatformTime::ToSeconds(FMath::Max3<uint32>( GRenderThreadTime, GGameThreadTime, GGPUFrameTime ));
// If DeltaSeconds is bigger than 34 ms we can take it into account as we're not VSYNCing in that case.
if( DeltaSeconds > 0.034 )
{
FrameTime = FMath::Max( FrameTime, DeltaSeconds );
}
}
else
{
FrameTime = DeltaSeconds;
}
const float FrameRate = FrameTime > 0 ? 1 / FrameTime : 0;
// When using FixedFrameRate, FrameRate here becomes FixedFrameRate (even if actual framerate is smaller).
const bool bTimeIsManipulated = FApp::IsBenchmarking() || FApp::UseFixedTimeStep() || GEngine->bUseFixedFrameRate;
// Drop detail if framerate is below threshold.
GetWorld()->bDropDetail = FrameRate < FMath::Clamp(GEngine->MinDesiredFrameRate, 1.f, 100.f) && !bTimeIsManipulated;
GetWorld()->bAggressiveLOD = FrameRate < FMath::Clamp(GEngine->MinDesiredFrameRate - 5.f, 1.f, 100.f) && !bTimeIsManipulated;
// this is slick way to be able to do something based on the frametime and whether we are bound by one thing or another
#if 0
// so where we check to see if we are above some threshold and below 150 ms (any thing above that is usually blocking loading of some sort)
// also we don't want to do the auto trace when we are blocking on async loading
if ((0.070 < FrameTime) && (FrameTime < 0.150) && IsAsyncLoading() == false && GetWorld()->bRequestedBlockOnAsyncLoading == false && (GetWorld()->GetTimeSeconds() > 30.0f))
{
// now check to see if we have done a trace in the last 30 seconds otherwise we will just trace ourselves to death
static float LastTraceTime = -9999.0f;
if( (LastTraceTime+30.0f < GetWorld()->GetTimeSeconds()))
{
LastTraceTime = GetWorld()->GetTimeSeconds();
UE_LOG(LogPlayerManagement, Warning, TEXT("Auto Trace initiated!! FrameTime: %f"), FrameTime );
// do what ever action you want here (e.g. trace <type>, GShouldLogOutAFrameOfMoveActor = true, c.f. LevelTick.cpp for more)
//GShouldLogOutAFrameOfMoveActor = true;
#if !WITH_EDITORONLY_DATA
UE_LOG(LogPlayerManagement, Warning, TEXT(" GGameThreadTime: %d GRenderThreadTime: %d "), GGameThreadTime, GRenderThreadTime );
#endif // WITH_EDITORONLY_DATA
}
}
#endif // 0
}
}
void UGameViewportClient::SetViewportFrame( FViewportFrame* InViewportFrame )
{
ViewportFrame = InViewportFrame;
SetViewport( ViewportFrame ? ViewportFrame->GetViewport() : NULL );
}
void UGameViewportClient::SetViewport( FViewport* InViewport )
{
FViewport* PreviousViewport = Viewport;
Viewport = InViewport;
if ( PreviousViewport == NULL && Viewport != NULL )
{
// ensure that the player's Origin and Size members are initialized the moment we get a viewport
LayoutPlayers();
}
}
void UGameViewportClient::GetViewportSize( FVector2D& out_ViewportSize ) const
{
if ( Viewport != NULL )
{
out_ViewportSize.X = Viewport->GetSizeXY().X;
out_ViewportSize.Y = Viewport->GetSizeXY().Y;
}
}
bool UGameViewportClient::IsFullScreenViewport() const
{
if (Viewport != nullptr)
{
return Viewport->IsFullscreen();
}
return false;
}
bool UGameViewportClient::ShouldForceFullscreenViewport() const
{
bool bResult = false;
if ( GForceFullscreen )
{
bResult = true;
}
else if ( GetOuterUEngine()->GetNumGamePlayers(this) == 0 )
{
bResult = true;
}
else if ( UWorld* MyWorld = GetWorld() )
{
if ( MyWorld->bIsDefaultLevel )
{
bResult = true;
}
else if ( GameInstance )
{
APlayerController* PlayerController = GameInstance->GetFirstLocalPlayerController();
if( ( PlayerController ) && ( PlayerController->bCinematicMode ) )
{
bResult = true;
}
}
}
return bResult;
}
/** Util to find named canvas in transient package, and create if not found */
static UCanvas* GetCanvasByName(FName CanvasName)
{
// Cache to avoid FString/FName conversions/compares
static TMap<FName, UCanvas*> CanvasMap;
UCanvas** FoundCanvas = CanvasMap.Find(CanvasName);
if (!FoundCanvas)
{
UCanvas* CanvasObject = FindObject<UCanvas>(GetTransientPackage(),*CanvasName.ToString());
if( !CanvasObject )
{
CanvasObject = NewObject<UCanvas>(GetTransientPackage(), CanvasName);
CanvasObject->AddToRoot();
}
CanvasMap.Add(CanvasName, CanvasObject);
return CanvasObject;
}
return *FoundCanvas;
}
void UGameViewportClient::Draw(FViewport* InViewport, FCanvas* SceneCanvas)
{
//Valid SceneCanvas is required. Make this explicit.
check(SceneCanvas);
BeginDrawDelegate.Broadcast();
const bool bStereoRendering = GEngine->IsStereoscopic3D(InViewport);
FCanvas* DebugCanvas = InViewport->GetDebugCanvas();
// Create a temporary canvas if there isn't already one.
static FName CanvasObjectName(TEXT("CanvasObject"));
UCanvas* CanvasObject = GetCanvasByName(CanvasObjectName);
CanvasObject->Canvas = SceneCanvas;
// Create temp debug canvas object
FIntPoint DebugCanvasSize = InViewport->GetSizeXY();
static FName DebugCanvasObjectName(TEXT("DebugCanvasObject"));
UCanvas* DebugCanvasObject = GetCanvasByName(DebugCanvasObjectName);
DebugCanvasObject->Init(DebugCanvasSize.X, DebugCanvasSize.Y, NULL, DebugCanvas);
if (DebugCanvas)
{
DebugCanvas->SetScaledToRenderTarget(bStereoRendering);
DebugCanvas->SetStereoRendering(bStereoRendering);
}
if (SceneCanvas)
{
SceneCanvas->SetScaledToRenderTarget(bStereoRendering);
SceneCanvas->SetStereoRendering(bStereoRendering);
}
bool bUIDisableWorldRendering = false;
FGameViewDrawer GameViewDrawer;
UWorld* MyWorld = GetWorld();
// create the view family for rendering the world scene to the viewport's render target
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
InViewport,
MyWorld->Scene,
EngineShowFlags)
.SetRealtimeUpdate(true));
#if WITH_EDITOR
if (GIsEditor)
{
// Force enable view family show flag for HighDPI derived's screen percentage.
ViewFamily.EngineShowFlags.ScreenPercentage = true;
}
#endif
ViewFamily.ViewExtensions = GEngine->ViewExtensions->GatherActiveExtensions(InViewport);
for (auto ViewExt : ViewFamily.ViewExtensions)
{
ViewExt->SetupViewFamily(ViewFamily);
}
if (bStereoRendering && GEngine->XRSystem.IsValid() && GEngine->XRSystem->GetHMDDevice())
{
// Allow HMD to modify screen settings
GEngine->XRSystem->GetHMDDevice()->UpdateScreenSettings(Viewport);
}
ESplitScreenType::Type SplitScreenConfig = GetCurrentSplitscreenConfiguration();
ViewFamily.ViewMode = EViewModeIndex(ViewModeIndex);
EngineShowFlagOverride(ESFIM_Game, ViewFamily.ViewMode, ViewFamily.EngineShowFlags, NAME_None, SplitScreenConfig != ESplitScreenType::None);
if (ViewFamily.EngineShowFlags.VisualizeBuffer && AllowDebugViewmodes())
{
// Process the buffer visualization console command
FName NewBufferVisualizationMode = NAME_None;
static IConsoleVariable* ICVar = IConsoleManager::Get().FindConsoleVariable(FBufferVisualizationData::GetVisualizationTargetConsoleCommandName());
if (ICVar)
{
static const FName OverviewName = TEXT("Overview");
FString ModeNameString = ICVar->GetString();
FName ModeName = *ModeNameString;
if (ModeNameString.IsEmpty() || ModeName == OverviewName || ModeName == NAME_None)
{
NewBufferVisualizationMode = NAME_None;
}
else
{
if (GetBufferVisualizationData().GetMaterial(ModeName) == NULL)
{
// Mode is out of range, so display a message to the user, and reset the mode back to the previous valid one
UE_LOG(LogConsoleResponse, Warning, TEXT("Buffer visualization mode '%s' does not exist"), *ModeNameString);
NewBufferVisualizationMode = CurrentBufferVisualizationMode;
// todo: cvars are user settings, here the cvar state is used to avoid log spam and to auto correct for the user (likely not what the user wants)
ICVar->Set(*NewBufferVisualizationMode.GetPlainNameString(), ECVF_SetByCode);
}
else
{
NewBufferVisualizationMode = ModeName;
}
}
}
if (NewBufferVisualizationMode != CurrentBufferVisualizationMode)
{
CurrentBufferVisualizationMode = NewBufferVisualizationMode;
}
}
TMap<ULocalPlayer*,FSceneView*> PlayerViewMap;
FAudioDevice* AudioDevice = MyWorld->GetAudioDevice();
TArray<FSceneView*> Views;
for (FLocalPlayerIterator Iterator(GEngine, MyWorld); Iterator; ++Iterator)
{
ULocalPlayer* LocalPlayer = *Iterator;
if (LocalPlayer)
{
APlayerController* PlayerController = LocalPlayer->PlayerController;
const bool bEnableStereo = GEngine->IsStereoscopic3D(InViewport);
const int32 NumViews = bStereoRendering ? ((ViewFamily.IsMonoscopicFarFieldEnabled()) ? 3 : GEngine->StereoRenderingDevice->GetDesiredNumberOfViews(bStereoRendering)) : 1;
for (int32 i = 0; i < NumViews; ++i)
{
// Calculate the player's view information.
FVector ViewLocation;
FRotator ViewRotation;
EStereoscopicPass PassType = bStereoRendering ? GEngine->StereoRenderingDevice->GetViewPassForIndex(bStereoRendering, i) : eSSP_FULL;
FSceneView* View = LocalPlayer->CalcSceneView(&ViewFamily, ViewLocation, ViewRotation, InViewport, &GameViewDrawer, PassType);
if (View)
{
Views.Add(View);
if (View->Family->EngineShowFlags.Wireframe)
{
// Wireframe color is emissive-only, and mesh-modifying materials do not use material substitution, hence...
View->DiffuseOverrideParameter = FVector4(0.f, 0.f, 0.f, 0.f);
View->SpecularOverrideParameter = FVector4(0.f, 0.f, 0.f, 0.f);
}
else if (View->Family->EngineShowFlags.OverrideDiffuseAndSpecular)
{
View->DiffuseOverrideParameter = FVector4(GEngine->LightingOnlyBrightness.R, GEngine->LightingOnlyBrightness.G, GEngine->LightingOnlyBrightness.B, 0.0f);
View->SpecularOverrideParameter = FVector4(.1f, .1f, .1f, 0.0f);
}
else if (View->Family->EngineShowFlags.ReflectionOverride)
{
View->DiffuseOverrideParameter = FVector4(0.f, 0.f, 0.f, 0.f);
View->SpecularOverrideParameter = FVector4(1, 1, 1, 0.0f);
View->NormalOverrideParameter = FVector4(0, 0, 1, 0.0f);
View->RoughnessOverrideParameter = FVector2D(0.0f, 0.0f);
}
if (!View->Family->EngineShowFlags.Diffuse)
{
View->DiffuseOverrideParameter = FVector4(0.f, 0.f, 0.f, 0.f);
}
if (!View->Family->EngineShowFlags.Specular)
{
View->SpecularOverrideParameter = FVector4(0.f, 0.f, 0.f, 0.f);
}
View->CurrentBufferVisualizationMode = CurrentBufferVisualizationMode;
View->CameraConstrainedViewRect = View->UnscaledViewRect;
// If this is the primary drawing pass, update things that depend on the view location
if (i == 0)
{
// Save the location of the view.
LocalPlayer->LastViewLocation = ViewLocation;
PlayerViewMap.Add(LocalPlayer, View);
// Update the listener.
if (AudioDevice != NULL && PlayerController != NULL)
{
bool bUpdateListenerPosition = true;
// If the main audio device is used for multiple PIE viewport clients, we only
// want to update the main audio device listener position if it is in focus
if (GEngine)
{
FAudioDeviceManager* AudioDeviceManager = GEngine->GetAudioDeviceManager();
// If there is more than one world referencing the main audio device
if (AudioDeviceManager->GetNumMainAudioDeviceWorlds() > 1)
{
uint32 MainAudioDeviceHandle = GEngine->GetAudioDeviceHandle();
if (AudioDevice->DeviceHandle == MainAudioDeviceHandle && !bHasAudioFocus)
{
bUpdateListenerPosition = false;
}
}
}
if (bUpdateListenerPosition)
{
FVector Location;
FVector ProjFront;
FVector ProjRight;
PlayerController->GetAudioListenerPosition(/*out*/ Location, /*out*/ ProjFront, /*out*/ ProjRight);
FTransform ListenerTransform(FRotationMatrix::MakeFromXY(ProjFront, ProjRight));
// Allow the HMD to adjust based on the head position of the player, as opposed to the view location
if (GEngine->XRSystem.IsValid() && GEngine->StereoRenderingDevice.IsValid() && GEngine->StereoRenderingDevice->IsStereoEnabled())
{
const FVector Offset = GEngine->XRSystem->GetAudioListenerOffset();
Location += ListenerTransform.TransformPositionNoScale(Offset);
}
ListenerTransform.SetTranslation(Location);
ListenerTransform.NormalizeRotation();
uint32 ViewportIndex = PlayerViewMap.Num() - 1;
AudioDevice->SetListener(MyWorld, ViewportIndex, ListenerTransform, (View->bCameraCut ? 0.f : MyWorld->GetDeltaSeconds()));
}
}
if (PassType == eSSP_LEFT_EYE)
{
// Save the size of the left eye view, so we can use it to reinitialize the DebugCanvasObject when rendering the console at the end of this method
DebugCanvasSize = View->UnscaledViewRect.Size();
}
}
// Add view information for resource streaming. Allow up to 5X boost for small FOV.
const float StreamingScale = 1.f / FMath::Clamp<float>(View->LODDistanceFactor, .2f, 1.f);
IStreamingManager::Get().AddViewInformation(View->ViewMatrices.GetViewOrigin(), View->UnscaledViewRect.Width(), View->UnscaledViewRect.Width() * View->ViewMatrices.GetProjectionMatrix().M[0][0], StreamingScale);
MyWorld->ViewLocationsRenderedLastFrame.Add(View->ViewMatrices.GetViewOrigin());
}
}
}
}
FinalizeViews(&ViewFamily, PlayerViewMap);
// Update level streaming.
MyWorld->UpdateLevelStreaming();
// Find largest rectangle bounded by all rendered views.
uint32 MinX=InViewport->GetSizeXY().X, MinY=InViewport->GetSizeXY().Y, MaxX=0, MaxY=0;
uint32 TotalArea = 0;
{
for( int32 ViewIndex = 0; ViewIndex < ViewFamily.Views.Num(); ++ViewIndex )
{
const FSceneView* View = ViewFamily.Views[ViewIndex];
FIntRect UpscaledViewRect = View->UnscaledViewRect;
MinX = FMath::Min<uint32>(UpscaledViewRect.Min.X, MinX);
MinY = FMath::Min<uint32>(UpscaledViewRect.Min.Y, MinY);
MaxX = FMath::Max<uint32>(UpscaledViewRect.Max.X, MaxX);
MaxY = FMath::Max<uint32>(UpscaledViewRect.Max.Y, MaxY);
TotalArea += FMath::TruncToInt(UpscaledViewRect.Width()) * FMath::TruncToInt(UpscaledViewRect.Height());
}
// To draw black borders around the rendered image (prevents artifacts from post processing passes that read outside of the image e.g. PostProcessAA)
{
int32 BlackBorders = FMath::Clamp(CVarSetBlackBordersEnabled.GetValueOnGameThread(), 0, 10);
if(ViewFamily.Views.Num() == 1 && BlackBorders)
{
MinX += BlackBorders;
MinY += BlackBorders;
MaxX -= BlackBorders;
MaxY -= BlackBorders;
TotalArea = (MaxX - MinX) * (MaxY - MinY);
}
}
}
// If the views don't cover the entire bounding rectangle, clear the entire buffer.
bool bBufferCleared = false;
if (ViewFamily.Views.Num() == 0 || TotalArea != (MaxX-MinX)*(MaxY-MinY) || bDisableWorldRendering)
{
bool bStereoscopicPass = (ViewFamily.Views.Num() != 0 && ViewFamily.Views[0]->StereoPass != eSSP_FULL);
if (bDisableWorldRendering || !bStereoscopicPass) // TotalArea computation does not work correctly for stereoscopic views
{
SceneCanvas->Clear(FLinearColor::Transparent);
}
bBufferCleared = true;
}
// Force screen percentage show flag to be turned off if not supported.
if (!ViewFamily.SupportsScreenPercentage())
{
ViewFamily.EngineShowFlags.ScreenPercentage = false;
}
// Set up secondary resolution fraction for the view family.
if (!bStereoRendering && ViewFamily.SupportsScreenPercentage())
{
float CustomSecondaruScreenPercentage = CVarSecondaryScreenPercentage.GetValueOnGameThread();
if (CustomSecondaruScreenPercentage > 0.0)
{
// Override secondary resolution fraction with CVar.
ViewFamily.SecondaryViewFraction = FMath::Min(CustomSecondaruScreenPercentage / 100.0f, 1.0f);
}
else
{
// Automatically compute secondary resolution fraction from DPI.
ViewFamily.SecondaryViewFraction = GetDPIDerivedResolutionFraction();
}
check(ViewFamily.SecondaryViewFraction > 0.0f);
}
checkf(ViewFamily.GetScreenPercentageInterface() == nullptr,
TEXT("Some code has tried to set up an alien screen percentage driver, that could be wrong if not supported very well by the RHI."));
// Setup main view family with screen percentage interface by dynamic resolution if screen percentage is supported.
//
// Do not allow dynamic resolution to touch the view family if not supported to ensure there is no possibility to ruin
// game play experience on platforms that does not support it, but have it enabled by mistake.
if (ViewFamily.EngineShowFlags.ScreenPercentage && GEngine->GetDynamicResolutionState() && GEngine->GetDynamicResolutionState()->IsSupported())
{
GEngine->EmitDynamicResolutionEvent(EDynamicResolutionStateEvent::BeginDynamicResolutionRendering);
GEngine->GetDynamicResolutionState()->SetupMainViewFamily(ViewFamily);
#if CSV_PROFILER
float ResolutionFraction = GEngine->GetDynamicResolutionState()->GetResolutionFractionApproximation();
if ( ResolutionFraction >= 0.0f )
{
CSV_CUSTOM_STAT_GLOBAL(DynamicResolutionFraction, ResolutionFraction, ECsvCustomStatOp::Set);
}
#endif
}
// If a screen percentage interface was not set by dynamic resolution, then create one matching legacy behavior.
if (ViewFamily.GetScreenPercentageInterface() == nullptr)
{
bool AllowPostProcessSettingsScreenPercentage = false;
float GlobalResolutionFraction = 1.0f;
if (ViewFamily.EngineShowFlags.ScreenPercentage)
{
// Allow FPostProcessSettings::ScreenPercentage.
AllowPostProcessSettingsScreenPercentage = true;
// Get global view fraction set by r.ScreenPercentage.
GlobalResolutionFraction = FLegacyScreenPercentageDriver::GetCVarResolutionFraction();
}
ViewFamily.SetScreenPercentageInterface(new FLegacyScreenPercentageDriver(
ViewFamily, GlobalResolutionFraction, AllowPostProcessSettingsScreenPercentage));
}
else if (bStereoRendering)
{
// Change screen percentage method to raw output when doing dynamic resolution with VR if not using TAA upsample.
for (FSceneView* View : Views)
{
if (View->PrimaryScreenPercentageMethod == EPrimaryScreenPercentageMethod::SpatialUpscale)
{
View->PrimaryScreenPercentageMethod = EPrimaryScreenPercentageMethod::RawOutput;
}
}
}
// Draw the player views.
if (!bDisableWorldRendering && !bUIDisableWorldRendering && PlayerViewMap.Num() > 0) //-V560
{
GetRendererModule().BeginRenderingViewFamily(SceneCanvas,&ViewFamily);
}
else
{
// Make sure RHI resources get flushed if we're not using a renderer
ENQUEUE_UNIQUE_RENDER_COMMAND( UGameViewportClient_FlushRHIResources,
{
FRHICommandListExecutor::GetImmediateCommandList().ImmediateFlush(EImmediateFlushType::FlushRHIThreadFlushResources);
});
}
// Beyond this point, only UI rendering independent from dynamc resolution.
GEngine->EmitDynamicResolutionEvent(EDynamicResolutionStateEvent::EndDynamicResolutionRendering);
// Clear areas of the rendertarget (backbuffer) that aren't drawn over by the views.
if (!bBufferCleared)
{
// clear left
if( MinX > 0 )
{
SceneCanvas->DrawTile(0,0,MinX,InViewport->GetSizeXY().Y,0.0f,0.0f,1.0f,1.f,FLinearColor::Black,NULL,false);
}
// clear right
if( MaxX < (uint32)InViewport->GetSizeXY().X )
{
SceneCanvas->DrawTile(MaxX,0,InViewport->GetSizeXY().X,InViewport->GetSizeXY().Y,0.0f,0.0f,1.0f,1.f,FLinearColor::Black,NULL,false);
}
// clear top
if( MinY > 0 )
{
SceneCanvas->DrawTile(MinX,0,MaxX,MinY,0.0f,0.0f,1.0f,1.f,FLinearColor::Black,NULL,false);
}
// clear bottom
if( MaxY < (uint32)InViewport->GetSizeXY().Y )
{
SceneCanvas->DrawTile(MinX,MaxY,MaxX,InViewport->GetSizeXY().Y,0.0f,0.0f,1.0f,1.f,FLinearColor::Black,NULL,false);
}
}
// Remove temporary debug lines.
if (MyWorld->LineBatcher != nullptr)
{
MyWorld->LineBatcher->Flush();
}
if (MyWorld->ForegroundLineBatcher != nullptr)
{
MyWorld->ForegroundLineBatcher->Flush();
}
// Draw FX debug information.
if (MyWorld->FXSystem)
{
MyWorld->FXSystem->DrawDebug(SceneCanvas);
}
// Render the UI.
{
SCOPE_CYCLE_COUNTER(STAT_UIDrawingTime);
// render HUD
bool bDisplayedSubtitles = false;
for( FConstPlayerControllerIterator Iterator = MyWorld->GetPlayerControllerIterator(); Iterator; ++Iterator )
{
APlayerController* PlayerController = Iterator->Get();
if (PlayerController)
{
ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(PlayerController->Player);
if( LocalPlayer )
{
FSceneView* View = PlayerViewMap.FindRef(LocalPlayer);
if (View != NULL)
{
// rendering to directly to viewport target
FVector CanvasOrigin(FMath::TruncToFloat(View->UnscaledViewRect.Min.X), FMath::TruncToInt(View->UnscaledViewRect.Min.Y), 0.f);
CanvasObject->Init(View->UnscaledViewRect.Width(), View->UnscaledViewRect.Height(), View, SceneCanvas);
// Set the canvas transform for the player's view rectangle.
check(SceneCanvas);
SceneCanvas->PushAbsoluteTransform(FTranslationMatrix(CanvasOrigin));
CanvasObject->ApplySafeZoneTransform();
// Render the player's HUD.
if( PlayerController->MyHUD )
{
SCOPE_CYCLE_COUNTER(STAT_HudTime);
DebugCanvasObject->SceneView = View;
PlayerController->MyHUD->SetCanvas(CanvasObject, DebugCanvasObject);
PlayerController->MyHUD->PostRender();
// Put these pointers back as if a blueprint breakpoint hits during HUD PostRender they can
// have been changed
CanvasObject->Canvas = SceneCanvas;
DebugCanvasObject->Canvas = DebugCanvas;
// A side effect of PostRender is that the playercontroller could be destroyed
if (!PlayerController->IsPendingKill())
{
PlayerController->MyHUD->SetCanvas(NULL, NULL);
}
}
if (DebugCanvas != NULL )
{
DebugCanvas->PushAbsoluteTransform(FTranslationMatrix(CanvasOrigin));
UDebugDrawService::Draw(ViewFamily.EngineShowFlags, InViewport, View, DebugCanvas);
DebugCanvas->PopTransform();
}
CanvasObject->PopSafeZoneTransform();
SceneCanvas->PopTransform();
// draw subtitles
if (!bDisplayedSubtitles)
{
FVector2D MinPos(0.f, 0.f);
FVector2D MaxPos(1.f, 1.f);
GetSubtitleRegion(MinPos, MaxPos);
const uint32 SizeX = SceneCanvas->GetRenderTarget()->GetSizeXY().X;
const uint32 SizeY = SceneCanvas->GetRenderTarget()->GetSizeXY().Y;
FIntRect SubtitleRegion(FMath::TruncToInt(SizeX * MinPos.X), FMath::TruncToInt(SizeY * MinPos.Y), FMath::TruncToInt(SizeX * MaxPos.X), FMath::TruncToInt(SizeY * MaxPos.Y));
FSubtitleManager::GetSubtitleManager()->DisplaySubtitles( SceneCanvas, SubtitleRegion, MyWorld->GetAudioTimeSeconds() );
bDisplayedSubtitles = true;
}
}
}
}
}
//ensure canvas has been flushed before rendering UI
SceneCanvas->Flush_GameThread();
DrawnDelegate.Broadcast();
// Allow the viewport to render additional stuff
PostRender(DebugCanvasObject);
}
// Grab the player camera location and orientation so we can pass that along to the stats drawing code.
FVector PlayerCameraLocation = FVector::ZeroVector;
FRotator PlayerCameraRotation = FRotator::ZeroRotator;
{
for( FConstPlayerControllerIterator Iterator = MyWorld->GetPlayerControllerIterator(); Iterator; ++Iterator )
{
(*Iterator)->GetPlayerViewPoint( PlayerCameraLocation, PlayerCameraRotation );
}
}
if (DebugCanvas)
{
// Reset the debug canvas to be full-screen before drawing the console
// (the debug draw service above has messed with the viewport size to fit it to a single player's subregion)
DebugCanvasObject->Init(DebugCanvasSize.X, DebugCanvasSize.Y, NULL, DebugCanvas);
DrawStatsHUD( MyWorld, InViewport, DebugCanvas, DebugCanvasObject, DebugProperties, PlayerCameraLocation, PlayerCameraRotation );
if (GEngine->IsStereoscopic3D(InViewport))
{
#if 0 //!UE_BUILD_SHIPPING
// TODO: replace implementation in OculusHMD with a debug renderer
if (GEngine->XRSystem.IsValid())
{
GEngine->XRSystem->DrawDebug(DebugCanvasObject);
}
#endif
}
// Render the console absolutely last because developer input is was matter the most.
if (ViewportConsole)
{
ViewportConsole->PostRender_Console(DebugCanvasObject);
}
}
EndDrawDelegate.Broadcast();
}
void UGameViewportClient::ProcessScreenShots(FViewport* InViewport)
{
if (GIsDumpingMovie || FScreenshotRequest::IsScreenshotRequested() || GIsHighResScreenshot)
{
TArray<FColor> Bitmap;
bool bShowUI = false;
TSharedPtr<SWindow> WindowPtr = GetWindow();
if (!GIsDumpingMovie && (FScreenshotRequest::ShouldShowUI() && WindowPtr.IsValid()))
{
bShowUI = true;
}
bool bScreenshotSuccessful = false;
FIntVector Size(InViewport->GetSizeXY().X, InViewport->GetSizeXY().Y, 0);
if( bShowUI && FSlateApplication::IsInitialized() )
{
TSharedRef<SWidget> WindowRef = WindowPtr.ToSharedRef();
bScreenshotSuccessful = FSlateApplication::Get().TakeScreenshot( WindowRef, Bitmap, Size);
GScreenshotResolutionX = Size.X;
GScreenshotResolutionY = Size.Y;
}
else
{
bScreenshotSuccessful = GetViewportScreenShot(InViewport, Bitmap);
}
if (bScreenshotSuccessful)
{
if (ScreenshotCapturedDelegate.IsBound() && CVarScreenshotDelegate.GetValueOnGameThread())
{
// Ensure that all pixels' alpha is set to 255
for (auto& Color : Bitmap)
{
Color.A = 255;
}
// If delegate subscribed, fire it instead of writing out a file to disk
ScreenshotCapturedDelegate.Broadcast(Size.X, Size.Y, Bitmap);
}
else
{
FString ScreenShotName = FScreenshotRequest::GetFilename();
if (GIsDumpingMovie && ScreenShotName.IsEmpty())
{
// Request a new screenshot with a formatted name
bShowUI = false;
const bool bAddFilenameSuffix = true;
FScreenshotRequest::RequestScreenshot(FString(), bShowUI, bAddFilenameSuffix);
ScreenShotName = FScreenshotRequest::GetFilename();
}
GetHighResScreenshotConfig().MergeMaskIntoAlpha(Bitmap);
FIntRect SourceRect(0, 0, GScreenshotResolutionX, GScreenshotResolutionY);
if (GIsHighResScreenshot)
{
SourceRect = GetHighResScreenshotConfig().CaptureRegion;
}
if (!FPaths::GetExtension(ScreenShotName).IsEmpty())
{
ScreenShotName = FPaths::GetBaseFilename(ScreenShotName, false);
ScreenShotName += TEXT(".png");
}
// Save the contents of the array to a png file.
TArray<uint8> CompressedBitmap;
FImageUtils::CompressImageArray(Size.X, Size.Y, Bitmap, CompressedBitmap);
FFileHelper::SaveArrayToFile(CompressedBitmap, *ScreenShotName);
}
}
FScreenshotRequest::Reset();
FScreenshotRequest::OnScreenshotRequestProcessed().ExecuteIfBound();
// Reeanble screen messages - if we are NOT capturing a movie
GAreScreenMessagesEnabled = GScreenMessagesRestoreState;
}
}
void UGameViewportClient::Precache()
{
if(!GIsEditor)
{
// Precache sounds...
if (FAudioDevice* AudioDevice = GetWorld()->GetAudioDevice())
{
UE_LOG(LogPlayerManagement, Log, TEXT("Precaching sounds..."));
for(TObjectIterator<USoundWave> It;It;++It)
{
USoundWave* SoundWave = *It;
AudioDevice->Precache( SoundWave );
}
UE_LOG(LogPlayerManagement, Log, TEXT("Precaching sounds completed..."));
}
}
// Log time till first precache is finished.
static bool bIsFirstCallOfFunction = true;
if( bIsFirstCallOfFunction )
{
UE_LOG(LogPlayerManagement, Log, TEXT("%5.2f seconds passed since startup."),FPlatformTime::Seconds()-GStartTime);
bIsFirstCallOfFunction = false;
}
}
TOptional<bool> UGameViewportClient::QueryShowFocus(const EFocusCause InFocusCause) const
{
UUserInterfaceSettings* UISettings = GetMutableDefault<UUserInterfaceSettings>(UUserInterfaceSettings::StaticClass());
if ( UISettings->RenderFocusRule == ERenderFocusRule::Never ||
(UISettings->RenderFocusRule == ERenderFocusRule::NonPointer && InFocusCause == EFocusCause::Mouse) ||
(UISettings->RenderFocusRule == ERenderFocusRule::NavigationOnly && InFocusCause != EFocusCause::Navigation))
{
return false;
}
return true;
}
void UGameViewportClient::LostFocus(FViewport* InViewport)
{
// We need to reset some key inputs, since keyup events will sometimes not be processed (such as going into immersive/maximized mode).
// Resetting them will prevent them from "sticking"
UWorld* const ViewportWorld = GetWorld();
if (ViewportWorld && !ViewportWorld->bIsTearingDown)
{
for (FConstPlayerControllerIterator Iterator = ViewportWorld->GetPlayerControllerIterator(); Iterator; ++Iterator)
{
APlayerController* const PlayerController = Iterator->Get();
if (PlayerController)
{
PlayerController->FlushPressedKeys();
}
}
}
if (GEngine && GEngine->GetAudioDeviceManager())
{
bHasAudioFocus = false;
}
}
void UGameViewportClient::ReceivedFocus(FViewport* InViewport)
{
#if PLATFORM_DESKTOP || PLATFORM_HTML5
if (GetDefault<UInputSettings>()->bUseMouseForTouch && GetGameViewport() && !GetGameViewport()->GetPlayInEditorIsSimulate())
{
FSlateApplication::Get().SetGameIsFakingTouchEvents(true);
}
#endif
if (GEngine && GEngine->GetAudioDeviceManager())
{
GEngine->GetAudioDeviceManager()->SetActiveDevice(AudioDeviceHandle);
bHasAudioFocus = true;
}
}
bool UGameViewportClient::IsFocused(FViewport* InViewport)
{
return InViewport->HasFocus() || InViewport->HasMouseCapture();
}
void UGameViewportClient::Activated(FViewport* InViewport, const FWindowActivateEvent& InActivateEvent)
{
ReceivedFocus(InViewport);
}
void UGameViewportClient::Deactivated(FViewport* InViewport, const FWindowActivateEvent& InActivateEvent)
{
LostFocus(InViewport);
}
bool UGameViewportClient::WindowCloseRequested()
{
return !WindowCloseRequestedDelegate.IsBound() || WindowCloseRequestedDelegate.Execute();
}
void UGameViewportClient::CloseRequested(FViewport* InViewport)
{
check(InViewport == Viewport);
#if PLATFORM_DESKTOP || PLATFORM_HTML5
FSlateApplication::Get().SetGameIsFakingTouchEvents(false);
#endif
// broadcast close request to anyone that registered an interest
CloseRequestedDelegate.Broadcast(InViewport);
SetViewportFrame(NULL);
// If this viewport has a high res screenshot window attached to it, close it
if (HighResScreenshotDialog.IsValid())
{
HighResScreenshotDialog.Pin()->RequestDestroyWindow();
HighResScreenshotDialog = NULL;
}
}
bool UGameViewportClient::IsOrtho() const
{
return false;
}
void UGameViewportClient::PostRender(UCanvas* Canvas)
{
if( bShowTitleSafeZone )
{
DrawTitleSafeArea(Canvas);
}
// Draw the transition screen.
DrawTransition(Canvas);
}
void UGameViewportClient::PeekTravelFailureMessages(UWorld* InWorld, ETravelFailure::Type FailureType, const FString& ErrorString)
{
UE_LOG(LogNet, Warning, TEXT("Travel Failure: [%s]: %s"), ETravelFailure::ToString(FailureType), *ErrorString);
}
void UGameViewportClient::PeekNetworkFailureMessages(UWorld *InWorld, UNetDriver *NetDriver, ENetworkFailure::Type FailureType, const FString& ErrorString)
{
UE_LOG(LogNet, Warning, TEXT("Network Failure: %s[%s]: %s"), NetDriver ? *NetDriver->NetDriverName.ToString() : TEXT("NULL"), ENetworkFailure::ToString(FailureType), *ErrorString);
}
void UGameViewportClient::SSSwapControllers()
{
#if !UE_BUILD_SHIPPING
UEngine* const Engine = GetOuterUEngine();
int32 const NumPlayers = Engine ? Engine->GetNumGamePlayers(this) : 0;
if (NumPlayers > 1)
{
ULocalPlayer* const LP = Engine ? Engine->GetFirstGamePlayer(this) : nullptr;
const int32 TmpControllerID = LP ? LP->GetControllerId() : 0;
for (int32 Idx = 0; Idx<NumPlayers-1; ++Idx)
{
Engine->GetGamePlayer(this, Idx)->SetControllerId(Engine->GetGamePlayer(this, Idx + 1)->GetControllerId());
}
Engine->GetGamePlayer(this, NumPlayers-1)->SetControllerId(TmpControllerID);
}
#endif
}
void UGameViewportClient::ShowTitleSafeArea()
{
#if !UE_BUILD_SHIPPING
bShowTitleSafeZone = !bShowTitleSafeZone;
#endif
}
void UGameViewportClient::SetConsoleTarget(int32 PlayerIndex)
{
#if !UE_BUILD_SHIPPING
if (ViewportConsole)
{
if(PlayerIndex >= 0 && PlayerIndex < GetOuterUEngine()->GetNumGamePlayers(this))
{
ViewportConsole->ConsoleTargetPlayer = GetOuterUEngine()->GetGamePlayer(this, PlayerIndex);
}
else
{
ViewportConsole->ConsoleTargetPlayer = NULL;
}
}
#endif
}
ULocalPlayer* UGameViewportClient::SetupInitialLocalPlayer(FString& OutError)
{
check(GetOuterUEngine()->ConsoleClass != NULL);
ActiveSplitscreenType = ESplitScreenType::None;
#if ALLOW_CONSOLE
// Create the viewport's console.
ViewportConsole = NewObject<UConsole>(this, GetOuterUEngine()->ConsoleClass);
// register console to get all log messages
GLog->AddOutputDevice(ViewportConsole);
#endif // !UE_BUILD_SHIPPING
// Keep an eye on any network or server travel failures
GEngine->OnTravelFailure().AddUObject(this, &UGameViewportClient::PeekTravelFailureMessages);
GEngine->OnNetworkFailure().AddUObject(this, &UGameViewportClient::PeekNetworkFailureMessages);
UGameInstance * ViewportGameInstance = GEngine->GetWorldContextFromGameViewportChecked(this).OwningGameInstance;
if ( !ensure( ViewportGameInstance != NULL ) )
{
return NULL;
}
// Create the initial player - this is necessary or we can't render anything in-game.
return ViewportGameInstance->CreateInitialPlayer(OutError);
}
void UGameViewportClient::UpdateActiveSplitscreenType()
{
ESplitScreenType::Type SplitType = ESplitScreenType::None;
const int32 NumPlayers = GEngine->GetNumGamePlayers(GetWorld());
const UGameMapsSettings* Settings = GetDefault<UGameMapsSettings>();
if (Settings->bUseSplitscreen && !bDisableSplitScreenOverride)
{
switch (NumPlayers)
{
case 0:
case 1:
SplitType = ESplitScreenType::None;
break;
case 2:
switch (Settings->TwoPlayerSplitscreenLayout)
{
case ETwoPlayerSplitScreenType::Horizontal:
SplitType = ESplitScreenType::TwoPlayer_Horizontal;
break;
case ETwoPlayerSplitScreenType::Vertical:
SplitType = ESplitScreenType::TwoPlayer_Vertical;
break;
default:
check(0);
}
break;
case 3:
switch (Settings->ThreePlayerSplitscreenLayout)
{
case EThreePlayerSplitScreenType::FavorTop:
SplitType = ESplitScreenType::ThreePlayer_FavorTop;
break;
case EThreePlayerSplitScreenType::FavorBottom:
SplitType = ESplitScreenType::ThreePlayer_FavorBottom;
break;
default:
check(0);
}
break;
default:
ensure(NumPlayers == 4);
SplitType = ESplitScreenType::FourPlayer;
break;
}
}
else
{
SplitType = ESplitScreenType::None;
}
ActiveSplitscreenType = SplitType;
}
void UGameViewportClient::LayoutPlayers()
{
UpdateActiveSplitscreenType();
const ESplitScreenType::Type SplitType = GetCurrentSplitscreenConfiguration();
// Initialize the players
const TArray<ULocalPlayer*>& PlayerList = GetOuterUEngine()->GetGamePlayers(this);
for ( int32 PlayerIdx = 0; PlayerIdx < PlayerList.Num(); PlayerIdx++ )
{
if ( SplitType < SplitscreenInfo.Num() && PlayerIdx < SplitscreenInfo[SplitType].PlayerData.Num() )
{
PlayerList[PlayerIdx]->Size.X = SplitscreenInfo[SplitType].PlayerData[PlayerIdx].SizeX;
PlayerList[PlayerIdx]->Size.Y = SplitscreenInfo[SplitType].PlayerData[PlayerIdx].SizeY;
PlayerList[PlayerIdx]->Origin.X = SplitscreenInfo[SplitType].PlayerData[PlayerIdx].OriginX;
PlayerList[PlayerIdx]->Origin.Y = SplitscreenInfo[SplitType].PlayerData[PlayerIdx].OriginY;
}
else
{
PlayerList[PlayerIdx]->Size.X = 0.f;
PlayerList[PlayerIdx]->Size.Y = 0.f;
PlayerList[PlayerIdx]->Origin.X = 0.f;
PlayerList[PlayerIdx]->Origin.Y = 0.f;
}
}
}
void UGameViewportClient::SetDisableSplitscreenOverride( const bool bDisabled )
{
bDisableSplitScreenOverride = bDisabled;
LayoutPlayers();
}
void UGameViewportClient::GetSubtitleRegion(FVector2D& MinPos, FVector2D& MaxPos)
{
MaxPos.X = 1.0f;
MaxPos.Y = (GetOuterUEngine()->GetNumGamePlayers(this) == 1) ? 0.9f : 0.5f;
}
int32 UGameViewportClient::ConvertLocalPlayerToGamePlayerIndex( ULocalPlayer* LPlayer )
{
return GetOuterUEngine()->GetGamePlayers(this).Find( LPlayer );
}
bool UGameViewportClient::HasTopSafeZone( int32 LocalPlayerIndex )
{
switch ( GetCurrentSplitscreenConfiguration() )
{
case ESplitScreenType::None:
case ESplitScreenType::TwoPlayer_Vertical:
return true;
case ESplitScreenType::TwoPlayer_Horizontal:
case ESplitScreenType::ThreePlayer_FavorTop:
return (LocalPlayerIndex == 0) ? true : false;
case ESplitScreenType::ThreePlayer_FavorBottom:
case ESplitScreenType::FourPlayer:
return (LocalPlayerIndex < 2) ? true : false;
}
return false;
}
bool UGameViewportClient::HasBottomSafeZone( int32 LocalPlayerIndex )
{
switch ( GetCurrentSplitscreenConfiguration() )
{
case ESplitScreenType::None:
case ESplitScreenType::TwoPlayer_Vertical:
return true;
case ESplitScreenType::TwoPlayer_Horizontal:
case ESplitScreenType::ThreePlayer_FavorTop:
return (LocalPlayerIndex == 0) ? false : true;
case ESplitScreenType::ThreePlayer_FavorBottom:
case ESplitScreenType::FourPlayer:
return (LocalPlayerIndex > 1) ? true : false;
}
return false;
}
bool UGameViewportClient::HasLeftSafeZone( int32 LocalPlayerIndex )
{
switch ( GetCurrentSplitscreenConfiguration() )
{
case ESplitScreenType::None:
case ESplitScreenType::TwoPlayer_Horizontal:
return true;
case ESplitScreenType::TwoPlayer_Vertical:
return (LocalPlayerIndex == 0) ? true : false;
case ESplitScreenType::ThreePlayer_FavorTop:
return (LocalPlayerIndex < 2) ? true : false;
case ESplitScreenType::ThreePlayer_FavorBottom:
case ESplitScreenType::FourPlayer:
return (LocalPlayerIndex == 0 || LocalPlayerIndex == 2) ? true : false;
}
return false;
}
bool UGameViewportClient::HasRightSafeZone( int32 LocalPlayerIndex )
{
switch ( GetCurrentSplitscreenConfiguration() )
{
case ESplitScreenType::None:
case ESplitScreenType::TwoPlayer_Horizontal:
return true;
case ESplitScreenType::TwoPlayer_Vertical:
case ESplitScreenType::ThreePlayer_FavorBottom:
return (LocalPlayerIndex > 0) ? true : false;
case ESplitScreenType::ThreePlayer_FavorTop:
return (LocalPlayerIndex == 1) ? false : true;
case ESplitScreenType::FourPlayer:
return (LocalPlayerIndex == 0 || LocalPlayerIndex == 2) ? false : true;
}
return false;
}
void UGameViewportClient::GetPixelSizeOfScreen( float& Width, float& Height, UCanvas* Canvas, int32 LocalPlayerIndex )
{
switch ( GetCurrentSplitscreenConfiguration() )
{
case ESplitScreenType::None:
Width = Canvas->ClipX;
Height = Canvas->ClipY;
return;
case ESplitScreenType::TwoPlayer_Horizontal:
Width = Canvas->ClipX;
Height = Canvas->ClipY * 2;
return;
case ESplitScreenType::TwoPlayer_Vertical:
Width = Canvas->ClipX * 2;
Height = Canvas->ClipY;
return;
case ESplitScreenType::ThreePlayer_FavorTop:
if (LocalPlayerIndex == 0)
{
Width = Canvas->ClipX;
}
else
{
Width = Canvas->ClipX * 2;
}
Height = Canvas->ClipY * 2;
return;
case ESplitScreenType::ThreePlayer_FavorBottom:
if (LocalPlayerIndex == 2)
{
Width = Canvas->ClipX;
}
else
{
Width = Canvas->ClipX * 2;
}
Height = Canvas->ClipY * 2;
return;
case ESplitScreenType::FourPlayer:
Width = Canvas->ClipX * 2;
Height = Canvas->ClipY * 2;
return;
}
}
void UGameViewportClient::CalculateSafeZoneValues( float& Horizontal, float& Vertical, UCanvas* Canvas, int32 LocalPlayerIndex, bool bUseMaxPercent )
{
float XSafeZoneToUse = bUseMaxPercent ? TitleSafeZone.MaxPercentX : TitleSafeZone.RecommendedPercentX;
float YSafeZoneToUse = bUseMaxPercent ? TitleSafeZone.MaxPercentY : TitleSafeZone.RecommendedPercentY;
float ScreenWidth, ScreenHeight;
GetPixelSizeOfScreen( ScreenWidth, ScreenHeight, Canvas, LocalPlayerIndex );
Horizontal = (ScreenWidth * (1 - XSafeZoneToUse) / 2.0f);
Vertical = (ScreenHeight * (1 - YSafeZoneToUse) / 2.0f);
}
bool UGameViewportClient::CalculateDeadZoneForAllSides( ULocalPlayer* LPlayer, UCanvas* Canvas, float& fTopSafeZone, float& fBottomSafeZone, float& fLeftSafeZone, float& fRightSafeZone, bool bUseMaxPercent )
{
// save separate - if the split screen is in bottom right, then
if ( LPlayer != NULL )
{
int32 LocalPlayerIndex = ConvertLocalPlayerToGamePlayerIndex( LPlayer );
if ( LocalPlayerIndex != -1 )
{
// see if this player should have a safe zone for any particular zonetype
bool bHasTopSafeZone = HasTopSafeZone( LocalPlayerIndex );
bool bHasBottomSafeZone = HasBottomSafeZone( LocalPlayerIndex );
bool bHasLeftSafeZone = HasLeftSafeZone( LocalPlayerIndex );
bool bHasRightSafeZone = HasRightSafeZone( LocalPlayerIndex );
// if they need a safezone, then calculate it and save it
if ( bHasTopSafeZone || bHasBottomSafeZone || bHasLeftSafeZone || bHasRightSafeZone)
{
// calculate the safezones
float HorizSafeZoneValue, VertSafeZoneValue;
CalculateSafeZoneValues( HorizSafeZoneValue, VertSafeZoneValue, Canvas, LocalPlayerIndex, bUseMaxPercent );
if (bHasTopSafeZone)
{
fTopSafeZone = VertSafeZoneValue;
}
else
{
fTopSafeZone = 0.f;
}
if (bHasBottomSafeZone)
{
fBottomSafeZone = VertSafeZoneValue;
}
else
{
fBottomSafeZone = 0.f;
}
if (bHasLeftSafeZone)
{
fLeftSafeZone = HorizSafeZoneValue;
}
else
{
fLeftSafeZone = 0.f;
}
if (bHasRightSafeZone)
{
fRightSafeZone = HorizSafeZoneValue;
}
else
{
fRightSafeZone = 0.f;
}
return true;
}
}
}
return false;
}
void UGameViewportClient::DrawTitleSafeArea( UCanvas* Canvas )
{
// red colored max safe area box
Canvas->SetDrawColor(255,0,0,255);
float X = Canvas->ClipX * (1 - TitleSafeZone.MaxPercentX) / 2.0f;
float Y = Canvas->ClipY * (1 - TitleSafeZone.MaxPercentY) / 2.0f;
FCanvasBoxItem BoxItem( FVector2D( X, Y ), FVector2D( Canvas->ClipX * TitleSafeZone.MaxPercentX, Canvas->ClipY * TitleSafeZone.MaxPercentY ) );
BoxItem.SetColor( FLinearColor::Red );
Canvas->DrawItem( BoxItem );
// yellow colored recommended safe area box
X = Canvas->ClipX * (1 - TitleSafeZone.RecommendedPercentX) / 2.0f;
Y = Canvas->ClipY * (1 - TitleSafeZone.RecommendedPercentY) / 2.0f;
BoxItem.SetColor( FLinearColor::Yellow );
BoxItem.Size = FVector2D( Canvas->ClipX * TitleSafeZone.RecommendedPercentX, Canvas->ClipY * TitleSafeZone.RecommendedPercentY );
Canvas->DrawItem( BoxItem, X, Y );
}
void UGameViewportClient::DrawTransition(UCanvas* Canvas)
{
if (bSuppressTransitionMessage == false)
{
switch (GetOuterUEngine()->TransitionType)
{
case TT_Loading:
DrawTransitionMessage(Canvas, NSLOCTEXT("GameViewportClient", "LoadingMessage", "LOADING").ToString());
break;
case TT_Saving:
DrawTransitionMessage(Canvas, NSLOCTEXT("GameViewportClient", "SavingMessage", "SAVING").ToString());
break;
case TT_Connecting:
DrawTransitionMessage(Canvas, NSLOCTEXT("GameViewportClient", "ConnectingMessage", "CONNECTING").ToString());
break;
case TT_Precaching:
DrawTransitionMessage(Canvas, NSLOCTEXT("GameViewportClient", "PrecachingMessage", "PRECACHING").ToString());
break;
case TT_Paused:
DrawTransitionMessage(Canvas, NSLOCTEXT("GameViewportClient", "PausedMessage", "PAUSED").ToString());
break;
case TT_WaitingToConnect:
DrawTransitionMessage(Canvas, TEXT("Waiting to connect...")); // Temp - localization of the FString messages is broke atm. Loc this when its fixed.
break;
}
}
}
void UGameViewportClient::DrawTransitionMessage(UCanvas* Canvas,const FString& Message)
{
UFont* Font = GEngine->GetLargeFont();
FCanvasTextItem TextItem( FVector2D::ZeroVector, FText::GetEmpty(), Font, FLinearColor::Blue);
TextItem.EnableShadow( FLinearColor::Black );
TextItem.Text = FText::FromString(Message);
float XL, YL;
Canvas->StrLen( Font , Message, XL, YL );
Canvas->DrawItem( TextItem, 0.5f * (Canvas->ClipX - XL), 0.66f * Canvas->ClipY - YL * 0.5f );
}
void UGameViewportClient::NotifyPlayerAdded( int32 PlayerIndex, ULocalPlayer* AddedPlayer )
{
LayoutPlayers();
FSlateApplication::Get().SetUserFocusToGameViewport(PlayerIndex);
TSharedPtr< IGameLayerManager > GameLayerManager(GameLayerManagerPtr.Pin());
if ( GameLayerManager.IsValid() )
{
GameLayerManager->NotifyPlayerAdded(PlayerIndex, AddedPlayer);
}
PlayerAddedDelegate.Broadcast( PlayerIndex );
}
void UGameViewportClient::NotifyPlayerRemoved( int32 PlayerIndex, ULocalPlayer* RemovedPlayer )
{
LayoutPlayers();
TSharedPtr< IGameLayerManager > GameLayerManager(GameLayerManagerPtr.Pin());
if ( GameLayerManager.IsValid() )
{
GameLayerManager->NotifyPlayerRemoved(PlayerIndex, RemovedPlayer);
}
PlayerRemovedDelegate.Broadcast( PlayerIndex );
}
void UGameViewportClient::AddViewportWidgetContent( TSharedRef<SWidget> ViewportContent, const int32 ZOrder )
{
TSharedPtr< SOverlay > PinnedViewportOverlayWidget( ViewportOverlayWidget.Pin() );
if( ensure( PinnedViewportOverlayWidget.IsValid() ) )
{
// NOTE: Returns FSimpleSlot but we're ignoring here. Could be used for alignment though.
PinnedViewportOverlayWidget->AddSlot( ZOrder )
[
ViewportContent
];
}
}
void UGameViewportClient::RemoveViewportWidgetContent( TSharedRef<SWidget> ViewportContent )
{
TSharedPtr< SOverlay > PinnedViewportOverlayWidget( ViewportOverlayWidget.Pin() );
if( PinnedViewportOverlayWidget.IsValid() )
{
PinnedViewportOverlayWidget->RemoveSlot( ViewportContent );
}
}
void UGameViewportClient::AddViewportWidgetForPlayer(ULocalPlayer* Player, TSharedRef<SWidget> ViewportContent, const int32 ZOrder)
{
TSharedPtr< IGameLayerManager > GameLayerManager(GameLayerManagerPtr.Pin());
if ( GameLayerManager.IsValid() )
{
GameLayerManager->AddWidgetForPlayer(Player, ViewportContent, ZOrder);
}
}
void UGameViewportClient::RemoveViewportWidgetForPlayer(ULocalPlayer* Player, TSharedRef<SWidget> ViewportContent)
{
TSharedPtr< IGameLayerManager > GameLayerManager(GameLayerManagerPtr.Pin());
if ( GameLayerManager.IsValid() )
{
GameLayerManager->RemoveWidgetForPlayer(Player, ViewportContent);
}
}
void UGameViewportClient::RemoveAllViewportWidgets()
{
CursorWidgets.Empty();
TSharedPtr< SOverlay > PinnedViewportOverlayWidget( ViewportOverlayWidget.Pin() );
if( PinnedViewportOverlayWidget.IsValid() )
{
PinnedViewportOverlayWidget->ClearChildren();
}
TSharedPtr< IGameLayerManager > GameLayerManager(GameLayerManagerPtr.Pin());
if ( GameLayerManager.IsValid() )
{
GameLayerManager->ClearWidgets();
}
}
void UGameViewportClient::VerifyPathRenderingComponents()
{
const bool bShowPaths = !!EngineShowFlags.Navigation;
UWorld* const ViewportWorld = GetWorld();
// make sure nav mesh has a rendering component
ANavigationData* const NavData = (ViewportWorld && ViewportWorld->GetNavigationSystem() != nullptr)
? ViewportWorld->GetNavigationSystem()->GetMainNavData(FNavigationSystem::DontCreate)
: NULL;
if(NavData && NavData->RenderingComp == NULL)
{
NavData->RenderingComp = NavData->ConstructRenderingComponent();
if (NavData->RenderingComp)
{
NavData->RenderingComp->SetVisibility(bShowPaths);
NavData->RenderingComp->RegisterComponent();
}
}
if(NavData == NULL)
{
UE_LOG(LogPlayerManagement, Warning, TEXT("No NavData found when calling UGameViewportClient::VerifyPathRenderingComponents()"));
}
}
bool UGameViewportClient::CaptureMouseOnLaunch()
{
return GetDefault<UInputSettings>()->bCaptureMouseOnLaunch;
}
bool UGameViewportClient::Exec( UWorld* InWorld, const TCHAR* Cmd,FOutputDevice& Ar)
{
if ( FParse::Command(&Cmd,TEXT("FORCEFULLSCREEN")) )
{
return HandleForceFullscreenCommand( Cmd, Ar );
}
else if( FParse::Command(&Cmd,TEXT("SHOW")) )
{
return HandleShowCommand( Cmd, Ar, InWorld );
}
else if( FParse::Command(&Cmd,TEXT("SHOWLAYER")) )
{
return HandleShowLayerCommand( Cmd, Ar, InWorld );
}
else if (FParse::Command(&Cmd,TEXT("VIEWMODE")))
{
return HandleViewModeCommand( Cmd, Ar, InWorld );
}
else if (FParse::Command(&Cmd, TEXT("NEXTVIEWMODE")))
{
return HandleNextViewModeCommand( Cmd, Ar, InWorld );
}
else if (FParse::Command(&Cmd, TEXT("PREVVIEWMODE")))
{
return HandlePrevViewModeCommand( Cmd, Ar, InWorld );
}
else if( FParse::Command(&Cmd,TEXT("PRECACHE")) )
{
return HandlePreCacheCommand( Cmd, Ar );
}
else if( FParse::Command(&Cmd,TEXT("TOGGLE_FULLSCREEN")) || FParse::Command(&Cmd,TEXT("FULLSCREEN")) )
{
return HandleToggleFullscreenCommand();
}
else if( FParse::Command(&Cmd,TEXT("SETRES")) )
{
return HandleSetResCommand( Cmd, Ar );
}
else if( FParse::Command(&Cmd,TEXT("HighResShot")) )
{
return HandleHighresScreenshotCommand( Cmd, Ar );
}
else if( FParse::Command(&Cmd,TEXT("HighResShotUI")) )
{
return HandleHighresScreenshotUICommand( Cmd, Ar );
}
else if( FParse::Command(&Cmd,TEXT("SHOT")) || FParse::Command(&Cmd,TEXT("SCREENSHOT")) )
{
return HandleScreenshotCommand( Cmd, Ar );
}
else if ( FParse::Command(&Cmd, TEXT("BUGSCREENSHOTWITHHUDINFO")) )
{
return HandleBugScreenshotwithHUDInfoCommand( Cmd, Ar );
}
else if ( FParse::Command(&Cmd,TEXT("BUGSCREENSHOT")) )
{
return HandleBugScreenshotCommand( Cmd, Ar );
}
else if( FParse::Command(&Cmd,TEXT("KILLPARTICLES")) )
{
return HandleKillParticlesCommand( Cmd, Ar );
}
else if( FParse::Command(&Cmd,TEXT("FORCESKELLOD")) )
{
return HandleForceSkelLODCommand( Cmd, Ar, InWorld );
}
else if (FParse::Command(&Cmd, TEXT("DISPLAY")))
{
return HandleDisplayCommand( Cmd, Ar );
}
else if (FParse::Command(&Cmd, TEXT("DISPLAYALL")))
{
return HandleDisplayAllCommand( Cmd, Ar );
}
else if (FParse::Command(&Cmd, TEXT("DISPLAYALLLOCATION")))
{
return HandleDisplayAllLocationCommand( Cmd, Ar );
}
else if (FParse::Command(&Cmd, TEXT("DISPLAYALLROTATION")))
{
return HandleDisplayAllRotationCommand( Cmd, Ar );
}
else if (FParse::Command(&Cmd, TEXT("DISPLAYCLEAR")))
{
return HandleDisplayClearCommand( Cmd, Ar );
}
else if(FParse::Command(&Cmd, TEXT("TEXTUREDEFRAG")))
{
return HandleTextureDefragCommand( Cmd, Ar );
}
else if (FParse::Command(&Cmd, TEXT("TOGGLEMIPFADE")))
{
return HandleToggleMIPFadeCommand( Cmd, Ar );
}
else if (FParse::Command(&Cmd, TEXT("PAUSERENDERCLOCK")))
{
return HandlePauseRenderClockCommand( Cmd, Ar );
}
if(ProcessConsoleExec(Cmd,Ar,NULL))
{
return true;
}
else if ( GameInstance && (GameInstance->Exec(InWorld, Cmd, Ar) || GameInstance->ProcessConsoleExec(Cmd, Ar, nullptr)) )
{
return true;
}
else if( GEngine->Exec( InWorld, Cmd,Ar) )
{
return true;
}
else
{
return false;
}
}
bool UGameViewportClient::HandleForceFullscreenCommand( const TCHAR* Cmd, FOutputDevice& Ar )
{
GForceFullscreen = !GForceFullscreen;
return true;
}
bool UGameViewportClient::HandleShowCommand( const TCHAR* Cmd, FOutputDevice& Ar, UWorld* InWorld )
{
#if UE_BUILD_SHIPPING
// don't allow show flags in net games, but on con
if ( InWorld->GetNetMode() != NM_Standalone || (GEngine->GetWorldContextFromWorldChecked(InWorld).PendingNetGame != NULL) )
{
return true;
}
// the effects of this cannot be easily reversed, so prevent the user from playing network games without restarting to avoid potential exploits
GDisallowNetworkTravel = true;
#endif // UE_BUILD_SHIPPING
// First, look for skeletal mesh show commands
bool bUpdateSkelMeshCompDebugFlags = false;
static bool bShowPrePhysSkelBones = false;
if(FParse::Command(&Cmd,TEXT("PREPHYSBONES")))
{
bShowPrePhysSkelBones = !bShowPrePhysSkelBones;
bUpdateSkelMeshCompDebugFlags = true;
}
// If we changed one of the skel mesh debug show flags, set it on each of the components in the World.
if(bUpdateSkelMeshCompDebugFlags)
{
for (TObjectIterator<USkeletalMeshComponent> It; It; ++It)
{
USkeletalMeshComponent* SkelComp = *It;
if( SkelComp->GetScene() == InWorld->Scene )
{
SkelComp->bShowPrePhysBones = bShowPrePhysSkelBones;
SkelComp->MarkRenderStateDirty();
}
}
// Now we are done.
return true;
}
// EngineShowFlags
{
int32 FlagIndex = FEngineShowFlags::FindIndexByName(Cmd);
if(FlagIndex != -1)
{
bool bCanBeToggled = true;
if(GIsEditor)
{
if(!FEngineShowFlags::CanBeToggledInEditor(Cmd))
{
bCanBeToggled = false;
}
}
bool bIsACollisionFlag = FEngineShowFlags::IsNameThere(Cmd, TEXT("Collision"));
if(bCanBeToggled)
{
bool bOldState = EngineShowFlags.GetSingleFlag(FlagIndex);
EngineShowFlags.SetSingleFlag(FlagIndex, !bOldState);
if(FEngineShowFlags::IsNameThere(Cmd, TEXT("Navigation,Cover")))
{
VerifyPathRenderingComponents();
}
if(FEngineShowFlags::IsNameThere(Cmd, TEXT("Volumes")))
{
// TODO: Investigate why this is doesn't appear to work
if (AllowDebugViewmodes())
{
ToggleShowVolumes();
}
else
{
Ar.Logf(TEXT("Debug viewmodes not allowed on consoles by default. See AllowDebugViewmodes()."));
}
}
}
if(bIsACollisionFlag)
{
ToggleShowCollision();
}
return true;
}
}
// create a sorted list of showflags
TSet<FString> LinesToSort;
{
struct FIterSink
{
FIterSink(TSet<FString>& InLinesToSort, const FEngineShowFlags InEngineShowFlags) : LinesToSort(InLinesToSort), EngineShowFlags(InEngineShowFlags)
{
}
bool OnEngineShowFlag(uint32 InIndex, const FString& InName)
{
FString Value = FString::Printf(TEXT("%s=%d"), *InName, EngineShowFlags.GetSingleFlag(InIndex) ? 1 : 0);
LinesToSort.Add(Value);
return true;
}
TSet<FString>& LinesToSort;
const FEngineShowFlags EngineShowFlags;
};
FIterSink Sink(LinesToSort, EngineShowFlags);
FEngineShowFlags::IterateAllFlags(Sink);
}
LinesToSort.Sort( TLess<FString>() );
for(TSet<FString>::TConstIterator It(LinesToSort); It; ++It)
{
const FString Value = *It;
Ar.Logf(TEXT("%s"), *Value);
}
return true;
}
FPopupMethodReply UGameViewportClient::OnQueryPopupMethod() const
{
return FPopupMethodReply::UseMethod(EPopupMethod::UseCurrentWindow)
.SetShouldThrottle(EShouldThrottle::No);
}
bool UGameViewportClient::HandleNavigation(const uint32 InUserIndex, TSharedPtr<SWidget> InDestination)
{
if (CustomNavigationEvent.IsBound())
{
return CustomNavigationEvent.Execute(InUserIndex, InDestination);
}
return false;
}
void UGameViewportClient::ToggleShowVolumes()
{
// Don't allow 'show collision' and 'show volumes' at the same time, so turn collision off
if (EngineShowFlags.Volumes && EngineShowFlags.Collision)
{
EngineShowFlags.SetCollision(false);
ToggleShowCollision();
}
// Iterate over all brushes
for (TObjectIterator<UBrushComponent> It; It; ++It)
{
UBrushComponent* BrushComponent = *It;
AVolume* Owner = Cast<AVolume>(BrushComponent->GetOwner());
// Only bother with volume brushes that belong to the world's scene
if (Owner && BrushComponent->GetScene() == GetWorld()->Scene && !FActorEditorUtils::IsABuilderBrush(Owner))
{
// We're expecting this to be in the game at this point
check(Owner->GetWorld()->IsGameWorld());
// Toggle visibility of this volume
if (BrushComponent->IsVisible())
{
BrushComponent->SetVisibility(false);
BrushComponent->SetHiddenInGame(true);
}
else
{
BrushComponent->SetVisibility(true);
BrushComponent->SetHiddenInGame(false);
}
}
}
}
void UGameViewportClient::ToggleShowCollision()
{
// special case: for the Engine.Collision flag, we need to un-hide any primitive components that collide so their collision geometry gets rendered
const bool bIsShowingCollision = EngineShowFlags.Collision;
if (bIsShowingCollision)
{
// Don't allow 'show collision' and 'show volumes' at the same time, so turn collision off
if (EngineShowFlags.Volumes)
{
EngineShowFlags.SetVolumes(false);
ToggleShowVolumes();
}
}
#if !UE_BUILD_SHIPPING
if (World != nullptr)
{
// Tell engine to create proxies for hidden components, so we can still draw collision
World->bCreateRenderStateForHiddenComponents = bIsShowingCollision;
// Need to recreate scene proxies when this flag changes.
FGlobalComponentRecreateRenderStateContext Recreate;
}
#endif // !UE_BUILD_SHIPPING
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (EngineShowFlags.Collision)
{
for (FLocalPlayerIterator It((UEngine*)GetOuter(), World); It; ++It)
{
APlayerController* PC = It->PlayerController;
if (PC != NULL && PC->GetPawn() != NULL)
{
PC->ClientMessage(FString::Printf(TEXT("!!!! Player Pawn %s Collision Info !!!!"), *PC->GetPawn()->GetName()));
if (PC->GetPawn()->GetMovementBase())
{
PC->ClientMessage(FString::Printf(TEXT("Base %s"), *PC->GetPawn()->GetMovementBase()->GetName()));
}
TSet<AActor*> TouchingActors;
PC->GetPawn()->GetOverlappingActors(TouchingActors);
int32 i = 0;
for (AActor* TouchingActor : TouchingActors)
{
PC->ClientMessage(FString::Printf(TEXT("Touching %d: %s"), i++, *TouchingActor->GetName()));
}
}
}
}
#endif
}
bool UGameViewportClient::HandleShowLayerCommand( const TCHAR* Cmd, FOutputDevice& Ar, UWorld* InWorld )
{
FString LayerName = FParse::Token(Cmd, 0);
// optional 0/1 for setting vis, instead of toggling
FString SetModeParam = FParse::Token(Cmd, 0);
int32 SetMode = -1;
if (SetModeParam != TEXT(""))
{
SetMode = FCString::Atoi(*SetModeParam);
}
bool bPrintValidEntries = false;
if (LayerName.IsEmpty())
{
Ar.Logf(TEXT("Missing layer name."));
bPrintValidEntries = true;
}
else
{
int32 NumActorsToggled = 0;
FName LayerFName = FName(*LayerName);
for (FActorIterator It(InWorld); It; ++It)
{
AActor* Actor = *It;
if (Actor->Layers.Contains(LayerFName))
{
// look for always toggle, or a set when it's unset, etc
if ((SetMode == -1) || (SetMode == 0 && !Actor->bHidden) || (SetMode != 0 && Actor->bHidden))
{
NumActorsToggled++;
// Note: overriding existing hidden property, ideally this would be something orthogonal
Actor->bHidden = !Actor->bHidden;
Actor->MarkComponentsRenderStateDirty();
}
}
}
Ar.Logf(TEXT("Toggled visibility of %u actors"), NumActorsToggled);
bPrintValidEntries = NumActorsToggled == 0;
}
if (bPrintValidEntries)
{
TArray<FName> LayerNames;
for (FActorIterator It(InWorld); It; ++It)
{
AActor* Actor = *It;
for (int32 LayerIndex = 0; LayerIndex < Actor->Layers.Num(); LayerIndex++)
{
LayerNames.AddUnique(Actor->Layers[LayerIndex]);
}
}
Ar.Logf(TEXT("Valid layer names:"));
for (int32 LayerIndex = 0; LayerIndex < LayerNames.Num(); LayerIndex++)
{
Ar.Logf(TEXT(" %s"), *LayerNames[LayerIndex].ToString());
}
}
return true;
}
bool UGameViewportClient::HandleViewModeCommand( const TCHAR* Cmd, FOutputDevice& Ar, UWorld* InWorld )
{
#if !UE_BUILD_DEBUG
// If there isn't a cheat manager, exit out
bool bCheatsEnabled = false;
for (FLocalPlayerIterator It((UEngine*)GetOuter(), InWorld); It; ++It)
{
if (It->PlayerController != NULL && It->PlayerController->CheatManager != NULL)
{
bCheatsEnabled = true;
break;
}
}
if (!bCheatsEnabled)
{
return true;
}
#endif
FString ViewModeName = FParse::Token(Cmd, 0);
if(!ViewModeName.IsEmpty())
{
uint32 i = 0;
for(; i < VMI_Max; ++i)
{
if(ViewModeName == GetViewModeName((EViewModeIndex)i))
{
ViewModeIndex = i;
Ar.Logf(TEXT("Set new viewmode: %s"), GetViewModeName((EViewModeIndex)ViewModeIndex));
break;
}
}
if(i == VMI_Max)
{
Ar.Logf(TEXT("Error: view mode not recognized: %s"), *ViewModeName);
}
}
else
{
Ar.Logf(TEXT("Current view mode: %s"), GetViewModeName((EViewModeIndex)ViewModeIndex));
FString ViewModes;
for(uint32 i = 0; i < VMI_Max; ++i)
{
if(i != 0)
{
ViewModes += TEXT(", ");
}
ViewModes += GetViewModeName((EViewModeIndex)i);
}
Ar.Logf(TEXT("Available view modes: %s"), *ViewModes);
}
if (ViewModeIndex == VMI_StationaryLightOverlap)
{
Ar.Logf(TEXT("This view mode is currently not supported in game."));
ViewModeIndex = VMI_Lit;
}
if (FPlatformProperties::SupportsWindowedMode() == false)
{
if(ViewModeIndex == VMI_Unlit
|| ViewModeIndex == VMI_StationaryLightOverlap
|| ViewModeIndex == VMI_Lit_DetailLighting
|| ViewModeIndex == VMI_ReflectionOverride)
{
Ar.Logf(TEXT("This view mode is currently not supported on consoles."));
ViewModeIndex = VMI_Lit;
}
}
if ((ViewModeIndex != VMI_Lit && ViewModeIndex != VMI_ShaderComplexity) && !AllowDebugViewmodes())
{
Ar.Logf(TEXT("Debug viewmodes not allowed on consoles by default. See AllowDebugViewmodes()."));
ViewModeIndex = VMI_Lit;
}
ApplyViewMode((EViewModeIndex)ViewModeIndex, true, EngineShowFlags);
return true;
}
bool UGameViewportClient::HandleNextViewModeCommand( const TCHAR* Cmd, FOutputDevice& Ar, UWorld* InWorld )
{
#if !UE_BUILD_DEBUG
// If there isn't a cheat manager, exit out
bool bCheatsEnabled = false;
for (FLocalPlayerIterator It((UEngine*)GetOuter(), InWorld); It; ++It)
{
if (It->PlayerController != NULL && It->PlayerController->CheatManager != NULL)
{
bCheatsEnabled = true;
break;
}
}
if (!bCheatsEnabled)
{
return true;
}
#endif
ViewModeIndex = ViewModeIndex + 1;
// wrap around
if(ViewModeIndex == VMI_Max)
{
ViewModeIndex = 0;
}
Ar.Logf(TEXT("New view mode: %s"), GetViewModeName((EViewModeIndex)ViewModeIndex));
ApplyViewMode((EViewModeIndex)ViewModeIndex, true, EngineShowFlags);
return true;
}
bool UGameViewportClient::HandlePrevViewModeCommand( const TCHAR* Cmd, FOutputDevice& Ar, UWorld* InWorld )
{
#if !UE_BUILD_DEBUG
// If there isn't a cheat manager, exit out
bool bCheatsEnabled = false;
for (FLocalPlayerIterator It((UEngine*)GetOuter(), InWorld); It; ++It)
{
if (It->PlayerController != NULL && It->PlayerController->CheatManager != NULL)
{
bCheatsEnabled = true;
break;
}
}
if (!bCheatsEnabled)
{
return true;
}
#endif
ViewModeIndex = ViewModeIndex - 1;
// wrap around
if(ViewModeIndex < 0)
{
ViewModeIndex = VMI_Max - 1;
}
Ar.Logf(TEXT("New view mode: %s"), GetViewModeName((EViewModeIndex)ViewModeIndex));
ApplyViewMode((EViewModeIndex)ViewModeIndex, true, EngineShowFlags);
return true;
}
bool UGameViewportClient::HandlePreCacheCommand( const TCHAR* Cmd, FOutputDevice& Ar )
{
Precache();
return true;
}
bool UGameViewportClient::SetDisplayConfiguration(const FIntPoint* Dimensions, EWindowMode::Type WindowMode)
{
if (Viewport == NULL || ViewportFrame == NULL)
{
return true;
}
UGameEngine* GameEngine = Cast<UGameEngine>(GEngine);
if (GameEngine)
{
UGameUserSettings* UserSettings = GameEngine->GetGameUserSettings();
UserSettings->SetFullscreenMode(WindowMode);
if (Dimensions)
{
UserSettings->SetScreenResolution(*Dimensions);
}
UserSettings->ApplySettings(false);
}
else
{
int32 NewX = GSystemResolution.ResX;
int32 NewY = GSystemResolution.ResY;
if (Dimensions)
{
NewX = Dimensions->X;
NewY = Dimensions->Y;
}
FSystemResolution::RequestResolutionChange(NewX, NewY, WindowMode);
}
return true;
}
bool UGameViewportClient::HandleToggleFullscreenCommand()
{
static auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.FullScreenMode"));
check(CVar);
auto FullScreenMode = CVar->GetValueOnGameThread() == 0 ? EWindowMode::Fullscreen : EWindowMode::WindowedFullscreen;
FullScreenMode = Viewport->IsFullscreen() ? EWindowMode::Windowed : FullScreenMode;
if (PLATFORM_WINDOWS && FullScreenMode == EWindowMode::Fullscreen)
{
// Handle fullscreen mode differently for D3D11/D3D12
static const bool bD3D12 = FParse::Param(FCommandLine::Get(), TEXT("d3d12")) || FParse::Param(FCommandLine::Get(), TEXT("dx12"));
if (bD3D12)
{
// Force D3D12 RHI to use windowed fullscreen mode
FullScreenMode = EWindowMode::WindowedFullscreen;
}
}
int32 ResolutionX = GSystemResolution.ResX;
int32 ResolutionY = GSystemResolution.ResY;
// Make sure the user's settings are updated after pressing Alt+Enter to toggle fullscreen. Note
// that we don't need to "apply" the setting change, as we already did that above directly.
UGameEngine* GameEngine = Cast<UGameEngine>( GEngine );
if( GameEngine )
{
UGameUserSettings* UserSettings = GameEngine->GetGameUserSettings();
if( UserSettings != nullptr )
{
// Ensure that our desired screen size will fit on the display
ResolutionX = UserSettings->GetScreenResolution().X;
ResolutionY = UserSettings->GetScreenResolution().Y;
UGameEngine::DetermineGameWindowResolution(ResolutionX, ResolutionY, FullScreenMode);
UserSettings->SetFullscreenMode(FullScreenMode);
UserSettings->ConfirmVideoMode();
}
}
FSystemResolution::RequestResolutionChange(ResolutionX, ResolutionY, FullScreenMode);
ToggleFullscreenDelegate.Broadcast(FullScreenMode != EWindowMode::Windowed);
return true;
}
bool UGameViewportClient::HandleSetResCommand( const TCHAR* Cmd, FOutputDevice& Ar )
{
if(Viewport && ViewportFrame)
{
int32 X=FCString::Atoi(Cmd);
const TCHAR* CmdTemp = FCString::Strchr(Cmd,'x') ? FCString::Strchr(Cmd,'x')+1 : FCString::Strchr(Cmd,'X') ? FCString::Strchr(Cmd,'X')+1 : TEXT("");
int32 Y=FCString::Atoi(CmdTemp);
Cmd = CmdTemp;
EWindowMode::Type WindowMode = Viewport->GetWindowMode();
if(FCString::Strchr(Cmd,'w') || FCString::Strchr(Cmd,'W'))
{
if(FCString::Strchr(Cmd, 'f') || FCString::Strchr(Cmd, 'F'))
{
WindowMode = EWindowMode::WindowedFullscreen;
}
else
{
WindowMode = EWindowMode::Windowed;
}
}
else if(FCString::Strchr(Cmd,'f') || FCString::Strchr(Cmd,'F'))
{
WindowMode = EWindowMode::Fullscreen;
}
if( X && Y )
{
FSystemResolution::RequestResolutionChange(X, Y, WindowMode);
}
}
return true;
}
bool UGameViewportClient::HandleHighresScreenshotCommand( const TCHAR* Cmd, FOutputDevice& Ar )
{
if(Viewport)
{
if (GetHighResScreenshotConfig().ParseConsoleCommand(Cmd, Ar))
{
Viewport->TakeHighResScreenShot();
}
}
return true;
}
bool UGameViewportClient::HandleHighresScreenshotUICommand( const TCHAR* Cmd, FOutputDevice& Ar )
{
// Open the highres screenshot UI. When the capture region editing works properly, we can pass CaptureRegionWidget through
// HighResScreenshotDialog = SHighResScreenshotDialog::OpenDialog(GetWorld(), Viewport, NULL /*CaptureRegionWidget*/);
// Disabled until mouse specification UI can be used correctly
return true;
}
bool UGameViewportClient::HandleScreenshotCommand( const TCHAR* Cmd, FOutputDevice& Ar )
{
if(Viewport)
{
const bool bShowUI = FParse::Command(&Cmd, TEXT("SHOWUI"));
const bool bAddFilenameSuffix = true;
FScreenshotRequest::RequestScreenshot( FString(), bShowUI, bAddFilenameSuffix );
GScreenshotResolutionX = Viewport->GetSizeXY().X;
GScreenshotResolutionY = Viewport->GetSizeXY().Y;
}
return true;
}
bool UGameViewportClient::HandleBugScreenshotwithHUDInfoCommand( const TCHAR* Cmd, FOutputDevice& Ar )
{
return RequestBugScreenShot(Cmd, true);
}
bool UGameViewportClient::HandleBugScreenshotCommand( const TCHAR* Cmd, FOutputDevice& Ar )
{
return RequestBugScreenShot(Cmd, false);
}
bool UGameViewportClient::HandleKillParticlesCommand( const TCHAR* Cmd, FOutputDevice& Ar )
{
// Don't kill in the Editor to avoid potential content clobbering.
if( !GIsEditor )
{
extern bool GIsAllowingParticles;
// Deactivate system and kill existing particles.
for( TObjectIterator<UParticleSystemComponent> It; It; ++It )
{
UParticleSystemComponent* ParticleSystemComponent = *It;
ParticleSystemComponent->DeactivateSystem();
ParticleSystemComponent->KillParticlesForced();
}
// No longer initialize particles from here on out.
GIsAllowingParticles = false;
}
return true;
}
bool UGameViewportClient::HandleForceSkelLODCommand( const TCHAR* Cmd, FOutputDevice& Ar, UWorld* InWorld )
{
int32 ForceLod = 0;
if(FParse::Value(Cmd,TEXT("LOD="),ForceLod))
{
ForceLod++;
}
for (TObjectIterator<USkeletalMeshComponent> It; It; ++It)
{
USkeletalMeshComponent* SkelComp = *It;
if( SkelComp->GetScene() == InWorld->Scene && !SkelComp->IsTemplate())
{
SkelComp->ForcedLodModel = ForceLod;
}
}
return true;
}
bool UGameViewportClient::HandleDisplayCommand( const TCHAR* Cmd, FOutputDevice& Ar )
{
TCHAR ObjectName[256];
TCHAR PropStr[256];
if ( FParse::Token(Cmd, ObjectName, ARRAY_COUNT(ObjectName), true) &&
FParse::Token(Cmd, PropStr, ARRAY_COUNT(PropStr), true) )
{
UObject* Obj = FindObject<UObject>(ANY_PACKAGE, ObjectName);
if (Obj != NULL)
{
FName PropertyName(PropStr, FNAME_Find);
if (PropertyName != NAME_None && FindField<UProperty>(Obj->GetClass(), PropertyName) != NULL)
{
FDebugDisplayProperty& NewProp = DebugProperties[DebugProperties.AddZeroed()];
NewProp.Obj = Obj;
NewProp.PropertyName = PropertyName;
}
else
{
Ar.Logf(TEXT("Property '%s' not found on object '%s'"), PropStr, *Obj->GetName());
}
}
else
{
Ar.Logf(TEXT("Object not found"));
}
}
return true;
}
bool UGameViewportClient::HandleDisplayAllCommand( const TCHAR* Cmd, FOutputDevice& Ar )
{
TCHAR ClassName[256];
TCHAR PropStr[256];
if (FParse::Token(Cmd, ClassName, ARRAY_COUNT(ClassName), true))
{
bool bValidClassToken = true;
UClass* WithinClass = NULL;
{
FString ClassStr(ClassName);
int32 DotIndex = ClassStr.Find(TEXT("."));
if (DotIndex != INDEX_NONE)
{
// first part is within class
WithinClass = FindObject<UClass>(ANY_PACKAGE, *ClassStr.Left(DotIndex));
if (WithinClass == NULL)
{
Ar.Logf(TEXT("Within class not found"));
bValidClassToken = false;
}
else
{
FCString::Strncpy(ClassName, *ClassStr.Right(ClassStr.Len() - DotIndex - 1), 256);
bValidClassToken = FCString::Strlen(ClassName) > 0;
}
}
}
if (bValidClassToken)
{
FParse::Token(Cmd, PropStr, ARRAY_COUNT(PropStr), true);
UClass* Cls = FindObject<UClass>(ANY_PACKAGE, ClassName);
if (Cls != NULL)
{
FName PropertyName(PropStr, FNAME_Find);
UProperty* Prop = PropertyName != NAME_None ? FindField<UProperty>(Cls, PropertyName) : NULL;
{
// add all un-GCable things immediately as that list is static
// so then we only have to iterate over dynamic things each frame
for (TObjectIterator<UObject> It; It; ++It)
{
if (!GUObjectArray.IsDisregardForGC(*It))
{
break;
}
else if (It->IsA(Cls) && !It->IsTemplate() && (WithinClass == NULL || (It->GetOuter() != NULL && It->GetOuter()->GetClass()->IsChildOf(WithinClass))))
{
FDebugDisplayProperty& NewProp = DebugProperties[DebugProperties.AddZeroed()];
NewProp.Obj = *It;
NewProp.PropertyName = PropertyName;
if (!Prop)
{
NewProp.bSpecialProperty = true;
}
}
}
FDebugDisplayProperty& NewProp = DebugProperties[DebugProperties.AddZeroed()];
NewProp.Obj = Cls;
NewProp.WithinClass = WithinClass;
NewProp.PropertyName = PropertyName;
if (!Prop)
{
NewProp.bSpecialProperty = true;
}
}
}
else
{
Ar.Logf(TEXT("Object not found"));
}
}
}
return true;
}
bool UGameViewportClient::HandleDisplayAllLocationCommand( const TCHAR* Cmd, FOutputDevice& Ar )
{
TCHAR ClassName[256];
if (FParse::Token(Cmd, ClassName, ARRAY_COUNT(ClassName), true))
{
UClass* Cls = FindObject<UClass>(ANY_PACKAGE, ClassName);
if (Cls != NULL)
{
// add all un-GCable things immediately as that list is static
// so then we only have to iterate over dynamic things each frame
for (TObjectIterator<UObject> It(true); It; ++It)
{
if (!GUObjectArray.IsDisregardForGC(*It))
{
break;
}
else if (It->IsA(Cls))
{
FDebugDisplayProperty& NewProp = DebugProperties[DebugProperties.AddZeroed()];
NewProp.Obj = *It;
NewProp.PropertyName = NAME_Location;
NewProp.bSpecialProperty = true;
}
}
FDebugDisplayProperty& NewProp = DebugProperties[DebugProperties.AddZeroed()];
NewProp.Obj = Cls;
NewProp.PropertyName = NAME_Location;
NewProp.bSpecialProperty = true;
}
else
{
Ar.Logf(TEXT("Object not found"));
}
}
return true;
}
bool UGameViewportClient::HandleDisplayAllRotationCommand( const TCHAR* Cmd, FOutputDevice& Ar )
{
TCHAR ClassName[256];
if (FParse::Token(Cmd, ClassName, ARRAY_COUNT(ClassName), true))
{
UClass* Cls = FindObject<UClass>(ANY_PACKAGE, ClassName);
if (Cls != NULL)
{
// add all un-GCable things immediately as that list is static
// so then we only have to iterate over dynamic things each frame
for (TObjectIterator<UObject> It(true); It; ++It)
{
if (!GUObjectArray.IsDisregardForGC(*It))
{
break;
}
else if (It->IsA(Cls))
{
FDebugDisplayProperty& NewProp = DebugProperties[DebugProperties.AddZeroed()];
NewProp.Obj = *It;
NewProp.PropertyName = NAME_Rotation;
NewProp.bSpecialProperty = true;
}
}
FDebugDisplayProperty& NewProp = DebugProperties[DebugProperties.AddZeroed()];
NewProp.Obj = Cls;
NewProp.PropertyName = NAME_Rotation;
NewProp.bSpecialProperty = true;
}
else
{
Ar.Logf(TEXT("Object not found"));
}
}
return true;
}
bool UGameViewportClient::HandleDisplayClearCommand( const TCHAR* Cmd, FOutputDevice& Ar )
{
DebugProperties.Empty();
return true;
}
bool UGameViewportClient::HandleTextureDefragCommand( const TCHAR* Cmd, FOutputDevice& Ar )
{
extern void appDefragmentTexturePool();
appDefragmentTexturePool();
return true;
}
bool UGameViewportClient::HandleToggleMIPFadeCommand( const TCHAR* Cmd, FOutputDevice& Ar )
{
GEnableMipLevelFading = (GEnableMipLevelFading >= 0.0f) ? -1.0f : 1.0f;
Ar.Logf(TEXT("Mip-fading is now: %s"), (GEnableMipLevelFading >= 0.0f) ? TEXT("ENABLED") : TEXT("DISABLED"));
return true;
}
bool UGameViewportClient::HandlePauseRenderClockCommand( const TCHAR* Cmd, FOutputDevice& Ar )
{
GPauseRenderingRealtimeClock = !GPauseRenderingRealtimeClock;
Ar.Logf(TEXT("The global realtime rendering clock is now: %s"), GPauseRenderingRealtimeClock ? TEXT("PAUSED") : TEXT("RUNNING"));
return true;
}
bool UGameViewportClient::RequestBugScreenShot(const TCHAR* Cmd, bool bDisplayHUDInfo)
{
// Path/name is the first (and only supported) argument
FString FileName = Cmd;
// Handle just a plain console command (e.g. "BUGSCREENSHOT").
if (FileName.Len() == 0)
{
FileName = TEXT("BugScreenShot.png");
}
// Handle a console command and name (e.g. BUGSCREENSHOT FOO)
if (FileName.Contains(TEXT("/")) == false)
{
// Path will be <gamename>/bugit/<platform>/desc_
const FString BaseFile = FString::Printf(TEXT("%s%s_"), *FPaths::BugItDir(), *FPaths::GetBaseFilename(FileName));
// find the next filename in the sequence, e.g <gamename>/bugit/<platform>/desc_00000.png
FFileHelper::GenerateNextBitmapFilename(BaseFile, TEXT("png"), FileName);
}
if (Viewport != NULL)
{
UWorld* const ViewportWorld = GetWorld();
if (bDisplayHUDInfo && (ViewportWorld != nullptr))
{
for (FConstPlayerControllerIterator Iterator = ViewportWorld->GetPlayerControllerIterator(); Iterator; ++Iterator)
{
APlayerController* PlayerController = Iterator->Get();
if (PlayerController && PlayerController->GetHUD())
{
PlayerController->GetHUD()->HandleBugScreenShot();
}
}
}
const bool bShowUI = true;
const bool bAddFilenameSuffix = false;
FScreenshotRequest::RequestScreenshot(FileName, true, bAddFilenameSuffix);
}
return true;
}
void UGameViewportClient::HandleViewportStatCheckEnabled(const TCHAR* InName, bool& bOutCurrentEnabled, bool& bOutOthersEnabled)
{
// Check to see which viewports have this enabled (current, non-current)
const bool bEnabled = IsStatEnabled(InName);
if (GStatProcessingViewportClient == this && GEngine->GameViewport == this)
{
bOutCurrentEnabled = bEnabled;
}
else
{
bOutOthersEnabled |= bEnabled;
}
}
void UGameViewportClient::HandleViewportStatEnabled(const TCHAR* InName)
{
// Just enable this on the active viewport
if (GStatProcessingViewportClient == this && GEngine->GameViewport == this)
{
SetStatEnabled(InName, true);
}
}
void UGameViewportClient::HandleViewportStatDisabled(const TCHAR* InName)
{
// Just disable this on the active viewport
if (GStatProcessingViewportClient == this && GEngine->GameViewport == this)
{
SetStatEnabled(InName, false);
}
}
void UGameViewportClient::HandleViewportStatDisableAll(const bool bInAnyViewport)
{
// Disable all on either all or the current viewport (depending on the flag)
if (bInAnyViewport || (GStatProcessingViewportClient == this && GEngine->GameViewport == this))
{
SetStatEnabled(NULL, false, true);
}
}
void UGameViewportClient::HandleWindowDPIScaleChanged(TSharedRef<SWindow> InWindow)
{
#if WITH_EDITOR
if (InWindow == Window)
{
RequestUpdateDPIScale();
}
#endif
}
bool UGameViewportClient::SetHardwareCursor(EMouseCursor::Type CursorShape, FName GameContentPath, FVector2D HotSpot)
{
TSharedPtr<FHardwareCursor> HardwareCursor = HardwareCursorCache.FindRef(GameContentPath);
if ( HardwareCursor.IsValid() == false )
{
HardwareCursor = MakeShared<FHardwareCursor>(FPaths::ProjectContentDir() / GameContentPath.ToString(), HotSpot);
if ( HardwareCursor->GetHandle() == nullptr )
{
return false;
}
HardwareCursorCache.Add(GameContentPath, HardwareCursor);
}
HardwareCursors.Add(CursorShape, HardwareCursor);
if ( bIsMouseOverClient )
{
TSharedPtr<ICursor> PlatformCursor = FSlateApplication::Get().GetPlatformCursor();
if ( ICursor* Cursor = PlatformCursor.Get() )
{
Cursor->SetTypeShape(CursorShape, HardwareCursor->GetHandle());
}
}
return true;
}
bool UGameViewportClient::IsSimulateInEditorViewport() const
{
const FSceneViewport* GameViewport = GetGameViewport();
return GameViewport ? GameViewport->GetPlayInEditorIsSimulate() : false;
}
#undef LOCTEXT_NAMESPACE
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment