512 lines
18 KiB
C#
512 lines
18 KiB
C#
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using UnityEngine.Rendering;
|
|
using UnityEngine.Rendering.Universal;
|
|
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 Toggle superResolution_Toggle;
|
|
|
|
public Image resolutionLock_Image;
|
|
|
|
private List<Vector2Int> availableResolutions = new List<Vector2Int>();
|
|
private int tempFreeResOptionIndex = -1;
|
|
private Vector2Int lastScreenSize;
|
|
|
|
private const string PREF_SUPER_RES = "SuperResolution";
|
|
|
|
void Start()
|
|
{
|
|
InitScreenModeDropdown();
|
|
InitResolutionDropdown();
|
|
InitFrameRateDropdown();
|
|
InitSuperResolutionToggle();
|
|
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);
|
|
if (superResolution_Toggle != null)
|
|
superResolution_Toggle.onValueChanged.RemoveListener(OnSuperResolutionChanged);
|
|
}
|
|
|
|
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, 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<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} 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<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);
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
private void InitSuperResolutionToggle()
|
|
{
|
|
// Default is OFF (0).
|
|
bool isOn = PlayerPrefs.GetInt(PREF_SUPER_RES, 0) == 1;
|
|
|
|
// Apply setting immediately to ensure correct render scale state
|
|
// even if the toggle UI is missing or not assigned yet.
|
|
ApplySuperResolution(isOn);
|
|
|
|
if (superResolution_Toggle != null)
|
|
{
|
|
superResolution_Toggle.isOn = isOn;
|
|
superResolution_Toggle.onValueChanged.AddListener(OnSuperResolutionChanged);
|
|
}
|
|
}
|
|
|
|
private void OnSuperResolutionChanged(bool isOn)
|
|
{
|
|
PlayerPrefs.SetInt(PREF_SUPER_RES, isOn ? 1 : 0);
|
|
PlayerPrefs.Save();
|
|
ApplySuperResolution(isOn);
|
|
}
|
|
|
|
private void ApplySuperResolution(bool isOn)
|
|
{
|
|
var urpAsset = GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset;
|
|
if (urpAsset != null)
|
|
{
|
|
urpAsset.renderScale = isOn ? 2.0f : 1.0f;
|
|
}
|
|
}
|
|
|
|
#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
|
|
}
|