You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity如何让游戏在外部启动器指定的显示器启动?

解决方案:通过全局坐标定位目标显示器

核心思路

放弃依赖Unity的-monitor参数(Electron与Unity的显示器枚举顺序无统一标准,极易不匹配),改为传递启动器窗口的全局中点坐标给Unity,再通过原生系统API获取显示器的全局布局,匹配坐标后指定游戏启动的显示器。


步骤1:Electron端传递全局中点坐标

在Electron启动器中,计算窗口的全局中点坐标,作为命令行参数传给Unity游戏:

// Electron主进程代码
const { app, BrowserWindow } = require('electron');
let mainWindow;

app.whenReady().then(() => {
  mainWindow = new BrowserWindow({ /* 你的窗口配置 */ });
  
  // 监听窗口关闭事件,启动Unity游戏
  mainWindow.on('close', (e) => {
    e.preventDefault();
    const bounds = mainWindow.getBounds();
    // 计算全局中点坐标(Electron的getBounds返回的是桌面全局坐标)
    const midX = bounds.x + bounds.width / 2;
    const midY = bounds.y + bounds.height / 2;
    // 启动Unity游戏,传入坐标参数
    const { spawn } = require('child_process');
    spawn('YourUnityGame.exe', [`--launcher-mid=${midX},${midY}`]);
    mainWindow.destroy();
  });
});

步骤2:Unity端解析坐标并匹配显示器

Unity本身没有直接获取显示器全局坐标的API,需要通过原生系统API实现,以下是Windows平台的示例:

1. 编写Win32 API调用的C#封装

在Unity中创建C#脚本,引入Win32函数实现显示器全局布局查询:

using System;
using System.Runtime.InteropServices;
using UnityEngine;

public class DisplayLocator : MonoBehaviour
{
    // Win32 API 结构体与函数定义
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    private struct MONITORINFOEX
    {
        public int cbSize;
        public RECT rcMonitor;
        public RECT rcWork;
        public uint dwFlags;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string szDevice;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }

    private delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumDelegate lpfnEnum, IntPtr dwData);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi);

    private static int targetDisplayIndex = 0;

    void Awake()
    {
        // 解析命令行参数
        string[] args = Environment.GetCommandLineArgs();
        foreach (string arg in args)
        {
            if (arg.StartsWith("--launcher-mid="))
            {
                string[] coords = arg.Split('=')[1].Split(',');
                if (coords.Length == 2 && int.TryParse(coords[0], out int midX) && int.TryParse(coords[1], out int midY))
                {
                    FindDisplayByGlobalPoint(midX, midY);
                    // 激活目标显示器
                    if (targetDisplayIndex < Display.displays.Length)
                    {
                        Display.displays[targetDisplayIndex].Activate();
                        // 可选:强制设置对应显示器的分辨率
                        Screen.SetResolution(Screen.currentResolution.width, Screen.currentResolution.height, true, targetDisplayIndex);
                    }
                }
                break;
            }
        }
    }

    private void FindDisplayByGlobalPoint(int x, int y)
    {
        int displayIndex = 0;
        EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, (hMonitor, hdcMonitor, ref RECT lprcMonitor, dwData) =>
        {
            MONITORINFOEX monitorInfo = new MONITORINFOEX();
            monitorInfo.cbSize = Marshal.SizeOf(monitorInfo);
            GetMonitorInfo(hMonitor, ref monitorInfo);

            // 判断中点是否在当前显示器的全局矩形范围内
            if (x >= monitorInfo.rcMonitor.left && x <= monitorInfo.rcMonitor.right &&
                y >= monitorInfo.rcMonitor.top && y <= monitorInfo.rcMonitor.bottom)
            {
                targetDisplayIndex = displayIndex;
                return false; // 找到目标后停止枚举
            }
            displayIndex++;
            return true;
        }, IntPtr.Zero);
    }
}

2. 部署脚本

将该脚本挂载到Unity场景的持久化GameObject(如主相机)上,确保游戏启动时自动执行Awake逻辑。


macOS平台适配补充

若需支持macOS,替换上述Win32部分为Cocoa API调用,核心逻辑一致:

using System;
using System.Runtime.InteropServices;
using UnityEngine;

public class DisplayLocatorMac : MonoBehaviour
{
    [DllImport("libobjc.dylib")]
    private static extern IntPtr objc_getClass(string className);

    [DllImport("libobjc.dylib")]
    private static extern IntPtr sel_registerName(string selectorName);

    [DllImport("libobjc.dylib")]
    private static extern IntPtr msgSend(IntPtr receiver, IntPtr selector);

    [DllImport("libobjc.dylib")]
    private static extern IntPtr msgSend(IntPtr receiver, IntPtr selector, IntPtr arg1);

    // 自定义NSRect结构体对应macOS的CGRect
    [StructLayout(LayoutKind.Sequential)]
    private struct NSRect
    {
        public float x;
        public float y;
        public float width;
        public float height;
    }

    void Awake()
    {
        // 解析命令行参数逻辑同Windows
        // 调用macOS显示器查找逻辑
        // FindDisplayByGlobalPoint(midX, midY);
    }

    private void FindDisplayByGlobalPoint(int x, int y)
    {
        IntPtr nsScreenClass = objc_getClass("NSScreen");
        IntPtr screensSel = sel_registerName("screens");
        IntPtr screensArray = msgSend(nsScreenClass, screensSel);

        IntPtr countSel = sel_registerName("count");
        int screenCount = (int)msgSend(screensArray, countSel);

        for (int i = 0; i < screenCount; i++)
        {
            IntPtr objectAtIndexSel = sel_registerName("objectAtIndex:");
            IntPtr screen = msgSend(screensArray, objectAtIndexSel, (IntPtr)i);

            IntPtr frameSel = sel_registerName("frame");
            NSRect frame = Marshal.PtrToStructure<NSRect>(msgSend(screen, frameSel));
            
            // 判断中点是否在当前显示器的全局frame内
            if (x >= frame.x && x <= frame.x + frame.width &&
                y >= frame.y && y <= frame.y + frame.height)
            {
                targetDisplayIndex = i;
                break;
            }
        }
    }
}

关键注意事项

  • Windows平台无需额外打包DLL,直接调用系统user32.dll即可;macOS平台需确保脚本在打包时被正确编译。
  • 测试时覆盖多显示器的各种排列方式(左右并排、上下堆叠、不同分辨率),验证中点坐标的匹配准确性。
  • 若启动器跨多屏,Electron的getBounds()返回的是全局桌面坐标,无需额外转换。

内容的提问来源于stack exchange,提问作者MiffTheFox

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 09:35:40