Unity 踩坑实录:彻底搞懂游戏分辨率与全屏模式设置

在 Unity 开发中,制作一个“画面设置”菜单看起来是件极其简单的事情。但如果 API 使用不当,玩家在切换全屏或修改分辨率时,经常会遇到画面连闪两次、UI 错位甚至状态不同步的 Bug。

本文将梳理 Unity 中关于屏幕设置的核心 API,并提供一套适用于现代 Windows 桌面游戏的标准实现方案。

1. 核心 API 角色解析:别再混用它们了!

处理分辨率和窗口状态时,最常见的错误就是混用 Screen.fullScreenFullScreenModeScreen.SetResolution。理清它们的关系,只需记住一个核心原则:

  • FullScreenMode(蓝图):它是一个枚举类型,精确描述了窗口的具体形态(独占全屏、无边框、窗口化等)。它本身不执行任何操作。
  • Screen.SetResolution(执行者):它是向操作系统发送指令的唯一推荐通道。它可以同时修改画面的宽高和窗口形态。
  • Screen.fullScreen(观察者):这是一个只读状态(bool)。只要当前不是窗口化(Windowed),它就是 true

🚫 避坑指南:绝对不要连环调用
很多新手会这样写代码:

Screen.fullScreen = true; // 系统触发一次全屏切换
Screen.SetResolution(1920, 1080, FullScreenMode.FullScreenWindow); // 系统紧接着又触发一次

Screen.fullScreen 赋值在底层会立刻触发一次窗口状态重置,导致画面闪烁。正确做法永远是只调用一次 Screen.SetResolution,把全屏模式作为参数传进去。

2. 现代 PC 游戏的三大标准显示模式

对于 Windows 桌面游戏,我们不需要把 Unity 原生的所有 FullScreenMode 都暴露给玩家(例如 macOS 专用的最大化窗口)。通常只需提供以下三个标准选项:

  1. 无边框全屏 (Borderless / FullScreenWindow)
  • 现代 PC 游戏的默认首选。本质是一个与显示器同分辨率的无边框窗口,玩家使用 Alt+Tab 切屏时极其顺滑,不会黑屏,对多屏用户非常友好。
  1. 独占全屏 (Exclusive Fullscreen / ExclusiveFullScreen)
  • 追求极限性能的选择。绕过 Windows 桌面窗口管理器 (DWM),帧率最高、延迟最低。但切出游戏时会有明显的黑屏卡顿。
  1. 窗口模式 (Windowed / Windowed)
  • 标准的带标题栏系统窗口,适合低分辨率运行或需要频繁与桌面交互的场景。

3. 标准 C# 实现方案 (结合 UGUI & TextMeshPro)

下面是一套可以直接拿来用的设置面板控制脚本。它在游戏启动时会自动获取设备支持的分辨率,并将其填充到 TMP_Dropdown 中,同时使用 Toggle 来控制全屏开关。

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class VideoSettings : MonoBehaviour
{
    [Header("UI 引用")]
    public TMP_Dropdown resolutionDropdown;
    public Toggle fullscreenToggle;

    private Resolution[] resolutions;

    void Start()
    {
        // 1. 获取设备支持的分辨率并初始化 UI
        resolutions = Screen.resolutions;
        resolutionDropdown.ClearOptions();
        fullscreenToggle.isOn = Screen.fullScreen; // 仅作为状态读取

        List<string> options = new List<string>();
        int currentResolutionIndex = 0;

        // 2. 遍历并填充下拉菜单
        for (int i = 0; i < resolutions.Length; i++)
        {
            string option = $"{resolutions[i].width} x {resolutions[i].height}";
            
            // 去重(防止同分辨率不同刷新率导致多条重复项)
            if (!options.Contains(option))
            {
                options.Add(option);
            }

            // 记录当前屏幕分辨率的索引
            if (resolutions[i].width == Screen.width && resolutions[i].height == Screen.height)
            {
                currentResolutionIndex = i;
            }
        }

        resolutionDropdown.AddOptions(options);
        resolutionDropdown.value = currentResolutionIndex;
        resolutionDropdown.RefreshShownValue();

        // 3. 监听 UI 事件
        resolutionDropdown.onValueChanged.AddListener(OnResolutionChanged);
        fullscreenToggle.onValueChanged.AddListener(OnFullscreenToggled);
    }

    /// <summary>
    /// 修改分辨率,保持当前的全屏状态
    /// </summary>
    public void OnResolutionChanged(int resolutionIndex)
    {
        Resolution resolution = resolutions[resolutionIndex];
        Screen.SetResolution(resolution.width, resolution.height, Screen.fullScreenMode);
    }

    /// <summary>
    /// 切换全屏状态(推荐使用无边框全屏)
    /// </summary>
    public void OnFullscreenToggled(bool isFullscreen)
    {
        FullScreenMode mode = isFullscreen ? FullScreenMode.FullScreenWindow : FullScreenMode.Windowed;
        // 一次性应用分辨率和窗口状态
        Screen.SetResolution(Screen.width, Screen.height, mode);
    }
}

4. 进阶打磨细节

在将这套系统接入你的游戏工程时,还有几个小细节值得注意:

  • 无边框模式的逻辑锁定:当玩家选择“无边框全屏”时,游戏应强制使用显示器的原生分辨率,此时可以在 UI 逻辑中禁用分辨率下拉菜单,防止画面拉伸。
  • 鼠标限制:如果是射击或 3D 动作游戏,在窗口化模式下务必加上 Cursor.lockState = CursorLockMode.Confined;,防止玩家在激战中鼠标点到桌面导致游戏失去焦点。

总结: 做好画面设置模块并不难,关键在于克制对 API 的重复调用。把 Screen.SetResolution 当作唯一的指令出口,你的设置菜单就会变得非常稳健。