472 lines
16 KiB
C#
472 lines
16 KiB
C#
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<Vector2Int> availableResolutions = new List<Vector2Int>();
|
||
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<string>() { "窗口(自由窗口)", "全屏(自适应)", "无边框窗口" };
|
||
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);
|
||
}
|
||
else if (index == 1)
|
||
{
|
||
Screen.fullScreenMode = FullScreenMode.ExclusiveFullScreen;
|
||
Screen.fullScreen = true;
|
||
Resolution res = Screen.currentResolution;
|
||
Screen.SetResolution(res.width, res.height, FullScreenMode.ExclusiveFullScreen, res.refreshRate);
|
||
}
|
||
else if (index == 2)
|
||
{
|
||
Screen.fullScreenMode = FullScreenMode.FullScreenWindow;
|
||
Screen.fullScreen = true;
|
||
Resolution res = Screen.currentResolution;
|
||
Screen.SetResolution(res.width, res.height, FullScreenMode.FullScreenWindow, res.refreshRate);
|
||
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 &= ~WS_OVERLAPPEDWINDOW;
|
||
style |= 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<Vector2Int>() {
|
||
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<string>();
|
||
|
||
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} × {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.refreshRate);
|
||
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.refreshRate);
|
||
EnsureTempFreeOptionExists();
|
||
resolution_Dropdown.options[tempFreeResOptionIndex].text = $"{cw} × {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} × {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;
|
||
int refresh = Screen.currentResolution.refreshRate;
|
||
Screen.SetResolution(r.x, r.y, mode, refresh);
|
||
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<string>() { "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);
|
||
int selectIndex = 2; // default to 60
|
||
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 = fpsOptions.Count - 2; // 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)
|
||
{
|
||
// 无限制
|
||
QualitySettings.vSyncCount = 0;
|
||
Application.targetFrameRate = -1; // unlimited / platform default
|
||
}
|
||
else if (index == fpsValues.Length + 1)
|
||
{
|
||
// 垂直同步
|
||
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
|
||
}
|