using System; using System.IO; using UnityEngine; namespace XGame.MiniGame { // PC 本地端到端烟测入口:连接本地 Gateway,再用 MiniGameHost 下载/加载 rps 小游戏。 // 挂到任意场景对象后 Play;也可以运行时由 AddComponent 创建。 public sealed class PcMiniGameSmokeLauncher : MonoBehaviour { public string GatewayUrl = "ws://127.0.0.1:5005/ws?pid=1"; public string CdnBase = ""; public string GameId = "rps"; public int Version = 1; public bool AutoStart; private string _status = "idle"; private XWebSocket _socket; private MiniGameHost _host; private void Start() { if (AutoStart) StartSmoke(); } private void OnDestroy() { _socket?.close(); _socket = null; } private void OnGUI() { GUILayout.BeginArea(new Rect(16, 16, 620, 230), GUI.skin.box); GUILayout.Label("PC MiniGame Smoke"); GUILayout.Label("Status: " + _status); GUILayout.Label("Gateway URL"); GatewayUrl = GUILayout.TextField(GatewayUrl); GUILayout.Label("CDN Base"); CdnBase = GUILayout.TextField(string.IsNullOrEmpty(CdnBase) ? DefaultCdnBase() : CdnBase); GUILayout.BeginHorizontal(); GUILayout.Label("Game", GUILayout.Width(48)); GameId = GUILayout.TextField(GameId, GUILayout.Width(120)); GUILayout.Label("Version", GUILayout.Width(56)); int.TryParse(GUILayout.TextField(Version.ToString(), GUILayout.Width(60)), out Version); GUILayout.EndHorizontal(); if (GUILayout.Button("Start Smoke")) StartSmoke(); GUILayout.Label("Expected: download rps DLLs -> connect -> MatchFound(+AI) -> snapshots -> RoomEnd."); GUILayout.EndArea(); } public void StartSmoke() { string gatewayUrl = WithLocalPid(GatewayUrl); _status = "connecting " + gatewayUrl; _socket?.close(); _socket = new XWebSocket(); _socket.Connect(gatewayUrl, (sock, err) => { if (!string.IsNullOrEmpty(err) || sock == null) { _status = "connect failed: " + err; Debug.LogError("[PcMiniGameSmoke] " + _status); return; } _status = "connected, entering " + GameId + " v" + Version; Debug.Log("[PcMiniGameSmoke] " + _status); _host = gameObject.GetComponent(); if (_host == null) _host = gameObject.AddComponent(); _host.EnterGame(ResolvedCdnBase(), GameId, Version, sock); }); } private static string WithLocalPid(string gatewayUrl) { if (string.IsNullOrWhiteSpace(gatewayUrl)) { return gatewayUrl; } int pid = GetLocalPlayerId(); int queryStart = gatewayUrl.IndexOf('?'); if (queryStart < 0) { return gatewayUrl + "?pid=" + pid; } string head = gatewayUrl.Substring(0, queryStart); string query = gatewayUrl.Substring(queryStart + 1); string[] parts = query.Split('&'); bool replaced = false; for (int i = 0; i < parts.Length; i++) { if (parts[i].StartsWith("pid=", StringComparison.OrdinalIgnoreCase)) { parts[i] = "pid=" + pid; replaced = true; break; } } return head + "?" + (replaced ? string.Join("&", parts) : query + "&pid=" + pid); } private static int GetLocalPlayerId() { const string key = "LocalClientId"; string id = XData.GetString(key, string.Empty); if (string.IsNullOrEmpty(id)) { id = SystemInfo.deviceUniqueIdentifier; if (string.IsNullOrEmpty(id) || id == "unsupported") { id = Guid.NewGuid().ToString("N"); } XData.SetString(key, id); XData.Save(); } unchecked { uint hash = 2166136261u; for (int i = 0; i < id.Length; i++) { hash ^= id[i]; hash *= 16777619u; } return (int)(hash % 2000000000u) + 1; } } private string ResolvedCdnBase() { return string.IsNullOrEmpty(CdnBase) ? DefaultCdnBase() : CdnBase; } private static string DefaultCdnBase() { // Editor: /Client/Assets -> /CDN/minigame/(由 XWorld/生成小游戏更新 菜单产出) var assets = new DirectoryInfo(Application.dataPath); var client = assets.Parent; var repo = client?.Parent; string root = repo != null ? repo.FullName : Directory.GetCurrentDirectory(); return "file:///" + root.Replace('\\', '/') + "/CDN/minigame/"; } } }