diff --git a/Assets/Art/XJMH/pw/pw_prefab/game/battery/PW_Battery_1.prefab b/Assets/Art/XJMH/pw/pw_prefab/game/battery/PW_Battery_1.prefab index fa336725d..f4f003c73 100644 --- a/Assets/Art/XJMH/pw/pw_prefab/game/battery/PW_Battery_1.prefab +++ b/Assets/Art/XJMH/pw/pw_prefab/game/battery/PW_Battery_1.prefab @@ -16084,6 +16084,22 @@ PrefabInstance: propertyPath: m_Name value: PW_Plane_1 objectReference: {fileID: 0} + - target: {fileID: 1305285418536295, guid: f4f7967ac88aaaf4f975719c27df12aa, type: 3} + propertyPath: m_IsActive + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1333113903768364, guid: f4f7967ac88aaaf4f975719c27df12aa, type: 3} + propertyPath: m_IsActive + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1364716670320141, guid: f4f7967ac88aaaf4f975719c27df12aa, type: 3} + propertyPath: m_IsActive + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1372994856248276, guid: f4f7967ac88aaaf4f975719c27df12aa, type: 3} + propertyPath: m_IsActive + value: 0 + objectReference: {fileID: 0} - target: {fileID: 4313960313673800, guid: f4f7967ac88aaaf4f975719c27df12aa, type: 3} propertyPath: m_LocalScale.x value: 4.4716 diff --git a/Assets/Art/XJMH/pw/pw_prefab/game/battery/PW_Battery_2.prefab b/Assets/Art/XJMH/pw/pw_prefab/game/battery/PW_Battery_2.prefab index 03f2e950e..c6872bdbe 100644 --- a/Assets/Art/XJMH/pw/pw_prefab/game/battery/PW_Battery_2.prefab +++ b/Assets/Art/XJMH/pw/pw_prefab/game/battery/PW_Battery_2.prefab @@ -17289,6 +17289,11 @@ PrefabInstance: propertyPath: m_SortingLayer value: 0 objectReference: {fileID: 0} + - target: {fileID: 1084716326253117915, guid: 29589d6bc93a47e499105e964f1f5c6b, + type: 3} + propertyPath: m_IsActive + value: 0 + objectReference: {fileID: 0} - target: {fileID: 1139735090001087078, guid: 29589d6bc93a47e499105e964f1f5c6b, type: 3} propertyPath: m_SortingLayer @@ -17354,6 +17359,11 @@ PrefabInstance: propertyPath: m_SortingLayer value: 0 objectReference: {fileID: 0} + - target: {fileID: 3686788617825013159, guid: 29589d6bc93a47e499105e964f1f5c6b, + type: 3} + propertyPath: m_IsActive + value: 0 + objectReference: {fileID: 0} - target: {fileID: 3706220059061985850, guid: 29589d6bc93a47e499105e964f1f5c6b, type: 3} propertyPath: m_SortingLayer @@ -17469,11 +17479,21 @@ PrefabInstance: propertyPath: m_SortingLayer value: 0 objectReference: {fileID: 0} + - target: {fileID: 8456440153089082935, guid: 29589d6bc93a47e499105e964f1f5c6b, + type: 3} + propertyPath: m_IsActive + value: 0 + objectReference: {fileID: 0} - target: {fileID: 8553831016633651482, guid: 29589d6bc93a47e499105e964f1f5c6b, type: 3} propertyPath: m_SortingLayer value: 0 objectReference: {fileID: 0} + - target: {fileID: 8990963234669806065, guid: 29589d6bc93a47e499105e964f1f5c6b, + type: 3} + propertyPath: m_IsActive + value: 0 + objectReference: {fileID: 0} - target: {fileID: 9011820818510108152, guid: 29589d6bc93a47e499105e964f1f5c6b, type: 3} propertyPath: m_SortingLayer diff --git a/Assets/Art/XJMH/pw/pw_prefab/game/battery/PW_Battery_3.prefab b/Assets/Art/XJMH/pw/pw_prefab/game/battery/PW_Battery_3.prefab index e922bba7e..f44a79487 100644 --- a/Assets/Art/XJMH/pw/pw_prefab/game/battery/PW_Battery_3.prefab +++ b/Assets/Art/XJMH/pw/pw_prefab/game/battery/PW_Battery_3.prefab @@ -28841,6 +28841,11 @@ PrefabInstance: propertyPath: m_SortingLayer value: 0 objectReference: {fileID: 0} + - target: {fileID: 1092006923787695085, guid: 9672abcceca61c846b14184d9bba766f, + type: 3} + propertyPath: m_IsActive + value: 0 + objectReference: {fileID: 0} - target: {fileID: 1898051008385034303, guid: 9672abcceca61c846b14184d9bba766f, type: 3} propertyPath: m_SortingLayer @@ -28856,6 +28861,11 @@ PrefabInstance: propertyPath: m_SortingLayer value: 0 objectReference: {fileID: 0} + - target: {fileID: 2372885871247165783, guid: 9672abcceca61c846b14184d9bba766f, + type: 3} + propertyPath: m_IsActive + value: 0 + objectReference: {fileID: 0} - target: {fileID: 2414516914643537171, guid: 9672abcceca61c846b14184d9bba766f, type: 3} propertyPath: m_SortingLayer @@ -28876,6 +28886,11 @@ PrefabInstance: propertyPath: m_SortingLayer value: 0 objectReference: {fileID: 0} + - target: {fileID: 3336049404196487681, guid: 9672abcceca61c846b14184d9bba766f, + type: 3} + propertyPath: m_IsActive + value: 0 + objectReference: {fileID: 0} - target: {fileID: 3424540851437192074, guid: 9672abcceca61c846b14184d9bba766f, type: 3} propertyPath: m_SortingLayer @@ -28886,6 +28901,11 @@ PrefabInstance: propertyPath: m_SortingLayer value: 0 objectReference: {fileID: 0} + - target: {fileID: 3684409744614040008, guid: 9672abcceca61c846b14184d9bba766f, + type: 3} + propertyPath: m_IsActive + value: 0 + objectReference: {fileID: 0} - target: {fileID: 4178955796969763688, guid: 9672abcceca61c846b14184d9bba766f, type: 3} propertyPath: m_SortingLayer diff --git a/Assets/Art/XJMH/pw/pw_prefab/game/battery/PW_Battery_4.prefab b/Assets/Art/XJMH/pw/pw_prefab/game/battery/PW_Battery_4.prefab index 10b2e3874..d8836c1e4 100644 --- a/Assets/Art/XJMH/pw/pw_prefab/game/battery/PW_Battery_4.prefab +++ b/Assets/Art/XJMH/pw/pw_prefab/game/battery/PW_Battery_4.prefab @@ -28297,6 +28297,11 @@ PrefabInstance: propertyPath: m_SortingLayer value: 0 objectReference: {fileID: 0} + - target: {fileID: 2736570820237217112, guid: 6bca3101cb2436147ba613930ade7bc6, + type: 3} + propertyPath: m_IsActive + value: 0 + objectReference: {fileID: 0} - target: {fileID: 2854444671909311366, guid: 6bca3101cb2436147ba613930ade7bc6, type: 3} propertyPath: m_SortingLayer @@ -28307,6 +28312,16 @@ PrefabInstance: propertyPath: m_SortingLayer value: 0 objectReference: {fileID: 0} + - target: {fileID: 3471896425151648088, guid: 6bca3101cb2436147ba613930ade7bc6, + type: 3} + propertyPath: m_IsActive + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3596577447608897696, guid: 6bca3101cb2436147ba613930ade7bc6, + type: 3} + propertyPath: m_IsActive + value: 0 + objectReference: {fileID: 0} - target: {fileID: 4277595997587927750, guid: 6bca3101cb2436147ba613930ade7bc6, type: 3} propertyPath: m_SortingLayer @@ -28397,6 +28412,11 @@ PrefabInstance: propertyPath: m_SortingLayer value: 0 objectReference: {fileID: 0} + - target: {fileID: 7242293744027100629, guid: 6bca3101cb2436147ba613930ade7bc6, + type: 3} + propertyPath: m_IsActive + value: 0 + objectReference: {fileID: 0} - target: {fileID: 7257220591867521751, guid: 6bca3101cb2436147ba613930ade7bc6, type: 3} propertyPath: m_SortingLayer diff --git a/Assets/Framework/Tool/UITool/UIManager.cs b/Assets/Framework/Tool/UITool/UIManager.cs index 52b5c64b5..0f53f460b 100644 --- a/Assets/Framework/Tool/UITool/UIManager.cs +++ b/Assets/Framework/Tool/UITool/UIManager.cs @@ -708,8 +708,17 @@ public class UIManager : MonoSingleton yield return handle; if (handle.Status == AsyncOperationStatus.Succeeded) { - prefabCache[type] = handle.Result; - handleCache[type] = handle; + // ⭐ 竞态守卫:预载期间 ShowAsync 已自行加载并缓存了同一面板(玩家点得快时发生), + // 这里若再覆盖缓存,预载拿到的句柄会丢失、引用计数 +1 泄漏 → 释放本句柄即可 + if (prefabCache.ContainsKey(type)) + { + Addressables.Release(handle); + } + else + { + prefabCache[type] = handle.Result; + handleCache[type] = handle; + } } else { diff --git a/Assets/MyGame/Scenes/UIScene.unity b/Assets/MyGame/Scenes/UIScene.unity index d6a99dafe..d78a064f1 100644 --- a/Assets/MyGame/Scenes/UIScene.unity +++ b/Assets/MyGame/Scenes/UIScene.unity @@ -3855,6 +3855,7 @@ GameObject: - component: {fileID: 2075409202} - component: {fileID: 2075409203} - component: {fileID: 2075409204} + - component: {fileID: 2075409205} m_Layer: 0 m_Name: Manager m_TagString: Untagged @@ -4345,6 +4346,18 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: audioMixer: {fileID: 24100000, guid: ca43f9a7549657a49a2978e97c7a621a, type: 2} +--- !u!114 &2075409205 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2075409194} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bd70cad3c45605a4f824c9eaf1eb081d, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1001 &3569938244496052059 PrefabInstance: m_ObjectHideFlags: 0 diff --git a/Assets/MyGame/Scripts/Manager/CreateFishManager.cs b/Assets/MyGame/Scripts/Manager/CreateFishManager.cs index 79491ab6e..87cbc9704 100644 --- a/Assets/MyGame/Scripts/Manager/CreateFishManager.cs +++ b/Assets/MyGame/Scripts/Manager/CreateFishManager.cs @@ -824,7 +824,10 @@ public class CreateFishManager : MonoSingleton /// private IEnumerator SpawnFishBatchedCoroutine(List queue, bool isSync) { - const int batchSize = 5; // 每帧生成5条 + // ⭐ 加载期(进度条还在走、面板未加载完成)每帧只出 2 条:单帧 5 条 Spine 鱼 + // (首次创建含骨架/贴图初始化)会卡住进度条和界面动画(玩家反馈"90% 卡"的一部分); + // 加载完成进房后恢复每帧 5 条(正常出鱼间隔大,不挤) + int batchSize = (currGameType != null && !currGameType.IsGameLoadCompleted) ? 2 : 5; int index = 0; while (index < queue.Count) { diff --git a/Assets/MyGame/Scripts/Net/WebSocketReponseList.cs b/Assets/MyGame/Scripts/Net/WebSocketReponseList.cs index be7ed2597..84d632ab6 100644 --- a/Assets/MyGame/Scripts/Net/WebSocketReponseList.cs +++ b/Assets/MyGame/Scripts/Net/WebSocketReponseList.cs @@ -170,6 +170,16 @@ namespace Assets.MyGame.Scripts.Net /// private static IEnumerator LoadSceneThenResources(UIBase panel, string sceneName) { + // ⭐ 第一步只加载 UI:面板刚实例化显示,先让 2 帧把 UI(含进度条)完整渲染出来, + // 再从"里面有进度条的画面"开始加载非 UI 资源(场景/鱼/音频)—— + // 避免面板第一帧和场景加载启动挤在同一帧,UI 出现那下发卡 + yield return null; + yield return null; + + // ⭐ 场景加载阶段进度条从 0 缓慢爬升(此阶段只加载了 UI 面板):条不会静止像卡死 + if (panel is GameMainPanel_FishBase barPanel) + barPanel.BeginSceneLoadingProgress(); + // ③ 目标场景仅加载一次:标记已加载后不再重复 LoadSceneAsync(防止人机通知触发重复加载) if (!string.IsNullOrEmpty(sceneName) && !_loadedGameScenes.Contains(sceneName)) { diff --git a/Assets/MyGame/Scripts/Turret/FlyTurretData.cs b/Assets/MyGame/Scripts/Turret/FlyTurretData.cs index 4a15fa842..58390725a 100644 --- a/Assets/MyGame/Scripts/Turret/FlyTurretData.cs +++ b/Assets/MyGame/Scripts/Turret/FlyTurretData.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using Spine.Unity; using UnityEngine; @@ -591,9 +592,11 @@ public class FlyTurretData : TurretData } // ==================== 入场飞机预热(进房加载收尾时由面板调用,卡顿被进度条遮住) ==================== - // XJMH 进入房间时"命中"的那架完整飞机(骨架 + 尾迹/护盾十多个粒子系统 + 贴图首次上传)首帧初始化很重; - // 全部堆在"加载到100%、进度条已隐藏"那一帧做,玩家会看到明显卡顿(其他玩法没有这套 XJMH 独有资产)。 - // 预热 = 在进度条还在走时把飞机临时挂到激活的父节点下、缩到 0 隐藏,激活一两帧完成初始化,再原样还原。 + // XJMH 进入房间时"命中"的那架完整飞机 PW_Plane(骨架 + 41 个粒子系统、2.2MB 预制体、52 个节点): + // 整根激活会让 41 套粒子在同一帧全部首初始化(分配缓冲/上传网格贴图)——单帧几百 ms 主线程尖峰。 + // 这就是"只有星际迷航进房卡、其它玩法不卡"的原因(其他玩法没有这套 XJMH 独有资产)。 + // 预热 = 进度条还在走时把飞机临时挂到激活父节点下、缩到 0 隐藏;骨架 1 帧 + 粒子按每帧 8ms 预算 + // 逐帧点亮完成初始化,再原样还原(玩家全程不可见)。 private Coroutine _prewarmRoutine; private Transform _prewarmPlane; // 预热中的飞机(被打断时还原用) @@ -603,6 +606,8 @@ public class FlyTurretData : TurretData private Quaternion _prewarmOrigRot; private Vector3 _prewarmOrigScale; private bool _prewarmOrigActive; + /// 粒子分帧点亮的帧内计时(Stopwatch 不受 Time.realtimeSinceStartup 帧内不刷新影响) + private static readonly System.Diagnostics.Stopwatch _prewarmFrameSw = new System.Diagnostics.Stopwatch(); /// 入场飞机预热(面板在加载收尾阶段调用;座位/炮台需已激活)。幂等:重复调用只跑一次。 public void PrewarmEnterPlane() @@ -625,27 +630,67 @@ public class FlyTurretData : TurretData _prewarmOrigScale = plane.localScale; _prewarmOrigActive = plane.gameObject.activeSelf; - // 只开入场要用的 Body、关掉免费三类——避免把五十来个粒子系统全部初始化(反而更重); - // ⭐ 护盾粒子拆到下一帧再开:和 Body 骨架/粒子挤同一帧 = 单帧尖峰(90% 收尾窗口卡顿的来源之一) + // 容器显隐(与入场动画 EnterActionRoutine 一致):开 Body/护盾、关免费三类 SetPlaneChildActive(plane, "Body", true); + SetPlaneChildActive(plane, "PW_Effect_Defend", true); SetPlaneChildActive(plane, "FreeEffect", false); SetPlaneChildActive(plane, "FreeEffect_Crit", false); SetPlaneChildActive(plane, "FreeEffect_Speedup", false); + // ⭐ 关键修复("只有 XJMH 进房卡主线程"的根因): + // 整根 SetActive(true) 会让 41 个粒子系统在同一帧全部首初始化(单帧几百 ms)。 + // 先把"会被激活的"粒子节点逐个关掉(飞机根此刻未激活,只是置标志、不做初始化), + // 等飞机激活(这一帧只初始化骨架等非粒子部分)后,按"每帧约 8ms 预算"逐帧点亮—— + // 设备快一帧多开几个、设备慢一帧只开一个,单帧尖峰摊平到十几帧,不再顶住主线程。 + // ⚠️ 只动"容器开着"的粒子(Body/护盾);免费三类容器已关、默认关着的粒子都不动,保持原状。 + var psNodes = new List(64); + foreach (var ps in plane.GetComponentsInChildren(true)) + { + if (ps == null || ps.gameObject == plane.gameObject) continue; + if (!IsActiveUnder(ps.transform, plane)) continue; // 容器关着的(免费三类等)不预热 + ps.gameObject.SetActive(false); + psNodes.Add(ps.gameObject); + } + // 临时挂到根节点(激活中)并缩到 0:既能触发初始化,又完全不可见 Transform holder = transform.root; if (holder != null && holder != _prewarmOrigParent) plane.SetParent(holder, true); plane.localScale = Vector3.zero; - plane.gameObject.SetActive(true); - yield return null; // Body 骨架首帧网格 - SetPlaneChildActive(plane, "PW_Effect_Defend", true); // 护盾粒子单独一帧开 - yield return null; // 护盾首帧模拟 - yield return null; // 再等一帧让首帧网格上传铺开 + LoadingStallWatchdog.Phase = "预热·飞机骨架激活帧"; + plane.gameObject.SetActive(true); // 这一帧只初始化骨架等非粒子部分 + yield return null; // 骨架首帧网格 + + // ⭐ 逐帧分批点亮粒子(帧内时间预算自适应:快机多开、慢机少开) + LoadingStallWatchdog.Phase = "预热·飞机粒子分帧"; + const double frameBudgetMs = 8.0; + _prewarmFrameSw.Restart(); + for (int i = 0; i < psNodes.Count; i++) + { + if (psNodes[i] == null) continue; + psNodes[i].SetActive(true); + if (_prewarmFrameSw.Elapsed.TotalMilliseconds >= frameBudgetMs) + { + _prewarmFrameSw.Restart(); + yield return null; + } + } + yield return null; // 最后一批首帧模拟 + yield return null; // 首帧网格上传铺开 FinishPrewarmRestore(); - Debug.Log($"[XJMH入场] 飞机预热完成({plane.name})"); + Debug.Log($"[XJMH入场] 飞机预热完成({plane.name},粒子分帧点亮 {psNodes.Count} 个)"); _prewarmRoutine = null; + // 诊断:预热结束,把卡顿监测阶段还给加载段(后续卡顿归因不错位到预热) + LoadingStallWatchdog.Phase = "60~78 音频加载"; + } + + /// 节点在"飞机根若激活"的前提下是否会被激活(检查自身到飞机根之间每级的 activeSelf) + private static bool IsActiveUnder(Transform node, Transform stopRoot) + { + for (var t = node; t != null && t != stopRoot; t = t.parent) + if (!t.gameObject.activeSelf) return false; + return true; } /// 预热还原:飞机隐藏并恢复到预热前的挂载/变换/激活状态(协程正常收尾或中途被打断都用它) diff --git a/Assets/MyGame/Scripts/Turret/SitPosData.cs b/Assets/MyGame/Scripts/Turret/SitPosData.cs index a9a2341b8..67590813f 100644 --- a/Assets/MyGame/Scripts/Turret/SitPosData.cs +++ b/Assets/MyGame/Scripts/Turret/SitPosData.cs @@ -346,6 +346,14 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler return rt; } + /// + /// ⭐ 预热用:本次座位首次激活时先"只整理炮台列表、不点亮默认炮台"—— + /// 把炮台 Spine 骨架的首初始化(几十~上百 ms)拆到下一帧单独做,避免座位激活那一帧 + /// 同时扛"座位整层 OnEnable + 炮台骨架首建"两个大活。由 GameMainPanel_FishBase 预热流程设置; + /// 任何入座/兜底路径(ShowPlayer)都会强制复位并保证炮台被点亮,不会留空座位。 + /// + [System.NonSerialized] public bool deferDefaultTurretLightUp; + /// /// XJMH 模式:炮台是座位自带子对象,预制体里已摆好,不需要 LoadTurret 加载。 /// ⭐ 默认炮台 = 排序后索引 0 的那把(对应座位 GetChild(0) 的绿炮台)。 @@ -359,9 +367,33 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler } NormalizeXJMHTurretList(); + // ⭐ 预热路径:只整理列表,点亮留给预热协程下一帧调 ActivateDefaultXJMHTurretForPrewarm + if (deferDefaultTurretLightUp) return; ApplyXJMHTurret(0); } + /// + /// 预热续帧:点亮默认炮台(配合 deferDefaultTurretLightUp,把炮台骨架首初始化拆到单独一帧)。 + /// ⚠️ "是否已点亮"必须看列表里有没有"激活状态"的炮台——currentTurret 是可序列化字段, + /// 预制体里预置了引用,不代表炮台已激活(用它当判据会出现"炮台永远不点亮"的 bug)。 + /// + public void ActivateDefaultXJMHTurretForPrewarm() + { + if (turretControlMode != TurretControlMode.XJMH) return; + if (HasActiveXJMHTurret()) return; // 已点亮(正常入座路径已做过) + deferDefaultTurretLightUp = false; + InitXJMHTurret(); + } + + /// 列表里当前是否有"激活状态"的炮台("默认炮台是否已点亮"的正确判据) + public bool HasActiveXJMHTurret() + { + if (xjmhTurretList == null) return false; + for (int i = 0; i < xjmhTurretList.Count; i++) + if (xjmhTurretList[i] != null && xjmhTurretList[i].gameObject.activeSelf) return true; + return false; + } + /// /// 整理 XJMH 炮台列表:剔除空引用,并按座位下的兄弟顺序排序。 /// ⭐ 排序原因:切换面板的 4 个按钮固定是 绿/蓝/金/VIP(索引 0~3), @@ -1137,6 +1169,11 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler } else { + // ⭐ 兜底:预热被中断(激活了座位但还没轮到点亮炮台就退出/销毁)时,入座这里补点亮, + // 保证任何时候都不会出现"座位亮着但炮台是空的"(判据:列表里有没有激活的炮台) + if (!HasActiveXJMHTurret()) + ActivateDefaultXJMHTurretForPrewarm(); + // ⭐ XJMH 进入房间入场动画(原版 Battery:EnterAction):仅本地座位播 // (Enter 音效 + 飞机从下方升起变回炮台 + PlaneBack 音效); // 本次激活周期只播一次(OnDisable 复位,重进房重新播),切炮台不受影响 diff --git a/Assets/MyGame/Scripts/fishResources/Resources/tbfishyz - 副本.json b/Assets/MyGame/Scripts/fishResources/Resources/tbfishyz - 副本.json new file mode 100644 index 000000000..60674b305 --- /dev/null +++ b/Assets/MyGame/Scripts/fishResources/Resources/tbfishyz - 副本.json @@ -0,0 +1,615 @@ +[ + { + "yzNumber": 70001, + "lineList": [ + 1 + ], + "count": 10 + }, + { + "yzNumber": 70002, + "lineList": [ + 2 + ], + "count": 10 + }, + { + "yzNumber": 70003, + "lineList": [ + 1, + 1, + 1, + 1, + 1 + ], + "count": 10 + }, + { + "yzNumber": 70004, + "lineList": [ + 2, + 2, + 2, + 2, + 2 + ], + "count": 10 + }, + { + "yzNumber": 70101, + "lineList": [ + 3 + ], + "count": 20 + }, + { + "yzNumber": 70102, + "lineList": [ + 4 + ], + "count": 20 + }, + { + "yzNumber": 70103, + "lineList": [ + 5 + ], + "count": 20 + }, + { + "yzNumber": 70104, + "lineList": [ + 3, + 4, + 5 + ], + "count": 10 + }, + { + "yzNumber": 70201, + "lineList": [ + 6 + ], + "count": 8 + }, + { + "yzNumber": 70202, + "lineList": [ + 7 + ], + "count": 8 + }, + { + "yzNumber": 70203, + "lineList": [ + 8 + ], + "count": 10 + }, + { + "yzNumber": 70204, + "lineList": [ + 9 + ], + "count": 10 + }, + { + "yzNumber": 70205, + "lineList": [ + 10 + ], + "count": 5 + }, + { + "yzNumber": 70206, + "lineList": [ + 11 + ], + "count": 5 + }, + { + "yzNumber": 70207, + "lineList": [ + 12 + ], + "count": 7 + }, + { + "yzNumber": 70208, + "lineList": [ + 13 + ], + "count": 9 + }, + { + "yzNumber": 70209, + "lineList": [ + 14 + ], + "count": 8 + }, + { + "yzNumber": 70210, + "lineList": [ + 15 + ], + "count": 6 + }, + { + "yzNumber": 70211, + "lineList": [ + 16 + ], + "count": 5 + }, + { + "yzNumber": 70212, + "lineList": [ + 17 + ], + "count": 8 + }, + { + "yzNumber": 70213, + "lineList": [ + 18 + ], + "count": 6 + }, + { + "yzNumber": 70214, + "lineList": [ + 19 + ], + "count": 20 + }, + { + "yzNumber": 70215, + "lineList": [ + 20 + ], + "count": 8 + }, + { + "yzNumber": 70216, + "lineList": [ + 21 + ], + "count": 20 + }, + { + "yzNumber": 70217, + "lineList": [ + 22 + ], + "count": 20 + }, + { + "yzNumber": 70218, + "lineList": [ + 23 + ], + "count": 9 + }, + { + "yzNumber": 70219, + "lineList": [ + 24 + ], + "count": 8 + }, + { + "yzNumber": 70220, + "lineList": [ + 25 + ], + "count": 12 + }, + { + "yzNumber": 70221, + "lineList": [ + 26 + ], + "count": 8 + }, + { + "yzNumber": 70222, + "lineList": [ + 27 + ], + "count": 6 + }, + { + "yzNumber": 70223, + "lineList": [ + 6, + 7 + ], + "count": 15 + }, + { + "yzNumber": 70224, + "lineList": [ + 10, + 11 + ], + "count": 8 + }, + { + "yzNumber": 70225, + "lineList": [ + 21, + 22 + ], + "count": 20 + }, + { + "yzNumber": 71001, + "lineList": [ + 1, + 2, + 3 + ], + "count": 3 + }, + { + "yzNumber": 71002, + "lineList": [ + 4, + 5, + 6 + ], + "count": 5 + }, + { + "yzNumber": 71003, + "lineList": [ + 7, + 8, + 9 + ], + "count": 4 + }, + { + "yzNumber": 71004, + "lineList": [ + 10, + 11, + 12 + ], + "count": 6 + }, + { + "yzNumber": 71005, + "lineList": [ + 13, + 14, + 15 + ], + "count": 3 + }, + { + "yzNumber": 71006, + "lineList": [ + 16, + 17, + 18 + ], + "count": 6 + }, + { + "yzNumber": 71007, + "lineList": [ + 19, + 20, + 21 + ], + "count": 4 + }, + { + "yzNumber": 71008, + "lineList": [ + 22, + 23, + 24 + ], + "count": 2 + }, + { + "yzNumber": 71009, + "lineList": [ + 25, + 26, + 27 + ], + "count": 8 + }, + { + "yzNumber": 71010, + "lineList": [ + 28, + 29, + 30 + ], + "count": 3 + }, + { + "yzNumber": 71011, + "lineList": [ + 31, + 32, + 33 + ], + "count": 3 + }, + { + "yzNumber": 71012, + "lineList": [ + 34, + 35, + 36 + ], + "count": 3 + }, + { + "yzNumber": 71013, + "lineList": [ + 1, + 3, + 5 + ], + "count": 2 + }, + { + "yzNumber": 70401, + "lineList": [ + 1 + ], + "count": 10 + }, + { + "yzNumber": 70402, + "lineList": [ + 2 + ], + "count": 10 + }, + { + "yzNumber": 70403, + "lineList": [ + 3 + ], + "count": 10 + }, + { + "yzNumber": 70404, + "lineList": [ + 4 + ], + "count": 10 + }, + { + "yzNumber": 70405, + "lineList": [ + 5 + ], + "count": 20 + }, + { + "yzNumber": 70406, + "lineList": [ + 6 + ], + "count": 20 + }, + { + "yzNumber": 70407, + "lineList": [ + 7 + ], + "count": 20 + }, + { + "yzNumber": 70408, + "lineList": [ + 8 + ], + "count": 10 + }, + { + "yzNumber": 70409, + "lineList": [ + 9 + ], + "count": 8 + }, + { + "yzNumber": 70410, + "lineList": [ + 10 + ], + "count": 8 + }, + { + "yzNumber": 70411, + "lineList": [ + 11 + ], + "count": 10 + }, + { + "yzNumber": 70412, + "lineList": [ + 12 + ], + "count": 10 + }, + { + "yzNumber": 70413, + "lineList": [ + 13 + ], + "count": 5 + }, + { + "yzNumber": 70414, + "lineList": [ + 14 + ], + "count": 5 + }, + { + "yzNumber": 70415, + "lineList": [ + 15 + ], + "count": 7 + }, + { + "yzNumber": 70416, + "lineList": [ + 16 + ], + "count": 9 + }, + { + "yzNumber": 70417, + "lineList": [ + 17 + ], + "count": 8 + }, + { + "yzNumber": 70418, + "lineList": [ + 18 + ], + "count": 6 + }, + { + "yzNumber": 70419, + "lineList": [ + 19 + ], + "count": 5 + }, + { + "yzNumber": 70420, + "lineList": [ + 20 + ], + "count": 8 + }, + { + "yzNumber": 70421, + "lineList": [ + 21 + ], + "count": 6 + }, + { + "yzNumber": 70422, + "lineList": [ + 22 + ], + "count": 20 + }, + { + "yzNumber": 70423, + "lineList": [ + 23 + ], + "count": 8 + }, + { + "yzNumber": 70424, + "lineList": [ + 24 + ], + "count": 20 + }, + { + "yzNumber": 70425, + "lineList": [ + 25 + ], + "count": 20 + }, + { + "yzNumber": 70426, + "lineList": [ + 26 + ], + "count": 9 + }, + { + "yzNumber": 70427, + "lineList": [ + 27 + ], + "count": 8 + }, + { + "yzNumber": 70428, + "lineList": [ + 28 + ], + "count": 12 + }, + { + "yzNumber": 70429, + "lineList": [ + 29 + ], + "count": 8 + }, + { + "yzNumber": 70430, + "lineList": [ + 30 + ], + "count": 6 + }, + { + "yzNumber": 70431, + "lineList": [ + 31 + ], + "count": 10 + }, + { + "yzNumber": 70432, + "lineList": [ + 32 + ], + "count": 10 + }, + { + "yzNumber": 70433, + "lineList": [ + 33 + ], + "count": 10 + }, + { + "yzNumber": 70434, + "lineList": [ + 34 + ], + "count": 10 + }, + { + "yzNumber": 70435, + "lineList": [ + 35 + ], + "count": 20 + }, + { + "yzNumber": 70436, + "lineList": [ + 36 + ], + "count": 20 + } +] \ No newline at end of file diff --git a/Assets/MyGame/Scripts/fishResources/Resources/tbfishyz - 副本.json.meta b/Assets/MyGame/Scripts/fishResources/Resources/tbfishyz - 副本.json.meta new file mode 100644 index 000000000..8320711ed --- /dev/null +++ b/Assets/MyGame/Scripts/fishResources/Resources/tbfishyz - 副本.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 37136faa1f387f2498c01033bc961bbc +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/MyGame/Scripts/fishResources/Resources/tbfishyz.json b/Assets/MyGame/Scripts/fishResources/Resources/tbfishyz.json index 60674b305..8d183fc88 100644 --- a/Assets/MyGame/Scripts/fishResources/Resources/tbfishyz.json +++ b/Assets/MyGame/Scripts/fishResources/Resources/tbfishyz.json @@ -246,119 +246,254 @@ { "yzNumber": 71001, "lineList": [ - 1, - 2, - 3 + 1 ], - "count": 3 + "count": 1 }, { "yzNumber": 71002, "lineList": [ - 4, - 5, - 6 + 2 ], - "count": 5 + "count": 1 }, { "yzNumber": 71003, "lineList": [ - 7, - 8, - 9 + 3 ], - "count": 4 + "count": 1 }, { "yzNumber": 71004, "lineList": [ - 10, - 11, - 12 + 4 ], - "count": 6 + "count": 1 }, { "yzNumber": 71005, "lineList": [ - 13, - 14, - 15 + 5 ], - "count": 3 + "count": 1 }, { "yzNumber": 71006, "lineList": [ - 16, - 17, - 18 + 6 ], - "count": 6 + "count": 1 }, { "yzNumber": 71007, "lineList": [ - 19, - 20, - 21 + 7 ], - "count": 4 + "count": 1 }, { "yzNumber": 71008, "lineList": [ - 22, - 23, - 24 + 8 ], - "count": 2 + "count": 1 }, { "yzNumber": 71009, "lineList": [ - 25, - 26, - 27 + 9 ], - "count": 8 + "count": 1 }, { "yzNumber": 71010, "lineList": [ - 28, - 29, - 30 + 10 ], - "count": 3 + "count": 1 }, { "yzNumber": 71011, "lineList": [ - 31, - 32, - 33 + 11 ], - "count": 3 + "count": 1 }, { "yzNumber": 71012, "lineList": [ - 34, - 35, - 36 + 12 ], - "count": 3 + "count": 1 }, { "yzNumber": 71013, "lineList": [ - 1, - 3, - 5 + 13 ], - "count": 2 + "count": 1 + }, + { + "yzNumber": 71014, + "lineList": [ + 14 + ], + "count": 1 + }, + { + "yzNumber": 71015, + "lineList": [ + 15 + ], + "count": 1 + }, + { + "yzNumber": 71016, + "lineList": [ + 16 + ], + "count": 1 + }, + { + "yzNumber": 71017, + "lineList": [ + 17 + ], + "count": 1 + }, + { + "yzNumber": 71018, + "lineList": [ + 18 + ], + "count": 1 + }, + { + "yzNumber": 71019, + "lineList": [ + 19 + ], + "count": 1 + }, + { + "yzNumber": 71020, + "lineList": [ + 20 + ], + "count": 1 + }, + { + "yzNumber": 71021, + "lineList": [ + 21 + ], + "count": 1 + }, + { + "yzNumber": 71022, + "lineList": [ + 22 + ], + "count": 1 + }, + { + "yzNumber": 71023, + "lineList": [ + 23 + ], + "count": 1 + }, + { + "yzNumber": 71024, + "lineList": [ + 24 + ], + "count": 1 + }, + { + "yzNumber": 71025, + "lineList": [ + 25 + ], + "count": 1 + }, + { + "yzNumber": 71026, + "lineList": [ + 26 + ], + "count": 1 + }, + { + "yzNumber": 71027, + "lineList": [ + 27 + ], + "count": 1 + }, + { + "yzNumber": 71028, + "lineList": [ + 28 + ], + "count": 1 + }, + { + "yzNumber": 71029, + "lineList": [ + 29 + ], + "count": 1 + }, + { + "yzNumber": 71030, + "lineList": [ + 30 + ], + "count": 1 + }, + { + "yzNumber": 71031, + "lineList": [ + 31 + ], + "count": 1 + }, + { + "yzNumber": 71032, + "lineList": [ + 32 + ], + "count": 1 + }, + { + "yzNumber": 71033, + "lineList": [ + 33 + ], + "count": 1 + }, + { + "yzNumber": 71034, + "lineList": [ + 34 + ], + "count": 1 + }, + { + "yzNumber": 71035, + "lineList": [ + 35 + ], + "count": 1 + }, + { + "yzNumber": 71036, + "lineList": [ + 36 + ], + "count": 1 }, { "yzNumber": 70401, diff --git a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs index f90f778e2..30b022e15 100644 --- a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs +++ b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs @@ -477,7 +477,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base } /// - /// XJMH 进房首帧预热(加载 90%→100% 收尾窗口调用,进度条还显示着玩家看不到): + /// XJMH 进房预热(60% 音频段起点调用,进度条还显示着玩家看不到): /// 把本地座位(座位自带炮台随 Awake 一起初始化)与"入场要用的那架完整飞机"提前点亮—— /// 这些 XJMH 独有资产首次初始化(骨架/粒子/贴图首上传)很重,全堆在 100% 那一帧玩家会看到明显卡顿。 /// ⭐ 按帧拆散(避免"90% 卡死"):先让一帧(进度条先刷新)→ 只留默认炮台、关掉其余 3 把 → @@ -497,39 +497,169 @@ public class GameMainPanel_FishBase : GameMainPanel_Base private IEnumerator PrewarmXJMHCoroutine(SitPosData sit) { - // ⭐ 先等一帧:StartCoroutine 会同步执行到第一个 yield —— 原先座位激活的重活 - // 和"90%"写入进度条是同一帧(数字还没刷新、主线程就被占住,看起来"90 卡死")。 - // 先让一帧,进度条和收尾循环跑起来再干重活 + // ⭐ 先等一帧:StartCoroutine 会同步执行到第一个 yield —— 座位激活的重活不能和进度条 + // 写数字挤在同一帧(玩家看到"卡在 60/61")。先让一帧,进度条先刷新 yield return null; bool willActivate = sit != null && !sit.gameObject.activeSelf; - // ⭐ 只有"座位从未激活、这次预热才激活"时才预关炮台: - // 预制体里 4 把 XJMH 炮台(SkeletonGraphic)默认激活,座位 SetActive 时 4 套骨架/材质/贴图 - // 挤在同一帧全部首初始化(单帧尖峰巨大);先关掉后,这一帧只初始化 Awake 点亮的默认那把。 - // ⚠️ 座位已在激活状态时不能动(Awake 不会再跑,关掉就回不来) - if (willActivate && sit.xjmhTurretList != null) + // ⭐ 分帧方案(XJMH 进房不卡主线程的关键)—— 把所有"可以错开的初始化"从同一帧剥出去: + // ① 座位外壳(1 帧,只扛座位整层 OnEnable) + // ② 默认炮台 Spine 骨架首初始化(单独 1 帧,见 SitPosData.deferDefaultTurretLightUp) + // ③ 座位外壳粒子 + 默认炮台粒子(本方法先关掉,之后逐帧点亮) + // ④ 入场飞机(FlyTurretData.PrewarmPlaneRoutine:骨架 1 帧 + 41 个粒子逐帧点亮) + List lazyPsNodes = null; + if (willActivate) { + // 预关全部 4 把炮台(Spine 骨架默认全开,不关的话激活帧一次初始化 4 套) + // ⚠️ 座位已在激活状态时不能动(Awake 不会再跑,关掉就回不来) + if (sit.xjmhTurretList != null) + { + for (int i = 0; i < sit.xjmhTurretList.Count; i++) + { + var t = sit.xjmhTurretList[i]; + if (t != null && t.gameObject.activeSelf) t.gameObject.SetActive(false); + } + } + + // 收集"激活后会被点亮"的粒子并先关掉(此刻根未激活,只是置标志、无初始化): + // 座位外壳层(炮台/飞机子树此刻未激活,自动被排除)+ 默认炮台子树(兄弟顺序最靠前=绿色那把) + lazyPsNodes = CollectActiveParticlesUnder(sit.transform); + var defaultTurret = GetDefaultXJMHTurret(sit); + if (defaultTurret != null) + { + var turretPsNodes = CollectActiveParticlesUnder(defaultTurret.transform); + if (turretPsNodes != null) + { + if (lazyPsNodes == null) lazyPsNodes = turretPsNodes; + else lazyPsNodes.AddRange(turretPsNodes); + } + } + + // ① 激活座位【外壳】:设 defer 标记 → Awake 只整理炮台列表、不点亮炮台 + // (这一帧只扛"座位整层 OnEnable",把炮台骨架首建推到下一帧) + sit.deferDefaultTurretLightUp = true; + LoadingStallWatchdog.Phase = "预热·座位外壳激活帧"; + sit.gameObject.SetActive(true); + } + + yield return null; // 座位外壳首帧 + + // ② 单独一帧点亮默认炮台:Spine 骨架首初始化(材质/网格/贴图上传)只花在这一帧 + // ⚠️ 判据用"列表里有没有激活的炮台",不能用 currentTurret(可序列化字段,预制体里预置了引用) + if (willActivate && !sit.HasActiveXJMHTurret()) + { + LoadingStallWatchdog.Phase = "预热·炮台骨架激活帧"; + sit.ActivateDefaultXJMHTurretForPrewarm(); + } + + yield return null; // 炮台骨架首帧网格 + + // ③ 逐帧点亮座位/炮台粒子(每帧约 8ms 预算) + if (lazyPsNodes != null && lazyPsNodes.Count > 0) + { + LoadingStallWatchdog.Phase = "预热·座位/炮台粒子分帧"; + yield return ChunkEnableParticles(lazyPsNodes); + } + + yield return null; + + // ④ 飞机隐藏预热(座位激活后 currentTurret 已就绪 = 入场会用的那把;内部对 41 个粒子分帧) + var turret = sit != null ? sit.currentTurret as FlyTurretData : null; + if (turret != null) + { + LoadingStallWatchdog.Phase = "预热·飞机(骨架+粒子分帧)"; + turret.PrewarmEnterPlane(); + } + } + + /// XJMH 默认炮台 = 兄弟顺序最靠前的那把(与 SitPosData.NormalizeXJMHTurretList 的排序一致) + private static TurretData GetDefaultXJMHTurret(SitPosData sit) + { + if (sit == null || sit.xjmhTurretList == null) return null; + TurretData best = null; + int bestIdx = int.MaxValue; + for (int i = 0; i < sit.xjmhTurretList.Count; i++) + { + var t = sit.xjmhTurretList[i]; + if (t == null) continue; + int idx = t.transform.GetSiblingIndex(); + if (idx < bestIdx) { bestIdx = idx; best = t; } + } + return best; + } + + /// + /// 收集"根若激活就会被激活"的粒子节点并先把它们关掉(此刻根未激活,SetActive(false) 只置标志、 + /// 不触发任何初始化)——配合 ChunkEnableParticles 把这些粒子系统的首初始化逐帧分摊。 + /// 容器(父级)关着的不收集;自身默认关闭的粒子也不动。 + /// + private static List CollectActiveParticlesUnder(Transform root) + { + if (root == null) return null; + List list = null; + var systems = root.GetComponentsInChildren(true); + for (int i = 0; i < systems.Length; i++) + { + var ps = systems[i]; + if (ps == null || ps.gameObject == root.gameObject) continue; + if (!IsActiveUnderRoot(ps.transform, root)) continue; + ps.gameObject.SetActive(false); + if (list == null) list = new List(16); + list.Add(ps.gameObject); + } + return list; + } + + /// 节点在"根若激活"的前提下是否会被激活(检查自身到根之间每级的 activeSelf) + private static bool IsActiveUnderRoot(Transform node, Transform root) + { + for (var t = node; t != null && t != root; t = t.parent) + if (!t.gameObject.activeSelf) return false; + return true; + } + + /// 逐帧分批点亮粒子节点(每帧约 8ms 预算:快机一帧多开、慢机一帧一个) + private static IEnumerator ChunkEnableParticles(List nodes) + { + const double budgetMs = 8.0; + var sw = System.Diagnostics.Stopwatch.StartNew(); + for (int i = 0; i < nodes.Count; i++) + { + if (nodes[i] != null) nodes[i].SetActive(true); + if (sw.Elapsed.TotalMilliseconds >= budgetMs) + { + sw.Restart(); + yield return null; + } + } + yield return null; + } + + /// + /// ⭐ 进房早期(0~2%)预关闭"未激活座位"的多余 XJMH 炮台。 + /// 原因:XJMH 座位预制体里 4 把炮台(SkeletonGraphic)默认激活,而座位首次 SetActive 时 + /// 4 套骨架/材质/贴图会挤在同一帧全部首初始化(单帧尖峰);预关后 Awake 的 InitXJMHTurret + /// 只点亮默认那把 → 激活帧只初始化 1 套。其它玩家/bot 的座位在 100% 入座时才激活, + /// 同样受益(否则每个座位入座都要初始化 4 套)。 + /// ⚠️ 只处理"尚未激活"的座位:已激活的座位 Awake 已跑过,会被 Admin 逻辑重新点亮, + /// 动它反而可能把炮台关掉回不来。 + /// + private void PreCloseInactiveSitExtraTurrets() + { + if (list_Sits == null) return; + foreach (var sit in list_Sits) + { + if (sit == null || sit.turretControlMode != TurretControlMode.XJMH) continue; + if (sit.gameObject.activeSelf) continue; // 已激活座位不动 + // 列表为空时 Awake 不会补开炮台 → 全关会留空座位,跳过 + if (sit.xjmhTurretList == null || sit.xjmhTurretList.Count == 0) continue; for (int i = 0; i < sit.xjmhTurretList.Count; i++) { var t = sit.xjmhTurretList[i]; if (t != null && t.gameObject.activeSelf) t.gameObject.SetActive(false); } } - - // ① 提前激活本地座位:Awake → InitXJMHTurret → 激活默认炮台(首初始化在这里完成, - // 被加载界面遮住)。100% 时 JoinGame 再 SetActive(true) 是幂等的,不影响正常入座流程 - if (willActivate) - sit.gameObject.SetActive(true); - - // 座位/炮台初始化(骨架首帧网格上传、材质铺开)多留两帧,再点飞机 - yield return null; - yield return null; - - // ② 飞机隐藏预热(座位激活后 currentTurret 已就绪 = 入场会用的那把) - var turret = sit != null ? sit.currentTurret as FlyTurretData : null; - if (turret != null) - turret.PrewarmEnterPlane(); } private void RefreshGGKCount() @@ -1379,6 +1509,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base // 90 ⭐确认点:走到这里时所有资源 await 都已完成 // 90~100 平滑推进收尾(替代原来的 2 秒死等,条一直在动) SetLoadProgress(0.01f); + LoadingStallWatchdog.Phase = "0~2 卸载大厅UI"; // ⭐ 卸载大厅 UI 资源(进游戏释放 HomeUIRes 内存,回大厅时由离房流程重新预载): // 大厅主面板此时已被关闭(实例销毁时 SubUIManager.ReleaseAll 已释放子面板句柄), @@ -1391,7 +1522,13 @@ public class GameMainPanel_FishBase : GameMainPanel_Base // 不再把"打开小包 + 标签求交集"的耗时压在 78% 那一帧(进度条卡死点) _ = LoadBulletClip(); + // ⭐ 预关"未激活座位"的多余 XJMH 炮台(4 把 SkeletonGraphic 默认全开): + // 座位首次 SetActive 才做骨架/材质首初始化——预关后激活那一刻只初始化默认那把(1 套); + // 其它玩家/bot 的座位在 100% 入座时才激活,4 套→1 套,大幅削掉入座瞬间的卡顿 + PreCloseInactiveSitExtraTurrets(); + // 路线:2~30(大包同步解压期间回调不刷新 → 预估进度保证条持续推进) + LoadingStallWatchdog.Phase = "2~30 路线"; StartCreepProgress(0.02f, 0.30f); await MonsterMoveLine.LoadBezier(p => { @@ -1400,6 +1537,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base if (flow != _resourceFlowToken) return; // ⭐ 期间已退出/切换,本轮作废 // 鱼包:30~60,内部子进度实时上报(预估进度兜底 LZMA 解压阻塞段,不再卡在 30) + LoadingStallWatchdog.Phase = "30~60 鱼包"; StartCreepProgress(0.30f, 0.60f); await FishResourcesData.LoadFishAssetsData(p => { @@ -1408,7 +1546,16 @@ public class GameMainPanel_FishBase : GameMainPanel_Base if (flow != _resourceFlowToken) return; // 玩法音频:60~78 + LoadingStallWatchdog.Phase = "60~78 音频"; StartCreepProgress(0.60f, 0.78f); + + // ⭐ XJMH 座位/飞机预热:把"首次实例化"的活提前到加载期做(这些活反正躲不掉,不做就会 + // 堆到 100% 入座/入场动画那一帧卡——已确认注释掉它 100% 会卡一下)。 + // 放在音频段起点:本段是真实加载耗时段(大量小包 IO,本来就有加载卡顿),混在里面的小帧不显眼。 + // 重活已全部按帧拆开:座位外壳 1 帧 / 炮台骨架 1 帧 / 座位·炮台·飞机粒子逐帧点亮(每帧 8ms 预算)。 + // 如果你更愿意"加载顺滑、接受 100% 卡一下",把下面这行注释回去即可。 + PrewarmXJMHForLocalSeat(); + await AudioResourcesData.LoadAudioGame(p => { if (flow == _resourceFlowToken) SetLoadProgress(Mathf.Max(_loadProgress, Mathf.Lerp(0.60f, 0.78f, p))); @@ -1418,6 +1565,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base // ⭐ 开火音效 PW_Shoot 在 Default Local Group(Audio+PTCBZS 双标签交集检索): // 只命中 Default 小包,不把其它玩法的巨型音频包拉进内存; // 资源在 0~2% 已后台 kick-off 且全局常驻,这里 await 基本瞬时通过(不再把打开包的耗时压在 78% 主线程上) + LoadingStallWatchdog.Phase = "78~84 开火音效"; StartCreepProgress(0.78f, 0.84f); await LoadBulletClip(); StopCreepProgress(); @@ -1436,6 +1584,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base } } + LoadingStallWatchdog.Phase = "84~90 EnterRoom/初始鱼"; CreateFishManager.Instance.EnterRoom(gameType); CreateFishManager.Instance.isResources = true; SetLoadProgress(0.87f); @@ -1468,12 +1617,10 @@ public class GameMainPanel_FishBase : GameMainPanel_Base AudioManager.Instance.ChangeBG(GameBgmAudioId); // ⭐ 90% 确认点:走到这里所有资源加载都已完成(上面全是 await 顺序执行) + // (XJMH 座位/飞机预热已挪到 60% 音频段起点——这里是纯净的平滑收尾,不再有重活) + LoadingStallWatchdog.Phase = "90~100 收尾"; SetLoadProgress(0.9f); - // ⭐ XJMH 预热:借 90→100 的收尾窗口(进度条还在走)提前把本地座位/炮台 + 入场飞机点亮。 - // 这些 XJMH 独有资产首次初始化(骨架/粒子/贴图)很重,堆到 100% 那帧玩家会看到卡一下 - PrewarmXJMHForLocalSeat(); - // ⭐ 90→100 平滑收尾:约 1 秒走完,进度条持续在动(替代原 2 秒死等; // 保留这段缓冲是等出鱼首帧/场景渲染稳定,比原来快 1 秒+) for (int i = 1; i <= 20; i++) @@ -1491,6 +1638,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base // ⭐ 加载到 100%(进度条已隐藏):标记完成 + 执行挂起的进房初始化(座位/房间信息)。 // 入场动画等表现由初始化链路在此之后触发——玩家可见、位置已就绪 + LoadingStallWatchdog.Phase = "100 首屏初始化"; IsGameLoadCompleted = true; if (_pendingFirstShowInit) { @@ -1498,6 +1646,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base ApplyFirstShowInit(); } + // 加载与首屏初始化完成 → 关闭监测(阶段置空,不再打日志) + LoadingStallWatchdog.Phase = ""; + // ⭐ 无操作自动退出:加载完成,从现在开始计无操作时间 ArmIdleAutoExit(); } @@ -1868,6 +2019,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base private void SetLoadProgress(float p) { p = Mathf.Clamp01(p); + // ⭐ 不回退:场景加载阶段的爬升/预估进度可能已把条推到前面, + // 后续固定值(0.01/0.02 等)不能把它拉回去(带上限取大值) + if (p < _loadProgress) p = _loadProgress; _loadProgress = p; if (_changeRoomBar != null) _changeRoomBar.SetProgress(0.55f + p * 0.45f); @@ -1875,6 +2029,22 @@ public class GameMainPanel_FishBase : GameMainPanel_Base JinDuTiao.SetProgress(p); } + /// + /// ⭐ 第一步(只加载了 UI 面板、游戏场景还在加载中):进度条从 0 缓慢爬升到约 3%。 + /// 由 WebSocketReponseList 在"面板显示后、场景加载前"调用;真实资源加载开始时由 + /// LoadResources 接管(SetLoadProgress 不回退,固定起点值不会把条拉回去)。 + /// + public void BeginSceneLoadingProgress() + { + SetLoadProgress(0.005f); + // creep 段末 = 0.05(内部上限 = 段末-2% ≈ 3%,给 LoadResources 的接管留空间) + StartCreepProgress(0.005f, 0.05f); + + // ⭐ 挂"加载卡顿监测"(诊断):单帧 ≥120ms 打日志+阶段名,直接定位卡主线程的环节 + LoadingStallWatchdog.Attach(this); + LoadingStallWatchdog.Phase = "场景加载(0~3%)"; + } + // ---- 预估进度(大包 LZMA 同步解压期间主线程阻塞、回调不刷新 → 条卡死像没响应)---- /// 加载进度当前值(真实或预估),预估推进时判断不回退 private float _loadProgress = 0f; @@ -6767,3 +6937,37 @@ public class GameMainPanel_FishBase : GameMainPanel_Base #endregion } + +/// +/// ⭐ 加载期卡顿监测(诊断用,挂面板对象、随面板销毁): +/// 加载全程 Update 检查单帧耗时,凡单帧 ≥120ms 就打一条日志并带"当前阶段"—— +/// 用于精确定位是哪个环节顶住了主线程(进度条/界面动画卡顿的根因)。 +/// 阶段名由 GameMainPanel_FishBase / FlyTurretData 在流程中随时更新;加载完成后置空即静默。 +/// 排查完成后可整体删除本类及各 Phase 赋值。 +/// +public class LoadingStallWatchdog : MonoBehaviour +{ + /// 当前阶段(由加载流程更新;为空=不监测) + public static string Phase = ""; + private static int _logged; + + /// 在面板下挂一个监测器(存在则不重复创建) + public static void Attach(MonoBehaviour host) + { + if (host == null || host.GetComponentInChildren(true) != null) return; + var go = new GameObject("LoadingStallWatchdog"); + go.transform.SetParent(host.transform, false); + go.AddComponent(); + } + + private void Update() + { + if (string.IsNullOrEmpty(Phase)) return; + float dt = Time.unscaledDeltaTime; + if (dt >= 0.12f && _logged < 80) + { + _logged++; + Debug.Log($"[加载卡顿] 「{Phase}」单帧 {dt * 1000f:F0}ms(第 {_logged} 条)"); + } + } +} diff --git a/Assets/MyGame/aa/UI/Game/XJMH/GameMainPanelXJMH.prefab b/Assets/MyGame/aa/UI/Game/XJMH/GameMainPanelXJMH.prefab index 9d1e2762b..d25b08618 100644 --- a/Assets/MyGame/aa/UI/Game/XJMH/GameMainPanelXJMH.prefab +++ b/Assets/MyGame/aa/UI/Game/XJMH/GameMainPanelXJMH.prefab @@ -4308,6 +4308,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 6dec7b077ca94844c8c1360f71e7aa58, type: 3} m_Name: m_EditorClassIdentifier: + buttonClickAudioID: 60009 panelAddressConfig: {fileID: 11400000, guid: 21aa1c64fb06c5d4c9769a8650a3dbc1, type: 2} subPanelParent: {fileID: 1078076760308578925} textMeshProUGUI_RoomID: {fileID: 222610915537559086} diff --git a/Assets/MyGame/aa/UI/Home/Sub/UIHome_BY_SelectRoomLevelPanel.cs b/Assets/MyGame/aa/UI/Home/Sub/UIHome_BY_SelectRoomLevelPanel.cs index 27861008e..b4e16ec0a 100644 --- a/Assets/MyGame/aa/UI/Home/Sub/UIHome_BY_SelectRoomLevelPanel.cs +++ b/Assets/MyGame/aa/UI/Home/Sub/UIHome_BY_SelectRoomLevelPanel.cs @@ -222,6 +222,17 @@ public class UIHome_BY_SelectRoomLevelPanel : SubUIBase } if (currGameType!= GameType.JQQD) { + // ⭐ 预载该玩法的主游戏面板(纯 UI 资源、异步后台加载):选场页停留的几秒足够加载完, + // 点场次进房时 ShowAsync 命中缓存直接秒开——"第一步只加载 UI,先把 UI 展示出来, + // 显得加载快",其它资源(场景/鱼/音频)等 UI 出来后再加载 + switch (currGameType) + { + case GameType.FLZY: UIManager.Instance.PreloadPanel(UIPanel.MainGamePanelFLZY); break; + case GameType.YYSC: UIManager.Instance.PreloadPanel(UIPanel.MainGamePanelYYSC); break; + case GameType.XJMH: UIManager.Instance.PreloadPanel(UIPanel.MainGamePanelXJMH); break; + case GameType.HHWK: UIManager.Instance.PreloadPanel(UIPanel.MainGamePanelHHWK); break; + } + GameObject LV1 = SelectRoom1.transform.GetChild(((int)currGameType)).gameObject; LV1.SetActive(true); var Lv1Item = LV1.GetComponent();