Unity多屏互动系统开发:Display组件原理与实战避坑指南

📅2026/7/13 10:10:17 👁️次浏览
Unity多屏互动系统开发:Display组件原理与实战避坑指南
1. 项目概述为什么需要多屏互动系统在Unity开发中尤其是涉及到展览展示、数据可视化、模拟训练或者大型游戏时单一屏幕的显示往往捉襟见肘。想象一下你正在为一个科技馆开发一个互动展项主屏幕展示宏观的宇宙星图旁边的几个副屏分别展示不同行星的细节、飞行器参数和实时数据流。如果只用一台显示器要么频繁切换画面割裂体验要么把所有信息挤在一起哪个都看不清。这就是多屏互动系统要解决的核心痛点扩展视觉空间实现内容的分屏、联动与协同展示。Unity内置的Display组件就是解决这个问题的“瑞士军刀”。它不是一个挂在GameObject上的普通组件而是一个管理物理显示器的系统级接口。很多开发者第一次接触多屏时可能会想当然地去折腾多个相机渲染到不同的Render Texture然后再用一套复杂的UI系统把纹理抛到不同的窗口——这条路不是不能走但相当于自己重新造轮子而且稳定性、性能和多显示器适配都是大坑。Display组件提供了一套官方的、跨平台的解决方案让你能直接调用操作系统层面的多显示器支持把不同的Camera输出“钉”到指定的物理屏幕上整个过程直接、高效。从最新的网络热词也能看出大家的关注点unity webgl初始化很久、unity程序打开黑屏无响应、unity分辨率设置——这些问题在多屏环境下更容易被放大。一个配置不当的多屏系统轻则导致副屏黑屏、窗口错位重则引发程序崩溃、编辑器无响应。所以今天我们不只讲“怎么用”更要深入“为什么这么用”以及在实际项目中趟过的那些坑。无论是做VR/AR的联排体验、建筑设计的多视角评审还是商业数字标牌这套基于Display的快速搭建思路都能让你事半功倍。2. Display组件核心原理与API精讲2.1 Display系统的工作机制要玩转多屏首先得明白Unity的Display系统是怎么和你的电脑硬件打交道的。你可以把Display类看作Unity引擎对操作系统“显示器列表”的一个抽象封装。当你启动一个Unity应用时系统会检测当前连接了多少台物理显示器并将这个信息初始化到Display.displays这个静态数组中。这里有一个至关重要的细节Display.displays[0]永远代表主显示器系统认定的主屏并且它始终处于激活状态。你不能停用它也不需要手动调用Activate()。我们所有多屏操作的起点都是从索引1开始的。也就是说如果你连接了两台显示器Display.displays.Length是2你需要手动激活的是Display.displays[1]。它的激活时机非常关键。官方手册建议在“创建新场景时”激活这是因为显示器的激活本质上是对图形API如DirectX, OpenGL和操作系统窗口系统的一次重配置。如果在错误的时机比如在渲染循环中反复激活调用极易导致图形上下文错误表现为黑屏、闪屏或崩溃。最稳妥的做法是在场景加载的早期、任何依赖于屏幕尺寸的渲染发生之前完成激活通常放在Start()或Awake()方法中。2.2 关键API详解与实战选择Display类提供的API很精简但每个都至关重要Display.displays(静态属性)这是一个Display[]数组存储了所有被系统识别到的显示器信息。它的长度Display.displays.Length是你判断可用显示器数量的直接依据。在编辑器模式下这个数组通常只包含一个主显示器即使你连接了多台。这意味着多屏功能必须在独立构建Build后的应用程序中才能完整测试这是第一个容易踩的坑。void Activate()与void Activate(int width, int height, int refreshRate)这是核心中的核心。无参的Activate()方法使用显示器当前的默认分辨率进行激活。这也是最常用、兼容性最好的方式。 带参数的Activate(width, height, refreshRate)则允许你指定激活的分辨率和刷新率。但请注意这个重载在Windows以外的平台如macOS、Linux上行为不一致可能被忽略而仍然使用系统默认设置。除非你有强烈的需求必须在Windows上锁定一个特定分辨率例如匹配一个非标准分辨率的投影仪否则建议优先使用无参版本让系统去管理。Display.renderingWidth与Display.renderingHeight这两个属性获取的是显示器当前渲染分辨率。这里有个重要概念它不一定等于显示器的物理分辨率。特别是在Retina屏高DPI或设置了屏幕缩放的系统上这个值可能会是物理分辨率除以缩放因子。你在处理UI布局、摄像机视口Viewport Rect时应该以这个渲染分辨率为准而不是想当然地使用Screen.width/height它们可能代表整个虚拟桌面的大小。systemWidth与systemHeight这两个属性获取的是显示器在操作系统桌面环境下的物理分辨率。它更适用于你需要精确计算像素位置或与原生系统交互的场景。注意很多开发者混淆Screen类和Display类。Screen类管理的是整个应用程序窗口在无边框全屏多屏模式下可视为跨所有显示器的虚拟桌面而Display类管理的是单个物理显示器。在多屏设置中修改Screen.SetResolution会影响所有被激活的显示器组成的整体区域通常不建议直接使用。2.3 平台差异与兼容性处理Unity支持多屏的平台包括Windows、macOS、Linux、Android和iOS。但各平台行为有细微差别Windows/macOS/Linux (桌面平台)支持最为完善。你可以通过-multidisplay命令行参数启动应用来禁止系统自动排列显示器这对于需要精确控制窗口位置的展览装置非常有用。Android从2020.1版本开始在Vulkan图形API下获得了更好的多屏支持。通常用于将内容输出到HDMI连接的电视或投影仪。需要留意Android设备的HDMI输出模式可能存在的分辨率限制和黑边问题。iOS支持通过AirPlay或线缆连接到外部显示器通常用于演示或扩展游戏画面。在编写代码时一个健壮的做法是进行平台判断void ActivateExtraDisplays() { // 只在支持多屏且非WebGL平台执行 #if !UNITY_WEBGL for (int i 1; i Display.displays.Length; i) { Display.displays[i].Activate(); Debug.Log($已激活显示器 {i}: {Display.displays[i].systemWidth}x{Display.displays[i].systemHeight}); } #endif }WebGL平台由于浏览器沙盒限制无法直接访问多显示器所以需要排除。3. 快速搭建多屏系统的四步法理论讲完我们进入实战。搭建一个基础的多屏互动系统可以分解为四个清晰的步骤。3.1 第一步硬件连接与系统配置在写代码之前确保你的硬件环境是正确的。将额外的显示器通过HDMI、DP等接口连接到你的电脑。进入操作系统以Windows为例的显示设置确认所有显示器都被正确识别。设置合适的显示模式“扩展这些显示器”。记下每个显示器的编号和相对位置。这一点极其重要因为Display.displays数组的顺序通常对应系统显示设置中“标识”的数字顺序。你可以通过系统设置拖拽显示器图标来调整它们的逻辑位置这会影响Display索引的映射关系。为每个显示器设置你期望的分辨率和刷新率。Unity应用在无参激活显示器时会尝试使用系统的当前设置。3.2 第二步创建显示器激活管理器在Unity项目中创建一个名为DisplayManager的C#脚本。它的核心职责就是在应用启动时安全地激活所有额外显示器。using UnityEngine; public class DisplayManager : MonoBehaviour { [Header(显示设置)] [Tooltip(是否在启动时自动激活所有连接的显示器)] public bool activateAllOnStart true; [Tooltip(是否在激活后输出详细信息)] public bool logDisplayInfo true; void Start() { if (!activateAllOnStart) return; // 检查显示器数量 int displayCount Display.displays.Length; if (logDisplayInfo) { Debug.Log($[DisplayManager] 检测到 {displayCount} 台显示器。); } // 主显示器索引0已默认激活从索引1开始激活其他显示器 for (int i 1; i displayCount; i) { try { Display.displays[i].Activate(); // 使用默认分辨率激活 if (logDisplayInfo) { Debug.Log($[DisplayManager] 已激活显示器 {i}。渲染分辨率: {Display.displays[i].renderingWidth}x{Display.displays[i].renderingHeight}); } } catch (System.Exception e) { Debug.LogError($[DisplayManager] 激活显示器 {i} 时发生错误: {e.Message}); } } // 可选在激活所有显示器后再次确认信息 if (logDisplayInfo) { Invoke(LogFinalDisplayStatus, 0.5f); // 延迟一小会儿等待激活完全生效 } } void LogFinalDisplayStatus() { for (int i 0; i Display.displays.Length; i) { var disp Display.displays[i]; Debug.Log($Display [{i}]: Active{disp.active}, SystemRes{disp.systemWidth}x{disp.systemHeight}, RenderRes{disp.renderingWidth}x{disp.renderingHeight}); } } }将这个脚本挂载到场景中一个不会被销毁的GameObject上例如GameManager。Start()方法中的try-catch块是一个重要的健壮性设计可以防止因为某台显示器意外异常而导致整个初始化过程崩溃。3.3 第三步配置摄像机与目标显示器显示器激活后内容从哪里来答案是摄像机Camera。Unity的每个Camera组件都有一个Target Display属性默认是Display 1对应主显示器。我们需要为每个屏幕分配至少一个摄像机。操作流程在场景中创建多个摄像机Camera。例如MainDisplayCamera、LeftDisplayCamera、RightDisplayCamera。选中一个摄像机在Inspector面板中找到Target Display下拉菜单。你会看到选项从Display 1到Display 8。这个数字索引就对应着Display.displays数组的索引。将MainDisplayCamera的Target Display设为Display 1。将LeftDisplayCamera的Target Display设为Display 2。将RightDisplayCamera的Target Display设为Display 3。根据需要调整每个摄像机的位置、旋转和投影参数如视场角FOV以构建不同的视图。重要技巧使用视口矩形Viewport Rect实现分屏一个显示器不仅可以由一个摄像机独占还可以由多个摄像机通过调整Viewport Rect来共享屏幕实现画中画或分屏效果。Viewport Rect的四个分量X, Y, W, H范围是[0,1]代表在目标显示器上的相对位置和大小。 例如在Display 2上实现左右分屏左半屏摄像机Viewport Rect (0, 0, 0.5, 1)右半屏摄像机Viewport Rect (0.5, 0, 0.5, 1) 这样即使只有一个物理屏幕也能通过多个摄像机构建复杂的画面布局。3.4 第四步编辑器内预览与调试在编辑器里如何预览多屏效果毕竟Display.displays在编辑器下通常只有主屏。Unity提供了便捷的预览工具确保你的Game视图是打开的。在Game视图的左上角找到一个下拉菜单默认显示“Display 1”。点击这个下拉菜单你会看到所有在摄像机中配置过的Target Display选项例如Display 1, Display 2...。选择不同的Display选项Game视图就会模拟切换到该显示器的渲染画面。你可以同时打开多个Game视图窗口分别选择不同的Display来并行调试。这个功能对于调整每个摄像机的画面、UI布局至关重要避免了反复打包测试的繁琐。但请记住编辑器预览无法完全模拟多屏激活时的性能开销和某些全屏行为最终测试仍需依赖独立构建。4. 高级应用构建互动与数据同步系统多屏系统搭建起来后真正的挑战在于如何让屏幕之间“互动”起来而不是各自为政的幻灯片。4.1 跨屏幕事件传递与交互假设主屏是一个可旋转的3D地球点击上面的某个国家副屏就显示该国家的详细信息。如何实现方案一基于物理射线投射的全局事件系统这是最直观的方法。虽然每个摄像机渲染到不同的显示器但它们共享同一个3D世界空间。你可以创建一个全局的InputManager脚本它使用主摄像机的ScreenPointToRay方法但传入的鼠标位置需要根据不同的显示器进行偏移计算。public class GlobalInputManager : MonoBehaviour { public Camera mainDisplayCamera; // Display 1的摄像机 public Camera leftDisplayCamera; // Display 2的摄像机 void Update() { // 获取当前鼠标在所有显示器组成的虚拟桌面上的位置 Vector3 mousePos Input.mousePosition; // 判断鼠标在哪块屏幕上简化逻辑根据x坐标判断 if (mousePos.x Screen.width / 2) { // 假设屏幕是左右并列鼠标在左半区Display 2 ProcessInputForDisplay(leftDisplayCamera, mousePos); } else { // 鼠标在右半区Display 1 ProcessInputForDisplay(mainDisplayCamera, mousePos); } } void ProcessInputForDisplay(Camera targetCamera, Vector3 screenPos) { if (Input.GetMouseButtonDown(0)) { Ray ray targetCamera.ScreenPointToRay(screenPos); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { // 触发被点击物体的交互逻辑 IInteractable interactable hit.collider.GetComponentIInteractable(); interactable?.OnInteract(); // 可以在这里发布一个事件让其他屏幕的UI控制器来订阅并更新内容 EventManager.Instance.Publish(new CountrySelectedEvent(hit.collider.name)); } } } }这种方法需要你精确计算每个显示器的屏幕空间范围在多显示器排列复杂如上下排列、不同分辨率时计算逻辑会变得复杂。方案二基于事件总线的松耦合通信更优雅的方案是引入一个事件总线Event Bus或消息系统。每个屏幕上的模块如3D控制器、UI面板只关心自己发出的和需要响应的事件。主屏的3D控制器在检测到点击后发布一个OnCountryClicked事件并携带国家ID数据。副屏的UI控制器订阅了OnCountryClicked事件。当事件触发时它根据收到的国家ID从数据管理器加载对应的详细信息并更新自己的UI。 这种方式彻底解耦了屏幕间的依赖无论屏幕数量如何增加交互逻辑都清晰可维护。Unity的UnityEvent、ScriptableObject事件通道或第三方插件如UniRx都能很好地实现这一模式。4.2 多屏UI系统的协同UI是多屏互动中最棘手的部分之一。Unity的UGUI Canvas默认渲染到Screen Space - Overlay模式它会覆盖所有显示器。这通常不是我们想要的。推荐方案每个显示器使用独立的Canvas为每个显示器创建一个独立的Canvas将其Render Mode设置为Screen Space - Camera并将Render Camera指定为对应显示器的摄像机。同时将该Canvas的Target Display属性也设置为对应的显示器索引。这样这个Canvas上的所有UI元素就只会被指定的摄像机渲染并显示在对应的物理屏幕上。数据同步多个屏幕的UI可能需要显示同一份数据的不同的视图。例如主屏是总览仪表盘副屏是详细的日志列表。这时应该采用MVC模型-视图-控制器或MVVM模式。Model (模型)唯一的数据源例如一个GameDataManager单例存储所有共享数据。View (视图)每个屏幕上的UI Canvas及其控件。Controller/Presenter (控制器/表现器)每个屏幕上的脚本负责监听Model数据的变化并更新自己管理的View。同时也处理本屏幕的用户输入并修改Model中的数据。 当主屏上的操作修改了Model中的数据如选择了新的项目所有订阅了该数据变化的副屏控制器都会自动收到通知并更新各自的视图从而实现数据的实时同步。4.3 性能优化与渲染策略多屏渲染意味着图形API需要输出多倍的像素对GPU压力巨大。如果不加优化很容易出现卡顿。按需渲染不是所有屏幕都需要每帧更新。例如一个只显示静态图片或图表的副屏可以将其摄像机的Camera组件的Render Type设置为Base并降低其Culling Mask减少不必要的物体渲染。更进一步可以设置一个帧率限制脚本让这个摄像机每秒只渲染几次如10FPS而不是满帧率运行。public class LowFrequencyCamera : MonoBehaviour { public Camera targetCamera; public float updateInterval 0.1f; // 每秒10次 private float timer 0f; void Update() { timer Time.deltaTime; if (timer updateInterval) { timer 0f; targetCamera.Render(); // 手动渲染一帧 } } }简化副屏内容对于非焦点屏幕尽量使用UI、2D Sprite或简单的3D模型。关闭或降低阴影质量、后处理效果Post Processing、抗锯齿MSAA等昂贵特性。可以针对不同的摄像机设置不同的Layer并为这些Layer配置简化的渲染设置。分辨率与刷新率权衡如果副屏只是用于展示信息可以考虑以低于其物理分辨率的分辨率进行渲染。虽然会有些模糊但能显著提升性能。这可以通过在激活显示器时调用带参数的Activate(width, height, refreshRate)来实现Windows平台或者通过调整摄像机的渲染目标尺寸来实现。5. 实战避坑指南与疑难排查纸上得来终觉浅绝知此事要踩坑。下面是我在多个多屏项目中总结出的“血泪经验”。5.1 常见问题速查表问题现象可能原因解决方案副屏黑屏无任何显示1. 显示器未在系统启动后激活。2. 没有摄像机将Target Display设置为该屏幕。3. 摄像机被禁用或Culling Mask设置为空。4. 构建时未包含多屏支持某些平台。1. 检查DisplayManager日志确认Activate()被调用且无报错。2. 检查场景中是否有摄像机指向该显示器如Display 2。3. 在编辑器的Game视图下拉菜单中切换到该Display预览检查画面。4. 确保在Player Settings中启用了多显示器支持。副屏显示内容错乱如画面拉伸、偏移1. 摄像机视口Viewport Rect设置错误。2. 显示器系统分辨率与Unity渲染分辨率不匹配高DPI缩放引起。3. 多个摄像机渲染到同一屏幕深度Depth或Clear Flags冲突。1. 检查摄像机的Viewport Rect是否为(0,0,1,1)独占全屏或分屏设置是否正确。2. 使用Display.renderingWidth/Height而非Screen.width/height进行UI坐标计算。3. 确保只有一个摄像机的Clear Flags为Solid Color或Skybox其他摄像机设为Depth only或Don‘t Clear并合理设置Depth值。编辑器预览正常打包后副屏黑屏1. 编辑器下Display.displays.Length始终为1未测试真实多屏代码路径。2. 激活显示器的代码放在了仅在编辑器下执行的#if UNITY_EDITOR块中。3. 构建时图形API设置不支持多屏。1. 必须通过独立构建的程序进行最终测试。2. 移除或修正条件编译指令确保激活代码在发布版本中也会执行。3. 对于Windows确保使用Direct3D11/12对于macOS使用Metal。OpenGL Core在多屏支持上可能有问题。鼠标坐标计算错误交互对不上1. 直接使用了Input.mousePosition而未考虑多显示器偏移。2. 显示器在系统中的排列顺序上下左右与代码中的假设不符。1. 使用Display.RelativeMouseAt(Input.mousePosition)来获取包含显示器索引的准确鼠标位置仅部分平台支持。2. 更通用的方法是记录每个显示器在虚拟桌面中的像素范围然后根据鼠标坐标判断所在显示器。多屏运行时性能急剧下降1. 所有摄像机都以最高质量每帧渲染所有内容。2. UI Canvas过于复杂且所有屏幕的Canvas都在更新。3. 物理计算、脚本更新等逻辑未做区分所有屏幕共享同一套高开销逻辑。1. 应用4.3节的性能优化策略对副屏摄像机进行降帧渲染、简化渲染设置。2. 将静态UI与动态UI分离对于不常变化的UI部分可以缓存其渲染结果。3. 使用脚本生命周期控制如OnEnable/OnDisable或自定义更新管理器让远离交互区域的屏幕逻辑进入低功耗模式。5.2 构建与部署的注意事项全屏与无边框窗口全屏模式每个激活的显示器会独占一个全屏窗口。优点是性能最好无干扰。缺点是切换程序AltTab可能不方便在多屏应用作为展项时常用。无边框窗口模式应用程序窗口会覆盖所有激活的显示器形成一个巨大的虚拟桌面。优点是窗口管理方便易于调试。缺点是可能会有性能损耗并且需要自己处理窗口移动通过Screen.MoveMainWindowTo等API但此API平台支持有限。 在Player Settings的Resolution and Presentation中设置。对于展览等固定场所推荐使用全屏模式。命令行参数对于Windows和Linux构建可以通过命令行参数获得更多控制-popupwindow以无边框窗口启动。-screen-fullscreen 0强制以窗口模式启动。-screen-width X -screen-height Y设置主窗口分辨率在多屏无边框模式下这定义了虚拟桌面的大小。-multidisplay关键参数。告诉Unity不要按照系统设置重新排列显示器使用应用内定义的显示器布局。这对于确保显示内容在正确的物理屏幕上至关重要。多平台构建检查清单Windows检查Graphics APIs包含Direct3D11/12。如果使用单显卡多输出一般没问题。如果使用多显卡如NVIDIA Mosaic或AMD Eyefinity需要查阅对应厂商的SDK进行特殊配置。macOS使用Metal图形API。注意macOS对多显示器的管理与Windows略有不同特别是当合上笔记本盖子使用外接显示器时Display.displays的索引可能会变化。建议在应用启动时重新检测并记录显示器信息。Android确保在Player Settings中勾选了Multithreaded Rendering并在需要输出的设备上开启“第二屏幕”或“有线投屏”模式。测试时务必使用真机模拟器无法测试多屏。5.3 一个健壮的DisplayManager增强版结合以上所有经验下面提供一个更健壮、功能更完整的DisplayManager示例它包含了错误处理、配置化和运行时信息展示。using UnityEngine; using System.Collections.Generic; [System.Serializable] public class DisplayConfig { public int displayIndex 1; // 对应Display.displays的索引 public string displayName 副屏; public bool activateOnStart true; [Tooltip(是否在激活时尝试设置特定分辨率仅Windows有效)] public bool setCustomResolution false; public int customWidth 1920; public int customHeight 1080; public int refreshRate 60; } public class EnhancedDisplayManager : MonoBehaviour { public ListDisplayConfig displayConfigs new ListDisplayConfig(); public bool debugMode true; private Dictionaryint, DisplayInfo activeDisplays new Dictionaryint, DisplayInfo(); [System.Serializable] public class DisplayInfo { public int systemWidth; public int systemHeight; public int renderWidth; public int renderHeight; public bool isPrimary; } void Start() { InitializeDisplays(); } void InitializeDisplays() { int totalDetected Display.displays.Length; Debug.Log($[EnhancedDisplayManager] 系统报告显示器数量: {totalDetected}); // 首先确保我们有主显示器的信息 if (totalDetected 0) { activeDisplays[0] new DisplayInfo { systemWidth Display.displays[0].systemWidth, systemHeight Display.displays[0].systemHeight, renderWidth Display.displays[0].renderingWidth, renderHeight Display.displays[0].renderingHeight, isPrimary true }; LogDisplayInfo(0); } // 根据配置激活其他显示器 foreach (var config in displayConfigs) { // 安全检查配置的索引是否有效 if (config.displayIndex totalDetected) { Debug.LogWarning($[EnhancedDisplayManager] 配置的显示器索引 {config.displayIndex} 超出系统检测范围 ({totalDetected})。跳过。); continue; } if (config.displayIndex 0) { Debug.LogWarning($[EnhancedDisplayManager] 显示器索引应从1开始0是主屏。跳过索引 {config.displayIndex}。); continue; } if (!config.activateOnStart) { Debug.Log($[EnhancedDisplayManager] 显示器 {config.displayIndex} 配置为不自动激活。); continue; } ActivateDisplay(config); } // 激活所有未配置但被系统检测到的显示器兜底逻辑 for (int i 1; i totalDetected; i) { bool alreadyConfigured false; foreach (var config in displayConfigs) { if (config.displayIndex i) { alreadyConfigured true; break; } } if (!alreadyConfigured) { Debug.Log($[EnhancedDisplayManager] 发现未配置的显示器 {i}尝试使用默认设置激活。); Display.displays[i].Activate(); LogDisplayInfo(i); } } } void ActivateDisplay(DisplayConfig config) { int idx config.displayIndex; try { if (config.setCustomResolution) { #if UNITY_STANDALONE_WIN Display.displays[idx].Activate(config.customWidth, config.customHeight, config.refreshRate); Debug.Log($[EnhancedDisplayManager] 已激活显示器 {idx} ({config.displayName})分辨率: {config.customWidth}x{config.customHeight}{config.refreshRate}Hz); #else Display.displays[idx].Activate(); Debug.LogWarning($[EnhancedDisplayManager] 自定义分辨率仅支持Windows平台。显示器 {idx} 已使用系统默认分辨率激活。); #endif } else { Display.displays[idx].Activate(); Debug.Log($[EnhancedDisplayManager] 已激活显示器 {idx} ({config.displayName})使用系统默认分辨率。); } // 记录激活后的信息 activeDisplays[idx] new DisplayInfo { systemWidth Display.displays[idx].systemWidth, systemHeight Display.displays[idx].systemHeight, renderWidth Display.displays[idx].renderingWidth, renderHeight Display.displays[idx].renderingHeight, isPrimary false }; LogDisplayInfo(idx); } catch (System.Exception e) { Debug.LogError($[EnhancedDisplayManager] 激活显示器 {idx} 失败: {e.Message}); } } void LogDisplayInfo(int index) { if (!debugMode) return; var disp Display.displays[index]; Debug.Log($显示器 [{index}]: 激活{disp.active}, 系统分辨率{disp.systemWidth}x{disp.systemHeight}, 渲染分辨率{disp.renderingWidth}x{disp.renderingHeight}); } // 提供一个方法供其他脚本查询显示器信息 public DisplayInfo GetDisplayInfo(int index) { if (activeDisplays.ContainsKey(index)) return activeDisplays[index]; return null; } void OnGUI() { if (!debugMode) return; GUI.Box(new Rect(10, 10, 300, 100), 显示器状态); int y 40; foreach (var kvp in activeDisplays) { GUI.Label(new Rect(20, y, 280, 20), $Display {kvp.Key}: {kvp.Value.renderWidth}x{kvp.Value.renderHeight} {(kvp.Value.isPrimary ? [主] : )}); y 20; } } }这个管理器允许你在Inspector中可视化配置每个副屏的激活行为加入了平台相关的安全判断并记录了所有激活显示器的信息供其他系统查询大大提升了项目的可维护性和健壮性。