using UnityEngine; using UnityEngine.UI; using System.Collections.Generic; using System; using System.Runtime.InteropServices; public class graphicSettings : MonoBehaviour { public Dropdown screenMode_Dropdown; public Dropdown resolution_Dropdown; public Dropdown frameRate_Dropdown; public Image resolutionLock_Image; private List availableResolutions = new List(); private int tempFreeResOptionIndex = -1; private Vector2Int lastScreenSize; void Start() { InitScreenModeDropdown(); InitResolutionDropdown(); InitFrameRateDropdown(); lastScreenSize = new Vector2Int(Screen.width, Screen.height); } void OnDestroy() { if (screenMode_Dropdown != null) screenMode_Dropdown.onValueChanged.RemoveListener(OnScreenModeChanged); if (resolution_Dropdown != null) resolution_Dropdown.onValueChanged.RemoveListener(OnResolutionChanged); if (frameRate_Dropdown != null) frameRate_Dropdown.onValueChanged.RemoveListener(OnFrameRateChanged); } void Update() { if (Screen.width != lastScreenSize.x || Screen.height != lastScreenSize.y) { lastScreenSize.x = Screen.width; lastScreenSize.y = Screen.height; OnUserResolutionChanged(new Vector2Int(Screen.width, Screen.height)); } } private void OnUserResolutionChanged(Vector2Int newSize) { if (Screen.fullScreenMode != FullScreenMode.Windowed) return; int match = availableResolutions.FindIndex(v => v.x == newSize.x && v.y == newSize.y); if (match >= 0) { RemoveTempFreeOptionIfExists(); resolution_Dropdown.SetValueWithoutNotify(match); } else { EnsureTempFreeOptionExists(); resolution_Dropdown.SetValueWithoutNotify(tempFreeResOptionIndex); } } private void EnsureTempFreeOptionExists() { if (resolution_Dropdown == null) return; if (tempFreeResOptionIndex >= 0) return; var opt = new Dropdown.OptionData("自定义分辨率"); resolution_Dropdown.options.Add(opt); tempFreeResOptionIndex = resolution_Dropdown.options.Count - 1; } private void RemoveTempFreeOptionIfExists() { if (resolution_Dropdown == null) return; if (tempFreeResOptionIndex < 0) return; if (tempFreeResOptionIndex < resolution_Dropdown.options.Count) resolution_Dropdown.options.RemoveAt(tempFreeResOptionIndex); tempFreeResOptionIndex = -1; } private void InitScreenModeDropdown() { if (screenMode_Dropdown == null) return; var options = new List() { "窗口化(可调整大小)", "全屏(独占模式)", "无边框窗口" }; screenMode_Dropdown.ClearOptions(); screenMode_Dropdown.AddOptions(options); int current = MapFullScreenModeToIndex(Screen.fullScreenMode); if (PlayerPrefs.HasKey("screenMode")) { int saved = PlayerPrefs.GetInt("screenMode", current); saved = Mathf.Clamp(saved, 0, options.Count - 1); current = saved; ApplyScreenMode(current); } screenMode_Dropdown.SetValueWithoutNotify(current); screenMode_Dropdown.onValueChanged.AddListener(OnScreenModeChanged); } private int MapFullScreenModeToIndex(FullScreenMode mode) { switch (mode) { case FullScreenMode.Windowed: return 0; case FullScreenMode.ExclusiveFullScreen: return 1; case FullScreenMode.FullScreenWindow: return 2; case FullScreenMode.MaximizedWindow: return 0; default: return 0; } } private void OnScreenModeChanged(int index) { ApplyScreenMode(index); PlayerPrefs.SetInt("screenMode", index); PlayerPrefs.Save(); bool isWindowed = (index == 0); if (resolutionLock_Image != null) resolutionLock_Image.gameObject.SetActive(!isWindowed); if (resolution_Dropdown != null) resolution_Dropdown.interactable = isWindowed; } private void ApplyScreenMode(int index) { try { if (index == 0) { Screen.fullScreenMode = FullScreenMode.Windowed; Screen.fullScreen = false; StopAllCoroutines(); StartCoroutine(ApplyWindowStyleNextFrame(true)); Screen.SetResolution(Screen.width, Screen.height, FullScreenMode.Windowed, Screen.currentResolution.refreshRateRatio); } else if (index == 1) { Screen.fullScreenMode = FullScreenMode.ExclusiveFullScreen; Screen.fullScreen = true; Resolution res = Screen.currentResolution; Screen.SetResolution(res.width, res.height, FullScreenMode.ExclusiveFullScreen, res.refreshRateRatio); } else if (index == 2) { Screen.fullScreenMode = FullScreenMode.FullScreenWindow; Screen.fullScreen = true; Resolution res = Screen.currentResolution; Screen.SetResolution(res.width, res.height, FullScreenMode.FullScreenWindow, res.refreshRateRatio); StopAllCoroutines(); StartCoroutine(ApplyWindowStyleNextFrame(false)); } } catch (System.Exception ex) { Debug.LogWarning($"ApplyScreenMode failed: {ex}"); } } private System.Collections.IEnumerator ApplyWindowStyleNextFrame(bool enable) { yield return null; yield return new WaitForSeconds(0.05f); EnableWindowedModeResizable(enable); } private void EnableWindowedModeResizable(bool enable) { #if UNITY_STANDALONE_WIN && !UNITY_EDITOR try { IntPtr hWnd = GetForegroundWindow(); if (hWnd == IntPtr.Zero) return; const int GWL_STYLE = -16; const int WS_OVERLAPPEDWINDOW = unchecked((int)0x00CF0000); const int WS_POPUP = unchecked((int)0x80000000); if (IntPtr.Size == 8) { long style = GetWindowLongPtr64(hWnd, GWL_STYLE); if (enable) { style &= ~((long)WS_POPUP); style |= WS_OVERLAPPEDWINDOW; } else { style &= ~((long)WS_OVERLAPPEDWINDOW); style |= (long)WS_POPUP; } SetWindowLongPtr64(hWnd, GWL_STYLE, style); SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); } else { int style = GetWindowLong32(hWnd, GWL_STYLE); if (enable) { style &= ~WS_POPUP; style |= WS_OVERLAPPEDWINDOW; } else { style &= ~((int)WS_OVERLAPPEDWINDOW); style |= (int)WS_POPUP; } SetWindowLong32(hWnd, GWL_STYLE, style); SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); } } catch (Exception e) { Debug.LogWarning($"EnableWindowedModeResizable failed: {e}"); } #endif } private void InitResolutionDropdown() { if (resolution_Dropdown == null) return; var candidates = new List() { new Vector2Int(1024, 576), new Vector2Int(1152, 648), new Vector2Int(1280, 720), new Vector2Int(1366, 768), new Vector2Int(1600, 900), new Vector2Int(1920, 1080), new Vector2Int(2560, 1440), new Vector2Int(3440, 1440), new Vector2Int(3200, 1800), new Vector2Int(3840, 2160), new Vector2Int(5120, 2880), new Vector2Int(7680, 4320), new Vector2Int(15360, 8640) }; int maxW = Screen.currentResolution.width; int maxH = Screen.currentResolution.height; const int MIN_W = 1024; const int MIN_H = 720; const int MAX_DIM = 16384; availableResolutions.Clear(); var options = new List(); foreach (var c in candidates) { if (c.x < MIN_W || c.y < MIN_H) continue; if (c.x > MAX_DIM || c.y > MAX_DIM) continue; if (c.x > maxW || c.y > maxH) continue; availableResolutions.Add(c); } if (availableResolutions.Count == 0) { var cur = new Vector2Int(Screen.width, Screen.height); if (cur.x >= MIN_W && cur.y >= MIN_H) availableResolutions.Add(cur); } foreach (var r in availableResolutions) { options.Add($"{r.x} x {r.y}"); } resolution_Dropdown.ClearOptions(); resolution_Dropdown.AddOptions(options); int selected = 0; for (int i = 0; i < availableResolutions.Count; i++) { if (availableResolutions[i].x == Screen.width && availableResolutions[i].y == Screen.height) { selected = i; break; } } int savedResIndex = PlayerPrefs.GetInt("resolutionIndex", -2); if (savedResIndex >= 0 && savedResIndex < availableResolutions.Count) { resolution_Dropdown.SetValueWithoutNotify(savedResIndex); var sav = availableResolutions[savedResIndex]; Screen.SetResolution(sav.x, sav.y, Screen.fullScreenMode, Screen.currentResolution.refreshRateRatio); RemoveTempFreeOptionIfExists(); } else if (savedResIndex == -1) { int cw = PlayerPrefs.GetInt("customResW", -1); int ch = PlayerPrefs.GetInt("customResH", -1); if (cw > 0 && ch > 0) { Screen.SetResolution(cw, ch, Screen.fullScreenMode, Screen.currentResolution.refreshRateRatio); EnsureTempFreeOptionExists(); resolution_Dropdown.options[tempFreeResOptionIndex].text = $"{cw} x {ch}"; resolution_Dropdown.SetValueWithoutNotify(tempFreeResOptionIndex); } else { resolution_Dropdown.SetValueWithoutNotify(selected); } } else { resolution_Dropdown.SetValueWithoutNotify(selected); } resolution_Dropdown.onValueChanged.AddListener(OnResolutionChanged); int screenMode = PlayerPrefs.GetInt("screenMode", MapFullScreenModeToIndex(Screen.fullScreenMode)); bool isWindowed = (screenMode == 0) || (Screen.fullScreenMode == FullScreenMode.Windowed); if (resolutionLock_Image != null) resolutionLock_Image.gameObject.SetActive(!isWindowed); resolution_Dropdown.interactable = isWindowed; } private void SyncResolutionDropdownToCurrent() { if (resolution_Dropdown == null) return; int idx = availableResolutions.FindIndex(v => v.x == Screen.width && v.y == Screen.height); if (idx >= 0) { RemoveTempFreeOptionIfExists(); resolution_Dropdown.SetValueWithoutNotify(idx); } else { EnsureTempFreeOptionExists(); resolution_Dropdown.options[tempFreeResOptionIndex].text = $"{Screen.width} x {Screen.height}"; resolution_Dropdown.SetValueWithoutNotify(tempFreeResOptionIndex); } } private void OnResolutionChanged(int index) { if (index < 0) return; if (index >= availableResolutions.Count) { PlayerPrefs.SetInt("resolutionIndex", -1); PlayerPrefs.SetInt("customResW", Screen.width); PlayerPrefs.SetInt("customResH", Screen.height); PlayerPrefs.Save(); return; } if (index >= availableResolutions.Count) return; var r = availableResolutions[index]; FullScreenMode mode = Screen.fullScreenMode; Screen.SetResolution(r.x, r.y, mode, Screen.currentResolution.refreshRateRatio); PlayerPrefs.SetInt("resolutionIndex", index); PlayerPrefs.Save(); RemoveTempFreeOptionIfExists(); if (mode == FullScreenMode.Windowed) { StopAllCoroutines(); StartCoroutine(ApplyWindowStyleNextFrame(true)); } } private void InitFrameRateDropdown() { if (frameRate_Dropdown == null) return; var fpsOptions = new List() { "24", "30", "60", "90", "120", "144", "165", "210", "240", "300", "无限制", "垂直同步" }; frameRate_Dropdown.ClearOptions(); frameRate_Dropdown.AddOptions(fpsOptions); // Determine current setting int saved = PlayerPrefs.GetInt("frameRateIndex", -999); // default to 90hz instead of unlimited int selectIndex = 3; // index for 90 if (saved != -999 && saved >= 0 && saved < fpsOptions.Count) { selectIndex = saved; } else { if (QualitySettings.vSyncCount > 0) selectIndex = fpsOptions.Count - 1; // VSync index else { int current = Application.targetFrameRate; if (current <= 0) selectIndex = 3; // default to 90 when targetFrameRate is unset/unlimited else { // find nearest match int[] candidates = new int[] { 24, 30, 60, 90, 120, 144, 165, 210, 240, 300 }; int best = 0; int bestDiff = int.MaxValue; for (int i = 0; i < candidates.Length; i++) { int d = Math.Abs(candidates[i] - current); if (d < bestDiff) { bestDiff = d; best = i; } } selectIndex = best; } } } frameRate_Dropdown.SetValueWithoutNotify(selectIndex); frameRate_Dropdown.onValueChanged.AddListener(OnFrameRateChanged); // apply selection ApplyFrameRateByIndex(selectIndex); } private void OnFrameRateChanged(int index) { ApplyFrameRateByIndex(index); PlayerPrefs.SetInt("frameRateIndex", index); PlayerPrefs.Save(); } private void ApplyFrameRateByIndex(int index) { if (index < 0) return; int[] fpsValues = new int[] { 24, 30, 60, 90, 120, 144, 165, 210, 240, 300 }; if (index < fpsValues.Length) { QualitySettings.vSyncCount = 0; Application.targetFrameRate = fpsValues[index]; } else if (index == fpsValues.Length) { // Documentation text normalized. QualitySettings.vSyncCount = 0; Application.targetFrameRate = -1; // unlimited / platform default } else if (index == fpsValues.Length + 1) { // Documentation text normalized. QualitySettings.vSyncCount = 1; // enable vsync Application.targetFrameRate = -1; } } #if UNITY_STANDALONE_WIN && !UNITY_EDITOR [DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll", EntryPoint = "GetWindowLong")] private static extern int GetWindowLong32(IntPtr hWnd, int nIndex); [DllImport("user32.dll", EntryPoint = "SetWindowLong")] private static extern int SetWindowLong32(IntPtr hWnd, int nIndex, int dwNewLong); [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")] private static extern long GetWindowLongPtr64(IntPtr hWnd, int nIndex); [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")] private static extern long SetWindowLongPtr64(IntPtr hWnd, int nIndex, long dwNewLong); [DllImport("user32.dll", SetLastError = true)] private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); private const uint SWP_NOSIZE = 0x0001; private const uint SWP_NOMOVE = 0x0002; private const uint SWP_NOZORDER = 0x0004; private const uint SWP_FRAMECHANGED = 0x0020; #endif }