ui基本完毕,修了一大把的bug
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using GameServer.Client;
|
||||
using UnityEngine;
|
||||
|
||||
public sealed class StartupSettingsApplier : MonoBehaviour
|
||||
{
|
||||
private const string BootstrapObjectName = "[StartupSettingsApplier]";
|
||||
private static StartupSettingsApplier _instance;
|
||||
private bool _started;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
private static void Bootstrap()
|
||||
{
|
||||
if (_instance != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject go = new GameObject(BootstrapObjectName);
|
||||
_instance = go.AddComponent<StartupSettingsApplier>();
|
||||
DontDestroyOnLoad(go);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_started = true;
|
||||
ApplyStartupSettings();
|
||||
StartCoroutine(SyncLeaderboardConsentRoutine());
|
||||
}
|
||||
|
||||
private void ApplyStartupSettings()
|
||||
{
|
||||
graphicSettings.ApplySavedDisplaySettingsAtStartup(this);
|
||||
ApplySavedAudioSettings();
|
||||
}
|
||||
|
||||
private void ApplySavedAudioSettings()
|
||||
{
|
||||
audioSettingsPreloader[] preloaders = FindObjectsByType<audioSettingsPreloader>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||
for (int i = 0; i < preloaders.Length; i++)
|
||||
{
|
||||
audioSettingsPreloader preloader = preloaders[i];
|
||||
if (preloader == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
preloader.ApplyAudioSettings();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[StartupSettingsApplier] Failed to apply audio settings via scene preloader: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator SyncLeaderboardConsentRoutine()
|
||||
{
|
||||
float timeoutAt = Time.realtimeSinceStartup + 8f;
|
||||
while (NetworkManager.Instance == null && Time.realtimeSinceStartup < timeoutAt)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
NetworkManager networkManager = NetworkManager.Instance;
|
||||
if (networkManager == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
bool isJoined = LeaderboardConsentUtility.HasLeaderboardConsent();
|
||||
var task = networkManager.UpdateLeaderboardMembership(isJoined);
|
||||
while (!task.IsCompleted)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
Exception ex = task.Exception;
|
||||
Debug.LogWarning($"[StartupSettingsApplier] Failed to sync leaderboard consent on startup: {ex?.GetBaseException().Message}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
LeaderboardMembershipResponse response = task.Result;
|
||||
if (response != null)
|
||||
{
|
||||
LeaderboardConsentUtility.SetLeaderboardConsent(response.is_joined);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e9ad3d519677da4bb6487b008a1eddb
|
||||
@@ -37,6 +37,7 @@ public class controllerSettings : MonoBehaviour
|
||||
private const float NoteSpeedMin = 0.5f;
|
||||
private const float NoteSpeedMax = 2f;
|
||||
private const string NoteSpeedPrefKey = "noteSpeedMultiplier";
|
||||
private const float NoteSpeedDefault = 1.0f;
|
||||
|
||||
// continuous change
|
||||
private Coroutine continuousChangeCoroutine = null;
|
||||
@@ -77,7 +78,8 @@ public class controllerSettings : MonoBehaviour
|
||||
{
|
||||
noteSpeedMultipler_slider.minValue = NoteSpeedMin;
|
||||
noteSpeedMultipler_slider.maxValue = NoteSpeedMax;
|
||||
float saved = PlayerPrefs.GetFloat(NoteSpeedPrefKey, 1.0f);
|
||||
EnsureDefaultNoteSpeedPreference();
|
||||
float saved = PlayerPrefs.GetFloat(NoteSpeedPrefKey, NoteSpeedDefault);
|
||||
saved = Mathf.Clamp(saved, NoteSpeedMin, NoteSpeedMax);
|
||||
noteSpeedMultipler_slider.value = saved;
|
||||
UpdateNoteSpeedText(saved);
|
||||
@@ -494,9 +496,20 @@ public class controllerSettings : MonoBehaviour
|
||||
private void ResetNoteSpeed()
|
||||
{
|
||||
if (noteSpeedMultipler_slider == null) return;
|
||||
noteSpeedMultipler_slider.value = 1.0f; // triggers change and save
|
||||
SaveNoteSpeedValue(1.0f);
|
||||
UpdateNoteSpeedText(1.0f);
|
||||
noteSpeedMultipler_slider.value = NoteSpeedDefault; // triggers change and save
|
||||
SaveNoteSpeedValue(NoteSpeedDefault);
|
||||
UpdateNoteSpeedText(NoteSpeedDefault);
|
||||
}
|
||||
|
||||
private static void EnsureDefaultNoteSpeedPreference()
|
||||
{
|
||||
if (PlayerPrefs.HasKey(NoteSpeedPrefKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerPrefs.SetFloat(NoteSpeedPrefKey, NoteSpeedDefault);
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
private void AddButtonContinuousEvents(Button btn, float delta)
|
||||
|
||||
@@ -1,32 +1,205 @@
|
||||
using System;
|
||||
using Bansonic;
|
||||
using GameServer.Client;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
public class gameSettings : MonoBehaviour
|
||||
{
|
||||
[Header("multiNotesSigns")]
|
||||
public Toggle multiNoteNoticeToggle;
|
||||
public Text multiNoteNoticeText; // Using Legacy Text object
|
||||
public Text multiNoteNoticeText;
|
||||
|
||||
private const string PrefKey_EnableSyncNotePrefab = "EnableSyncNotePrefab";
|
||||
[Header("joinRankingList")]
|
||||
public Toggle user_accept_joinRankingList;
|
||||
public Text jrlWarningText;
|
||||
|
||||
void Start()
|
||||
private const string PrefKeyEnableSyncNotePrefab = "EnableSyncNotePrefab";
|
||||
private bool _isUpdatingJoinRankingToggle;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (multiNoteNoticeToggle != null)
|
||||
{
|
||||
// Initialize toggle state from PlayerPrefs, default to 1 (true)
|
||||
bool isEnabled = PlayerPrefs.GetInt(PrefKey_EnableSyncNotePrefab, 1) == 1;
|
||||
multiNoteNoticeToggle.isOn = isEnabled;
|
||||
multiNoteNoticeToggle.onValueChanged.RemoveListener(HandleMultiNoteNoticeToggleChanged);
|
||||
multiNoteNoticeToggle.onValueChanged.AddListener(HandleMultiNoteNoticeToggleChanged);
|
||||
}
|
||||
|
||||
// Initialize text based on current toggle value
|
||||
UpdateText(isEnabled);
|
||||
if (user_accept_joinRankingList != null)
|
||||
{
|
||||
user_accept_joinRankingList.onValueChanged.RemoveListener(HandleJoinRankingListToggleChanged);
|
||||
user_accept_joinRankingList.onValueChanged.AddListener(HandleJoinRankingListToggleChanged);
|
||||
}
|
||||
}
|
||||
|
||||
// Add listener to save value and update text when changed
|
||||
multiNoteNoticeToggle.onValueChanged.AddListener((isOn) =>
|
||||
private void OnEnable()
|
||||
{
|
||||
RefreshMultiNoteNoticeState();
|
||||
_ = RefreshJoinRankingStateFromServerAsync();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (multiNoteNoticeToggle != null)
|
||||
{
|
||||
multiNoteNoticeToggle.onValueChanged.RemoveListener(HandleMultiNoteNoticeToggleChanged);
|
||||
}
|
||||
|
||||
if (user_accept_joinRankingList != null)
|
||||
{
|
||||
user_accept_joinRankingList.onValueChanged.RemoveListener(HandleJoinRankingListToggleChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshMultiNoteNoticeState()
|
||||
{
|
||||
if (multiNoteNoticeToggle == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool isEnabled = PlayerPrefs.GetInt(PrefKeyEnableSyncNotePrefab, 1) == 1;
|
||||
multiNoteNoticeToggle.SetIsOnWithoutNotify(isEnabled);
|
||||
UpdateText(isEnabled);
|
||||
}
|
||||
|
||||
private async Awaitable RefreshJoinRankingStateFromServerAsync()
|
||||
{
|
||||
if (user_accept_joinRankingList == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
NetworkManager nm = NetworkManager.Instance;
|
||||
if (nm == null)
|
||||
{
|
||||
PlayerPrefs.SetInt(PrefKey_EnableSyncNotePrefab, isOn ? 1 : 0);
|
||||
PlayerPrefs.Save();
|
||||
UpdateText(isOn);
|
||||
});
|
||||
RefreshJoinRankingListStateFromLocal();
|
||||
await RefreshJoinRankingWarningTextAsync(null);
|
||||
return;
|
||||
}
|
||||
|
||||
LeaderboardMembershipResponse resp = await nm.GetLeaderboardMembership();
|
||||
bool isJoined = resp != null ? resp.is_joined : LeaderboardConsentUtility.HasLeaderboardConsent();
|
||||
LeaderboardConsentUtility.SetLeaderboardConsent(isJoined);
|
||||
|
||||
_isUpdatingJoinRankingToggle = true;
|
||||
user_accept_joinRankingList.SetIsOnWithoutNotify(isJoined);
|
||||
_isUpdatingJoinRankingToggle = false;
|
||||
|
||||
await RefreshJoinRankingWarningTextAsync(resp);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[gameSettings] Failed to refresh leaderboard membership state: {ex.Message}");
|
||||
RefreshJoinRankingListStateFromLocal();
|
||||
await RefreshJoinRankingWarningTextAsync(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshJoinRankingListStateFromLocal()
|
||||
{
|
||||
if (user_accept_joinRankingList == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isUpdatingJoinRankingToggle = true;
|
||||
user_accept_joinRankingList.SetIsOnWithoutNotify(LeaderboardConsentUtility.HasLeaderboardConsent());
|
||||
_isUpdatingJoinRankingToggle = false;
|
||||
}
|
||||
|
||||
private async Awaitable RefreshJoinRankingWarningTextAsync(LeaderboardMembershipResponse membership)
|
||||
{
|
||||
if (jrlWarningText == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (membership != null && membership.changed_today && !string.IsNullOrWhiteSpace(membership.next_change_at))
|
||||
{
|
||||
jrlWarningText.text = BuildChangeCooldownText(membership.next_change_at);
|
||||
return;
|
||||
}
|
||||
|
||||
jrlWarningText.text = "下次排行榜更新时间:正在获取...";
|
||||
|
||||
try
|
||||
{
|
||||
jrlWarningText.text = membership != null && !string.IsNullOrWhiteSpace(membership.next_settle_at)
|
||||
? "下次排行榜更新时间:" + membership.next_settle_at
|
||||
: await LeaderboardConsentUtility.BuildNextSettleWarningTextAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[gameSettings] Failed to refresh ranking warning text: {ex.Message}");
|
||||
jrlWarningText.text = "下次排行榜更新时间:--";
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildChangeCooldownText(string nextChangeAt)
|
||||
{
|
||||
if (DateTime.TryParse(nextChangeAt, out DateTime parsed))
|
||||
{
|
||||
return $"{parsed.Hour}时{parsed.Minute:D2}分后可切换";
|
||||
}
|
||||
|
||||
return "今日已切换";
|
||||
}
|
||||
|
||||
private void HandleMultiNoteNoticeToggleChanged(bool isOn)
|
||||
{
|
||||
PlayerPrefs.SetInt(PrefKeyEnableSyncNotePrefab, isOn ? 1 : 0);
|
||||
PlayerPrefs.Save();
|
||||
UpdateText(isOn);
|
||||
}
|
||||
|
||||
private async void HandleJoinRankingListToggleChanged(bool isOn)
|
||||
{
|
||||
if (_isUpdatingJoinRankingToggle)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
NetworkManager nm = NetworkManager.Instance;
|
||||
if (nm == null)
|
||||
{
|
||||
LeaderboardConsentUtility.SetLeaderboardConsent(isOn);
|
||||
await RefreshJoinRankingWarningTextAsync(null);
|
||||
return;
|
||||
}
|
||||
|
||||
LeaderboardMembershipResponse resp = await nm.UpdateLeaderboardMembership(isOn);
|
||||
bool finalJoined = resp != null ? resp.is_joined : isOn;
|
||||
LeaderboardConsentUtility.SetLeaderboardConsent(finalJoined);
|
||||
|
||||
_isUpdatingJoinRankingToggle = true;
|
||||
user_accept_joinRankingList.SetIsOnWithoutNotify(finalJoined);
|
||||
_isUpdatingJoinRankingToggle = false;
|
||||
|
||||
if (resp != null && !resp.success)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(resp.next_change_at))
|
||||
{
|
||||
gNotice.warning.display(BuildChangeCooldownText(resp.next_change_at));
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(resp.message))
|
||||
{
|
||||
gNotice.warning.display(resp.message);
|
||||
}
|
||||
}
|
||||
|
||||
await RefreshJoinRankingWarningTextAsync(resp);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[gameSettings] Failed to update leaderboard membership: {ex.Message}");
|
||||
gNotice.error.display("排行榜加入状态更新失败");
|
||||
RefreshJoinRankingListStateFromLocal();
|
||||
await RefreshJoinRankingWarningTextAsync(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,14 +207,18 @@ public class gameSettings : MonoBehaviour
|
||||
{
|
||||
if (multiNoteNoticeText != null)
|
||||
{
|
||||
multiNoteNoticeText.text = isOn ? "已启用多押指示器" : "已禁用多押指示器";
|
||||
multiNoteNoticeText.text = isOn ? "已开启多押提示音符同步显示" : "已关闭多押提示音符同步显示";
|
||||
}
|
||||
|
||||
if (multiNoteNoticeToggle == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Also update the Toggle's own label text if it exists (usually a child object)
|
||||
Text toggleLabel = multiNoteNoticeToggle.GetComponentInChildren<Text>();
|
||||
if (toggleLabel != null)
|
||||
{
|
||||
toggleLabel.text = isOn ? "启用" : "禁用";
|
||||
toggleLabel.text = isOn ? "已开启" : "已关闭";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@ using System.Runtime.InteropServices;
|
||||
|
||||
public class graphicSettings : MonoBehaviour
|
||||
{
|
||||
private const string PrefKeyScreenMode = "screenMode";
|
||||
private const string PrefKeyResolutionIndex = "resolutionIndex";
|
||||
private const string PrefKeyCustomResW = "customResW";
|
||||
private const string PrefKeyCustomResH = "customResH";
|
||||
|
||||
public Dropdown screenMode_Dropdown;
|
||||
public Dropdown resolution_Dropdown;
|
||||
public Dropdown frameRate_Dropdown;
|
||||
@@ -30,6 +35,22 @@ public class graphicSettings : MonoBehaviour
|
||||
lastScreenSize = new Vector2Int(Screen.width, Screen.height);
|
||||
}
|
||||
|
||||
public static void ApplySavedDisplaySettingsAtStartup(MonoBehaviour coroutineRunner)
|
||||
{
|
||||
int screenMode = PlayerPrefs.GetInt(PrefKeyScreenMode, 0);
|
||||
screenMode = Mathf.Clamp(screenMode, 0, 2);
|
||||
|
||||
try
|
||||
{
|
||||
ApplyScreenModeStatic(screenMode, coroutineRunner);
|
||||
ApplySavedResolutionStatic();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[graphicSettings] Failed to apply saved display settings at startup: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (screenMode_Dropdown != null)
|
||||
@@ -99,7 +120,7 @@ public class graphicSettings : MonoBehaviour
|
||||
int current = MapFullScreenModeToIndex(Screen.fullScreenMode);
|
||||
if (PlayerPrefs.HasKey("screenMode"))
|
||||
{
|
||||
int saved = PlayerPrefs.GetInt("screenMode", current);
|
||||
int saved = PlayerPrefs.GetInt(PrefKeyScreenMode, current);
|
||||
saved = Mathf.Clamp(saved, 0, options.Count - 1);
|
||||
current = saved;
|
||||
ApplyScreenMode(current);
|
||||
@@ -129,7 +150,7 @@ public class graphicSettings : MonoBehaviour
|
||||
private void OnScreenModeChanged(int index)
|
||||
{
|
||||
ApplyScreenMode(index);
|
||||
PlayerPrefs.SetInt("screenMode", index);
|
||||
PlayerPrefs.SetInt(PrefKeyScreenMode, index);
|
||||
PlayerPrefs.Save();
|
||||
|
||||
bool isWindowed = (index == 0);
|
||||
@@ -141,96 +162,17 @@ public class graphicSettings : MonoBehaviour
|
||||
|
||||
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}");
|
||||
}
|
||||
ApplyScreenModeStatic(index, this);
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator ApplyWindowStyleNextFrame(bool enable)
|
||||
{
|
||||
yield return null;
|
||||
yield return new WaitForSeconds(0.05f);
|
||||
EnableWindowedModeResizable(enable);
|
||||
yield return ApplyWindowStyleNextFrameStatic(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
|
||||
EnableWindowedModeResizableStatic(enable);
|
||||
}
|
||||
|
||||
private void InitResolutionDropdown()
|
||||
@@ -295,7 +237,7 @@ public class graphicSettings : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
int savedResIndex = PlayerPrefs.GetInt("resolutionIndex", -2);
|
||||
int savedResIndex = PlayerPrefs.GetInt(PrefKeyResolutionIndex, -2);
|
||||
if (savedResIndex >= 0 && savedResIndex < availableResolutions.Count)
|
||||
{
|
||||
resolution_Dropdown.SetValueWithoutNotify(savedResIndex);
|
||||
@@ -305,8 +247,8 @@ public class graphicSettings : MonoBehaviour
|
||||
}
|
||||
else if (savedResIndex == -1)
|
||||
{
|
||||
int cw = PlayerPrefs.GetInt("customResW", -1);
|
||||
int ch = PlayerPrefs.GetInt("customResH", -1);
|
||||
int cw = PlayerPrefs.GetInt(PrefKeyCustomResW, -1);
|
||||
int ch = PlayerPrefs.GetInt(PrefKeyCustomResH, -1);
|
||||
if (cw > 0 && ch > 0)
|
||||
{
|
||||
Screen.SetResolution(cw, ch, Screen.fullScreenMode, Screen.currentResolution.refreshRateRatio);
|
||||
@@ -325,7 +267,7 @@ public class graphicSettings : MonoBehaviour
|
||||
}
|
||||
resolution_Dropdown.onValueChanged.AddListener(OnResolutionChanged);
|
||||
|
||||
int screenMode = PlayerPrefs.GetInt("screenMode", MapFullScreenModeToIndex(Screen.fullScreenMode));
|
||||
int screenMode = PlayerPrefs.GetInt(PrefKeyScreenMode, MapFullScreenModeToIndex(Screen.fullScreenMode));
|
||||
bool isWindowed = (screenMode == 0) || (Screen.fullScreenMode == FullScreenMode.Windowed);
|
||||
if (resolutionLock_Image != null)
|
||||
resolutionLock_Image.gameObject.SetActive(!isWindowed);
|
||||
@@ -354,9 +296,9 @@ public class graphicSettings : MonoBehaviour
|
||||
if (index < 0) return;
|
||||
if (index >= availableResolutions.Count)
|
||||
{
|
||||
PlayerPrefs.SetInt("resolutionIndex", -1);
|
||||
PlayerPrefs.SetInt("customResW", Screen.width);
|
||||
PlayerPrefs.SetInt("customResH", Screen.height);
|
||||
PlayerPrefs.SetInt(PrefKeyResolutionIndex, -1);
|
||||
PlayerPrefs.SetInt(PrefKeyCustomResW, Screen.width);
|
||||
PlayerPrefs.SetInt(PrefKeyCustomResH, Screen.height);
|
||||
PlayerPrefs.Save();
|
||||
return;
|
||||
}
|
||||
@@ -364,7 +306,7 @@ public class graphicSettings : MonoBehaviour
|
||||
var r = availableResolutions[index];
|
||||
FullScreenMode mode = Screen.fullScreenMode;
|
||||
Screen.SetResolution(r.x, r.y, mode, Screen.currentResolution.refreshRateRatio);
|
||||
PlayerPrefs.SetInt("resolutionIndex", index);
|
||||
PlayerPrefs.SetInt(PrefKeyResolutionIndex, index);
|
||||
PlayerPrefs.Save();
|
||||
|
||||
RemoveTempFreeOptionIfExists();
|
||||
@@ -484,6 +426,155 @@ public class graphicSettings : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyScreenModeStatic(int index, MonoBehaviour coroutineRunner)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
Screen.fullScreenMode = FullScreenMode.Windowed;
|
||||
Screen.fullScreen = false;
|
||||
Screen.SetResolution(Screen.width, Screen.height, FullScreenMode.Windowed, Screen.currentResolution.refreshRateRatio);
|
||||
if (coroutineRunner != null)
|
||||
{
|
||||
coroutineRunner.StartCoroutine(ApplyWindowStyleNextFrameStatic(true));
|
||||
}
|
||||
}
|
||||
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);
|
||||
if (coroutineRunner != null)
|
||||
{
|
||||
coroutineRunner.StartCoroutine(ApplyWindowStyleNextFrameStatic(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"ApplyScreenMode failed: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplySavedResolutionStatic()
|
||||
{
|
||||
int savedResIndex = PlayerPrefs.GetInt(PrefKeyResolutionIndex, -2);
|
||||
if (savedResIndex == -1)
|
||||
{
|
||||
int customWidth = PlayerPrefs.GetInt(PrefKeyCustomResW, -1);
|
||||
int customHeight = PlayerPrefs.GetInt(PrefKeyCustomResH, -1);
|
||||
if (customWidth > 0 && customHeight > 0)
|
||||
{
|
||||
Screen.SetResolution(customWidth, customHeight, Screen.fullScreenMode, Screen.currentResolution.refreshRateRatio);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (savedResIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2Int[] candidates =
|
||||
{
|
||||
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)
|
||||
};
|
||||
|
||||
if (savedResIndex >= candidates.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2Int resolution = candidates[savedResIndex];
|
||||
Resolution currentResolution = Screen.currentResolution;
|
||||
if (resolution.x > currentResolution.width || resolution.y > currentResolution.height)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Screen.SetResolution(resolution.x, resolution.y, Screen.fullScreenMode, currentResolution.refreshRateRatio);
|
||||
}
|
||||
|
||||
private static System.Collections.IEnumerator ApplyWindowStyleNextFrameStatic(bool enable)
|
||||
{
|
||||
yield return null;
|
||||
yield return new WaitForSeconds(0.05f);
|
||||
EnableWindowedModeResizableStatic(enable);
|
||||
}
|
||||
|
||||
private static void EnableWindowedModeResizableStatic(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
|
||||
}
|
||||
|
||||
#if UNITY_STANDALONE_WIN && !UNITY_EDITOR
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetForegroundWindow();
|
||||
|
||||
@@ -3,11 +3,17 @@ using UnityEngine.UI;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
public class readDeviceInfo : MonoBehaviour
|
||||
{
|
||||
private const string LabelColorHex = "#2457c6";
|
||||
private const string ValueColorHex = "#6b8acd";
|
||||
private const float ColumnPaddingPixels = 32f;
|
||||
private const int MinimumGapSpaces = 2;
|
||||
|
||||
public Button reread_button;
|
||||
public Button export_button;
|
||||
public Text deviceInfo_text;
|
||||
@@ -25,7 +31,9 @@ public class readDeviceInfo : MonoBehaviour
|
||||
}
|
||||
|
||||
if (deviceInfo_text != null)
|
||||
deviceInfo_text.supportRichText = true;
|
||||
{
|
||||
ConfigureDeviceInfoText();
|
||||
}
|
||||
|
||||
read_deviceInfo();
|
||||
}
|
||||
@@ -45,7 +53,8 @@ public class readDeviceInfo : MonoBehaviour
|
||||
|
||||
private void read_deviceInfo()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
ConfigureDeviceInfoText();
|
||||
var entries = new System.Collections.Generic.List<KeyValuePair<string, string>>();
|
||||
|
||||
void AddEntry(string title, object value)
|
||||
{
|
||||
@@ -54,9 +63,7 @@ public class readDeviceInfo : MonoBehaviour
|
||||
else if (value is bool b) str = b ? "是" : "否";
|
||||
else str = value.ToString();
|
||||
|
||||
sb.AppendLine($"<b>{title}</b>");
|
||||
sb.AppendLine(str);
|
||||
sb.AppendLine();
|
||||
entries.Add(new KeyValuePair<string, string>(title ?? string.Empty, str ?? string.Empty));
|
||||
}
|
||||
|
||||
AddEntry("平台", Application.platform.ToString());
|
||||
@@ -101,7 +108,7 @@ public class readDeviceInfo : MonoBehaviour
|
||||
AddEntry("启用后台运行", Application.runInBackground);
|
||||
AddEntry("启用多线程渲染", SystemInfo.graphicsMultiThreaded);
|
||||
|
||||
string output = sb.ToString();
|
||||
string output = BuildTwoColumnOutput(entries);
|
||||
|
||||
if (deviceInfo_text != null)
|
||||
{
|
||||
@@ -125,8 +132,7 @@ public class readDeviceInfo : MonoBehaviour
|
||||
try
|
||||
{
|
||||
string raw = deviceInfo_text.text;
|
||||
// Remove <b> tags and add colon after title
|
||||
string cleaned = Regex.Replace(raw, "<b>(.*?)</b>", "$1:");
|
||||
string cleaned = Regex.Replace(raw, "<.*?>", string.Empty);
|
||||
File.WriteAllText(path, cleaned);
|
||||
}
|
||||
catch (System.Exception)
|
||||
@@ -134,6 +140,246 @@ public class readDeviceInfo : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildTwoColumnOutput(System.Collections.Generic.IReadOnlyList<KeyValuePair<string, string>> entries)
|
||||
{
|
||||
if (entries == null || entries.Count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
float maxLabelWidth = 0f;
|
||||
for (int i = 0; i < entries.Count; i++)
|
||||
{
|
||||
maxLabelWidth = Mathf.Max(maxLabelWidth, MeasurePlainTextWidth(entries[i].Key));
|
||||
}
|
||||
|
||||
float availableWidth = GetAvailableTextWidth();
|
||||
float targetLabelWidth = maxLabelWidth + ColumnPaddingPixels;
|
||||
if (availableWidth > 0f)
|
||||
{
|
||||
targetLabelWidth = Mathf.Min(targetLabelWidth, availableWidth * 0.38f);
|
||||
}
|
||||
|
||||
float spaceWidth = Mathf.Max(1f, MeasurePlainTextWidth(" "));
|
||||
int continuationIndentSpaces = Mathf.Max(MinimumGapSpaces, Mathf.CeilToInt(targetLabelWidth / spaceWidth) + MinimumGapSpaces);
|
||||
string continuationIndent = new string(' ', continuationIndentSpaces);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < entries.Count; i++)
|
||||
{
|
||||
string label = entries[i].Key ?? string.Empty;
|
||||
string value = entries[i].Value ?? string.Empty;
|
||||
|
||||
int gapSpaces = Mathf.Max(
|
||||
MinimumGapSpaces,
|
||||
Mathf.CeilToInt((targetLabelWidth - MeasurePlainTextWidth(label)) / spaceWidth) + MinimumGapSpaces);
|
||||
|
||||
string padding = new string(' ', gapSpaces);
|
||||
float firstLineValueWidth = availableWidth > 0f
|
||||
? Mathf.Max(32f, availableWidth - MeasurePlainTextWidth(label) - MeasurePlainTextWidth(padding))
|
||||
: 0f;
|
||||
float continuationValueWidth = availableWidth > 0f
|
||||
? Mathf.Max(32f, availableWidth - MeasurePlainTextWidth(continuationIndent))
|
||||
: 0f;
|
||||
|
||||
List<string> wrappedLines = WrapValueLines(value, firstLineValueWidth, continuationValueWidth);
|
||||
|
||||
sb.Append("<color=").Append(LabelColorHex).Append("><b>")
|
||||
.Append(EscapeRichText(label))
|
||||
.Append("</b></color>")
|
||||
.Append(padding)
|
||||
.Append("<color=").Append(ValueColorHex).Append(">")
|
||||
.Append(EscapeRichText(wrappedLines[0]))
|
||||
.Append("</color>");
|
||||
|
||||
for (int lineIndex = 1; lineIndex < wrappedLines.Count; lineIndex++)
|
||||
{
|
||||
sb.AppendLine();
|
||||
sb.Append(continuationIndent)
|
||||
.Append("<color=").Append(ValueColorHex).Append(">")
|
||||
.Append(EscapeRichText(wrappedLines[lineIndex]))
|
||||
.Append("</color>");
|
||||
}
|
||||
|
||||
if (i < entries.Count - 1)
|
||||
{
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private void ConfigureDeviceInfoText()
|
||||
{
|
||||
if (deviceInfo_text == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
deviceInfo_text.supportRichText = true;
|
||||
deviceInfo_text.alignment = TextAnchor.UpperLeft;
|
||||
deviceInfo_text.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
deviceInfo_text.verticalOverflow = VerticalWrapMode.Overflow;
|
||||
deviceInfo_text.resizeTextForBestFit = false;
|
||||
}
|
||||
|
||||
private float GetAvailableTextWidth()
|
||||
{
|
||||
if (deviceInfo_text == null)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
RectTransform rectTransform = deviceInfo_text.rectTransform;
|
||||
if (rectTransform == null)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
float width = rectTransform.rect.width;
|
||||
return width > 0f ? width : 0f;
|
||||
}
|
||||
|
||||
private float MeasurePlainTextWidth(string text)
|
||||
{
|
||||
if (deviceInfo_text == null || string.IsNullOrEmpty(text))
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
TextGenerationSettings settings = deviceInfo_text.GetGenerationSettings(Vector2.zero);
|
||||
settings.richText = false;
|
||||
settings.generateOutOfBounds = true;
|
||||
|
||||
TextGenerator generator = new TextGenerator();
|
||||
return generator.GetPreferredWidth(text, settings) / deviceInfo_text.pixelsPerUnit;
|
||||
}
|
||||
|
||||
private List<string> WrapValueLines(string value, float firstLineWidth, float continuationLineWidth)
|
||||
{
|
||||
string normalized = NormalizeLineBreaks(value);
|
||||
if (string.IsNullOrEmpty(normalized))
|
||||
{
|
||||
return new List<string> { string.Empty };
|
||||
}
|
||||
|
||||
string[] rawLines = normalized.Split('\n');
|
||||
List<string> wrapped = new List<string>();
|
||||
for (int i = 0; i < rawLines.Length; i++)
|
||||
{
|
||||
string rawLine = rawLines[i];
|
||||
List<string> lineParts = WrapSingleLine(rawLine, wrapped.Count == 0 ? firstLineWidth : continuationLineWidth);
|
||||
if (lineParts.Count == 0)
|
||||
{
|
||||
lineParts.Add(string.Empty);
|
||||
}
|
||||
|
||||
wrapped.Add(lineParts[0]);
|
||||
for (int j = 1; j < lineParts.Count; j++)
|
||||
{
|
||||
wrapped.Add(lineParts[j]);
|
||||
}
|
||||
}
|
||||
|
||||
return wrapped.Count > 0 ? wrapped : new List<string> { string.Empty };
|
||||
}
|
||||
|
||||
private List<string> WrapSingleLine(string text, float maxWidth)
|
||||
{
|
||||
List<string> lines = new List<string>();
|
||||
if (string.IsNullOrEmpty(text) || maxWidth <= 0f)
|
||||
{
|
||||
lines.Add(text ?? string.Empty);
|
||||
return lines;
|
||||
}
|
||||
|
||||
string remaining = text;
|
||||
while (!string.IsNullOrEmpty(remaining))
|
||||
{
|
||||
if (MeasurePlainTextWidth(remaining) <= maxWidth)
|
||||
{
|
||||
lines.Add(remaining);
|
||||
break;
|
||||
}
|
||||
|
||||
int splitIndex = FindSplitIndex(remaining, maxWidth);
|
||||
if (splitIndex <= 0 || splitIndex >= remaining.Length)
|
||||
{
|
||||
lines.Add(remaining);
|
||||
break;
|
||||
}
|
||||
|
||||
string current = remaining.Substring(0, splitIndex).TrimEnd();
|
||||
if (current.Length == 0)
|
||||
{
|
||||
current = remaining.Substring(0, Mathf.Min(1, remaining.Length));
|
||||
splitIndex = current.Length;
|
||||
}
|
||||
|
||||
lines.Add(current);
|
||||
remaining = remaining.Substring(splitIndex).TrimStart();
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
private int FindSplitIndex(string text, float maxWidth)
|
||||
{
|
||||
int lastBreakableIndex = -1;
|
||||
int fallbackIndex = -1;
|
||||
|
||||
for (int i = 1; i <= text.Length; i++)
|
||||
{
|
||||
string candidate = text.Substring(0, i);
|
||||
if (MeasurePlainTextWidth(candidate) <= maxWidth)
|
||||
{
|
||||
fallbackIndex = i;
|
||||
if (IsBreakableCharacter(text[i - 1]))
|
||||
{
|
||||
lastBreakableIndex = i;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (lastBreakableIndex > 0)
|
||||
{
|
||||
return lastBreakableIndex;
|
||||
}
|
||||
|
||||
return fallbackIndex;
|
||||
}
|
||||
|
||||
private static bool IsBreakableCharacter(char c)
|
||||
{
|
||||
return char.IsWhiteSpace(c) || c == '/' || c == '\\' || c == '_' || c == '-' || c == '.' || c == ':' || c == ')' || c == ']';
|
||||
}
|
||||
|
||||
private static string NormalizeLineBreaks(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return input.Replace("\r\n", "\n").Replace('\r', '\n');
|
||||
}
|
||||
|
||||
private static string EscapeRichText(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return input.Replace("&", "&")
|
||||
.Replace("<", "<")
|
||||
.Replace(">", ">");
|
||||
}
|
||||
|
||||
string ShowSaveFileDialog()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
|
||||
@@ -238,7 +238,15 @@ public class userSettings : MonoBehaviour
|
||||
index = 0;
|
||||
}
|
||||
|
||||
LocalizationService.SetLanguage(_availableLanguageCodes[index]);
|
||||
string targetLanguageCode = _availableLanguageCodes[index];
|
||||
if (IsTemporarilyUnsupportedLanguage(targetLanguageCode))
|
||||
{
|
||||
gNotice.error.display(LocalizationService.LocalizeLiteral("暂不支持该语言"));
|
||||
SyncLanguageDropdownValue();
|
||||
return;
|
||||
}
|
||||
|
||||
LocalizationService.SetLanguage(targetLanguageCode);
|
||||
}
|
||||
|
||||
private void HandleLanguageChanged(string languageCode)
|
||||
@@ -247,6 +255,12 @@ public class userSettings : MonoBehaviour
|
||||
UpdateUserInfoDisplay();
|
||||
}
|
||||
|
||||
private static bool IsTemporarilyUnsupportedLanguage(string languageCode)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(languageCode) &&
|
||||
languageCode.StartsWith("en", System.StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private void OnClearupSaveDataClicked()
|
||||
{
|
||||
if (clearupClickCount > 0 && Time.unscaledTime > clearupExpireTime)
|
||||
|
||||
Reference in New Issue
Block a user