UE4 : Specify default monitor location at launch

As I recently moved from a two screens to three screens setup for my desktop computer I met a little annoyance in the way the Unreal Engine 4 create its game window (everything is fine with the editor).

By default the engine will create a new game window on the first screen it finds, probably because the Operating System sort them in a specific order and the engine parse the list as-is. My main problem is that the first screen chosen by the engine may not be the primary screen set in the OS settings. That was my case :

A bit annoying as you can imagine. Especially when you launch the game in fullscreen mode.

Using an existing solution : the command line

The first idea I had was looking for an existing setting. I was hoping for the engine to already support a way for specifying the default monitor at least.

The only thing I was able to find was a command line argument that allows to specify the position of the new window in pixels when it is created. It helps but in my case as I switch across multiple computer this could be annoying to change each time. Also for future users of my game this could also be annoying to use as well.

You can specify two arguments :

  • WinX : Set the horizontal position of the game window on the screen
  • WinY : Set the vertical position of the game window on the screen

Example:

UE4Editor.exe -game -WinX=1920 -WinY=0

You can get more details over here : Unreal Engine Command-Line Arguments

Using a custom solution : engine modification

This code is based on the Unreal Engine 4.18 What else could we do to solve this problem ?
Well, after looking around I came across this answerhub page : Is it possible to choose the default screen in a multi monitor configuration?

The answer seemed promising, basically it allows to specify via the command line on which monitor you want to put the window. However I had troubles implementing it at first, the compiler had troubles linking the GetDisplayMetrics() function from the BeginPlay() function of my PlayerController. I don’t know for sure why, but I’m assuming it’s because this pass of code should be called much earlier on the engine side. Gameplay code probably happens too late.

So where do we put this code ? To answer this question I looked into the engine to find where the WinX/WinY command line argument where parsed/used. I didn’t find where the parsing was, but how the value where used however, so I hooked myself in the same location.

Everything happens in UGameEngine::CreateGameViewport() which can be found in Engine\Source\Runtime\Engine\Private\GameEngine.cpp. Now look at the following lines of code :

	// SAVEWINPOS tells us to load/save window positions to user settings (this is disabled by default)
	int32 SaveWinPos;
	if (FParse::Value(FCommandLine::Get(), TEXT("SAVEWINPOS="), SaveWinPos) && SaveWinPos > 0 )
	{
		// Get WinX/WinY from GameSettings, apply them if valid.
		FIntPoint PiePosition = GetGameUserSettings()->GetWindowPosition();
		if (PiePosition.X >= 0 && PiePosition.Y >= 0)
		{
			int32 WinX = GetGameUserSettings()->GetWindowPosition().X;
			int32 WinY = GetGameUserSettings()->GetWindowPosition().Y;
			Window->MoveWindowTo(FVector2D(WinX, WinY));
		}
		Window->SetOnWindowMoved( FOnWindowMoved::CreateUObject( this, &UGameEngine::OnGameWindowMoved ) );
	}

You can see the Window->MoveWindowTo() function call which is exactly what we want. So after these lines I added the following :

	FDisplayMetrics DisplayMetrics;
	FSlateApplication::Get().GetDisplayMetrics(DisplayMetrics);

	int MonitorNumber = 0;
	FParse::Value(FCommandLine::Get(), L"monitor=", MonitorNumber);

	//Reset to primary if the monitor index is invalid
	if( MonitorNumber >= DisplayMetrics.MonitorInfo.Num() || MonitorNumber < 0 )
	{
		FString Message = "_____ Incorrect monitor index, will use primary screen instead";
		UE_LOG(LogTemp, Warning, TEXT( "%s" ), *Message);
		MonitorNumber = 0;
	}

	//If monitor index is 0, we default to primary screen
	if( MonitorNumber == 0 )
	{
		for( int i = 0; i < DisplayMetrics.MonitorInfo.Num(); i++ ) 
		{ 
			FString MonitorInfo = "_____ Found monitor \"" + DisplayMetrics.MonitorInfo[i].Name + "\" (is primary : ";
			MonitorInfo += FString::FromInt( DisplayMetrics.MonitorInfo[i].bIsPrimary ) + FString(")"); 
			UE_LOG(LogTemp, Warning, TEXT( "%s" ), *MonitorInfo); 

			if( DisplayMetrics.MonitorInfo[i].bIsPrimary ) 
			{ 
				MonitorNumber = i + 1; 
			} 
		} 
	} 

	//Move window to desired screen 
	int8 MonitorIndex = MonitorNumber - 1; 
	int32 CurrentMonitorWidth = DisplayMetrics.MonitorInfo[MonitorIndex].NativeWidth; 

	float WidthPosition = (MonitorIndex) * DisplayMetrics.PrimaryDisplayWidth - CurrentMonitorWidth; 

	Window->MoveWindowTo( FVector2D(WidthPosition, 0.f) );
	Window->SetOnWindowMoved( FOnWindowMoved::CreateUObject( this, &UGameEngine::OnGameWindowMoved ) );
//

It’s a bit modified version of the code found in the link above.
Here I move the window on the primary screen if no monitor as been specified on the command line. A much better behavior ! 🙂