浮动小游戏已经基本搞好
新做了两个设置界面 一些bug修复和优化
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5d91e03c8efeb234d9459bb72c7c1b63
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!241 &24100000
|
||||||
|
AudioMixerController:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: master
|
||||||
|
m_OutputGroup: {fileID: 0}
|
||||||
|
m_MasterGroup: {fileID: 24300002}
|
||||||
|
m_Snapshots:
|
||||||
|
- {fileID: 24500006}
|
||||||
|
m_StartSnapshot: {fileID: 24500006}
|
||||||
|
m_SuspendThreshold: -80
|
||||||
|
m_EnableSuspend: 1
|
||||||
|
m_UpdateMode: 0
|
||||||
|
m_ExposedParameters: []
|
||||||
|
m_AudioMixerGroupViews:
|
||||||
|
- guids:
|
||||||
|
- 1529ad54cf74b5f4985b087ec1fd1e7f
|
||||||
|
name: View
|
||||||
|
m_CurrentViewIndex: 0
|
||||||
|
m_TargetSnapshot: {fileID: 24500006}
|
||||||
|
--- !u!243 &24300002
|
||||||
|
AudioMixerGroupController:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: Master
|
||||||
|
m_AudioMixer: {fileID: 24100000}
|
||||||
|
m_GroupID: 1529ad54cf74b5f4985b087ec1fd1e7f
|
||||||
|
m_Children: []
|
||||||
|
m_Volume: c8c70230870be5e48bb845dfafee9376
|
||||||
|
m_Pitch: 6b6b95e31a7b02a49938315306b8e45e
|
||||||
|
m_Send: 00000000000000000000000000000000
|
||||||
|
m_Effects:
|
||||||
|
- {fileID: 24400004}
|
||||||
|
m_UserColorIndex: 0
|
||||||
|
m_Mute: 0
|
||||||
|
m_Solo: 0
|
||||||
|
m_BypassEffects: 0
|
||||||
|
--- !u!244 &24400004
|
||||||
|
AudioMixerEffectController:
|
||||||
|
m_ObjectHideFlags: 3
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name:
|
||||||
|
m_EffectID: 12a08001bed866844be3ec0b9da00f48
|
||||||
|
m_EffectName: Attenuation
|
||||||
|
m_MixLevel: c7e75a4c3776e1c4b8b0a5cad989b591
|
||||||
|
m_Parameters: []
|
||||||
|
m_SendTarget: {fileID: 0}
|
||||||
|
m_EnableWetMix: 0
|
||||||
|
m_Bypass: 0
|
||||||
|
--- !u!245 &24500006
|
||||||
|
AudioMixerSnapshotController:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: Snapshot
|
||||||
|
m_AudioMixer: {fileID: 24100000}
|
||||||
|
m_SnapshotID: d04e357e5e5d8844ebe07f1d3039c651
|
||||||
|
m_FloatValues: {}
|
||||||
|
m_TransitionOverrides: {}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1ec6b149cf654494d8d97065d8f2ccac
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 24100000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -3,6 +3,9 @@ using System.IO;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
using Debug = UnityEngine.Debug;
|
using Debug = UnityEngine.Debug;
|
||||||
|
|
||||||
@@ -29,6 +32,30 @@ public class WebViewLauncher : MonoBehaviour
|
|||||||
// Event invoked when web process main window handle becomes available
|
// Event invoked when web process main window handle becomes available
|
||||||
public event Action WebViewReady;
|
public event Action WebViewReady;
|
||||||
|
|
||||||
|
[Header("Control Buttons (optional)")]
|
||||||
|
public Button refreshButton; // 刷新当前webview页面
|
||||||
|
public Button closeButton; // 关闭webview
|
||||||
|
public Button restartButton; // 重启webview
|
||||||
|
public Button backButton; // 返回
|
||||||
|
public Button forwardButton; // 前进
|
||||||
|
public Button startRestartButton; // 新增:根据运行状态启动或重启 webview2
|
||||||
|
public Button forceKillButton; // 强制结束进程按钮
|
||||||
|
public Button hideWindowButton; // 隐藏webview窗口
|
||||||
|
public Button showWindowButton; // 显示webview窗口
|
||||||
|
|
||||||
|
[Header("Autostart")]
|
||||||
|
public UnityEngine.UI.Toggle autostartToggle; // UI toggle to control auto-start
|
||||||
|
private bool autostart = true;
|
||||||
|
private const string PREF_AUTOSTART = "WebView_AutoStart";
|
||||||
|
|
||||||
|
[Header("Homepage / URL UI")]
|
||||||
|
public Button homepageButton; // 返回主页
|
||||||
|
public InputField urlInputField; // 显示/输入当前 url
|
||||||
|
[Tooltip("Homepage URL (can be file:// or http(s) or local path)")]
|
||||||
|
public string homepageUrl = "";
|
||||||
|
[Header("Go button")]
|
||||||
|
public Button goButton; // 点击跳转,和回车效果一致
|
||||||
|
|
||||||
void Awake()
|
void Awake()
|
||||||
{
|
{
|
||||||
if (Instance != null)
|
if (Instance != null)
|
||||||
@@ -38,11 +65,52 @@ public class WebViewLauncher : MonoBehaviour
|
|||||||
}
|
}
|
||||||
Instance = this;
|
Instance = this;
|
||||||
DontDestroyOnLoad(gameObject);
|
DontDestroyOnLoad(gameObject);
|
||||||
|
|
||||||
|
// wire up optional UI buttons
|
||||||
|
if (refreshButton != null) refreshButton.onClick.AddListener(RefreshWebView);
|
||||||
|
if (closeButton != null) closeButton.onClick.AddListener(CloseWebView);
|
||||||
|
if (restartButton != null) restartButton.onClick.AddListener(RestartWebView);
|
||||||
|
if (backButton != null) backButton.onClick.AddListener(GoBackWebView);
|
||||||
|
if (forwardButton != null) forwardButton.onClick.AddListener(GoForwardWebView);
|
||||||
|
if (startRestartButton != null) startRestartButton.onClick.AddListener(OnStartRestartButtonClicked);
|
||||||
|
if (forceKillButton != null) forceKillButton.onClick.AddListener(OnForceKillClicked);
|
||||||
|
if (hideWindowButton != null) hideWindowButton.onClick.AddListener(OnHideWindowClicked);
|
||||||
|
if (showWindowButton != null) showWindowButton.onClick.AddListener(OnShowWindowClicked);
|
||||||
|
|
||||||
|
if (homepageButton != null) homepageButton.onClick.AddListener(OnHomepageClicked);
|
||||||
|
if (urlInputField != null) urlInputField.onEndEdit.AddListener(OnUrlInputEndEdit);
|
||||||
|
if (goButton != null) goButton.onClick.AddListener(OnGoButtonClicked);
|
||||||
|
|
||||||
|
// Load autostart preference and bind toggle
|
||||||
|
autostart = PlayerPrefs.GetInt(PREF_AUTOSTART, 1) == 1;
|
||||||
|
if (autostartToggle != null)
|
||||||
|
{
|
||||||
|
autostartToggle.isOn = autostart;
|
||||||
|
autostartToggle.onValueChanged.AddListener(OnAutostartToggleChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set default homepage if empty
|
||||||
|
if (string.IsNullOrEmpty(homepageUrl))
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
string candidate = Path.Combine(h5EditorPath, "_officialDocs", "openings", "index.html");
|
||||||
|
if (File.Exists(candidate))
|
||||||
|
homepageUrl = new Uri(candidate).AbsoluteUri;
|
||||||
|
else
|
||||||
|
homepageUrl = startUrl;
|
||||||
|
#else
|
||||||
|
string candidate = Path.Combine(Application.streamingAssetsPath, "h5LG", "_officialDocs", "openings", "index.html");
|
||||||
|
if (File.Exists(candidate))
|
||||||
|
homepageUrl = new Uri(candidate).AbsoluteUri;
|
||||||
|
else
|
||||||
|
homepageUrl = startUrl;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Start()
|
void Start()
|
||||||
{
|
{
|
||||||
// 扫描 H5 文件夹
|
// Ensure H5 root exists
|
||||||
string h5Folder = GetH5FolderPath();
|
string h5Folder = GetH5FolderPath();
|
||||||
if (!Directory.Exists(h5Folder))
|
if (!Directory.Exists(h5Folder))
|
||||||
{
|
{
|
||||||
@@ -50,12 +118,28 @@ public class WebViewLauncher : MonoBehaviour
|
|||||||
Debug.Log("Created H5 folder: " + h5Folder);
|
Debug.Log("Created H5 folder: " + h5Folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prefer explicit openings index as startup page when available
|
||||||
|
string preferred = Path.Combine(h5Folder, "_officialDocs", "openings", "index.html");
|
||||||
|
if (File.Exists(preferred))
|
||||||
|
{
|
||||||
|
startUrl = new Uri(preferred).AbsoluteUri;
|
||||||
|
Debug.Log("Using preferred startup page: " + startUrl);
|
||||||
|
// show initial url in input field
|
||||||
|
if (urlInputField != null)
|
||||||
|
urlInputField.text = GetDisplayUrl(startUrl);
|
||||||
|
|
||||||
|
if (autostart) LaunchWebView();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扫描 H5 文件夹
|
||||||
var h5SubFolders = ScanH5Folders(h5Folder);
|
var h5SubFolders = ScanH5Folders(h5Folder);
|
||||||
if (h5SubFolders.Count > 0)
|
if (h5SubFolders.Count > 0)
|
||||||
{
|
{
|
||||||
Debug.Log("Found H5 folders: " + string.Join(", ", h5SubFolders));
|
Debug.Log("Found H5 folders: " + string.Join(", ", h5SubFolders));
|
||||||
string selectedFolder = h5SubFolders[0]; // 选择第一个
|
string selectedFolder = h5SubFolders[0]; // 选择第一个
|
||||||
startUrl = "file://" + Path.Combine(h5Folder, selectedFolder, "index.html");
|
string candidatePath = Path.Combine(h5Folder, selectedFolder, "index.html");
|
||||||
|
startUrl = new Uri(candidatePath).AbsoluteUri; // ensure proper file:/// URI
|
||||||
Debug.Log("Selected H5 folder: " + selectedFolder + ", URL: " + startUrl);
|
Debug.Log("Selected H5 folder: " + selectedFolder + ", URL: " + startUrl);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -63,19 +147,87 @@ public class WebViewLauncher : MonoBehaviour
|
|||||||
Debug.LogWarning("No H5 subfolders with index.html found in: " + h5Folder);
|
Debug.LogWarning("No H5 subfolders with index.html found in: " + h5Folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchWebView();
|
// show initial url in input field if available (use shortened display for local files that are indexed)
|
||||||
|
if (urlInputField != null)
|
||||||
|
{
|
||||||
|
urlInputField.text = GetDisplayUrl(startUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (autostart) LaunchWebView();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnDestroy()
|
void OnDestroy()
|
||||||
{
|
{
|
||||||
KillWebView();
|
KillWebView();
|
||||||
|
|
||||||
|
// remove listeners
|
||||||
|
if (refreshButton != null) refreshButton.onClick.RemoveListener(RefreshWebView);
|
||||||
|
if (closeButton != null) closeButton.onClick.RemoveListener(CloseWebView);
|
||||||
|
if (restartButton != null) restartButton.onClick.RemoveListener(RestartWebView);
|
||||||
|
if (backButton != null) backButton.onClick.RemoveListener(GoBackWebView);
|
||||||
|
if (forwardButton != null) forwardButton.onClick.RemoveListener(GoForwardWebView);
|
||||||
|
if (startRestartButton != null) startRestartButton.onClick.RemoveListener(OnStartRestartButtonClicked);
|
||||||
|
if (forceKillButton != null) forceKillButton.onClick.RemoveListener(OnForceKillClicked);
|
||||||
|
if (hideWindowButton != null) hideWindowButton.onClick.RemoveListener(OnHideWindowClicked);
|
||||||
|
if (showWindowButton != null) showWindowButton.onClick.RemoveListener(OnShowWindowClicked);
|
||||||
|
if (autostartToggle != null) autostartToggle.onValueChanged.RemoveListener(OnAutostartToggleChanged);
|
||||||
|
|
||||||
|
if (homepageButton != null) homepageButton.onClick.RemoveListener(OnHomepageClicked);
|
||||||
|
if (urlInputField != null) urlInputField.onEndEdit.RemoveListener(OnUrlInputEndEdit);
|
||||||
|
if (goButton != null) goButton.onClick.RemoveListener(OnGoButtonClicked);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
// if input field focused and Enter pressed -> navigate
|
||||||
|
if (urlInputField != null && urlInputField.isFocused && (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter)))
|
||||||
|
{
|
||||||
|
string text = urlInputField.text;
|
||||||
|
if (!string.IsNullOrEmpty(text))
|
||||||
|
{
|
||||||
|
OpenUrl(text);
|
||||||
|
// remove focus
|
||||||
|
EventSystem.current?.SetSelectedGameObject(null);
|
||||||
|
urlInputField.DeactivateInputField();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clicking elsewhere should unfocus input field (end edit)
|
||||||
|
if (Input.GetMouseButtonDown(0) && urlInputField != null && urlInputField.isFocused)
|
||||||
|
{
|
||||||
|
var sel = EventSystem.current != null ? EventSystem.current.currentSelectedGameObject : null;
|
||||||
|
if (sel != urlInputField.gameObject)
|
||||||
|
{
|
||||||
|
urlInputField.DeactivateInputField();
|
||||||
|
EventSystem.current?.SetSelectedGameObject(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnGoButtonClicked()
|
||||||
|
{
|
||||||
|
if (urlInputField == null) return;
|
||||||
|
string text = urlInputField.text;
|
||||||
|
if (!string.IsNullOrEmpty(text))
|
||||||
|
{
|
||||||
|
OpenUrl(text);
|
||||||
|
EventSystem.current?.SetSelectedGameObject(null);
|
||||||
|
urlInputField.DeactivateInputField();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void KillWebView()
|
public void KillWebView()
|
||||||
{
|
{
|
||||||
if (webProcess != null && !webProcess.HasExited)
|
if (webProcess != null && !webProcess.HasExited)
|
||||||
{
|
{
|
||||||
webProcess.Kill();
|
try
|
||||||
|
{
|
||||||
|
webProcess.Kill();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("KillWebView failed: " + e.Message);
|
||||||
|
}
|
||||||
webProcess = null;
|
webProcess = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,10 +321,24 @@ public class WebViewLauncher : MonoBehaviour
|
|||||||
string final;
|
string final;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Uri.TryCreate(url, UriKind.Absolute, out var parsed) && !string.IsNullOrEmpty(parsed.Scheme))
|
if (IsLikelyWebUrl(url))
|
||||||
{
|
{
|
||||||
// If it's already a file/http/https URI, use its AbsoluteUri
|
if (!Uri.TryCreate(url, UriKind.Absolute, out var parsed) || string.IsNullOrEmpty(parsed.Scheme))
|
||||||
final = parsed.AbsoluteUri;
|
{
|
||||||
|
final = "http://" + url;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final = parsed.AbsoluteUri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (url.StartsWith("localhost::"))
|
||||||
|
{
|
||||||
|
// Map localhost::display back to actual file URI
|
||||||
|
string relativePath = url.Substring("localhost::".Length).Replace('/', Path.DirectorySeparatorChar);
|
||||||
|
string h5Folder = GetH5FolderPath();
|
||||||
|
string fullPath = Path.Combine(h5Folder, relativePath);
|
||||||
|
final = new Uri(fullPath).AbsoluteUri;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -190,6 +356,10 @@ public class WebViewLauncher : MonoBehaviour
|
|||||||
Debug.Log($"OpenUrl requested. original='{url}', normalized='{final}'");
|
Debug.Log($"OpenUrl requested. original='{url}', normalized='{final}'");
|
||||||
startUrl = final;
|
startUrl = final;
|
||||||
|
|
||||||
|
// update input field display
|
||||||
|
if (urlInputField != null)
|
||||||
|
urlInputField.text = GetDisplayUrl(final);
|
||||||
|
|
||||||
// if process not running, launch; otherwise try to send url to existing window
|
// if process not running, launch; otherwise try to send url to existing window
|
||||||
if (webProcess == null || webProcess.HasExited)
|
if (webProcess == null || webProcess.HasExited)
|
||||||
{
|
{
|
||||||
@@ -222,6 +392,160 @@ public class WebViewLauncher : MonoBehaviour
|
|||||||
// after relaunch, WaitForMainWindowHandle will invoke WebViewReady when handle ready
|
// after relaunch, WaitForMainWindowHandle will invoke WebViewReady when handle ready
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to send a simple command string to the webview process via WM_COPYDATA. Returns true when SendMessage returned non-zero.
|
||||||
|
bool TrySendCommand(string cmd)
|
||||||
|
{
|
||||||
|
if (webProcess == null || webProcess.HasExited)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("TrySendCommand: webProcess not running");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sent = WebViewWin32.TrySendUrlToWindow(webProcess, cmd);
|
||||||
|
Debug.Log($"TrySendCommand('{cmd}') returned: {sent}");
|
||||||
|
return sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI control methods
|
||||||
|
public void RefreshWebView()
|
||||||
|
{
|
||||||
|
// Try sending a refresh command; fallback to restarting to refresh
|
||||||
|
if (TrySendCommand("CMD:REFRESH"))
|
||||||
|
{
|
||||||
|
Debug.Log("Sent REFRESH command to webview");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log("REFRESH command not supported, restarting webview to refresh");
|
||||||
|
KillWebView();
|
||||||
|
LaunchWebView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CloseWebView()
|
||||||
|
{
|
||||||
|
Debug.Log("Closing webview process");
|
||||||
|
KillWebView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RestartWebView()
|
||||||
|
{
|
||||||
|
Debug.Log("Restarting webview process");
|
||||||
|
KillWebView();
|
||||||
|
LaunchWebView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GoBackWebView()
|
||||||
|
{
|
||||||
|
if (TrySendCommand("CMD:BACK"))
|
||||||
|
{
|
||||||
|
Debug.Log("Sent BACK command to webview");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Debug.LogWarning("BACK command not supported by webview");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GoForwardWebView()
|
||||||
|
{
|
||||||
|
if (TrySendCommand("CMD:FORWARD"))
|
||||||
|
{
|
||||||
|
Debug.Log("Sent FORWARD command to webview");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Debug.LogWarning("FORWARD command not supported by webview");
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnHomepageClicked()
|
||||||
|
{
|
||||||
|
// Always navigate to StreamingAssets/h5LG/_officialDocs/openings/index.html
|
||||||
|
string candidate = Path.Combine(Application.streamingAssetsPath, "h5LG", "_officialDocs", "openings", "index.html");
|
||||||
|
if (!File.Exists(candidate))
|
||||||
|
{
|
||||||
|
Debug.LogWarning("Homepage index not found at: " + candidate + ". Falling back to homepageUrl or startUrl.");
|
||||||
|
if (!string.IsNullOrEmpty(homepageUrl))
|
||||||
|
{
|
||||||
|
LaunchWebViewWithUrl(homepageUrl);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LaunchWebViewWithUrl(startUrl);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string fileUri = new Uri(candidate).AbsoluteUri;
|
||||||
|
LaunchWebViewWithUrl(fileUri);
|
||||||
|
|
||||||
|
// OpenUrl will update the input field using GetDisplayUrl(final). Ensure display shows the shortened localhost form.
|
||||||
|
if (urlInputField != null)
|
||||||
|
urlInputField.text = GetDisplayUrl(fileUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnUrlInputEndEdit(string text)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(text)) return;
|
||||||
|
OpenUrl(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnStartRestartButtonClicked()
|
||||||
|
{
|
||||||
|
if (webProcess == null || webProcess.HasExited)
|
||||||
|
{
|
||||||
|
Debug.Log("WebView process not running. Starting webview.");
|
||||||
|
StartWebView();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.Log("WebView process running. Restarting webview.");
|
||||||
|
RestartWebView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnForceKillClicked()
|
||||||
|
{
|
||||||
|
Debug.Log("Force killing webview process");
|
||||||
|
KillWebView();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnHideWindowClicked()
|
||||||
|
{
|
||||||
|
if (webProcess == null || webProcess.HasExited)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("HideWindow: webview process not running");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool ok = WebViewWin32.TryShowWindow(webProcess, false);
|
||||||
|
Debug.Log("HideWindow clicked, result: " + ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnShowWindowClicked()
|
||||||
|
{
|
||||||
|
if (webProcess == null || webProcess.HasExited)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("ShowWindow: webview process not running");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool ok = WebViewWin32.TryShowWindow(webProcess, true);
|
||||||
|
Debug.Log("ShowWindow clicked, result: " + ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartWebView()
|
||||||
|
{
|
||||||
|
LaunchWebView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopWebView()
|
||||||
|
{
|
||||||
|
KillWebView();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnAutostartToggleChanged(bool value)
|
||||||
|
{
|
||||||
|
autostart = value;
|
||||||
|
PlayerPrefs.SetInt(PREF_AUTOSTART, autostart ? 1 : 0);
|
||||||
|
PlayerPrefs.Save();
|
||||||
|
Debug.Log("Autostart toggled: " + autostart);
|
||||||
|
}
|
||||||
|
|
||||||
string GetExePath()
|
string GetExePath()
|
||||||
{
|
{
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
@@ -298,4 +622,113 @@ public class WebViewLauncher : MonoBehaviour
|
|||||||
|
|
||||||
return WebViewWin32.TrySetWindowRect(webProcess, x, y, w, h);
|
return WebViewWin32.TrySetWindowRect(webProcess, x, y, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string GetDisplayUrl(string url)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(url))
|
||||||
|
return url;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Uri uri = new Uri(url);
|
||||||
|
if (uri.Scheme == Uri.UriSchemeFile)
|
||||||
|
{
|
||||||
|
string localPath = uri.LocalPath;
|
||||||
|
string h5Folder = GetH5FolderPath();
|
||||||
|
if (localPath.StartsWith(h5Folder, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
string relativePath = localPath.Substring(h5Folder.Length).TrimStart(Path.DirectorySeparatorChar);
|
||||||
|
return $"localhost::{relativePath.Replace(Path.DirectorySeparatorChar, '/')}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("GetDisplayUrl: failed to parse url '" + url + "' : " + e.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsLikelyWebUrl(string url)
|
||||||
|
{
|
||||||
|
return Regex.IsMatch(url, @"^(https?:\/\/|www\.|[a-zA-Z0-9-]+\.[a-zA-Z]{2,})");
|
||||||
|
}
|
||||||
|
|
||||||
|
string NormalizeToAbsolute(string url)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(url))
|
||||||
|
{
|
||||||
|
Debug.LogWarning("NormalizeToAbsolute called with empty url");
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Uri.TryCreate(url, UriKind.Absolute, out var parsed) && parsed.IsAbsoluteUri)
|
||||||
|
{
|
||||||
|
return parsed.AbsoluteUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.StartsWith("localhost::"))
|
||||||
|
{
|
||||||
|
// Map localhost::display back to actual file URI
|
||||||
|
string relativePath = url.Substring("localhost::".Length).Replace('/', Path.DirectorySeparatorChar);
|
||||||
|
string h5Folder = GetH5FolderPath();
|
||||||
|
string fullPath = Path.Combine(h5Folder, relativePath);
|
||||||
|
return new Uri(fullPath).AbsoluteUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Treat as local file path
|
||||||
|
string abs = Path.GetFullPath(url);
|
||||||
|
return new Uri(abs).AbsoluteUri; // yields file:///C:/...
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("NormalizeToAbsolute: failed to normalize url '" + url + "' : " + e.Message);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LaunchWebViewWithUrl(string url)
|
||||||
|
{
|
||||||
|
string finalUrl = NormalizeToAbsolute(url);
|
||||||
|
Debug.Log($"LaunchWebViewWithUrl requested. original='{url}', normalized='{finalUrl}'");
|
||||||
|
startUrl = finalUrl;
|
||||||
|
|
||||||
|
// update input field display
|
||||||
|
if (urlInputField != null)
|
||||||
|
urlInputField.text = GetDisplayUrl(finalUrl);
|
||||||
|
|
||||||
|
// if process not running, launch; otherwise try to send url to existing window
|
||||||
|
if (webProcess == null || webProcess.HasExited)
|
||||||
|
{
|
||||||
|
Debug.Log("WebView process not running. Launching with URL: " + finalUrl);
|
||||||
|
LaunchWebView();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Debug.Log($"WebView process running. PID={webProcess.Id}, MainWindowHandle=0x{webProcess.MainWindowHandle.ToInt64():X}");
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
// 尝试通过 WM_COPYDATA 将 URL 发送给已有进程窗口(需要 fdBrowser 实现接收处理)
|
||||||
|
bool sent = WebViewWin32.TrySendUrlToWindow(webProcess, finalUrl);
|
||||||
|
Debug.Log($"TrySendUrlToWindow returned: {sent}");
|
||||||
|
if (sent)
|
||||||
|
{
|
||||||
|
Debug.Log("Sent URL to existing WebView process: " + finalUrl);
|
||||||
|
// Notify followers that webview content may have changed and they should reapply positioning
|
||||||
|
WebViewReady?.Invoke();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送失败,退回到重启进程打开 URL 的方式
|
||||||
|
Debug.Log("Failed to send URL to existing process, restarting to open: " + finalUrl);
|
||||||
|
KillWebView();
|
||||||
|
LaunchWebView();
|
||||||
|
// after relaunch, WaitForMainWindowHandle will invoke WebViewReady when handle ready
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ public static class WebViewWin32
|
|||||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||||
private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
|
private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct RECT
|
public struct RECT
|
||||||
{
|
{
|
||||||
@@ -45,10 +48,26 @@ public static class WebViewWin32
|
|||||||
public IntPtr lpData;
|
public IntPtr lpData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetWindowPos flags
|
||||||
const uint SWP_NOZORDER = 0x0004;
|
const uint SWP_NOZORDER = 0x0004;
|
||||||
const uint SWP_NOACTIVATE = 0x0010;
|
const uint SWP_NOACTIVATE = 0x0010;
|
||||||
|
const uint SWP_NOMOVE = 0x0002;
|
||||||
|
const uint SWP_NOSIZE = 0x0001;
|
||||||
|
|
||||||
|
// HWND insert-after special values
|
||||||
|
static readonly IntPtr HWND_TOP = new IntPtr(0);
|
||||||
|
static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
|
||||||
|
static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
|
||||||
|
static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
|
||||||
|
|
||||||
const uint WM_COPYDATA = 0x004A;
|
const uint WM_COPYDATA = 0x004A;
|
||||||
|
|
||||||
|
// ShowWindow commands
|
||||||
|
const int SW_HIDE = 0;
|
||||||
|
const int SW_SHOW = 5;
|
||||||
|
const int SW_MINIMIZE = 6;
|
||||||
|
const int SW_RESTORE = 9;
|
||||||
|
|
||||||
public static bool TrySetWindowRect(Process process, int x, int y, int width, int height)
|
public static bool TrySetWindowRect(Process process, int x, int y, int width, int height)
|
||||||
{
|
{
|
||||||
if (process == null || process.HasExited)
|
if (process == null || process.HasExited)
|
||||||
@@ -74,6 +93,44 @@ public static class WebViewWin32
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Place the given process window directly above the target process window in Z-order
|
||||||
|
/// and set its position/size. This ensures the webview sits just above the Unity window.
|
||||||
|
/// </summary>
|
||||||
|
public static bool TrySetWindowRectAboveProcess(Process process, Process targetProcess, int x, int y, int width, int height)
|
||||||
|
{
|
||||||
|
if (process == null || process.HasExited || targetProcess == null || targetProcess.HasExited)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (process.MainWindowHandle == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
process.Refresh();
|
||||||
|
if (process.MainWindowHandle == IntPtr.Zero)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetProcess.MainWindowHandle == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
targetProcess.Refresh();
|
||||||
|
if (targetProcess.MainWindowHandle == IntPtr.Zero)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place process window after targetProcess window in Z-order (i.e. on top of target)
|
||||||
|
// Do not use SWP_NOZORDER so that z-order is changed.
|
||||||
|
bool ok = SetWindowPos(
|
||||||
|
process.MainWindowHandle,
|
||||||
|
targetProcess.MainWindowHandle,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
SWP_NOACTIVATE
|
||||||
|
);
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
public static bool TryGetWindowRect(Process process, out RECT rect)
|
public static bool TryGetWindowRect(Process process, out RECT rect)
|
||||||
{
|
{
|
||||||
rect = new RECT();
|
rect = new RECT();
|
||||||
@@ -114,6 +171,48 @@ public static class WebViewWin32
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Try to show or hide the given process main window.
|
||||||
|
/// </summary>
|
||||||
|
public static bool TryShowWindow(Process process, bool show)
|
||||||
|
{
|
||||||
|
if (process == null || process.HasExited)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (process.MainWindowHandle == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
process.Refresh();
|
||||||
|
if (process.MainWindowHandle == IntPtr.Zero)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!show)
|
||||||
|
{
|
||||||
|
// Minimize instead of hide
|
||||||
|
return ShowWindow(process.MainWindowHandle, SW_MINIMIZE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Restore and bring to top
|
||||||
|
bool r = ShowWindow(process.MainWindowHandle, SW_RESTORE);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// bring to top without making it topmost
|
||||||
|
SetWindowPos(process.MainWindowHandle, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogWarning("TryShowWindow failed: " + e.Message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 尝试向指定进程的主窗口发送 WM_COPYDATA 消息,lParam 为包含 Unicode 字符串的 COPYDATASTRUCT。
|
/// 尝试向指定进程的主窗口发送 WM_COPYDATA 消息,lParam 为包含 Unicode 字符串的 COPYDATASTRUCT。
|
||||||
/// 接收方需要处理 WM_COPYDATA 并解释字符串为 URL 并导航。
|
/// 接收方需要处理 WM_COPYDATA 并解释字符串为 URL 并导航。
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using UnityEngine;
|
|||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
using UnityEngine.EventSystems;
|
using UnityEngine.EventSystems;
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
public class game_card_Prefab : MonoBehaviour, IPointerClickHandler
|
public class game_card_Prefab : MonoBehaviour, IPointerClickHandler
|
||||||
{
|
{
|
||||||
@@ -9,6 +10,7 @@ public class game_card_Prefab : MonoBehaviour, IPointerClickHandler
|
|||||||
public Image card_icon_image;
|
public Image card_icon_image;
|
||||||
public Text card_name_text;
|
public Text card_name_text;
|
||||||
public Text authorName_text;
|
public Text authorName_text;
|
||||||
|
public Text versionCode_text;
|
||||||
public Text description_text;
|
public Text description_text;
|
||||||
public Text _isOfficial_text;
|
public Text _isOfficial_text;
|
||||||
public Text source_text;
|
public Text source_text;
|
||||||
@@ -21,7 +23,7 @@ public class game_card_Prefab : MonoBehaviour, IPointerClickHandler
|
|||||||
private string indexFilePath;
|
private string indexFilePath;
|
||||||
|
|
||||||
// 给 loadLittleGamesPrefab 调用以填充文本和路径
|
// 给 loadLittleGamesPrefab 调用以填充文本和路径
|
||||||
public void SetData(string indexPath, string cardName, string authorName, string description, string isOfficial, string source, string yyyyMmDd)
|
public void SetData(string indexPath, string cardName, string authorName, string description, string isOfficial, string source, string yyyyMmDd, string version = null)
|
||||||
{
|
{
|
||||||
indexFilePath = indexPath;
|
indexFilePath = indexPath;
|
||||||
if (card_name_text != null) card_name_text.text = cardName ?? "";
|
if (card_name_text != null) card_name_text.text = cardName ?? "";
|
||||||
@@ -31,7 +33,11 @@ public class game_card_Prefab : MonoBehaviour, IPointerClickHandler
|
|||||||
if (source_text != null) source_text.text = source ?? "";
|
if (source_text != null) source_text.text = source ?? "";
|
||||||
if (yyyy_mm_dd != null) yyyy_mm_dd.text = yyyyMmDd ?? "";
|
if (yyyy_mm_dd != null) yyyy_mm_dd.text = yyyyMmDd ?? "";
|
||||||
|
|
||||||
UnityEngine.Debug.Log($"game_card_Prefab.SetData: indexPath='{indexFilePath}', card_name='{cardName}', author='{authorName}', isOfficial='{isOfficial}', source='{source}'");
|
// version
|
||||||
|
string ver = string.IsNullOrEmpty(version) ? "0.0.0" : version;
|
||||||
|
if (versionCode_text != null) versionCode_text.text = ver;
|
||||||
|
|
||||||
|
UnityEngine.Debug.Log($"game_card_Prefab.SetData: indexPath='{indexFilePath}', card_name='{cardName}', author='{authorName}', isOfficial='{isOfficial}', source='{source}', version='{ver}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Awake()
|
void Awake()
|
||||||
@@ -110,6 +116,21 @@ public class game_card_Prefab : MonoBehaviour, IPointerClickHandler
|
|||||||
{
|
{
|
||||||
UnityEngine.Debug.Log("Calling WebViewLauncher.OpenUrl with: " + indexFilePath);
|
UnityEngine.Debug.Log("Calling WebViewLauncher.OpenUrl with: " + indexFilePath);
|
||||||
WebViewLauncher.Instance.OpenUrl(indexFilePath);
|
WebViewLauncher.Instance.OpenUrl(indexFilePath);
|
||||||
|
|
||||||
|
// After initiating navigation, set the input field display to localhost::parent/index.html
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (WebViewLauncher.Instance != null && WebViewLauncher.Instance.urlInputField != null)
|
||||||
|
{
|
||||||
|
string parent = Path.GetFileName(Path.GetDirectoryName(indexFilePath));
|
||||||
|
string display = $"localhost::{parent}/{Path.GetFileName(indexFilePath)}";
|
||||||
|
WebViewLauncher.Instance.urlInputField.text = display;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogWarning("Failed to set url input display: " + e.Message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -120,6 +141,20 @@ public class game_card_Prefab : MonoBehaviour, IPointerClickHandler
|
|||||||
{
|
{
|
||||||
UnityEngine.Debug.Log("Found launcher in scene, calling OpenUrl: " + indexFilePath);
|
UnityEngine.Debug.Log("Found launcher in scene, calling OpenUrl: " + indexFilePath);
|
||||||
launcherObj.OpenUrl(indexFilePath);
|
launcherObj.OpenUrl(indexFilePath);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (launcherObj.urlInputField != null)
|
||||||
|
{
|
||||||
|
string parent = Path.GetFileName(Path.GetDirectoryName(indexFilePath));
|
||||||
|
string display = $"localhost::{parent}/{Path.GetFileName(indexFilePath)}";
|
||||||
|
launcherObj.urlInputField.text = display;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogWarning("Failed to set url input display: " + e.Message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -129,6 +164,20 @@ public class game_card_Prefab : MonoBehaviour, IPointerClickHandler
|
|||||||
var launcher = go.AddComponent<WebViewLauncher>();
|
var launcher = go.AddComponent<WebViewLauncher>();
|
||||||
launcher.OpenUrl(indexFilePath);
|
launcher.OpenUrl(indexFilePath);
|
||||||
GameObject.DontDestroyOnLoad(go);
|
GameObject.DontDestroyOnLoad(go);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (launcher.urlInputField != null)
|
||||||
|
{
|
||||||
|
string parent = Path.GetFileName(Path.GetDirectoryName(indexFilePath));
|
||||||
|
string display = $"localhost::{parent}/{Path.GetFileName(indexFilePath)}";
|
||||||
|
launcher.urlInputField.text = display;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogWarning("Failed to set url input display: " + e.Message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ RectTransform:
|
|||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||||
m_AnchoredPosition: {x: 10.717, y: 14.07}
|
m_AnchoredPosition: {x: -12.6, y: 14.07}
|
||||||
m_SizeDelta: {x: 252.359, y: 30}
|
m_SizeDelta: {x: 204.359, y: 30}
|
||||||
m_Pivot: {x: 0.5, y: 0.5}
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
--- !u!222 &7480212223569318349
|
--- !u!222 &7480212223569318349
|
||||||
CanvasRenderer:
|
CanvasRenderer:
|
||||||
@@ -316,6 +316,85 @@ MonoBehaviour:
|
|||||||
m_FillOrigin: 0
|
m_FillOrigin: 0
|
||||||
m_UseSpriteMesh: 0
|
m_UseSpriteMesh: 0
|
||||||
m_PixelsPerUnitMultiplier: 1
|
m_PixelsPerUnitMultiplier: 1
|
||||||
|
--- !u!1 &3596489648732047302
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 6607639485689222063}
|
||||||
|
- component: {fileID: 552011013437464851}
|
||||||
|
- component: {fileID: 6022216317311205539}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: version_code
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!224 &6607639485689222063
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 3596489648732047302}
|
||||||
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 3566709735925945696}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||||
|
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||||
|
m_AnchoredPosition: {x: 105.7, y: 15.6}
|
||||||
|
m_SizeDelta: {x: 47.359, y: 30}
|
||||||
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
|
--- !u!222 &552011013437464851
|
||||||
|
CanvasRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 3596489648732047302}
|
||||||
|
m_CullTransparentMesh: 1
|
||||||
|
--- !u!114 &6022216317311205539
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 3596489648732047302}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
|
||||||
|
m_RaycastTarget: 1
|
||||||
|
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
m_Maskable: 1
|
||||||
|
m_OnCullStateChanged:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls: []
|
||||||
|
m_FontData:
|
||||||
|
m_Font: {fileID: 12800000, guid: cc180dff846d13a4d88ddaed6f77e5cd, type: 3}
|
||||||
|
m_FontSize: 10
|
||||||
|
m_FontStyle: 0
|
||||||
|
m_BestFit: 0
|
||||||
|
m_MinSize: 1
|
||||||
|
m_MaxSize: 40
|
||||||
|
m_Alignment: 8
|
||||||
|
m_AlignByGeometry: 0
|
||||||
|
m_RichText: 1
|
||||||
|
m_HorizontalOverflow: 0
|
||||||
|
m_VerticalOverflow: 0
|
||||||
|
m_LineSpacing: 1
|
||||||
|
m_Text: 1.0.1
|
||||||
--- !u!1 &5110857998175197173
|
--- !u!1 &5110857998175197173
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -429,6 +508,7 @@ RectTransform:
|
|||||||
- {fileID: 4583698199465520998}
|
- {fileID: 4583698199465520998}
|
||||||
- {fileID: 604392950021451895}
|
- {fileID: 604392950021451895}
|
||||||
- {fileID: 872492842950718926}
|
- {fileID: 872492842950718926}
|
||||||
|
- {fileID: 6607639485689222063}
|
||||||
- {fileID: 2019306417107768352}
|
- {fileID: 2019306417107768352}
|
||||||
- {fileID: 2622440068852620012}
|
- {fileID: 2622440068852620012}
|
||||||
- {fileID: 2138782682665805939}
|
- {fileID: 2138782682665805939}
|
||||||
@@ -653,6 +733,7 @@ MonoBehaviour:
|
|||||||
card_icon_image: {fileID: 6004099248657222439}
|
card_icon_image: {fileID: 6004099248657222439}
|
||||||
card_name_text: {fileID: 3389633104739560455}
|
card_name_text: {fileID: 3389633104739560455}
|
||||||
authorName_text: {fileID: 9193153990832272737}
|
authorName_text: {fileID: 9193153990832272737}
|
||||||
|
versionCode_text: {fileID: 6022216317311205539}
|
||||||
description_text: {fileID: 8118505406259206449}
|
description_text: {fileID: 8118505406259206449}
|
||||||
_isOfficial_text: {fileID: 5166008895911254744}
|
_isOfficial_text: {fileID: 5166008895911254744}
|
||||||
source_text: {fileID: 4780041332280513338}
|
source_text: {fileID: 4780041332280513338}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
public class loadLittleGamesPrefab : MonoBehaviour
|
public class loadLittleGamesPrefab : MonoBehaviour
|
||||||
{
|
{
|
||||||
@@ -12,44 +14,66 @@ public class loadLittleGamesPrefab : MonoBehaviour
|
|||||||
|
|
||||||
[Header("Prefab")]
|
[Header("Prefab")]
|
||||||
public GameObject littleGames_card_Prefab;
|
public GameObject littleGames_card_Prefab;
|
||||||
|
[Tooltip("默认icon图")]
|
||||||
|
public Sprite default_card_icon_sprite;
|
||||||
|
|
||||||
|
[Header("Refresh")]
|
||||||
|
public Button refreshButton; // 点击触发重新扫描并刷新左侧列表
|
||||||
|
|
||||||
[Header("H5 Root Path (optional, leave empty to use WebViewLauncher settings)")]
|
[Header("H5 Root Path (optional, leave empty to use WebViewLauncher settings)")]
|
||||||
public string h5RootPath;
|
public string h5RootPath;
|
||||||
|
|
||||||
|
void Awake()
|
||||||
|
{
|
||||||
|
// attach refresh listener if button provided
|
||||||
|
if (refreshButton != null)
|
||||||
|
{
|
||||||
|
refreshButton.onClick.AddListener(RefreshList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDestroy()
|
||||||
|
{
|
||||||
|
if (refreshButton != null)
|
||||||
|
refreshButton.onClick.RemoveListener(RefreshList);
|
||||||
|
}
|
||||||
|
|
||||||
void Start()
|
void Start()
|
||||||
{
|
{
|
||||||
LoadAllLittleGames();
|
LoadAllLittleGames();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// Clear children of a content transform
|
||||||
/// 在 WebViewLauncher 的 h5 目录下扫描 three categories 的所有子文件夹(递归一层),
|
void ClearContent(GameObject content)
|
||||||
/// 找到含有 index.html 的目录(例如: ...\h5LG\_thirdPartyGames\kjb\index.html),
|
{
|
||||||
/// 读取同目录下的 info.xml(如果存在),并把 prefab 实例化到对应的 content 中。
|
if (content == null) return;
|
||||||
///
|
for (int i = content.transform.childCount - 1; i >= 0; i--)
|
||||||
/// info.xml 示例格式(放在同目录下,编码 UTF-8):
|
{
|
||||||
/// <?xml version="1.0" encoding="utf-8"?>
|
var child = content.transform.GetChild(i).gameObject;
|
||||||
/// <Info>
|
Destroy(child);
|
||||||
/// <card_name>示例游戏名</card_name>
|
}
|
||||||
/// <authorName>作者名</authorName>
|
}
|
||||||
/// <description>这里是描述文本</description>
|
|
||||||
/// <isOfficial>True</isOfficial>
|
// Public method to allow external callers to refresh
|
||||||
/// <source>来源信息</source>
|
public void RefreshList()
|
||||||
/// <yyyy_mm_dd>2025-12-23</yyyy_mm_dd>
|
{
|
||||||
/// </Info>
|
// clear all three contents then reload
|
||||||
///
|
ClearContent(officialDocs_scrollView_content);
|
||||||
/// 请保证节点名如上所示以便能被正确读取。
|
ClearContent(officialGames_scrollView_content);
|
||||||
/// </summary>
|
ClearContent(thirdPartyGames_scrollView_content);
|
||||||
|
LoadAllLittleGames();
|
||||||
|
}
|
||||||
|
|
||||||
public void LoadAllLittleGames()
|
public void LoadAllLittleGames()
|
||||||
{
|
{
|
||||||
string root = GetH5RootPath();
|
string root = ResolveH5RootPath();
|
||||||
if (string.IsNullOrEmpty(root) || !Directory.Exists(root))
|
if (string.IsNullOrEmpty(root) || !Directory.Exists(root))
|
||||||
{
|
{
|
||||||
Debug.LogWarning("H5 root path not found: " + root);
|
Debug.LogWarning("H5 root path not found: " + root);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 目标一级子目录名称(与 WebViewLauncher 中保持一致)
|
string[] categories = new string[] { "_officialDocs", "_officialGames", "_thirdPartyGames" };
|
||||||
string[] categories = { "_officialDocs", "_officialGames", "_thirdPartyGames" };
|
|
||||||
|
|
||||||
foreach (string cat in categories)
|
foreach (string cat in categories)
|
||||||
{
|
{
|
||||||
@@ -57,95 +81,148 @@ public class loadLittleGamesPrefab : MonoBehaviour
|
|||||||
if (!Directory.Exists(catPath))
|
if (!Directory.Exists(catPath))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// 遍历 catPath 下的第一层子目录,检查是否含有 index.html
|
|
||||||
string[] subdirs = Directory.GetDirectories(catPath);
|
string[] subdirs = Directory.GetDirectories(catPath);
|
||||||
foreach (string subdir in subdirs)
|
foreach (string subdir in subdirs)
|
||||||
{
|
{
|
||||||
string indexPath = Path.Combine(subdir, "index.html");
|
string indexHtml = Path.Combine(subdir, "index.html");
|
||||||
if (!File.Exists(indexPath))
|
if (!File.Exists(indexHtml))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// read xml if exists
|
||||||
|
string xmlPath = Path.Combine(subdir, "info.xml");
|
||||||
|
Dictionary<string, string> meta = null;
|
||||||
|
if (File.Exists(xmlPath))
|
||||||
|
meta = ReadInfoXml(xmlPath);
|
||||||
|
|
||||||
|
// target content
|
||||||
|
GameObject target = GetTargetContent(cat);
|
||||||
|
if (target == null || littleGames_card_Prefab == null)
|
||||||
{
|
{
|
||||||
// 也可能 index.html 在更深一层,但按照约定通常是 subdir/index.html
|
Debug.LogWarning("Missing target content or prefab for category: " + cat);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取 info.xml(可选)
|
GameObject item = Instantiate(littleGames_card_Prefab, target.transform);
|
||||||
string infoXmlPath = Path.Combine(subdir, "info.xml");
|
var card = item.GetComponent<game_card_Prefab>();
|
||||||
var meta = ReadInfoXml(infoXmlPath);
|
if (card == null)
|
||||||
|
|
||||||
// 根据分类选择目标 content
|
|
||||||
GameObject targetContent = GetContentByCategory(cat);
|
|
||||||
if (targetContent == null || littleGames_card_Prefab == null)
|
|
||||||
{
|
|
||||||
Debug.LogWarning("Missing prefab or content for category: " + cat);
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// defaults
|
||||||
|
string defName = "未知Html文件";
|
||||||
|
string defAuthor = "未知作者";
|
||||||
|
string defDesc = "暂无相关描述信息";
|
||||||
|
string defIsOfficial = "未知来源";
|
||||||
|
string defSource = "自定义渠道";
|
||||||
|
string defDateRaw = "19700101";
|
||||||
|
|
||||||
|
string cardName = GetMeta(meta, "card_name") ?? defName;
|
||||||
|
string author = GetMeta(meta, "authorName") ?? defAuthor;
|
||||||
|
string desc = GetMeta(meta, "description") ?? defDesc;
|
||||||
|
string isOfficial = GetMeta(meta, "isOfficial");
|
||||||
|
string source = GetMeta(meta, "source");
|
||||||
|
string dateRaw = GetMeta(meta, "yyyy_mm_dd");
|
||||||
|
string cardIcon = GetMeta(meta, "card_icon");
|
||||||
|
string versionCode = GetMeta(meta, "versionCode");
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(dateRaw)) dateRaw = defDateRaw;
|
||||||
|
|
||||||
|
if (cat == "_officialDocs" || cat == "_officialGames")
|
||||||
|
{
|
||||||
|
isOfficial = "官方";
|
||||||
|
source = "小游戏DLC";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(isOfficial)) isOfficial = "第三方来源";
|
||||||
|
if (string.IsNullOrEmpty(source)) source = defSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 实例化 prefab
|
// ensure no nulls
|
||||||
GameObject go = Instantiate(littleGames_card_Prefab, targetContent.transform);
|
if (string.IsNullOrEmpty(cardName)) cardName = defName;
|
||||||
|
if (string.IsNullOrEmpty(author)) author = defAuthor;
|
||||||
|
if (string.IsNullOrEmpty(desc)) desc = defDesc;
|
||||||
|
if (string.IsNullOrEmpty(isOfficial)) isOfficial = defIsOfficial;
|
||||||
|
if (string.IsNullOrEmpty(source)) source = defSource;
|
||||||
|
|
||||||
// 设置数据(indexPath 使用本地文件绝对路径)
|
string formattedDate = FormatDate(dateRaw);
|
||||||
string absIndexPath = indexPath; // 使用绝对路径,WebViewLauncher.OpenUrl 会添加 file://
|
|
||||||
var prefabComp = go.GetComponent<game_card_Prefab>();
|
card.SetData(indexHtml, cardName, author, desc, isOfficial, source, formattedDate, versionCode);
|
||||||
if (prefabComp != null)
|
|
||||||
|
// Set default icon first (if provided)
|
||||||
|
if (card.card_icon_image != null && default_card_icon_sprite != null)
|
||||||
{
|
{
|
||||||
// 若来自官方分类,覆盖 isOfficial 与 source 字段为固定值
|
card.card_icon_image.sprite = default_card_icon_sprite;
|
||||||
string finalIsOfficial = meta.GetValueOrDefault("isOfficial");
|
card.card_icon_image.enabled = true;
|
||||||
string finalSource = meta.GetValueOrDefault("source");
|
}
|
||||||
|
|
||||||
if (cat == "_officialDocs" || cat == "_officialGames")
|
// load icon if specified (override default)
|
||||||
|
if (!string.IsNullOrEmpty(cardIcon))
|
||||||
|
{
|
||||||
|
string iconPath = cardIcon;
|
||||||
|
if (!Path.IsPathRooted(iconPath))
|
||||||
|
iconPath = Path.Combine(subdir, iconPath);
|
||||||
|
|
||||||
|
if (File.Exists(iconPath))
|
||||||
{
|
{
|
||||||
finalIsOfficial = "官方";
|
try
|
||||||
finalSource = "小游戏DLC";
|
{
|
||||||
|
byte[] bytes = File.ReadAllBytes(iconPath);
|
||||||
|
Texture2D tex = new Texture2D(2, 2);
|
||||||
|
if (tex.LoadImage(bytes))
|
||||||
|
{
|
||||||
|
Sprite sp = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f));
|
||||||
|
if (card.card_icon_image != null)
|
||||||
|
{
|
||||||
|
card.card_icon_image.sprite = sp;
|
||||||
|
card.card_icon_image.enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogWarning("Failed to LoadImage for card_icon: " + iconPath + ". Using default sprite if available.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (System.Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("Failed to load card_icon: " + e.Message + ". Using default sprite if available.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 非官方分类统一标记为第三方来源
|
Debug.LogWarning("card_icon not found: " + iconPath + ". Using default sprite if available.");
|
||||||
finalIsOfficial = "第三方来源";
|
|
||||||
// 若 info.xml 没有 source,则可以保留原值或设置默认,这里若无则为空
|
|
||||||
if (string.IsNullOrEmpty(finalSource))
|
|
||||||
finalSource = "";
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// 格式化日期为 yyyy\nmm\ndd 的形式
|
// if no cardIcon specified and no default was assigned earlier, ensure image disabled
|
||||||
string rawDate = meta.GetValueOrDefault("yyyy_mm_dd");
|
else
|
||||||
string formattedDate = FormatDateWithNewlines(rawDate);
|
{
|
||||||
|
if (card.card_icon_image != null && card.card_icon_image.sprite == null)
|
||||||
prefabComp.SetData(absIndexPath,
|
card.card_icon_image.enabled = false;
|
||||||
meta.GetValueOrDefault("card_name"),
|
|
||||||
meta.GetValueOrDefault("authorName"),
|
|
||||||
meta.GetValueOrDefault("description"),
|
|
||||||
finalIsOfficial,
|
|
||||||
finalSource,
|
|
||||||
formattedDate
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string GetH5RootPath()
|
string ResolveH5RootPath()
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(h5RootPath) && Directory.Exists(h5RootPath))
|
if (!string.IsNullOrEmpty(h5RootPath) && Directory.Exists(h5RootPath))
|
||||||
return h5RootPath;
|
return h5RootPath;
|
||||||
|
|
||||||
// 从 WebViewLauncher 获取编辑器路径
|
var launcher = GameObject.FindObjectOfType<WebViewLauncher>();
|
||||||
var w = GameObject.FindObjectOfType<WebViewLauncher>();
|
if (launcher != null)
|
||||||
if (w != null)
|
|
||||||
{
|
{
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
return w.h5EditorPath;
|
return launcher.h5EditorPath;
|
||||||
#else
|
#else
|
||||||
return Path.Combine(Application.streamingAssetsPath, "h5LG");
|
return Path.Combine(Application.streamingAssetsPath, "h5LG");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// 作为回退,使用 StreamingAssets/h5LG
|
|
||||||
return Path.Combine(Application.streamingAssetsPath, "h5LG");
|
return Path.Combine(Application.streamingAssetsPath, "h5LG");
|
||||||
}
|
}
|
||||||
|
|
||||||
GameObject GetContentByCategory(string categoryName)
|
GameObject GetTargetContent(string category)
|
||||||
{
|
{
|
||||||
switch (categoryName)
|
switch (category)
|
||||||
{
|
{
|
||||||
case "_officialDocs": return officialDocs_scrollView_content;
|
case "_officialDocs": return officialDocs_scrollView_content;
|
||||||
case "_officialGames": return officialGames_scrollView_content;
|
case "_officialGames": return officialGames_scrollView_content;
|
||||||
@@ -162,54 +239,43 @@ public class loadLittleGamesPrefab : MonoBehaviour
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var doc = new XmlDocument();
|
XmlDocument doc = new XmlDocument();
|
||||||
doc.Load(xmlPath);
|
doc.Load(xmlPath);
|
||||||
var root = doc.DocumentElement;
|
XmlElement root = doc.DocumentElement;
|
||||||
if (root == null)
|
if (root == null) return dict;
|
||||||
return dict;
|
|
||||||
|
|
||||||
// 读取预定义字段
|
string[] keys = new string[] { "card_name", "authorName", "description", "isOfficial", "source", "yyyy_mm_dd", "card_icon", "versionCode" };
|
||||||
string[] keys = { "card_name", "authorName", "description", "isOfficial", "source", "yyyy_mm_dd" };
|
foreach (string k in keys)
|
||||||
foreach (var k in keys)
|
|
||||||
{
|
{
|
||||||
var node = root.SelectSingleNode(k);
|
XmlNode node = root.SelectSingleNode(k);
|
||||||
if (node != null)
|
if (node != null) dict[k] = node.InnerText;
|
||||||
dict[k] = node.InnerText;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (System.Exception e)
|
catch (System.Exception ex)
|
||||||
{
|
{
|
||||||
Debug.LogWarning("Failed to read info.xml: " + xmlPath + " error: " + e.Message);
|
Debug.LogWarning("ReadInfoXml failed: " + ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return dict;
|
return dict;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将日期文本尝试解析成 yyyy\nmm\ndd 格式,若解析失败则做简单替换(支持 2025-12-23, 2025/12/23, 2025_12_23, 20251223 等)
|
string GetMeta(Dictionary<string, string> meta, string key)
|
||||||
string FormatDateWithNewlines(string raw)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(raw))
|
if (meta == null) return null;
|
||||||
return "";
|
string v;
|
||||||
|
if (meta.TryGetValue(key, out v)) return v;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// 尝试按常见分隔符拆分
|
string FormatDate(string raw)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(raw)) return "";
|
||||||
char[] seps = new char[] { '-', '/', '_', ' ' };
|
char[] seps = new char[] { '-', '/', '_', ' ' };
|
||||||
var parts = raw.Split(seps, System.StringSplitOptions.RemoveEmptyEntries);
|
var parts = raw.Split(seps, System.StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (parts.Length == 3)
|
if (parts.Length == 3) return parts[0] + "\n" + parts[1] + "\n" + parts[2];
|
||||||
{
|
|
||||||
return parts[0] + "\n" + parts[1] + "\n" + parts[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试连续数字 8 位
|
string digits = Regex.Replace(raw, "\\D", "");
|
||||||
var digits = System.Text.RegularExpressions.Regex.Replace(raw, "\\D", "");
|
if (digits.Length == 8) return digits.Substring(0, 4) + "\n" + digits.Substring(4, 2) + "\n" + digits.Substring(6, 2);
|
||||||
if (digits.Length == 8)
|
|
||||||
{
|
|
||||||
string y = digits.Substring(0, 4);
|
|
||||||
string m = digits.Substring(4, 2);
|
|
||||||
string d = digits.Substring(6, 2);
|
|
||||||
return y + "\n" + m + "\n" + d;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 回退:尝试用第一个 4 位作为年,接下来的两位为月,之后为日
|
|
||||||
if (digits.Length >= 6)
|
if (digits.Length >= 6)
|
||||||
{
|
{
|
||||||
string y = digits.Substring(0, 4);
|
string y = digits.Substring(0, 4);
|
||||||
@@ -218,13 +284,8 @@ public class loadLittleGamesPrefab : MonoBehaviour
|
|||||||
return y + "\n" + m + "\n" + d;
|
return y + "\n" + m + "\n" + d;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 最后回退直接把原文本按字符换行(有限长度)
|
|
||||||
return raw.Replace("-", "\n").Replace("/", "\n").Replace("_", "\n");
|
return raw.Replace("-", "\n").Replace("/", "\n").Replace("_", "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update is called once per frame
|
void Update() { }
|
||||||
void Update()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
public class audioMerger : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("sv object")]
|
||||||
|
public GameObject sv_audioSettings;
|
||||||
|
[Header("所有声音给我站一边")]
|
||||||
|
[Tooltip("因为静音键我要出现")]
|
||||||
|
public Toggle enableGlobalMute_toggle;
|
||||||
|
[Header("一堆slider")]
|
||||||
|
public Slider mainVolumn_slider;
|
||||||
|
public Slider noteHit_slider;
|
||||||
|
public Slider musicInGame_slider;
|
||||||
|
public Slider musicOutGame_slider;
|
||||||
|
public Slider skillEffect_slider;
|
||||||
|
public Slider uiInteration_slider;
|
||||||
|
public Slider cvVolumn_slider;
|
||||||
|
[Header("配套的一堆text")]
|
||||||
|
public Text mainVolumn_text;
|
||||||
|
public Text noteHit_text;
|
||||||
|
public Text musicInGame_text;
|
||||||
|
public Text musicOutGame_text;
|
||||||
|
public Text skillEffect_text;
|
||||||
|
public Text uiInteration_text;
|
||||||
|
public Text cvVolumn_text;
|
||||||
|
|
||||||
|
// PlayerPrefs key for storing the toggle state
|
||||||
|
const string PlayerPrefKey_EnableGlobalMute = "EnableGlobalMute";
|
||||||
|
|
||||||
|
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
// Load saved state (default: false)
|
||||||
|
bool isOn = PlayerPrefs.GetInt(PlayerPrefKey_EnableGlobalMute, 0) == 1;
|
||||||
|
|
||||||
|
// Apply to toggle (if available) and sv_audioSettings
|
||||||
|
if (enableGlobalMute_toggle != null)
|
||||||
|
{
|
||||||
|
// Prevent accidentally invoking listeners while initializing
|
||||||
|
enableGlobalMute_toggle.onValueChanged.RemoveAllListeners();
|
||||||
|
enableGlobalMute_toggle.isOn = isOn;
|
||||||
|
enableGlobalMute_toggle.onValueChanged.AddListener(OnEnableGlobalMuteChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sv_audioSettings != null)
|
||||||
|
{
|
||||||
|
// 非模式:当 toggle 为开启 (isOn == true) 时,禁用 sv_audioSettings
|
||||||
|
sv_audioSettings.SetActive(!isOn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDestroy()
|
||||||
|
{
|
||||||
|
if (enableGlobalMute_toggle != null)
|
||||||
|
{
|
||||||
|
enableGlobalMute_toggle.onValueChanged.RemoveListener(OnEnableGlobalMuteChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listener called when the toggle value changes
|
||||||
|
void OnEnableGlobalMuteChanged(bool isOn)
|
||||||
|
{
|
||||||
|
// Save to PlayerPrefs
|
||||||
|
PlayerPrefs.SetInt(PlayerPrefKey_EnableGlobalMute, isOn ? 1 : 0);
|
||||||
|
PlayerPrefs.Save();
|
||||||
|
|
||||||
|
// 应用非模式:当 toggle 为开启时,禁用 sv_audioSettings
|
||||||
|
if (sv_audioSettings != null)
|
||||||
|
{
|
||||||
|
sv_audioSettings.SetActive(!isOn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update is called once per frame
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2ee999443e0788e4ba1f60dec644e601
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
public class readDeviceInfo : MonoBehaviour
|
||||||
|
{
|
||||||
|
public Button reread_button;
|
||||||
|
public Button export_button;
|
||||||
|
public Text deviceInfo_text;
|
||||||
|
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
if (reread_button != null)
|
||||||
|
{
|
||||||
|
reread_button.onClick.AddListener(read_deviceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (export_button != null)
|
||||||
|
{
|
||||||
|
export_button.onClick.AddListener(ExportToFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deviceInfo_text != null)
|
||||||
|
deviceInfo_text.supportRichText = true;
|
||||||
|
|
||||||
|
read_deviceInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDestroy()
|
||||||
|
{
|
||||||
|
if (reread_button != null)
|
||||||
|
reread_button.onClick.RemoveListener(read_deviceInfo);
|
||||||
|
if (export_button != null)
|
||||||
|
export_button.onClick.RemoveListener(ExportToFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void read_deviceInfo()
|
||||||
|
{
|
||||||
|
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
||||||
|
|
||||||
|
void AddEntry(string title, object value)
|
||||||
|
{
|
||||||
|
string str;
|
||||||
|
if (value == null) str = "";
|
||||||
|
else if (value is bool b) str = b ? "启用" : "禁用";
|
||||||
|
else str = value.ToString();
|
||||||
|
|
||||||
|
sb.AppendLine($"<b>{title}</b>");
|
||||||
|
sb.AppendLine(str);
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
AddEntry("平台", Application.platform.ToString());
|
||||||
|
AddEntry("运行目录", Application.dataPath);
|
||||||
|
AddEntry("persistent 数据路径", Application.persistentDataPath);
|
||||||
|
|
||||||
|
AddEntry("操作系统", SystemInfo.operatingSystem);
|
||||||
|
AddEntry("设备名称", SystemInfo.deviceName);
|
||||||
|
AddEntry("设备型号", SystemInfo.deviceModel);
|
||||||
|
AddEntry("设备唯一ID", SystemInfo.deviceUniqueIdentifier);
|
||||||
|
|
||||||
|
AddEntry("CPU", SystemInfo.processorType);
|
||||||
|
AddEntry("CPU核心数", SystemInfo.processorCount);
|
||||||
|
AddEntry("系统内存 (MB)", SystemInfo.systemMemorySize);
|
||||||
|
|
||||||
|
AddEntry("图形设备名称", SystemInfo.graphicsDeviceName);
|
||||||
|
AddEntry("图形设备供应商", SystemInfo.graphicsDeviceVendor);
|
||||||
|
AddEntry("DirectX", SystemInfo.graphicsDeviceType.ToString());
|
||||||
|
AddEntry("显存 (MB)", SystemInfo.graphicsMemorySize);
|
||||||
|
|
||||||
|
AddEntry("屏幕分辨率", $"{Screen.currentResolution.width} x {Screen.currentResolution.height} @ {Screen.currentResolution.refreshRate}Hz");
|
||||||
|
AddEntry("屏幕DPI", Screen.dpi);
|
||||||
|
AddEntry("显示模式", Screen.fullScreenMode.ToString());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AddEntry("电池电量", $"{SystemInfo.batteryLevel * 100:F0}%");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
AddEntry("电池状态", "不支持");
|
||||||
|
}
|
||||||
|
|
||||||
|
AddEntry("浮动·本索尼克谱面编辑器", "v1.1h02e");
|
||||||
|
AddEntry("浮动·浮动小游戏引擎", "v1.301");
|
||||||
|
AddEntry("本游戏版本号", "code::testingVersion1062");
|
||||||
|
AddEntry("登录渠道", "Steam Client");
|
||||||
|
|
||||||
|
AddEntry(".NET Framework", "8.0 +");
|
||||||
|
AddEntry("最大纹理尺寸", SystemInfo.maxTextureSize);
|
||||||
|
|
||||||
|
AddEntry("禁用后台运行", Application.runInBackground);
|
||||||
|
AddEntry("多线程渲染", SystemInfo.graphicsMultiThreaded);
|
||||||
|
|
||||||
|
string output = sb.ToString();
|
||||||
|
|
||||||
|
if (deviceInfo_text != null)
|
||||||
|
{
|
||||||
|
deviceInfo_text.text = output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExportToFile()
|
||||||
|
{
|
||||||
|
if (deviceInfo_text == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string path = ShowSaveFileDialog();
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string raw = deviceInfo_text.text;
|
||||||
|
// Remove <b> tags and add colon after title
|
||||||
|
string cleaned = Regex.Replace(raw, "<b>(.*?)</b>", "$1:");
|
||||||
|
File.WriteAllText(path, cleaned);
|
||||||
|
}
|
||||||
|
catch (System.Exception)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string ShowSaveFileDialog()
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
||||||
|
string path = UnityEditor.EditorUtility.SaveFilePanel("保存设备信息", desktop, "device_info", "txt");
|
||||||
|
return string.IsNullOrEmpty(path) ? null : path;
|
||||||
|
#elif UNITY_STANDALONE_WIN
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use native Windows SaveFile dialog via comdlg32 GetSaveFileName
|
||||||
|
OpenFileName ofn = new OpenFileName();
|
||||||
|
ofn.structSize = Marshal.SizeOf(ofn);
|
||||||
|
ofn.filter = "Text files (*.txt)\0*.txt\0All files (*.*)\0*.*\0";
|
||||||
|
ofn.file = new string('\0', 260);
|
||||||
|
ofn.maxFile = ofn.file.Length;
|
||||||
|
ofn.fileTitle = new string('\0', 260);
|
||||||
|
ofn.maxFileTitle = ofn.fileTitle.Length;
|
||||||
|
ofn.initialDir = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
||||||
|
ofn.title = "保存设备信息";
|
||||||
|
ofn.defExt = "txt";
|
||||||
|
ofn.flags = OpenFileNameFlags.OFN_OVERWRITEPROMPT;
|
||||||
|
|
||||||
|
if (GetSaveFileName(ofn))
|
||||||
|
{
|
||||||
|
// Trim any trailing nulls
|
||||||
|
return ofn.file.TrimEnd('\0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
#else
|
||||||
|
string defaultPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "device_info.txt");
|
||||||
|
return defaultPath;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if UNITY_STANDALONE_WIN
|
||||||
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||||
|
private class OpenFileName
|
||||||
|
{
|
||||||
|
public int structSize = 0;
|
||||||
|
public IntPtr dlgOwner = IntPtr.Zero;
|
||||||
|
public IntPtr instance = IntPtr.Zero;
|
||||||
|
public string filter = null;
|
||||||
|
public string customFilter = null;
|
||||||
|
public int maxCustFilter = 0;
|
||||||
|
public int filterIndex = 0;
|
||||||
|
public string file = null;
|
||||||
|
public int maxFile = 0;
|
||||||
|
public string fileTitle = null;
|
||||||
|
public int maxFileTitle = 0;
|
||||||
|
public string initialDir = null;
|
||||||
|
public string title = null;
|
||||||
|
public int flags = 0;
|
||||||
|
public short fileOffset = 0;
|
||||||
|
public short fileExtension = 0;
|
||||||
|
public string defExt = null;
|
||||||
|
public IntPtr custData = IntPtr.Zero;
|
||||||
|
public IntPtr hook = IntPtr.Zero;
|
||||||
|
public string templateName = null;
|
||||||
|
public IntPtr reservedPtr = IntPtr.Zero;
|
||||||
|
public int reservedInt = 0;
|
||||||
|
public int flagsEx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class OpenFileNameFlags
|
||||||
|
{
|
||||||
|
public const int OFN_OVERWRITEPROMPT = 0x00000002;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("comdlg32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||||
|
private static extern bool GetSaveFileName([In, Out] OpenFileName ofn);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9af26f04de2ee934ab267f3448336566
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class selectSettingsOptions : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("用户")]
|
||||||
|
public Button userSettingsButton;
|
||||||
|
public GameObject userSettingsGameObject;
|
||||||
|
public Image userSettings_selectedMark;
|
||||||
|
|
||||||
|
[Header("画面设置")]
|
||||||
|
public Button graphicSettingsButton;
|
||||||
|
public GameObject graphicSettingsGameObject;
|
||||||
|
public Image graphicSettings_selectedMark;
|
||||||
|
|
||||||
|
[Header("声音设置")]
|
||||||
|
public Button audioSettingsButton;
|
||||||
|
public GameObject audioSettingsGameObject;
|
||||||
|
public Image audioSettings_selectedMark;
|
||||||
|
|
||||||
|
[Header("操作设置")]
|
||||||
|
public Button controlSettingsButton;
|
||||||
|
public GameObject controlSettingsGameObject;
|
||||||
|
public Image controlSettings_selectedMark;
|
||||||
|
|
||||||
|
[Header("游戏设置")]
|
||||||
|
public Button gameSettingsButton;
|
||||||
|
public GameObject gameSettingsGameObject;
|
||||||
|
public Image gameSettings_selectedMark;
|
||||||
|
|
||||||
|
[Header("关于")]
|
||||||
|
public Button aboutUsButton;
|
||||||
|
public GameObject aboutUsGameObject;
|
||||||
|
public Image aboutUs_selectedMark;
|
||||||
|
|
||||||
|
[Header("设备信息")]
|
||||||
|
public Button deviceInfoButton;
|
||||||
|
public GameObject deviceInfoGameObject;
|
||||||
|
public Image deviceInfo_selectedMark;
|
||||||
|
|
||||||
|
[Header("Fade Settings")]
|
||||||
|
[Tooltip("时间(秒)")] public float fadeDuration = 0.25f;
|
||||||
|
[Tooltip("Hover alpha when mouse over (0-1)")] public float hoverAlpha = 0.75f;
|
||||||
|
[Tooltip("Alpha when selected (常亮)")] public float selectedAlpha = 1f;
|
||||||
|
|
||||||
|
// internal state
|
||||||
|
private class OptionEntry
|
||||||
|
{
|
||||||
|
public Button button;
|
||||||
|
public GameObject panel;
|
||||||
|
public Image mark;
|
||||||
|
public Coroutine fadeCoroutine;
|
||||||
|
public bool selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<OptionEntry> options = new List<OptionEntry>();
|
||||||
|
private OptionEntry currentSelected = null;
|
||||||
|
|
||||||
|
void Awake()
|
||||||
|
{
|
||||||
|
options.Clear();
|
||||||
|
AddOption(userSettingsButton, userSettingsGameObject, userSettings_selectedMark);
|
||||||
|
AddOption(graphicSettingsButton, graphicSettingsGameObject, graphicSettings_selectedMark);
|
||||||
|
AddOption(audioSettingsButton, audioSettingsGameObject, audioSettings_selectedMark);
|
||||||
|
AddOption(controlSettingsButton, controlSettingsGameObject, controlSettings_selectedMark);
|
||||||
|
AddOption(gameSettingsButton, gameSettingsGameObject, gameSettings_selectedMark);
|
||||||
|
AddOption(aboutUsButton, aboutUsGameObject, aboutUs_selectedMark);
|
||||||
|
AddOption(deviceInfoButton, deviceInfoGameObject, deviceInfo_selectedMark);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
// default select userSettings
|
||||||
|
if (userSettingsButton != null)
|
||||||
|
SelectOptionByButton(userSettingsButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDestroy()
|
||||||
|
{
|
||||||
|
foreach (var opt in options)
|
||||||
|
{
|
||||||
|
if (opt.button != null)
|
||||||
|
opt.button.onClick.RemoveAllListeners();
|
||||||
|
RemoveEventTrigger(opt.button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddOption(Button btn, GameObject panel, Image mark)
|
||||||
|
{
|
||||||
|
var entry = new OptionEntry { button = btn, panel = panel, mark = mark, selected = false, fadeCoroutine = null };
|
||||||
|
options.Add(entry);
|
||||||
|
|
||||||
|
if (btn != null)
|
||||||
|
{
|
||||||
|
btn.onClick.AddListener(() => OnOptionClicked(entry));
|
||||||
|
EnsureEventTrigger(btn, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mark != null)
|
||||||
|
{
|
||||||
|
var c = mark.color;
|
||||||
|
c.a = 0f;
|
||||||
|
mark.color = c;
|
||||||
|
mark.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panel != null)
|
||||||
|
panel.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnsureEventTrigger(Button btn, OptionEntry entry)
|
||||||
|
{
|
||||||
|
if (btn == null) return;
|
||||||
|
EventTrigger trig = btn.GetComponent<EventTrigger>();
|
||||||
|
if (trig == null) trig = btn.gameObject.AddComponent<EventTrigger>();
|
||||||
|
|
||||||
|
// pointer enter
|
||||||
|
var entryEnter = new EventTrigger.Entry { eventID = EventTriggerType.PointerEnter };
|
||||||
|
entryEnter.callback.AddListener((data) => { OnOptionPointerEnter(entry); });
|
||||||
|
trig.triggers.Add(entryEnter);
|
||||||
|
|
||||||
|
// pointer exit
|
||||||
|
var entryExit = new EventTrigger.Entry { eventID = EventTriggerType.PointerExit };
|
||||||
|
entryExit.callback.AddListener((data) => { OnOptionPointerExit(entry); });
|
||||||
|
trig.triggers.Add(entryExit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoveEventTrigger(Button btn)
|
||||||
|
{
|
||||||
|
if (btn == null) return;
|
||||||
|
var trig = btn.GetComponent<EventTrigger>();
|
||||||
|
if (trig != null) Destroy(trig);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnOptionPointerEnter(OptionEntry entry)
|
||||||
|
{
|
||||||
|
if (entry == null) return;
|
||||||
|
if (entry.selected) return;
|
||||||
|
StartFade(entry, hoverAlpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnOptionPointerExit(OptionEntry entry)
|
||||||
|
{
|
||||||
|
if (entry == null) return;
|
||||||
|
if (entry.selected) return;
|
||||||
|
StartFade(entry, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnOptionClicked(OptionEntry entry)
|
||||||
|
{
|
||||||
|
if (entry == null) return;
|
||||||
|
SelectEntry(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SelectOptionByButton(Button btn)
|
||||||
|
{
|
||||||
|
var entry = options.Find(o => o.button == btn);
|
||||||
|
if (entry != null) SelectEntry(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SelectEntry(OptionEntry entry)
|
||||||
|
{
|
||||||
|
if (entry == null) return;
|
||||||
|
|
||||||
|
if (currentSelected != null && currentSelected != entry)
|
||||||
|
{
|
||||||
|
currentSelected.selected = false;
|
||||||
|
StartFade(currentSelected, 0f);
|
||||||
|
if (currentSelected.panel != null) currentSelected.panel.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSelected = entry;
|
||||||
|
currentSelected.selected = true;
|
||||||
|
if (currentSelected.panel != null) currentSelected.panel.SetActive(true);
|
||||||
|
StartFade(currentSelected, selectedAlpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartFade(OptionEntry entry, float targetAlpha)
|
||||||
|
{
|
||||||
|
if (entry == null || entry.mark == null) return;
|
||||||
|
entry.mark.enabled = true;
|
||||||
|
if (entry.fadeCoroutine != null) StopCoroutine(entry.fadeCoroutine);
|
||||||
|
entry.fadeCoroutine = StartCoroutine(FadeImage(entry.mark, targetAlpha, fadeDuration, () =>
|
||||||
|
{
|
||||||
|
if (Mathf.Approximately(targetAlpha, 0f))
|
||||||
|
{
|
||||||
|
if (!entry.selected)
|
||||||
|
entry.mark.enabled = false;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator FadeImage(Image img, float targetAlpha, float duration, System.Action onComplete = null)
|
||||||
|
{
|
||||||
|
if (img == null) yield break;
|
||||||
|
float start = img.color.a;
|
||||||
|
float t = 0f;
|
||||||
|
while (t < duration)
|
||||||
|
{
|
||||||
|
t += Time.unscaledDeltaTime;
|
||||||
|
float a = Mathf.Lerp(start, targetAlpha, duration <= 0f ? 1f : (t / duration));
|
||||||
|
var c = img.color;
|
||||||
|
c.a = a;
|
||||||
|
img.color = c;
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
var fc = img.color;
|
||||||
|
fc.a = targetAlpha;
|
||||||
|
img.color = fc;
|
||||||
|
onComplete?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e27c16de0caf79944b0631760576ae31
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
public class userSettings : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("登陆渠道")]
|
||||||
|
[Tooltip("渠道")]
|
||||||
|
public Text user_login_source_text;
|
||||||
|
[Tooltip("渠道ID")]
|
||||||
|
public Text user_source_uid_text;
|
||||||
|
|
||||||
|
[Header("语言设置")]
|
||||||
|
[Tooltip("当前语言")]
|
||||||
|
public Dropdown language_dropdown;
|
||||||
|
|
||||||
|
[Header("存档管理")]
|
||||||
|
[Tooltip("导出存档")]
|
||||||
|
public Button export_saveData_button;
|
||||||
|
[Tooltip("导入存档")]
|
||||||
|
public Button import_saveData_button;
|
||||||
|
[Tooltip("清理存档")]
|
||||||
|
public Button clearup_saveData_button;
|
||||||
|
|
||||||
|
[Header("查看详细高光")]
|
||||||
|
[Tooltip("查看按钮")]
|
||||||
|
public Button view_detailedHighlight_button;
|
||||||
|
|
||||||
|
// 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: 0ff926d5dac4bae45a8e098937f5ae52
|
||||||
@@ -718,7 +718,7 @@ PlayerSettings:
|
|||||||
gcIncremental: 1
|
gcIncremental: 1
|
||||||
gcWBarrierValidation: 0
|
gcWBarrierValidation: 0
|
||||||
apiCompatibilityLevelPerPlatform: {}
|
apiCompatibilityLevelPerPlatform: {}
|
||||||
editorAssembliesCompatibilityLevel: 1
|
editorAssembliesCompatibilityLevel: 2
|
||||||
m_RenderingPath: 1
|
m_RenderingPath: 1
|
||||||
m_MobileRenderingPath: 1
|
m_MobileRenderingPath: 1
|
||||||
metroPackageName: EaseOut_Main_FreshNew
|
metroPackageName: EaseOut_Main_FreshNew
|
||||||
@@ -795,7 +795,7 @@ PlayerSettings:
|
|||||||
hmiCpuConfiguration:
|
hmiCpuConfiguration:
|
||||||
hmiLogStartupTiming: 0
|
hmiLogStartupTiming: 0
|
||||||
qnxGraphicConfPath:
|
qnxGraphicConfPath:
|
||||||
apiCompatibilityLevel: 6
|
apiCompatibilityLevel: 3
|
||||||
captureStartupLogs: {}
|
captureStartupLogs: {}
|
||||||
activeInputHandler: 2
|
activeInputHandler: 2
|
||||||
windowsGamepadBackendHint: 0
|
windowsGamepadBackendHint: 0
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"description" : "AutoLaunch Protocols Preregistration",
|
||||||
|
"name" : "Protocol Preregistration",
|
||||||
|
"version" : "1.0.0.10"
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
{
|
||||||
|
"allow": [
|
||||||
|
{
|
||||||
|
"origins": [
|
||||||
|
"https://.get.microsoft.com",
|
||||||
|
"https://.apps.microsoft.com"
|
||||||
|
],
|
||||||
|
"protocol": "ms-windows-store"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"origins": [
|
||||||
|
"https://.onedrive.com",
|
||||||
|
"https://.onedrive.live.com",
|
||||||
|
"https://sharepoint.com"
|
||||||
|
],
|
||||||
|
"protocol": "ms-word"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"origins": [
|
||||||
|
"https://[a-z1-9-]*word-edit.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*word-view.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*onenote.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*eap.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*shared.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*afhs.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*vhs.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*optin.online.office.com"
|
||||||
|
],
|
||||||
|
"use_regex": true,
|
||||||
|
"protocol": "ms-word"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"origins": [
|
||||||
|
"https://.onedrive.com",
|
||||||
|
"https://.onedrive.live.com",
|
||||||
|
"https://sharepoint.com"
|
||||||
|
],
|
||||||
|
"protocol": "ms-excel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"origins": [
|
||||||
|
"https://[a-z1-9-]*excel.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*onenote.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*eap.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*shared.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*afhs.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*vhs.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*optin.online.office.com"
|
||||||
|
],
|
||||||
|
"use_regex": true,
|
||||||
|
"protocol": "ms-excel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"origins": [
|
||||||
|
"https://.onedrive.com",
|
||||||
|
"https://.onedrive.live.com",
|
||||||
|
"https://sharepoint.com"
|
||||||
|
],
|
||||||
|
"protocol": "ms-powerpoint"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"origins": [
|
||||||
|
"https://[a-z1-9-]*powerpoint.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*onenote.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*eap.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*shared.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*afhs.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*vhs.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*optin.online.office.com"
|
||||||
|
],
|
||||||
|
"use_regex": true,
|
||||||
|
"protocol": "ms-powerpoint"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"origins": [
|
||||||
|
"https://.onedrive.com",
|
||||||
|
"https://.onedrive.live.com",
|
||||||
|
"https://sharepoint.com"
|
||||||
|
],
|
||||||
|
"protocol": "ms-visio"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"origins": [
|
||||||
|
"https://[a-z1-9-]*visio.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*onenote.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*eap.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*shared.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*afhs.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*vhs.officeapps.live.com",
|
||||||
|
"https://[a-z1-9-]*optin.online.office.com"
|
||||||
|
],
|
||||||
|
"use_regex": true,
|
||||||
|
"protocol": "ms-visio"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"origins": [
|
||||||
|
"https://www.microsoft.com",
|
||||||
|
"https://copilot.microsoft.com"
|
||||||
|
],
|
||||||
|
"protocol": "ms-copilot"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"warn": [
|
||||||
|
{
|
||||||
|
"origins": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"protocol": "ms-test-warn"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"block": [
|
||||||
|
{
|
||||||
|
"origins": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"protocol": "ms-test-block"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,84 +2,7 @@
|
|||||||
0:00:00 Tab1 PageLoad
|
0:00:00 Tab1 PageLoad
|
||||||
0:00:00 Tab1 FinishNav2
|
0:00:00 Tab1 FinishNav2
|
||||||
0:00:00 Tab1 PageLoad
|
0:00:00 Tab1 PageLoad
|
||||||
0:00:06 Microsoft.Shutdown.TabStripModel.SendDetachWebContentsNotifications
|
0:00:04 Microsoft.Shutdown.TabStripModel.SendDetachWebContentsNotifications
|
||||||
0:00:00 Startup
|
|
||||||
0:00:00 Microsoft.DeleteProfileHelper.CleanUpEphemeralProfiles
|
|
||||||
0:00:00 Microsoft.DeleteProfileHelper.CleanUpDeletedProfiles
|
|
||||||
0:00:00 Microsoft.NewBrowser_Popup
|
|
||||||
0:00:00 Microsoft.BrowserList.AddBrowser
|
|
||||||
0:00:00 Browser1 Insert active Tab1 at 0
|
|
||||||
0:00:00 Tab1 StartNav1 #auto_toplevel
|
|
||||||
0:00:00 Tab1 StartNav2 #typed
|
|
||||||
0:00:00 Tab1 FinishNav1
|
|
||||||
0:00:00 Tab1 PageLoad
|
|
||||||
0:00:00 Tab1 FinishNav2
|
|
||||||
0:00:00 Tab1 PageLoad
|
|
||||||
0:00:05 Microsoft.Shutdown.TabStripModel.SendDetachWebContentsNotifications
|
|
||||||
0:00:00 Startup
|
|
||||||
0:00:00 Microsoft.DeleteProfileHelper.CleanUpEphemeralProfiles
|
|
||||||
0:00:00 Microsoft.DeleteProfileHelper.CleanUpDeletedProfiles
|
|
||||||
0:00:00 Microsoft.NewBrowser_Popup
|
|
||||||
0:00:00 Microsoft.BrowserList.AddBrowser
|
|
||||||
0:00:00 Browser1 Insert active Tab1 at 0
|
|
||||||
0:00:00 Tab1 StartNav1 #auto_toplevel
|
|
||||||
0:00:00 Tab1 StartNav2 #typed
|
|
||||||
0:00:00 Tab1 FinishNav1
|
|
||||||
0:00:00 Tab1 PageLoad
|
|
||||||
0:00:00 Tab1 FinishNav2
|
|
||||||
0:00:00 Tab1 PageLoad
|
|
||||||
0:00:00 Startup
|
|
||||||
0:00:00 Microsoft.DeleteProfileHelper.CleanUpEphemeralProfiles
|
|
||||||
0:00:00 Microsoft.DeleteProfileHelper.CleanUpDeletedProfiles
|
|
||||||
0:00:00 Microsoft.NewBrowser_Popup
|
|
||||||
0:00:00 Microsoft.BrowserList.AddBrowser
|
|
||||||
0:00:00 Browser1 Insert active Tab1 at 0
|
|
||||||
0:00:00 Tab1 StartNav1 #auto_toplevel
|
|
||||||
0:00:00 Tab1 StartNav2 #typed
|
|
||||||
0:00:00 Tab1 FinishNav1
|
|
||||||
0:00:00 Tab1 PageLoad
|
|
||||||
0:00:00 Tab1 FinishNav2
|
|
||||||
0:00:00 Tab1 PageLoad
|
|
||||||
0:00:07 Microsoft.Shutdown.TabStripModel.SendDetachWebContentsNotifications
|
|
||||||
0:00:00 Startup
|
|
||||||
0:00:00 Microsoft.DeleteProfileHelper.CleanUpEphemeralProfiles
|
|
||||||
0:00:00 Microsoft.DeleteProfileHelper.CleanUpDeletedProfiles
|
|
||||||
0:00:00 Microsoft.NewBrowser_Popup
|
|
||||||
0:00:00 Microsoft.BrowserList.AddBrowser
|
|
||||||
0:00:00 Browser1 Insert active Tab1 at 0
|
|
||||||
0:00:00 Tab1 StartNav1 #auto_toplevel
|
|
||||||
0:00:00 Tab1 StartNav2 #typed
|
|
||||||
0:00:00 Tab1 FinishNav1
|
|
||||||
0:00:00 Tab1 PageLoad
|
|
||||||
0:00:00 Tab1 FinishNav2
|
|
||||||
0:00:00 Tab1 PageLoad
|
|
||||||
0:00:07 Microsoft.Shutdown.TabStripModel.SendDetachWebContentsNotifications
|
|
||||||
0:00:00 Startup
|
|
||||||
0:00:00 Microsoft.DeleteProfileHelper.CleanUpEphemeralProfiles
|
|
||||||
0:00:00 Microsoft.DeleteProfileHelper.CleanUpDeletedProfiles
|
|
||||||
0:00:00 Microsoft.NewBrowser_Popup
|
|
||||||
0:00:00 Microsoft.BrowserList.AddBrowser
|
|
||||||
0:00:00 Browser1 Insert active Tab1 at 0
|
|
||||||
0:00:00 Tab1 StartNav1 #auto_toplevel
|
|
||||||
0:00:00 Tab1 StartNav2 #typed
|
|
||||||
0:00:00 Tab1 FinishNav1
|
|
||||||
0:00:00 Tab1 PageLoad
|
|
||||||
0:00:00 Tab1 FinishNav2
|
|
||||||
0:00:00 Tab1 PageLoad
|
|
||||||
0:00:05 Microsoft.Shutdown.TabStripModel.SendDetachWebContentsNotifications
|
|
||||||
0:00:00 Startup
|
|
||||||
0:00:00 Microsoft.DeleteProfileHelper.CleanUpEphemeralProfiles
|
|
||||||
0:00:00 Microsoft.DeleteProfileHelper.CleanUpDeletedProfiles
|
|
||||||
0:00:00 Microsoft.NewBrowser_Popup
|
|
||||||
0:00:00 Microsoft.BrowserList.AddBrowser
|
|
||||||
0:00:00 Browser1 Insert active Tab1 at 0
|
|
||||||
0:00:00 Tab1 StartNav1 #auto_toplevel
|
|
||||||
0:00:00 Tab1 StartNav2 #typed
|
|
||||||
0:00:00 Tab1 FinishNav1
|
|
||||||
0:00:00 Tab1 PageLoad
|
|
||||||
0:00:00 Tab1 FinishNav2
|
|
||||||
0:00:00 Tab1 PageLoad
|
|
||||||
0:00:03 Microsoft.Shutdown.TabStripModel.SendDetachWebContentsNotifications
|
|
||||||
0:00:00 Startup
|
0:00:00 Startup
|
||||||
0:00:00 Microsoft.DeleteProfileHelper.CleanUpEphemeralProfiles
|
0:00:00 Microsoft.DeleteProfileHelper.CleanUpEphemeralProfiles
|
||||||
0:00:00 Microsoft.DeleteProfileHelper.CleanUpDeletedProfiles
|
0:00:00 Microsoft.DeleteProfileHelper.CleanUpDeletedProfiles
|
||||||
@@ -105,20 +28,7 @@
|
|||||||
0:00:00 Tab1 PageLoad
|
0:00:00 Tab1 PageLoad
|
||||||
0:00:00 Tab1 FinishNav2
|
0:00:00 Tab1 FinishNav2
|
||||||
0:00:00 Tab1 PageLoad
|
0:00:00 Tab1 PageLoad
|
||||||
0:00:14 Microsoft.Shutdown.TabStripModel.SendDetachWebContentsNotifications
|
0:00:04 Microsoft.Shutdown.TabStripModel.SendDetachWebContentsNotifications
|
||||||
0:00:00 Startup
|
|
||||||
0:00:00 Microsoft.DeleteProfileHelper.CleanUpEphemeralProfiles
|
|
||||||
0:00:00 Microsoft.DeleteProfileHelper.CleanUpDeletedProfiles
|
|
||||||
0:00:00 Microsoft.NewBrowser_Popup
|
|
||||||
0:00:00 Microsoft.BrowserList.AddBrowser
|
|
||||||
0:00:00 Browser1 Insert active Tab1 at 0
|
|
||||||
0:00:00 Tab1 StartNav1 #auto_toplevel
|
|
||||||
0:00:00 Tab1 StartNav2 #typed
|
|
||||||
0:00:00 Tab1 FinishNav1
|
|
||||||
0:00:00 Tab1 PageLoad
|
|
||||||
0:00:00 Tab1 FinishNav2
|
|
||||||
0:00:00 Tab1 PageLoad
|
|
||||||
0:00:07 Microsoft.Shutdown.TabStripModel.SendDetachWebContentsNotifications
|
|
||||||
0:00:00 Startup
|
0:00:00 Startup
|
||||||
0:00:00 Microsoft.DeleteProfileHelper.CleanUpEphemeralProfiles
|
0:00:00 Microsoft.DeleteProfileHelper.CleanUpEphemeralProfiles
|
||||||
0:00:00 Microsoft.DeleteProfileHelper.CleanUpDeletedProfiles
|
0:00:00 Microsoft.DeleteProfileHelper.CleanUpDeletedProfiles
|
||||||
@@ -144,17 +54,4 @@
|
|||||||
0:00:00 Tab1 PageLoad
|
0:00:00 Tab1 PageLoad
|
||||||
0:00:00 Tab1 FinishNav2
|
0:00:00 Tab1 FinishNav2
|
||||||
0:00:00 Tab1 PageLoad
|
0:00:00 Tab1 PageLoad
|
||||||
0:00:14 Microsoft.Shutdown.TabStripModel.SendDetachWebContentsNotifications
|
0:00:03 Microsoft.Shutdown.TabStripModel.SendDetachWebContentsNotifications
|
||||||
0:00:00 Startup
|
|
||||||
0:00:00 Microsoft.DeleteProfileHelper.CleanUpEphemeralProfiles
|
|
||||||
0:00:00 Microsoft.DeleteProfileHelper.CleanUpDeletedProfiles
|
|
||||||
0:00:00 Microsoft.NewBrowser_Popup
|
|
||||||
0:00:00 Microsoft.BrowserList.AddBrowser
|
|
||||||
0:00:00 Browser1 Insert active Tab1 at 0
|
|
||||||
0:00:00 Tab1 StartNav1 #auto_toplevel
|
|
||||||
0:00:00 Tab1 StartNav2 #typed
|
|
||||||
0:00:00 Tab1 FinishNav1
|
|
||||||
0:00:00 Tab1 PageLoad
|
|
||||||
0:00:00 Tab1 FinishNav2
|
|
||||||
0:00:00 Tab1 PageLoad
|
|
||||||
0:00:04 Microsoft.Shutdown.TabStripModel.SendDetachWebContentsNotifications
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"description": "Microsoft CRLSet",
|
||||||
|
"name": "MicrosoftCRLSet",
|
||||||
|
"version": "6498.2025.9.4"
|
||||||
|
}
|
||||||
@@ -8,5 +8,5 @@
|
|||||||
"top_topics_and_observing_domains": [ ]
|
"top_topics_and_observing_domains": [ ]
|
||||||
} ],
|
} ],
|
||||||
"hex_encoded_hmac_key": "4D1DB9F9D9946BB292656E50DD475F31DE4DDC2077303BD386E66DA8DD5D0AE3",
|
"hex_encoded_hmac_key": "4D1DB9F9D9946BB292656E50DD475F31DE4DDC2077303BD386E66DA8DD5D0AE3",
|
||||||
"next_scheduled_calculation_time": "13411534659755965"
|
"next_scheduled_calculation_time": "13411534659756767"
|
||||||
}
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 335 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 278 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 349 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 28 KiB |