Search Unity

  1. Are you interested in providing feedback directly to Unity teams? Sign up to become a member of Unity Pulse, our new product feedback and research community.
    Dismiss Notice

Switch Monitor at runtime

Discussion in 'Scripting' started by InvincibleCat, Oct 23, 2017.

  1. InvincibleCat

    InvincibleCat

    Joined:
    Dec 23, 2014
    Posts:
    73
    Hey!

    I have the option the change which monitor you want to use at runtime from a video setting menu on PC.
    It worked fine so far. I am using this method to do so:
    Code (CSharp):
    1.     public IEnumerator ChangeMonitorAsync()
    2.     {
    3.         if (Monitor >= Display.displays.Length)
    4.         {
    5.             Monitor = 0;
    6.         }
    7.         PlayerPrefs.SetInt("UnitySelectMonitor", Monitor);
    8.         Screen.SetResolution(800, 600, FullScreen);
    9.         yield return null;
    10.         Resolution = Screen.resolutions.Length - 1;
    11.         Apply();
    12.     }
    Since I updated to Unity 2017.1 it does not work anymore. I have to restart the game to apply the changes.

    It seems that it is a known issue but it says won't fix...
    https://issuetracker.unity3d.com/is...ntime-with-unityselectmonitor-no-longer-works

    Any clue?

    Thanks,
    -Tim
     
    deus0 likes this.
  2. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,517
  3. InvincibleCat

    InvincibleCat

    Joined:
    Dec 23, 2014
    Posts:
    73
    Unfortunately this won't work as it adds a new display (a secondary one). It does not change the main display!
     
    futurlab_xbox likes this.
  4. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,517
    Yeah you're right. Seems like they need a Deactivate method to counter. My guess is they won't fix it because something is slated to support the functionality properly (not setting magic strings in PlayerPrefs)
     
  5. InvincibleCat

    InvincibleCat

    Joined:
    Dec 23, 2014
    Posts:
    73
    Maybe... it is kind of annoying though since it broke my game...
    Last resort for me would be to tell the user to restart the game then
     
  6. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    8,776
    The reason I marked it as won't fix was because it was relying on an undocumented internal implementation detail (setting the UnitySelectMonitor player pref) of the engine. It broke by accident as the whole windowing manager was rewritten to fix some serious design flaws related to multi-monitor setups. Since this wasn't a documented or supported feature, it was never noticed when the build was tested. And I have no idea how this was discovered to achieve wanted effect in the first place... Furthermore, "fixing" this to behave like it did before would make moving the window across monitors and entering/leaving fullscreen on a particular monitor broken.

    We don't have any APIs to manipulate window position in Unity, but you can P/Invoke into MoveWindow function instead and manipulate the window position with it.
     
    Joe-Censored likes this.
  7. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    5,973
    What is the recommended and supported Unity API to switch monitors in Unity 2017 while the game is running?
     
    cxode likes this.
  8. InvincibleCat

    InvincibleCat

    Joined:
    Dec 23, 2014
    Posts:
    73
    I understand. In the meantime I just change it so a restart is required.
     
  9. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    8,776
    Use the Win32 function I mentioned.
     
  10. InvincibleCat

    InvincibleCat

    Joined:
    Dec 23, 2014
    Posts:
    73
    But how can we get the window position then?
     
  11. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    8,776
    You can use EnumDisplayMonitors function to find coordinates of all monitors in the virtual desktop space.
     
  12. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    5,973
    This functionality is needed in the majority of Desktop games. Games often provide an option to select the monitor in which the game is running via an options screen of the game, as shown in the screenshot below.

    video options.jpg

    Do you think it would be beneficial if a multi platform game engine supports such functionality?

    Having support for that allows game developers to write code for switching monitors once and it works across all platforms, such as Windows, OS X and Linux, versus everyone needs to implement it from scratch and has to deal with platform specifics.
     
    Last edited: Jan 29, 2020
    deus0 and DaDonik like this.
  13. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    8,776
    Yes, I definitely believe so. ScreenManager improvements in general are in my team (desktop team) roadmap. Being realistic though, we don't support that today, so I mentioned that API as a way to solve your issues now, rather than at some unspecified time in the future.
     
    Peter77 likes this.
  14. HarryCodder

    HarryCodder

    Joined:
    Feb 20, 2015
    Posts:
    75
    Hi @Tautvydas-Zilys

    Do you have any news on progress on this feature ?
    Thanks in advance.
     
    deus0 and Tom60chat like this.
  15. Hyp-X

    Hyp-X

    Joined:
    Jun 24, 2015
    Posts:
    400
    Is there any improvement for this?
    Thanks in advance.
     
    deus0 and SuperNeon like this.
  16. Scott-Steffes

    Scott-Steffes

    Joined:
    Dec 31, 2013
    Posts:
    44
    With the release of 2019.3 the resolution dialogue has been removed meaning this feature is needed more than ever.
    Now there is no way to specify which screen your app runs on besides changing your main display in Windows.
     
  17. cxode

    cxode

    Joined:
    Jun 7, 2017
    Posts:
    218
    @markv12 The user can use OS hotkeys to switch monitors, or switch the application to windowed mode and drag the window over to a different monitor before going back to fullscreen. However, this solution is far from ideal; in particular, Display.main does not update, so we cannot get an updated list of supported resolutions to present to the user. I hope @Tautvydas-Zilys and the rest of the desktop team get around to adding this functionality soon.

    The Win32 API mentioned in this thread does not work on Mac or Linux, so it's not an option for cross-platform games.
     
  18. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    8,776
    Screen.resolutions should update. They did last time I checked...
     
  19. VoodooDetective

    VoodooDetective

    Joined:
    Oct 11, 2019
    Posts:
    184
    @Tautvydas-Zilys is there a roadmap we could follow? It's pretty rough on multi-monitor users to have to go out of fullscreen, drag, and go back into fullscreen every time they play.

    Trying to figure out how much time to put into this. If there's a feature upcoming in the next year, I'll hold off. Otherwise, I'll have to do the cross-platform code myself through p/invoke which will take lots of time and testing.
     
    cxode likes this.
  20. cxode

    cxode

    Joined:
    Jun 7, 2017
    Posts:
    218
    I really, really wish Unity was open source. There are so many tiny issues like this... with open source, we could fix them ourselves, and Unity would be better for everyone.
     
    deus0 likes this.
  21. Holy-Manfred

    Holy-Manfred

    Joined:
    Nov 30, 2013
    Posts:
    13
    Since it has been almost three years since the question has been asked: Is there any progress on this?
    Code (CSharp):
    1. PlayerPrefs.SetInt("UnitySelectMonitor", Monitor);
    still works but requires a restart, which is not ideal.
    @Tautvydas-Zilys Could you provide a bit of information and let us know if this is something that will ever get addressed?
     
    deus0 and futurlab_xbox like this.
  22. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    8,776
    This is on my team's backlog... I am trying to make it happen.
     
  23. talecrafter

    talecrafter

    Joined:
    Mar 26, 2013
    Posts:
    34
    Oh, this is cool to hear.

    And I don't want to hijack this, but if the code that has to be touched for making switching monitors possible is also the part where it's responsible that we can activate more displays but not deactivate them, a fix for that would be most welcome too.
     
    deus0 and futurlab_xbox like this.
  24. cfloutier

    cfloutier

    Joined:
    Jul 30, 2009
    Posts:
    34
    yes most wanted since the dialog box disapeared in version 2019.3...;
    It's hard to ask for the user to restart the application
     
  25. chadfranklin47

    chadfranklin47

    Joined:
    Aug 11, 2015
    Posts:
    137
    A very desired feature
     
  26. Weightless

    Weightless

    Joined:
    Sep 7, 2017
    Posts:
    13
    yeah would be nice to see this
     
  27. Weightless

    Weightless

    Joined:
    Sep 7, 2017
    Posts:
    13
    Alright so for anyone interested I made this work in 2019.3 using the stuff recommended by Tautvydas-Zilys

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Runtime.InteropServices;
    5. using UnityEngine;
    6.  
    7. public class DisplayChanger : MonoBehaviour
    8. {
    9.     //cycles through the connected displays by calling the ChangeDisplayClicked() method
    10.     //does not work with windowed mode
    11.  
    12.     List<DisplayInfo> myDisplays = new List<DisplayInfo>();
    13.     List<MyMonitor> myMonitors = new List<MyMonitor>();
    14.     int monitorNumber = 0;
    15.  
    16.     [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, ExactSpelling = true, SetLastError = true)]
    17.     internal static extern void MoveWindow(IntPtr hwnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
    18.  
    19.     [DllImport("user32.dll")]
    20.     private static extern IntPtr GetActiveWindow();
    21.  
    22.     [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, ExactSpelling = true, SetLastError = true)]
    23.     internal static extern bool GetWindowRect(IntPtr hWnd, ref RECT rect);
    24.  
    25.     [DllImport("user32.dll")]
    26.     static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumDelegate lpfnEnum, IntPtr dwData);
    27.  
    28.     delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData);
    29.  
    30.     [StructLayout(LayoutKind.Sequential)]
    31.     public struct RECT
    32.     {
    33.         public int left;
    34.         public int top;
    35.         public int right;
    36.         public int bottom;
    37.     }
    38.  
    39.     public class DisplayInfo
    40.     {
    41.         public string Availability { get; set; }
    42.         public string ScreenHeight { get; set; }
    43.         public string ScreenWidth { get; set; }
    44.         public RECT MonitorArea { get; set; }
    45.         public RECT WorkArea { get; set; }
    46.     }
    47.  
    48.     public class DisplayInfoCollection : List<DisplayInfo>
    49.     {
    50.     }
    51.  
    52.     [DllImport("User32.dll", CharSet = CharSet.Auto)]
    53.     public static extern bool GetMonitorInfo(IntPtr hmonitor, [In, Out] MONITORINFOEX info);
    54.     [DllImport("User32.dll", ExactSpelling = true)]
    55.     public static extern IntPtr MonitorFromPoint(POINTSTRUCT pt, int flags);
    56.  
    57.     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)]
    58.     public class MONITORINFOEX
    59.     {
    60.         public int cbSize = Marshal.SizeOf(typeof(MONITORINFOEX));
    61.         public RECT rcMonitor = new RECT();
    62.         public RECT rcWork = new RECT();
    63.         public int dwFlags = 0;
    64.         [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
    65.         public char[] szDevice = new char[32];
    66.     }
    67.  
    68.     [StructLayout(LayoutKind.Sequential)]
    69.     public struct POINTSTRUCT
    70.     {
    71.         public int x;
    72.         public int y;
    73.         public POINTSTRUCT(int x, int y)
    74.         {
    75.             this.x = x;
    76.             this.y = y;
    77.         }
    78.     }
    79.  
    80.     public DisplayInfoCollection GetDisplays()
    81.     {
    82.         DisplayInfoCollection col = new DisplayInfoCollection();
    83.  
    84.         EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero,
    85.             delegate (IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData)
    86.             {
    87.                 MONITORINFOEX mi = new MONITORINFOEX();
    88.                 mi.cbSize = (int)Marshal.SizeOf(mi);
    89.                 bool success = GetMonitorInfo(hMonitor, mi);
    90.                 if (success)
    91.                 {
    92.                     DisplayInfo di = new DisplayInfo();
    93.                     di.ScreenWidth = (mi.rcMonitor.right - mi.rcMonitor.left).ToString();
    94.                     di.ScreenHeight = (mi.rcMonitor.bottom - mi.rcMonitor.top).ToString();
    95.                     di.MonitorArea = mi.rcMonitor;
    96.                     di.WorkArea = mi.rcWork;
    97.                     di.Availability = mi.dwFlags.ToString();
    98.                     col.Add(di);
    99.                 }
    100.                 return true;
    101.             }, IntPtr.Zero);
    102.         return col;
    103.     }
    104.  
    105.     public class MyMonitor
    106.     {
    107.         public int targetX;
    108.         public int monitorNumber;
    109.         public int height;
    110.         public int width;
    111.  
    112.         public MyMonitor(int targetX, int monitorNumber, int height, int width)
    113.         {
    114.             this.targetX = targetX;
    115.             this.monitorNumber = monitorNumber;
    116.             this.height = height;
    117.             this.width = width;
    118.         }
    119.     }
    120.  
    121.     private void Start()
    122.     {
    123.         myDisplays = GetDisplays();
    124.         for (int i = 0; i < myDisplays.Count; i++)
    125.         {
    126.             myMonitors.Add(new MyMonitor(myDisplays[i].WorkArea.left, i, Convert.ToInt32(myDisplays[i].ScreenHeight), Convert.ToInt32(myDisplays[i].ScreenWidth)));
    127.         }
    128.     }
    129.  
    130.     public void ChangeDisplayClicked()
    131.     {
    132.         if (monitorNumber < myDisplays.Count)
    133.         {
    134.             StartCoroutine(MyMoveWindow(myMonitors[monitorNumber].height, myMonitors[monitorNumber].width, myMonitors[monitorNumber].targetX));
    135.             monitorNumber++;
    136.         }
    137.         else if (monitorNumber >= myDisplays.Count)
    138.         {
    139.             monitorNumber = 0;
    140.             StartCoroutine(MyMoveWindow(myMonitors[monitorNumber].height, myMonitors[monitorNumber].width, myMonitors[monitorNumber].targetX));
    141.         }
    142.     }
    143.  
    144.     public IEnumerator MyMoveWindow(int newHeight, int newWidth, int targetX)
    145.     {
    146.         IntPtr hwnd;
    147.         RECT Rect = new RECT();
    148.         yield return new WaitForSeconds(2);
    149.         hwnd = GetActiveWindow();
    150.         GetWindowRect(hwnd, ref Rect);
    151.         MoveWindow(hwnd, targetX, Rect.top, newWidth, newHeight, true);
    152.     }
    153. }
    154.  
     
    ExtraCat, Roman-Ilyin, deus0 and 4 others like this.
  28. Hyp-X

    Hyp-X

    Joined:
    Jun 24, 2015
    Posts:
    400
    Does this work with Exclusive fullscreen?
    I'm not gonna implement this just to find out it doesn't.
     
    deus0 likes this.
  29. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    8,776
    You need to switch to fullscreen window mode before moving the window, and switch back to exclusive fullscreen after moving it.
     
    Munchy2007 likes this.
  30. holyfot

    holyfot

    Joined:
    Dec 16, 2016
    Posts:
    42
    Me and many others have the issue of the game freezing when you try to: Screen.SetResolution(x, y, FullScreenMode.ExclusiveFullScreen, refreshRate);

    If you just use true/false instead of FullScreenMode it doesn't freeze, the FullScreenMode seems to actually use the hardware to set the screen mode, while true/false is kind of a soft mode.
     
  31. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    8,776
    Can we have a bug report on it? I've heard of such issues happening in the wild before but we haven't been able to narrow it down or reproduce it ourselves. Even a crash dump from a frozen state would help.
     
  32. Jelvand

    Jelvand

    Joined:
    Oct 4, 2019
    Posts:
    3
    Hi, sorry to necro this, but since I still haven't found any support for doing this with Unity APIs, I've resorted to the above method, which works great in Mono.
    However, for IL2CPP I have problems.
    The first one was fairly easy to fix: The delegate needs to be a static function, and cannot be supplied anonymously like in the above code, and the static function implementing the delegate must have the MonoPInvokeCallback attribute with the type of the delegate declaration you need to do.

    After that I ran into another problem. The call to the GetMonitorInfo fails, and the structs provided to it remain unfilled (I've checked). The handles provided to the callback, and subsequently to the failing function, seem to be legit (at least they're not 0), and the callback is in fact called as many times as the number of monitors. I've tried to call the direct functions that the GetMonitorInfo aliases to (GetMonitorInfoA, GetMonitorInfoW) without any change in luck.
    Does anyone have any idea why this call would fail just for IL2CPP builds, and what I could possibly do about it?
     
  33. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    8,776
    Looks like you ran into an IL2CPP marshaling bug. It seems like IL2CPP isn't picking up the CharSet property from the StructLayout attribute on MONITORINFOEX when marshaling the szDevice field. Can you report a bug on it?

    In the mean time, you can work around like this:

    Code (csharp):
    1.     [DllImport("User32.dll", CharSet = CharSet.Unicode)]
    2.     public static extern bool GetMonitorInfo(IntPtr hmonitor, [In, Out] MONITORINFOEX info);
    3.     [DllImport("User32.dll", ExactSpelling = true)]
    4.     public static extern IntPtr MonitorFromPoint(POINTSTRUCT pt, int flags);
    5.  
    6.     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 4)]
    7.     public class MONITORINFOEX
    8.     {
    9.         public int cbSize = Marshal.SizeOf(typeof(MONITORINFOEX));
    10.         public RECT rcMonitor = new RECT();
    11.         public RECT rcWork = new RECT();
    12.         public int dwFlags = 0;
    13.         [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U2, SizeConst = 32)]
    14.         public char[] szDevice = new char[32];
    15.     }
     
  34. Jelvand

    Jelvand

    Joined:
    Oct 4, 2019
    Posts:
    3
    Thanks Tautvydas-Zilus, worked like a charm. I'll look into posting a bug report.
     
  35. deus0

    deus0

    Joined:
    May 12, 2015
    Posts:
    181
    Hi does this work around work for linux/mac?
    Is there any progress for monitor support with unity?
     
    Last edited: Dec 31, 2020
  36. piede828

    piede828

    Joined:
    Dec 12, 2017
    Posts:
    4
    would something like this work if I want to set a camera to display on a monitor that has been connected after the application has started? right now, the build can only detect monitors that are connecting before launch but I need to be able to detected newly connected monitors at runtime? thanks!
     
    deus0 likes this.
  37. BKinAK

    BKinAK

    Joined:
    Mar 6, 2014
    Posts:
    2
    I found that above code only moves a window's targetX and not targetY, if you have a monitor above or below the others in real world space then the switch fails. Here is an updated version that also sets targetY to support this scenario:

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Runtime.InteropServices;
    5. using UnityEngine;
    6.  
    7. public class DisplayChanger : MonoBehaviour
    8. {
    9.     //cycles through the connected displays by calling the ChangeDisplayClicked() method
    10.     //does not work with windowed mode
    11.  
    12.     List<DisplayInfo> myDisplays = new List<DisplayInfo>();
    13.     List<MyMonitor> myMonitors = new List<MyMonitor>();
    14.     int monitorNumber = 0;
    15.  
    16.     [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, ExactSpelling = true, SetLastError = true)]
    17.     internal static extern void MoveWindow(IntPtr hwnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
    18.  
    19.     [DllImport("user32.dll")]
    20.     private static extern IntPtr GetActiveWindow();
    21.  
    22.     [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, ExactSpelling = true, SetLastError = true)]
    23.     internal static extern bool GetWindowRect(IntPtr hWnd, ref RECT rect);
    24.  
    25.     [DllImport("user32.dll")]
    26.     static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumDelegate lpfnEnum, IntPtr dwData);
    27.  
    28.     delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData);
    29.  
    30.     [StructLayout(LayoutKind.Sequential)]
    31.     public struct RECT
    32.     {
    33.         public int left;
    34.         public int top;
    35.         public int right;
    36.         public int bottom;
    37.     }
    38.  
    39.     public class DisplayInfo
    40.     {
    41.         public string Availability { get; set; }
    42.         public string ScreenHeight { get; set; }
    43.         public string ScreenWidth { get; set; }
    44.         public RECT MonitorArea { get; set; }
    45.         public RECT WorkArea { get; set; }
    46.     }
    47.  
    48.     public class DisplayInfoCollection : List<DisplayInfo>
    49.     {
    50.     }
    51.  
    52.     [DllImport("User32.dll", CharSet = CharSet.Unicode)]
    53.     public static extern bool GetMonitorInfo(IntPtr hmonitor, [In, Out] MONITORINFOEX info);
    54.     [DllImport("User32.dll", ExactSpelling = true)]
    55.     public static extern IntPtr MonitorFromPoint(POINTSTRUCT pt, int flags);
    56.  
    57.     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 4)]
    58.     public class MONITORINFOEX
    59.     {
    60.         public int cbSize = Marshal.SizeOf(typeof(MONITORINFOEX));
    61.         public RECT rcMonitor = new RECT();
    62.         public RECT rcWork = new RECT();
    63.         public int dwFlags = 0;
    64.         [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U2, SizeConst = 32)]
    65.         public char[] szDevice = new char[32];
    66.     }
    67.  
    68.     [StructLayout(LayoutKind.Sequential)]
    69.     public struct POINTSTRUCT
    70.     {
    71.         public int x;
    72.         public int y;
    73.         public POINTSTRUCT(int x, int y)
    74.         {
    75.             this.x = x;
    76.             this.y = y;
    77.         }
    78.     }
    79.  
    80.     public DisplayInfoCollection GetDisplays()
    81.     {
    82.         DisplayInfoCollection col = new DisplayInfoCollection();
    83.  
    84.         EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero,
    85.             delegate (IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData)
    86.             {
    87.                 MONITORINFOEX mi = new MONITORINFOEX();
    88.                 mi.cbSize = (int)Marshal.SizeOf(mi);
    89.                 bool success = GetMonitorInfo(hMonitor, mi);
    90.                 if (success)
    91.                 {
    92.                     DisplayInfo di = new DisplayInfo();
    93.                     di.ScreenWidth = (mi.rcMonitor.right - mi.rcMonitor.left).ToString();
    94.                     di.ScreenHeight = (mi.rcMonitor.bottom - mi.rcMonitor.top).ToString();
    95.                     di.MonitorArea = mi.rcMonitor;
    96.                     di.WorkArea = mi.rcWork;
    97.                     di.Availability = mi.dwFlags.ToString();
    98.                     col.Add(di);
    99.                 }
    100.                 return true;
    101.             }, IntPtr.Zero);
    102.         return col;
    103.     }
    104.  
    105.     public class MyMonitor
    106.     {
    107.         public int targetX;
    108.         public int targetY;
    109.         public int monitorNumber;
    110.         public int height;
    111.         public int width;
    112.  
    113.         public MyMonitor(int targetX, int targetY, int monitorNumber, int height, int width)
    114.         {
    115.             this.targetX = targetX;
    116.             this.targetY = targetY;
    117.             this.monitorNumber = monitorNumber;
    118.             this.height = height;
    119.             this.width = width;
    120.         }
    121.     }
    122.  
    123.     private void Start()
    124.     {
    125.         myDisplays = GetDisplays();
    126.         for (int i = 0; i < myDisplays.Count; i++)
    127.         {
    128.             myMonitors.Add(new MyMonitor(myDisplays[i].WorkArea.left, myDisplays[i].WorkArea.top ,i, Convert.ToInt32(myDisplays[i].ScreenHeight), Convert.ToInt32(myDisplays[i].ScreenWidth)));
    129.         }
    130.     }
    131.  
    132.     public void ChangeDisplayClicked()
    133.     {
    134.         if (monitorNumber < myDisplays.Count)
    135.         {
    136.             StartCoroutine(MyMoveWindow(myMonitors[monitorNumber].height, myMonitors[monitorNumber].width, myMonitors[monitorNumber].targetX, myMonitors[monitorNumber].targetY));
    137.             monitorNumber++;
    138.         }
    139.         else if (monitorNumber >= myDisplays.Count)
    140.         {
    141.             monitorNumber = 0;
    142.             StartCoroutine(MyMoveWindow(myMonitors[monitorNumber].height, myMonitors[monitorNumber].width, myMonitors[monitorNumber].targetX, myMonitors[monitorNumber].targetY));
    143.         }
    144.     }
    145.  
    146.     public IEnumerator MyMoveWindow(int newHeight, int newWidth, int targetX, int targetY)
    147.     {
    148.         IntPtr hwnd;
    149.         RECT Rect = new RECT();
    150.         yield return new WaitForSeconds(2);
    151.         hwnd = GetActiveWindow();
    152.         GetWindowRect(hwnd, ref Rect);
    153.         MoveWindow(hwnd, targetX, targetY, newWidth, newHeight, true);
    154.     }
    155. }
    156.  
     
    Munchy2007 likes this.
  38. mdsitton

    mdsitton

    Joined:
    Jan 27, 2018
    Posts:
    38
    Any update on getting this supported natively in engine for desktop platforms?
     
    Gladyon likes this.
  39. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    8,776
    Actually yes, we're working on it right now. I just finished the Windows implementation on Monday.
     
    Clonkex, dvoronin, Hypnotoad0 and 4 others like this.
  40. Gladyon

    Gladyon

    Joined:
    Sep 10, 2015
    Posts:
    322
    Thank you for your work.
     
  41. deus0

    deus0

    Joined:
    May 12, 2015
    Posts:
    181
    That sounds really good! Not sure if that will work using windows build on a linux (wine) but i'm willing to try it out! I guess this will be in 2021.2 or something haha. Looking forward to it though.
     
  42. dvoronin

    dvoronin

    Joined:
    Oct 19, 2016
    Posts:
    16
    Sounds great! Any ETA when it gets to the stable Unity version?
     
  43. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    8,776
    Later this year (2021.2) if all goes well.
     
    cxode and dvoronin like this.
  44. dvoronin

    dvoronin

    Joined:
    Oct 19, 2016
    Posts:
    16
    Happy to hear that, thanks!
     
  45. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    8,776
    rzubek, cxode, chadfranklin47 and 2 others like this.
  46. KimmoFactor

    KimmoFactor

    Joined:
    Dec 29, 2020
    Posts:
    4
    Great! Now as the API is "complete" (I think?), may I also suggest adding a flag to player settings or something to stop Unity from reading/writing these settings from/to the Windows registry by itself? (or just deprecate the whole behaviour) Just so that I can assume full control without Unity doing any magic anywhere.
     
  47. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    8,776
    Can you elaborate what you mean? Which behavior do you find problematic? Unity uses the Windows registry to remember rendering settings (fullscreen mode, resolution, now also position) so that it creates it in the same place where the app was closed.
     
  48. KimmoFactor

    KimmoFactor

    Joined:
    Dec 29, 2020
    Posts:
    4
    Now Unity is doing "clever" things behind my back and there's no documentation about it that I can find beyond some forum posts so it feels like it's some old legacy behaviour. For example Screen.SetResolution says nothing about it storing it permanently on desktop platforms. So if it was fully documented, it would be good too? Last game I did I saved the resolution settings to my own save, as I just didn't know any better.

    Questions like, the answer to I now know, but were not anywhere in documentation when I last needed this. Which settings are saved? Only desktop platforms I assume, where? What are the keys? When are these read, applied and saved? (on start/on exit?) Is it official behaviour that I am to trust Unity to save all window settings and not save anything myself? If I put something in Player Settings / Resolution (full screen mode / windowed / etc.), does that only do something on the first run and then it's in the registry? When in editor play mode, does that disable messing with any of these settings?

    And then there's the fact that they're in the registry. So if the user deletes the application and the save, there's still these ghost settings waiting for you when you next time you install it. Personally I don't like that and I wish I could override it (or get a callback to do it myself into my own save), but that's just me.
     
    Last edited: Apr 29, 2021
    KelsoMRK likes this.
  49. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    8,776
    Alright, let me explain how it works and why it works the way it does.

    The intention is that if the game player changes some settings of the game, like resolution or quality, they expect that these settings persist between launches. While some of the settings the game can control right off the bat (like quality, where you can load different assets depending on the setting), settings like resolution or fullscreen mode need to be set before any of your code executes. Unity has to know the initial resolution it needs to render the first frame in. Otherwise, you'd get weird flickering where a window appears in the center of the display using wrong settings and then snap to the right settings as the game code starts executing.

    Unity stores these settings in PlayerPrefs. The fact that they end up in the registry is an implementation detail of the PlayerPrefs class. Each platform will store them differently.

    These are settings Unity currently saves:

    • Fullscreen mode;
    • Resolution (width and height);
    • Display and game window position (starting with Unity 2021.2.0a16).

    The settings are saved when:

    • The rendering resolution changes for whatever reason (be it calling Screen.SetResolution, the gamer dragging the edge of the window, the window is dragged to a monitor with different DPI, etc etc);
    • Fullscreen mode changes (due to Screen.SetResolution, gamer pressing Alt + Enter, etc);
    • The window moving for whatever reason (since Unity 2021.2.0a16).

    When deciding the initial settings at startup, we use this priority:

    1. If a setting value is set via command line, we use that;
    2. Otherwise, if default player setting changed (like fullscreen mode or default resolution), we use that;
    3. Otherwise, if player pref is present, we use that;
    4. Otherwise, we default to the player setting.

    The registry keys are undocumented because they're internal implementation details and are subject to change. "UnitySelectMonitor" from the original post is a good example of that.

    That said, all this doesn't mean that you cannot implement your own logic for these things. It's just that your logic will be applied after a few seconds since the engine needs to load the first scene before your code starts executing.
     
  50. KimmoFactor

    KimmoFactor

    Joined:
    Dec 29, 2020
    Posts:
    4
    Perfect, thanks for the thorough answer! Maybe put that into the 2021.2 documentation too somewhere (like Screen class?) so others can find it too in the future? :)
     
    Clonkex likes this.
unityunity