using System; using System.Collections; using System.IO; using UnityEngine; using System.Collections.Generic; using XGame; using Unity.Burst; using Unity.Collections; using Unity.Jobs; using Unity.Mathematics; using XWorld; using UnityEngine.UI; using UnityEngine.Rendering.Universal; using UnityEngine.Rendering; /// /// 游戏启动器 /// public class GameLauncher : MonoBehaviour { private const int AndroidTargetFrameRate = 60; class SphereObj { public GameObject obj; public Transform tr; public Vector3 pos; public Vector3 vec; }; List sphereObjs = new List (); private NativeArray positions = new NativeArray(); private NativeArray velocities = new NativeArray(); public int objectCount = 1; public float speed = 1.0f; public static GameLauncher Instance; string m_sFPS = "FPS"; float m_fLastTime = 0; int m_nCount = 0; GUIStyle m_Style = new GUIStyle(); //系统ui组件 GameObject m_PathObj; InputField m_InputField; Button m_LocalButton; Button m_ExitButton; static public float m_fSize = 75; //内网 DevDiscovery(替代老 Broadcast/UDP9999),StartGame 等其完成后回填 CurNode/CurCDN private XGame.MiniGame.DevServerDiscovery m_LanDiscovery; private const string LocalClientIdKey = "LocalClientId"; private void Awake() { ConfigureFrameRate(); Instance = this; gameObject.name = "Main";//第一时间改为标准名称 InitStart(); DontDestroyOnLoad(gameObject); } private void Start() { //ud 设置字体 m_Style.normal.background = Texture2D.blackTexture; m_Style.normal.textColor = new Color(1, 1, 1); m_Style.fontSize = 24; m_Style.fontStyle = FontStyle.Bold; } /// /// 初始化管理器等(原来直接挂在GameManager这个节点下) /// void InitStart() { #if UNITY_EDITOR System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcessesByName("XNode"); if (processes.Length ==0) { System.Threading.Thread.Sleep(1000); } #endif //初始化 GlobalData.bWX = false;// LoadDll.m_bInWX; #if UNITY_IOS GlobalData.CurPlatform = "ios"; #elif UNITY_ANDROID GlobalData.CurPlatform = "android"; #elif UNITY_WEBGL GlobalData.CurPlatform = "webgl"; #else GlobalData.CurPlatform = "pc"; #endif #if UNITY_EDITOR GlobalData.CurPlatform = "pc"; #endif #if UNITY_WEBGL Debug.Log("WEBGL平台"); Shader.EnableKeyword("_PLATFORM_WEBGL"); #else Debug.Log("非WEBGL平台"); Shader.DisableKeyword("_PLATFORM_WEBGL"); #endif //XGame.UpdateModule module = gameObject.AddComponent(); //Debug.Log("GameLauncher : Add UpdateModule"); //除了WebGL都要进入更新流程,后再开始游戏 // //开始流程 #if XW_DEVTEST if (gameObject.GetComponent() == null) { gameObject.AddComponent(); } #endif StartCoroutine(this.StartGame()); #if UWA //ud 211112 增加uwa组件 GameObject uwa = Instantiate(Resources.Load("UWA_Launcher") as GameObject); if(uwa != null) uwa.transform.SetParent(gameObject.transform); #endif #if UWA_AUTO GameObject uwa = Instantiate(Resources.Load("UWA_LauncherAuto") as GameObject); if(uwa != null) uwa.transform.SetParent(gameObject.transform); #endif } IEnumerator StartGame() { SystemUIInit(); UpdateNodeAddress(); //内网模式:等 DevDiscovery 完成并回填 CurNode/CurCDN(须在 XResLoader.Init / 全量热更之前) if (GlobalData.bLocal && m_LanDiscovery != null) { float deadline = Time.realtimeSinceStartup + 2.0f; while (m_LanDiscovery.IsRunning && Time.realtimeSinceStartup < deadline) yield return null; var found = m_LanDiscovery.TakeResults(); m_LanDiscovery = null; if (found.Count > 0) { var r = found[0]; GlobalData.CurNode = WithPid(r.GatewayUrl); GlobalData.CurCDN = r.ResourceBaseUrl; // discovery 下发的是 CDN 根 Debug.Log($"[GameLauncher] LAN discovered {GlobalData.CurNode} {GlobalData.CurCDN}"); } else { Debug.LogWarning("[GameLauncher] LAN discovery found no server"); } } else if (GlobalData.CurNode == null) {//等待网络获得节点(外网) yield return new WaitForSeconds(0.5f); } //大厅/基础资源(XWorld) base:编辑器走本地 CDN(CurCDN),其它 app 端走生产(与 bLocal 无关)。UpdateModule 内部再 +/ string lobbyRoot = GlobalData.bEditor ? NormalizeCdnUrl(GlobalData.CurCDN) : GlobalData.ProductionResBase; XGame.AppConst.UpdateServer = lobbyRoot + "XWorld/"; Debug.Log($"UpdateNodeAddress {GlobalData.CurNode} {GlobalData.CurCDN} update={XGame.AppConst.UpdateServer}"); //重新启用旧全量热更链路(大厅相关热更):从 /XWorld// 增量下载 #if UNITY_WEBGL { XGame.UpdateModule upd = gameObject.GetComponent(); if (upd == null) upd = gameObject.AddComponent(); //传非空空回调:UpdateModule.DownLoadAssets 多处无守卫地调 onDownloadComplete(),传 null 会 NRE。我们靠 yield 等待,不依赖回调 yield return upd.UpdateResAllWebGL(() => { }); } #else { XGame.UpdateModule upd = gameObject.GetComponent(); if (upd == null) upd = gameObject.AddComponent(); //传非空空回调(同上,避免 DownLoadAssets 内 onDownloadComplete() 为 null 时 NRE) yield return upd.UpdateResAll(() => { }); upd.SetAllResHash(); } #endif //画质控制 //RenderTextureDescriptor descriptor = new RenderTextureDescriptor(Screen.width, Screen.height, RenderTextureFormat.Default); //bool isRenderScaleSupported = SystemInfo.SupportsRenderTextureFormat(descriptor.colorFormat) && SystemInfo.supportedRenderTargetCount > 0; //int TargetWidth = 720; QualitySettings.SetQualityLevel(3);//最高画质VeryHigh 3 ConfigureFrameRate(); EnforceNativeRenderScale(); /*if (UnityEngine.Screen.width > TargetWidth) { if (isRenderScaleSupported) { UniversalRenderPipelineAsset URPAsset = GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset; URPAsset.renderScale = Math.Min((float)TargetWidth / (float)UnityEngine.Screen.width, 1); URPAsset.upscalingFilter = UpscalingFilterSelection.Linear; Debug.Log($"renderScale {URPAsset.renderScale} {Screen.width}x{Screen.height}"); } }//*/ gameObject.AddComponent(); //gameObject.AddComponent(); Component obj = gameObject.GetComponent(); //初始化加载模块(静态) yield return XResLoader.Init(); //单例 Game game = gameObject.AddComponent(); yield return game.Init(); EnforceNativeRenderScale(); if (gameObject.GetComponent() == null) { gameObject.AddComponent(); } //如果仍然没有,则连接默认节点(内网回退本机 WS 网关;外网走生产 WSS 网关。均不再走已废弃的 tcp 7777) if (GlobalData.CurNode == null || GlobalData.CurNode == "") GlobalData.CurNode = GlobalData.bLocal ? WithPid("ws://127.0.0.1:5005/ws") : GlobalData.ProductionGatewayUrl; yield return CSharpClientApp.Init(); //yield return TerrainManager.Init(); #if UNITY_EDITOR //ConsoleWindow.ConnectConsoleServer(SystemInfo.deviceName); #endif //加载基础资源lua yield break; } //更新内外网网络连接 void UpdateNodeAddress() { #if UNITY_EDITOR if (!XData.HasKey("XWorldLocal")) { GlobalData.bLocal = true; XData.SetInt("XWorldLocal", 1); XData.Save(); } #endif if (XData.HasKey("XWorldLocal")) { GlobalData.bLocal = XData.GetInt("XWorldLocal") == 1; } #if XW_DEVTEST if (XGame.MiniGame.MiniGameTestOverride.IsActive) { GlobalData.CurNode = AddDevPid(XGame.MiniGame.MiniGameTestOverride.GatewayUrl); GlobalData.CurCDN = NormalizeCdnUrl(XGame.MiniGame.MiniGameTestOverride.CdnBase); Debug.Log($"Using dev test override: {GlobalData.CurNode} {GlobalData.CurCDN}"); return; } #endif if (GlobalData.bLocal) { //if (XData.HasKey("CurNode")) //{ // string savedNode = XData.GetString("CurNode"); // if (!string.IsNullOrWhiteSpace(savedNode) && // (savedNode.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) || // savedNode.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))) // { // GlobalData.CurNode = savedNode; // GlobalData.CurCDN = NormalizeCdnUrl(XData.GetString("CurCDN")); // Debug.Log($"Using local Gateway node: {GlobalData.CurNode} {GlobalData.CurCDN}"); // return; // } //} //内网发现:用已有 DevDiscovery(UDP 48923,二进制+token),结果在 StartGame 里等待回填。 //服务端:dotnet run --project Server/Gateway.Runner -- --lan --devToken xw-dev m_LanDiscovery = new XGame.MiniGame.DevServerDiscovery(GlobalData.LanDiscoveryToken); m_LanDiscovery.Begin(1500); Debug.Log("[GameLauncher] LAN discovery started (DevDiscovery, token=" + GlobalData.LanDiscoveryToken + ")"); } else { //如果有保存当前节点,则直接连接 if (XData.HasKey("CurNode")) { GlobalData.CurNode = XData.GetString("CurNode"); GlobalData.CurCDN = NormalizeCdnUrl(XData.GetString("CurCDN")); //检查链接是否有效 Debug.Log($"Check CurNode {GlobalData.CurNode} {GlobalData.CurCDN}"); } else { GlobalData.CurNode = GlobalData.ProductionGatewayUrl; GlobalData.CurCDN = NormalizeCdnUrl(GlobalData.ProductionResBase); Debug.Log($"Using default nodes: {GlobalData.CurNode} {GlobalData.CurCDN}"); } } } //内网网关地址补设备级稳定 pid,避免两个端都用 pid=1 被服务端当作同一玩家重连。 static string WithPid(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); } static int GetLocalPlayerId() { string id = XData.GetString(LocalClientIdKey, string.Empty); if (string.IsNullOrEmpty(id)) { id = SystemInfo.deviceUniqueIdentifier; if (string.IsNullOrEmpty(id) || id == "unsupported") { id = Guid.NewGuid().ToString("N"); } XData.SetString(LocalClientIdKey, 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; } } static string NormalizeCdnUrl(string cdn) { string url = string.IsNullOrWhiteSpace(cdn) ? "http://127.0.0.1:15081/" : cdn.Trim(); if (url.StartsWith("http://file://", StringComparison.OrdinalIgnoreCase)) { url = url.Substring("http://".Length); } else if (url.StartsWith("https://file://", StringComparison.OrdinalIgnoreCase)) { url = url.Substring("https://".Length); } if (url.StartsWith("file://", StringComparison.OrdinalIgnoreCase)) { url = NormalizeFileUrl(url); } if (!url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && !url.StartsWith("https://", StringComparison.OrdinalIgnoreCase) && !url.StartsWith("file://", StringComparison.OrdinalIgnoreCase)) { url = "http://" + url; } if (!url.EndsWith("/")) { url += "/"; } return url; } static string NormalizeFileUrl(string url) { string rawPath = url.Substring("file://".Length).Replace('\\', '/'); while (rawPath.StartsWith("/") && rawPath.Length > 2 && rawPath[2] == ':') { rawPath = rawPath.Substring(1); } string localPath = Uri.UnescapeDataString(rawPath).Replace('/', Path.DirectorySeparatorChar); try { return new Uri(localPath).AbsoluteUri; } catch { return "file:///" + rawPath.Replace("#", "%23"); } } static string NormalizeNodeAddress(string node, int defaultPort) { string address = string.IsNullOrWhiteSpace(node) ? $"127.0.0.1:{defaultPort}" : node.Trim(); if (address.StartsWith("http://", StringComparison.OrdinalIgnoreCase)) { address = address.Substring("http://".Length); } else if (address.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) { address = address.Substring("https://".Length); } int slash = address.IndexOf('/'); if (slash >= 0) { address = address.Substring(0, slash); } int colon = address.LastIndexOf(':'); string host = colon >= 0 ? address.Substring(0, colon) : address; if (string.IsNullOrWhiteSpace(host)) { host = "127.0.0.1"; } return $"{host}:{defaultPort}"; } #if XW_DEVTEST static string AddDevPid(string gatewayUrl) { if (string.IsNullOrWhiteSpace(gatewayUrl)) { return gatewayUrl; } return WithPid(gatewayUrl); } #endif private void SystemUIInit() { //用按钮区域 Transform objTR = GameObject.Find("Main/Touch/OpenPath").transform; Button button = objTR.GetComponent