加入了浮动小游戏功能和设置界面

运行时请手动修改运行库目录fdBrowser
This commit is contained in:
FloatGaming
2025-12-24 06:02:06 +08:00
parent 1ab80e504e
commit eb9b6ba78a
1131 changed files with 148096 additions and 313 deletions
@@ -0,0 +1,494 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class controllerSettings : MonoBehaviour
{
[Header("按键绑定")]
public Button[] keyButtons; // assign 5 buttons in Inspector
public Text errorText; // UI text to show error/success messages
[Header("Message Colors")]
public Color messageErrorColor = Color.red;
public Color messageSuccessColor = Color.green;
private Coroutine messageCoroutine = null;
[Header("音符流动速度滑动变阻器")]
public Slider noteSpeedMultipler_slider;
public Text noteSpeedMultipler_valueText;
public Button noteSpeed_DecreaseButton;
public Button noteSpeed_IncreaseButton;
public Button noteSpeed_ResetButton;
// internal colors/order must match KeyBindingManager usage
private readonly string[] colors = new string[] { "red", "green", "yellow", "purple", "blue" };
private bool isRebinding = false;
private int rebindingIndex = -1;
private Coroutine blinkCoroutine = null;
// store previous key so we can restore if user cancels
private KeyCode previousKey = KeyCode.None;
private bool previousKeyValid = false;
// slider range
private const float NoteSpeedMin = 0.8f;
private const float NoteSpeedMax = 1.25f;
private const string NoteSpeedPrefKey = "noteSpeedMultiplier";
// continuous change
private Coroutine continuousChangeCoroutine = null;
private const float ContinuousInitialDelay = 0.5f; // increased to avoid accidental long-press
private const float ContinuousRepeatRate = 0.05f;
private const float StepAmount = 0.01f;
void Start()
{
// initialize button labels from KeyBindingManager
RefreshButtonLabels();
// initialize errorText
if (errorText != null)
{
errorText.text = string.Empty;
}
// hook up listeners for key buttons
if (keyButtons != null)
{
for (int i = 0; i < keyButtons.Length && i < colors.Length; i++)
{
int idx = i;
keyButtons[i].onClick.RemoveAllListeners();
keyButtons[i].onClick.AddListener(() => OnKeyButtonClicked(idx));
}
}
// initialize slider
if (noteSpeedMultipler_slider != null)
{
noteSpeedMultipler_slider.minValue = NoteSpeedMin;
noteSpeedMultipler_slider.maxValue = NoteSpeedMax;
float saved = PlayerPrefs.GetFloat(NoteSpeedPrefKey, 1.0f);
saved = Mathf.Clamp(saved, NoteSpeedMin, NoteSpeedMax);
noteSpeedMultipler_slider.value = saved;
UpdateNoteSpeedText(saved);
noteSpeedMultipler_slider.onValueChanged.RemoveAllListeners();
noteSpeedMultipler_slider.onValueChanged.AddListener(OnNoteSpeedSliderChanged);
// Add PointerUp event to save value when sliding ends (kept for compatibility)
EventTrigger trigger = noteSpeedMultipler_slider.gameObject.GetComponent<EventTrigger>();
if (trigger == null) trigger = noteSpeedMultipler_slider.gameObject.AddComponent<EventTrigger>();
trigger.triggers.RemoveAll(e => e.eventID == EventTriggerType.PointerUp);
var entry = new EventTrigger.Entry { eventID = EventTriggerType.PointerUp };
entry.callback.AddListener((data) => { OnNoteSpeedSliderPointerUp(); });
trigger.triggers.Add(entry);
}
// setup buttons
if (noteSpeed_DecreaseButton != null)
{
noteSpeed_DecreaseButton.onClick.RemoveAllListeners();
noteSpeed_DecreaseButton.onClick.AddListener(() => ChangeNoteSpeedBy(-StepAmount));
AddButtonContinuousEvents(noteSpeed_DecreaseButton, -StepAmount);
}
if (noteSpeed_IncreaseButton != null)
{
noteSpeed_IncreaseButton.onClick.RemoveAllListeners();
noteSpeed_IncreaseButton.onClick.AddListener(() => ChangeNoteSpeedBy(StepAmount));
AddButtonContinuousEvents(noteSpeed_IncreaseButton, StepAmount);
}
if (noteSpeed_ResetButton != null)
{
noteSpeed_ResetButton.onClick.RemoveAllListeners();
noteSpeed_ResetButton.onClick.AddListener(() => ResetNoteSpeed());
}
}
void OnDestroy()
{
if (keyButtons != null)
{
foreach (var b in keyButtons)
if (b != null) b.onClick.RemoveAllListeners();
}
if (noteSpeedMultipler_slider != null)
{
noteSpeedMultipler_slider.onValueChanged.RemoveListener(OnNoteSpeedSliderChanged);
// Do not remove EventTrigger to avoid affecting other listeners, but it's okay on destroy
}
if (noteSpeed_DecreaseButton != null) noteSpeed_DecreaseButton.onClick.RemoveAllListeners();
if (noteSpeed_IncreaseButton != null) noteSpeed_IncreaseButton.onClick.RemoveAllListeners();
if (noteSpeed_ResetButton != null) noteSpeed_ResetButton.onClick.RemoveAllListeners();
}
void Update()
{
// If waiting for a new key, also cancel if user clicks outside UI/buttons
if (isRebinding)
{
if (Input.GetMouseButtonDown(0))
{
if (!IsPointerOverAnyKeyButton())
{
// clicked outside the key buttons while rebinding -> cancel and restore
CancelRebind();
return;
}
}
}
if (!isRebinding) return;
// detect any keydown by iterating KeyCode values
foreach (KeyCode kc in Enum.GetValues(typeof(KeyCode)))
{
// ignore mouse buttons and joystick buttons
if (kc >= KeyCode.Mouse0 && kc <= KeyCode.Mouse6) continue;
if (kc >= KeyCode.JoystickButton0 && kc <= KeyCode.Joystick8Button19) continue;
if (Input.GetKeyDown(kc))
{
HandleRebindKey(kc);
break;
}
}
}
private bool IsPointerOverAnyKeyButton()
{
if (EventSystem.current == null) return false;
var pointerData = new PointerEventData(EventSystem.current) { position = Input.mousePosition };
var results = new List<RaycastResult>();
EventSystem.current.RaycastAll(pointerData, results);
if (results.Count == 0) return false;
foreach (var res in results)
{
var go = res.gameObject;
// check if this gameobject is one of the keyButtons or a child of one
for (int i = 0; i < keyButtons.Length; i++)
{
var btn = keyButtons[i];
if (btn == null) continue;
if (go == btn.gameObject || go.transform.IsChildOf(btn.transform))
return true;
}
}
return false;
}
private void OnKeyButtonClicked(int index)
{
if (isRebinding) return; // ignore while another rebind in progress
if (index < 0 || index >= colors.Length) return;
isRebinding = true;
rebindingIndex = index;
// remember previous key so we can restore if user cancels
previousKey = KeyBindingManager.GetKeyForColor(colors[index]);
previousKeyValid = true;
// start blinking underscore on the clicked button
blinkCoroutine = StartCoroutine(BlinkUnderscore(index));
// show persistent prompt
ShowPersistentMessage("按下新按键", messageErrorColor);
}
private IEnumerator BlinkUnderscore(int index)
{
Text txt = GetButtonText(index);
if (txt == null) yield break;
while (isRebinding && rebindingIndex == index)
{
txt.text = "_";
yield return new WaitForSeconds(0.5f);
txt.text = "";
yield return new WaitForSeconds(0.25f);
}
// restore label when finished
RefreshButtonLabel(index);
}
private void HandleRebindKey(KeyCode key)
{
// ignore ESC
if (key == KeyCode.Escape)
{
CancelRebind();
return;
}
// check if key already assigned to another color
for (int i = 0; i < colors.Length; i++)
{
if (i == rebindingIndex) continue;
KeyCode existing = KeyBindingManager.GetKeyForColor(colors[i]);
if (existing == key)
{
// conflict: reject and keep waiting
string msg = $"按键冲突:{KeyBindingManager.GetDisplayName(key)}已被分配给按键{i+1}。";
ShowMessage(msg, messageErrorColor, 2f);
StartCoroutine(FlashButtonInvalid(rebindingIndex));
return;
}
}
// accept binding
KeyBindingManager.ChangeKeyBinding(colors[rebindingIndex], key);
// update UI labels
RefreshButtonLabel(rebindingIndex);
InputManager.Instance?.RefreshKeyLabels();
// clear persistent prompt and show success message
ClearMessage();
ShowMessage("按键设置已保存", messageSuccessColor, 2f);
// finish
previousKeyValid = false;
previousKey = KeyCode.None;
StopRebind();
}
private void ShowMessage(string message, Color color, float duration = 2f)
{
if (errorText == null) return;
if (messageCoroutine != null) StopCoroutine(messageCoroutine);
messageCoroutine = StartCoroutine(ShowMessageCoroutine(message, color, duration));
}
private IEnumerator ShowMessageCoroutine(string message, Color color, float duration)
{
errorText.text = message;
errorText.color = color;
float t = 0f;
while (t < duration)
{
t += Time.deltaTime;
yield return null;
}
errorText.text = string.Empty;
messageCoroutine = null;
}
// Show a persistent message (until explicitly cleared)
private void ShowPersistentMessage(string message, Color color)
{
if (errorText == null) return;
// stop any timed message
if (messageCoroutine != null)
{
StopCoroutine(messageCoroutine);
messageCoroutine = null;
}
errorText.text = message;
errorText.color = color;
}
// Clear any displayed message immediately
private void ClearMessage()
{
if (messageCoroutine != null)
{
StopCoroutine(messageCoroutine);
messageCoroutine = null;
}
if (errorText != null) errorText.text = string.Empty;
}
private IEnumerator FlashButtonInvalid(int index)
{
Button b = GetButton(index);
if (b == null) yield break;
Color original = b.image.color;
b.image.color = Color.red;
yield return new WaitForSeconds(0.35f);
b.image.color = original;
}
private void CancelRebind()
{
// restore previous key if we have one
if (previousKeyValid && rebindingIndex >= 0 && rebindingIndex < colors.Length)
{
KeyBindingManager.ChangeKeyBinding(colors[rebindingIndex], previousKey);
InputManager.Instance?.RefreshKeyLabels();
ClearMessage();
ShowMessage("按键设置已保存", messageSuccessColor, 2f);
}
previousKeyValid = false;
previousKey = KeyCode.None;
StopRebind();
}
private void StopRebind()
{
isRebinding = false;
rebindingIndex = -1;
if (blinkCoroutine != null)
{
StopCoroutine(blinkCoroutine);
blinkCoroutine = null;
}
RefreshButtonLabels();
}
private void RefreshButtonLabels()
{
for (int i = 0; i < colors.Length; i++)
{
RefreshButtonLabel(i);
}
}
// update RefreshButtonLabel to use display name
private void RefreshButtonLabel(int index)
{
Text txt = GetButtonText(index);
if (txt == null) return;
KeyCode k = KeyBindingManager.GetKeyForColor(colors[index]);
txt.text = KeyBindingManager.GetDisplayName(k);
}
private Button GetButton(int index)
{
if (keyButtons == null) return null;
if (index < 0 || index >= keyButtons.Length) return null;
return keyButtons[index];
}
private Text GetButtonText(int index)
{
Button b = GetButton(index);
if (b == null) return null;
Text t = b.GetComponentInChildren<Text>();
return t;
}
private void OnNoteSpeedSliderChanged(float value)
{
UpdateNoteSpeedText(value);
// save on every change
SaveNoteSpeedValue(value);
}
private void OnNoteSpeedSliderPointerUp()
{
if (noteSpeedMultipler_slider != null)
{
float value = noteSpeedMultipler_slider.value;
PlayerPrefs.SetFloat(NoteSpeedPrefKey, value);
PlayerPrefs.Save();
}
}
private void UpdateNoteSpeedText(float value)
{
if (noteSpeedMultipler_valueText != null)
{
noteSpeedMultipler_valueText.text = value.ToString("F3");
}
}
private void SaveNoteSpeedValue(float value)
{
float v = Mathf.Clamp(value, NoteSpeedMin, NoteSpeedMax);
PlayerPrefs.SetFloat(NoteSpeedPrefKey, v);
PlayerPrefs.Save();
}
private void ChangeNoteSpeedBy(float delta)
{
if (noteSpeedMultipler_slider == null) return;
float cur = noteSpeedMultipler_slider.value;
float step = StepAmount;
float newVal = cur;
const float eps = 1e-5f;
if (delta > 0f)
{
float ceilStep = Mathf.Ceil(cur / step) * step;
if (Mathf.Abs(cur - ceilStep) < eps)
{
newVal = Mathf.Min(cur + step, NoteSpeedMax);
}
else
{
newVal = Mathf.Min(ceilStep, NoteSpeedMax);
}
}
else if (delta < 0f)
{
float floorStep = Mathf.Floor(cur / step) * step;
if (Mathf.Abs(cur - floorStep) < eps)
{
newVal = Mathf.Max(cur - step, NoteSpeedMin);
}
else
{
newVal = Mathf.Max(floorStep, NoteSpeedMin);
}
}
newVal = Mathf.Clamp(newVal, NoteSpeedMin, NoteSpeedMax);
// round to 3 decimals for display and consistency
newVal = (float)System.Math.Round(newVal, 3);
noteSpeedMultipler_slider.value = newVal; // will trigger OnNoteSpeedSliderChanged and save
}
private void ResetNoteSpeed()
{
if (noteSpeedMultipler_slider == null) return;
noteSpeedMultipler_slider.value = 1.0f; // triggers change and save
SaveNoteSpeedValue(1.0f);
UpdateNoteSpeedText(1.0f);
}
private void AddButtonContinuousEvents(Button btn, float delta)
{
EventTrigger trigger = btn.gameObject.GetComponent<EventTrigger>();
if (trigger == null) trigger = btn.gameObject.AddComponent<EventTrigger>();
// PointerDown -> start continuous change
var downEntry = new EventTrigger.Entry { eventID = EventTriggerType.PointerDown };
downEntry.callback.AddListener((data) => { StartContinuousChange(delta); });
trigger.triggers.Add(downEntry);
// PointerUp -> stop continuous change
var upEntry = new EventTrigger.Entry { eventID = EventTriggerType.PointerUp };
upEntry.callback.AddListener((data) => { StopContinuousChange(); });
trigger.triggers.Add(upEntry);
// Also stop on PointerExit in case cursor leaves button while pressed
var exitEntry = new EventTrigger.Entry { eventID = EventTriggerType.PointerExit };
exitEntry.callback.AddListener((data) => { StopContinuousChange(); });
trigger.triggers.Add(exitEntry);
}
private void StartContinuousChange(float delta)
{
// perform immediate single step
ChangeNoteSpeedBy(delta);
if (continuousChangeCoroutine != null) StopCoroutine(continuousChangeCoroutine);
continuousChangeCoroutine = StartCoroutine(ContinuousChangeRoutine(delta));
}
private void StopContinuousChange()
{
if (continuousChangeCoroutine != null)
{
StopCoroutine(continuousChangeCoroutine);
continuousChangeCoroutine = null;
}
}
private IEnumerator ContinuousChangeRoutine(float delta)
{
yield return new WaitForSeconds(ContinuousInitialDelay);
while (true)
{
ChangeNoteSpeedBy(delta);
yield return new WaitForSeconds(ContinuousRepeatRate);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 47a45212060d63a4f98f13e30622253a
+471
View File
@@ -0,0 +1,471 @@
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
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f0d532c98d6ca9a4ea5a2bd80cc4f193
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 32d5540b04c3c3d41909f074d774d8bc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
using UnityEngine;
public class offsetDeterminer : MonoBehaviour
{
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 28e4b9a4015da4847a8e725526e80be9