This commit is contained in:
2026-10-12 19:28:14 +08:00
parent c1c5f05d3f
commit 3ee5643e82
16 changed files with 1259 additions and 93 deletions
@@ -16084,6 +16084,22 @@ PrefabInstance:
propertyPath: m_Name propertyPath: m_Name
value: PW_Plane_1 value: PW_Plane_1
objectReference: {fileID: 0} 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} - target: {fileID: 4313960313673800, guid: f4f7967ac88aaaf4f975719c27df12aa, type: 3}
propertyPath: m_LocalScale.x propertyPath: m_LocalScale.x
value: 4.4716 value: 4.4716
@@ -17289,6 +17289,11 @@ PrefabInstance:
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 1084716326253117915, guid: 29589d6bc93a47e499105e964f1f5c6b,
type: 3}
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1139735090001087078, guid: 29589d6bc93a47e499105e964f1f5c6b, - target: {fileID: 1139735090001087078, guid: 29589d6bc93a47e499105e964f1f5c6b,
type: 3} type: 3}
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
@@ -17354,6 +17359,11 @@ PrefabInstance:
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 3686788617825013159, guid: 29589d6bc93a47e499105e964f1f5c6b,
type: 3}
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3706220059061985850, guid: 29589d6bc93a47e499105e964f1f5c6b, - target: {fileID: 3706220059061985850, guid: 29589d6bc93a47e499105e964f1f5c6b,
type: 3} type: 3}
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
@@ -17469,11 +17479,21 @@ PrefabInstance:
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 8456440153089082935, guid: 29589d6bc93a47e499105e964f1f5c6b,
type: 3}
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8553831016633651482, guid: 29589d6bc93a47e499105e964f1f5c6b, - target: {fileID: 8553831016633651482, guid: 29589d6bc93a47e499105e964f1f5c6b,
type: 3} type: 3}
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 8990963234669806065, guid: 29589d6bc93a47e499105e964f1f5c6b,
type: 3}
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
- target: {fileID: 9011820818510108152, guid: 29589d6bc93a47e499105e964f1f5c6b, - target: {fileID: 9011820818510108152, guid: 29589d6bc93a47e499105e964f1f5c6b,
type: 3} type: 3}
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
@@ -28841,6 +28841,11 @@ PrefabInstance:
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 1092006923787695085, guid: 9672abcceca61c846b14184d9bba766f,
type: 3}
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1898051008385034303, guid: 9672abcceca61c846b14184d9bba766f, - target: {fileID: 1898051008385034303, guid: 9672abcceca61c846b14184d9bba766f,
type: 3} type: 3}
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
@@ -28856,6 +28861,11 @@ PrefabInstance:
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 2372885871247165783, guid: 9672abcceca61c846b14184d9bba766f,
type: 3}
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2414516914643537171, guid: 9672abcceca61c846b14184d9bba766f, - target: {fileID: 2414516914643537171, guid: 9672abcceca61c846b14184d9bba766f,
type: 3} type: 3}
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
@@ -28876,6 +28886,11 @@ PrefabInstance:
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 3336049404196487681, guid: 9672abcceca61c846b14184d9bba766f,
type: 3}
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3424540851437192074, guid: 9672abcceca61c846b14184d9bba766f, - target: {fileID: 3424540851437192074, guid: 9672abcceca61c846b14184d9bba766f,
type: 3} type: 3}
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
@@ -28886,6 +28901,11 @@ PrefabInstance:
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 3684409744614040008, guid: 9672abcceca61c846b14184d9bba766f,
type: 3}
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4178955796969763688, guid: 9672abcceca61c846b14184d9bba766f, - target: {fileID: 4178955796969763688, guid: 9672abcceca61c846b14184d9bba766f,
type: 3} type: 3}
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
@@ -28297,6 +28297,11 @@ PrefabInstance:
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 2736570820237217112, guid: 6bca3101cb2436147ba613930ade7bc6,
type: 3}
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2854444671909311366, guid: 6bca3101cb2436147ba613930ade7bc6, - target: {fileID: 2854444671909311366, guid: 6bca3101cb2436147ba613930ade7bc6,
type: 3} type: 3}
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
@@ -28307,6 +28312,16 @@ PrefabInstance:
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
value: 0 value: 0
objectReference: {fileID: 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, - target: {fileID: 4277595997587927750, guid: 6bca3101cb2436147ba613930ade7bc6,
type: 3} type: 3}
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
@@ -28397,6 +28412,11 @@ PrefabInstance:
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 7242293744027100629, guid: 6bca3101cb2436147ba613930ade7bc6,
type: 3}
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7257220591867521751, guid: 6bca3101cb2436147ba613930ade7bc6, - target: {fileID: 7257220591867521751, guid: 6bca3101cb2436147ba613930ade7bc6,
type: 3} type: 3}
propertyPath: m_SortingLayer propertyPath: m_SortingLayer
+11 -2
View File
@@ -708,8 +708,17 @@ public class UIManager : MonoSingleton<UIManager>
yield return handle; yield return handle;
if (handle.Status == AsyncOperationStatus.Succeeded) if (handle.Status == AsyncOperationStatus.Succeeded)
{ {
prefabCache[type] = handle.Result; // ⭐ 竞态守卫:预载期间 ShowAsync 已自行加载并缓存了同一面板(玩家点得快时发生),
handleCache[type] = handle; // 这里若再覆盖缓存,预载拿到的句柄会丢失、引用计数 +1 泄漏 → 释放本句柄即可
if (prefabCache.ContainsKey(type))
{
Addressables.Release(handle);
}
else
{
prefabCache[type] = handle.Result;
handleCache[type] = handle;
}
} }
else else
{ {
+13
View File
@@ -3855,6 +3855,7 @@ GameObject:
- component: {fileID: 2075409202} - component: {fileID: 2075409202}
- component: {fileID: 2075409203} - component: {fileID: 2075409203}
- component: {fileID: 2075409204} - component: {fileID: 2075409204}
- component: {fileID: 2075409205}
m_Layer: 0 m_Layer: 0
m_Name: Manager m_Name: Manager
m_TagString: Untagged m_TagString: Untagged
@@ -4345,6 +4346,18 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
audioMixer: {fileID: 24100000, guid: ca43f9a7549657a49a2978e97c7a621a, type: 2} 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 --- !u!1001 &3569938244496052059
PrefabInstance: PrefabInstance:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -824,7 +824,10 @@ public class CreateFishManager : MonoSingleton<CreateFishManager>
/// </summary> /// </summary>
private IEnumerator SpawnFishBatchedCoroutine(List<OnFishSpawnFishItem> queue, bool isSync) private IEnumerator SpawnFishBatchedCoroutine(List<OnFishSpawnFishItem> queue, bool isSync)
{ {
const int batchSize = 5; // 每帧生成5条 // ⭐ 加载期(进度条还在走、面板未加载完成)每帧只出 2 条:单帧 5 条 Spine 鱼
// (首次创建含骨架/贴图初始化)会卡住进度条和界面动画(玩家反馈"90% 卡"的一部分);
// 加载完成进房后恢复每帧 5 条(正常出鱼间隔大,不挤)
int batchSize = (currGameType != null && !currGameType.IsGameLoadCompleted) ? 2 : 5;
int index = 0; int index = 0;
while (index < queue.Count) while (index < queue.Count)
{ {
@@ -170,6 +170,16 @@ namespace Assets.MyGame.Scripts.Net
/// </summary> /// </summary>
private static IEnumerator LoadSceneThenResources(UIBase panel, string sceneName) 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(防止人机通知触发重复加载) // ③ 目标场景仅加载一次:标记已加载后不再重复 LoadSceneAsync(防止人机通知触发重复加载)
if (!string.IsNullOrEmpty(sceneName) && !_loadedGameScenes.Contains(sceneName)) if (!string.IsNullOrEmpty(sceneName) && !_loadedGameScenes.Contains(sceneName))
{ {
+56 -11
View File
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using Spine.Unity; using Spine.Unity;
using UnityEngine; using UnityEngine;
@@ -591,9 +592,11 @@ public class FlyTurretData : TurretData
} }
// ==================== 入场飞机预热(进房加载收尾时由面板调用,卡顿被进度条遮住) ==================== // ==================== 入场飞机预热(进房加载收尾时由面板调用,卡顿被进度条遮住) ====================
// XJMH 进入房间时"命中"的那架完整飞机(骨架 + 尾迹/护盾十多个粒子系统 + 贴图首次上传)首帧初始化很重; // XJMH 进入房间时"命中"的那架完整飞机 PW_Plane(骨架 + 41 个粒子系统、2.2MB 预制体、52 个节点):
// 全部堆在"加载到100%、进度条已隐藏"那一帧做,玩家会看到明显卡顿(其他玩法没有这套 XJMH 独有资产)。 // 整根激活会让 41 套粒子在同一帧全部首初始化(分配缓冲/上传网格贴图)——单帧几百 ms 主线程尖峰。
// 预热 = 在进度条还在走时把飞机临时挂到激活的父节点下、缩到 0 隐藏,激活一两帧完成初始化,再原样还原。 // 这就是"只有星际迷航进房卡、其它玩法不卡"的原因(其他玩法没有这套 XJMH 独有资产)。
// 预热 = 进度条还在走时把飞机临时挂到激活父节点下、缩到 0 隐藏;骨架 1 帧 + 粒子按每帧 8ms 预算
// 逐帧点亮完成初始化,再原样还原(玩家全程不可见)。
private Coroutine _prewarmRoutine; private Coroutine _prewarmRoutine;
private Transform _prewarmPlane; // 预热中的飞机(被打断时还原用) private Transform _prewarmPlane; // 预热中的飞机(被打断时还原用)
@@ -603,6 +606,8 @@ public class FlyTurretData : TurretData
private Quaternion _prewarmOrigRot; private Quaternion _prewarmOrigRot;
private Vector3 _prewarmOrigScale; private Vector3 _prewarmOrigScale;
private bool _prewarmOrigActive; private bool _prewarmOrigActive;
/// <summary>粒子分帧点亮的帧内计时(Stopwatch 不受 Time.realtimeSinceStartup 帧内不刷新影响)</summary>
private static readonly System.Diagnostics.Stopwatch _prewarmFrameSw = new System.Diagnostics.Stopwatch();
/// <summary>入场飞机预热(面板在加载收尾阶段调用;座位/炮台需已激活)。幂等:重复调用只跑一次。</summary> /// <summary>入场飞机预热(面板在加载收尾阶段调用;座位/炮台需已激活)。幂等:重复调用只跑一次。</summary>
public void PrewarmEnterPlane() public void PrewarmEnterPlane()
@@ -625,27 +630,67 @@ public class FlyTurretData : TurretData
_prewarmOrigScale = plane.localScale; _prewarmOrigScale = plane.localScale;
_prewarmOrigActive = plane.gameObject.activeSelf; _prewarmOrigActive = plane.gameObject.activeSelf;
// 只开入场要用的 Body、关掉免费三类——避免把五十来个粒子系统全部初始化(反而更重); // 容器显隐(与入场动画 EnterActionRoutine 一致):开 Body/护盾、关免费三类
// ⭐ 护盾粒子拆到下一帧再开:和 Body 骨架/粒子挤同一帧 = 单帧尖峰(90% 收尾窗口卡顿的来源之一)
SetPlaneChildActive(plane, "Body", true); SetPlaneChildActive(plane, "Body", true);
SetPlaneChildActive(plane, "PW_Effect_Defend", true);
SetPlaneChildActive(plane, "FreeEffect", false); SetPlaneChildActive(plane, "FreeEffect", false);
SetPlaneChildActive(plane, "FreeEffect_Crit", false); SetPlaneChildActive(plane, "FreeEffect_Crit", false);
SetPlaneChildActive(plane, "FreeEffect_Speedup", false); SetPlaneChildActive(plane, "FreeEffect_Speedup", false);
// ⭐ 关键修复("只有 XJMH 进房卡主线程"的根因):
// 整根 SetActive(true) 会让 41 个粒子系统在同一帧全部首初始化(单帧几百 ms)。
// 先把"会被激活的"粒子节点逐个关掉(飞机根此刻未激活,只是置标志、不做初始化),
// 等飞机激活(这一帧只初始化骨架等非粒子部分)后,按"每帧约 8ms 预算"逐帧点亮——
// 设备快一帧多开几个、设备慢一帧只开一个,单帧尖峰摊平到十几帧,不再顶住主线程。
// ⚠️ 只动"容器开着"的粒子(Body/护盾);免费三类容器已关、默认关着的粒子都不动,保持原状。
var psNodes = new List<GameObject>(64);
foreach (var ps in plane.GetComponentsInChildren<ParticleSystem>(true))
{
if (ps == null || ps.gameObject == plane.gameObject) continue;
if (!IsActiveUnder(ps.transform, plane)) continue; // 容器关着的(免费三类等)不预热
ps.gameObject.SetActive(false);
psNodes.Add(ps.gameObject);
}
// 临时挂到根节点(激活中)并缩到 0:既能触发初始化,又完全不可见 // 临时挂到根节点(激活中)并缩到 0:既能触发初始化,又完全不可见
Transform holder = transform.root; Transform holder = transform.root;
if (holder != null && holder != _prewarmOrigParent) if (holder != null && holder != _prewarmOrigParent)
plane.SetParent(holder, true); plane.SetParent(holder, true);
plane.localScale = Vector3.zero; plane.localScale = Vector3.zero;
plane.gameObject.SetActive(true); LoadingStallWatchdog.Phase = "预热·飞机骨架激活帧";
yield return null; // Body 骨架首帧网格 plane.gameObject.SetActive(true); // 这一帧只初始化骨架等非粒子部分
SetPlaneChildActive(plane, "PW_Effect_Defend", true); // 护盾粒子单独一帧开 yield return null; // 骨架首帧网格
yield return null; // 护盾首帧模拟
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(); FinishPrewarmRestore();
Debug.Log($"[XJMH入场] 飞机预热完成({plane.name})"); Debug.Log($"[XJMH入场] 飞机预热完成({plane.name},粒子分帧点亮 {psNodes.Count} 个)");
_prewarmRoutine = null; _prewarmRoutine = null;
// 诊断:预热结束,把卡顿监测阶段还给加载段(后续卡顿归因不错位到预热)
LoadingStallWatchdog.Phase = "60~78 音频加载";
}
/// <summary>节点在"飞机根若激活"的前提下是否会被激活(检查自身到飞机根之间每级的 activeSelf)</summary>
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;
} }
/// <summary>预热还原:飞机隐藏并恢复到预热前的挂载/变换/激活状态(协程正常收尾或中途被打断都用它)</summary> /// <summary>预热还原:飞机隐藏并恢复到预热前的挂载/变换/激活状态(协程正常收尾或中途被打断都用它)</summary>
@@ -346,6 +346,14 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
return rt; return rt;
} }
/// <summary>
/// ⭐ 预热用:本次座位首次激活时先"只整理炮台列表、不点亮默认炮台"——
/// 把炮台 Spine 骨架的首初始化(几十~上百 ms)拆到下一帧单独做,避免座位激活那一帧
/// 同时扛"座位整层 OnEnable + 炮台骨架首建"两个大活。由 GameMainPanel_FishBase 预热流程设置;
/// 任何入座/兜底路径(ShowPlayer)都会强制复位并保证炮台被点亮,不会留空座位。
/// </summary>
[System.NonSerialized] public bool deferDefaultTurretLightUp;
/// <summary> /// <summary>
/// XJMH 模式:炮台是座位自带子对象,预制体里已摆好,不需要 LoadTurret 加载。 /// XJMH 模式:炮台是座位自带子对象,预制体里已摆好,不需要 LoadTurret 加载。
/// ⭐ 默认炮台 = 排序后索引 0 的那把(对应座位 GetChild(0) 的绿炮台)。 /// ⭐ 默认炮台 = 排序后索引 0 的那把(对应座位 GetChild(0) 的绿炮台)。
@@ -359,9 +367,33 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
} }
NormalizeXJMHTurretList(); NormalizeXJMHTurretList();
// ⭐ 预热路径:只整理列表,点亮留给预热协程下一帧调 ActivateDefaultXJMHTurretForPrewarm
if (deferDefaultTurretLightUp) return;
ApplyXJMHTurret(0); ApplyXJMHTurret(0);
} }
/// <summary>
/// 预热续帧:点亮默认炮台(配合 deferDefaultTurretLightUp,把炮台骨架首初始化拆到单独一帧)。
/// ⚠️ "是否已点亮"必须看列表里有没有"激活状态"的炮台——currentTurret 是可序列化字段,
/// 预制体里预置了引用,不代表炮台已激活(用它当判据会出现"炮台永远不点亮"的 bug)。
/// </summary>
public void ActivateDefaultXJMHTurretForPrewarm()
{
if (turretControlMode != TurretControlMode.XJMH) return;
if (HasActiveXJMHTurret()) return; // 已点亮(正常入座路径已做过)
deferDefaultTurretLightUp = false;
InitXJMHTurret();
}
/// <summary>列表里当前是否有"激活状态"的炮台("默认炮台是否已点亮"的正确判据)</summary>
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;
}
/// <summary> /// <summary>
/// 整理 XJMH 炮台列表:剔除空引用,并按座位下的兄弟顺序排序。 /// 整理 XJMH 炮台列表:剔除空引用,并按座位下的兄弟顺序排序。
/// ⭐ 排序原因:切换面板的 4 个按钮固定是 绿/蓝/金/VIP(索引 0~3), /// ⭐ 排序原因:切换面板的 4 个按钮固定是 绿/蓝/金/VIP(索引 0~3),
@@ -1137,6 +1169,11 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
} }
else else
{ {
// ⭐ 兜底:预热被中断(激活了座位但还没轮到点亮炮台就退出/销毁)时,入座这里补点亮,
// 保证任何时候都不会出现"座位亮着但炮台是空的"(判据:列表里有没有激活的炮台)
if (!HasActiveXJMHTurret())
ActivateDefaultXJMHTurretForPrewarm();
// ⭐ XJMH 进入房间入场动画(原版 Battery:EnterAction):仅本地座位播 // ⭐ XJMH 进入房间入场动画(原版 Battery:EnterAction):仅本地座位播
// (Enter 音效 + 飞机从下方升起变回炮台 + PlaneBack 音效); // (Enter 音效 + 飞机从下方升起变回炮台 + PlaneBack 音效);
// 本次激活周期只播一次(OnDisable 复位,重进房重新播),切炮台不受影响 // 本次激活周期只播一次(OnDisable 复位,重进房重新播),切炮台不受影响
@@ -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
}
]
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 37136faa1f387f2498c01033bc961bbc
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -246,119 +246,254 @@
{ {
"yzNumber": 71001, "yzNumber": 71001,
"lineList": [ "lineList": [
1, 1
2,
3
], ],
"count": 3 "count": 1
}, },
{ {
"yzNumber": 71002, "yzNumber": 71002,
"lineList": [ "lineList": [
4, 2
5,
6
], ],
"count": 5 "count": 1
}, },
{ {
"yzNumber": 71003, "yzNumber": 71003,
"lineList": [ "lineList": [
7, 3
8,
9
], ],
"count": 4 "count": 1
}, },
{ {
"yzNumber": 71004, "yzNumber": 71004,
"lineList": [ "lineList": [
10, 4
11,
12
], ],
"count": 6 "count": 1
}, },
{ {
"yzNumber": 71005, "yzNumber": 71005,
"lineList": [ "lineList": [
13, 5
14,
15
], ],
"count": 3 "count": 1
}, },
{ {
"yzNumber": 71006, "yzNumber": 71006,
"lineList": [ "lineList": [
16, 6
17,
18
], ],
"count": 6 "count": 1
}, },
{ {
"yzNumber": 71007, "yzNumber": 71007,
"lineList": [ "lineList": [
19, 7
20,
21
], ],
"count": 4 "count": 1
}, },
{ {
"yzNumber": 71008, "yzNumber": 71008,
"lineList": [ "lineList": [
22, 8
23,
24
], ],
"count": 2 "count": 1
}, },
{ {
"yzNumber": 71009, "yzNumber": 71009,
"lineList": [ "lineList": [
25, 9
26,
27
], ],
"count": 8 "count": 1
}, },
{ {
"yzNumber": 71010, "yzNumber": 71010,
"lineList": [ "lineList": [
28, 10
29,
30
], ],
"count": 3 "count": 1
}, },
{ {
"yzNumber": 71011, "yzNumber": 71011,
"lineList": [ "lineList": [
31, 11
32,
33
], ],
"count": 3 "count": 1
}, },
{ {
"yzNumber": 71012, "yzNumber": 71012,
"lineList": [ "lineList": [
34, 12
35,
36
], ],
"count": 3 "count": 1
}, },
{ {
"yzNumber": 71013, "yzNumber": 71013,
"lineList": [ "lineList": [
1, 13
3,
5
], ],
"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, "yzNumber": 70401,
@@ -477,7 +477,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
} }
/// <summary> /// <summary>
/// XJMH 进房首帧预热(加载 90%→100% 收尾窗口调用,进度条还显示着玩家看不到): /// XJMH 进房预热(60% 音频段起点调用,进度条还显示着玩家看不到):
/// 把本地座位(座位自带炮台随 Awake 一起初始化)与"入场要用的那架完整飞机"提前点亮—— /// 把本地座位(座位自带炮台随 Awake 一起初始化)与"入场要用的那架完整飞机"提前点亮——
/// 这些 XJMH 独有资产首次初始化(骨架/粒子/贴图首上传)很重,全堆在 100% 那一帧玩家会看到明显卡顿。 /// 这些 XJMH 独有资产首次初始化(骨架/粒子/贴图首上传)很重,全堆在 100% 那一帧玩家会看到明显卡顿。
/// ⭐ 按帧拆散(避免"90% 卡死"):先让一帧(进度条先刷新)→ 只留默认炮台、关掉其余 3 把 → /// ⭐ 按帧拆散(避免"90% 卡死"):先让一帧(进度条先刷新)→ 只留默认炮台、关掉其余 3 把 →
@@ -497,39 +497,169 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
private IEnumerator PrewarmXJMHCoroutine(SitPosData sit) private IEnumerator PrewarmXJMHCoroutine(SitPosData sit)
{ {
// ⭐ 先等一帧:StartCoroutine 会同步执行到第一个 yield —— 原先座位激活的重活 // ⭐ 先等一帧:StartCoroutine 会同步执行到第一个 yield —— 座位激活的重活不能和进度条
// 和"90%"写入进度条是同一帧(数字还没刷新、主线程就被占住,看起来"90 卡死")。 // 写数字挤在同一帧(玩家看到"卡在 60/61")。先让一帧,进度条先刷新
// 先让一帧,进度条和收尾循环跑起来再干重活
yield return null; yield return null;
bool willActivate = sit != null && !sit.gameObject.activeSelf; bool willActivate = sit != null && !sit.gameObject.activeSelf;
// ⭐ 只有"座位从未激活、这次预热才激活"时才预关炮台: // ⭐ 分帧方案(XJMH 进房不卡主线程的关键)—— 把所有"可以错开的初始化"从同一帧剥出去:
// 预制体里 4 把 XJMH 炮台(SkeletonGraphic)默认激活,座位 SetActive 时 4 套骨架/材质/贴图 // ① 座位外壳(1 帧,只扛座位整层 OnEnable)
// 挤在同一帧全部首初始化(单帧尖峰巨大);先关掉后,这一帧只初始化 Awake 点亮的默认那把。 // ② 默认炮台 Spine 骨架首初始化(单独 1 帧,见 SitPosData.deferDefaultTurretLightUp)
// ⚠️ 座位已在激活状态时不能动(Awake 不会再跑,关掉就回不来) // ③ 座位外壳粒子 + 默认炮台粒子(本方法先关掉,之后逐帧点亮)
if (willActivate && sit.xjmhTurretList != null) // ④ 入场飞机(FlyTurretData.PrewarmPlaneRoutine:骨架 1 帧 + 41 个粒子逐帧点亮)
List<GameObject> 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();
}
}
/// <summary>XJMH 默认炮台 = 兄弟顺序最靠前的那把(与 SitPosData.NormalizeXJMHTurretList 的排序一致)</summary>
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;
}
/// <summary>
/// 收集"根若激活就会被激活"的粒子节点并先把它们关掉(此刻根未激活,SetActive(false) 只置标志、
/// 不触发任何初始化)——配合 ChunkEnableParticles 把这些粒子系统的首初始化逐帧分摊。
/// 容器(父级)关着的不收集;自身默认关闭的粒子也不动。
/// </summary>
private static List<GameObject> CollectActiveParticlesUnder(Transform root)
{
if (root == null) return null;
List<GameObject> list = null;
var systems = root.GetComponentsInChildren<ParticleSystem>(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<GameObject>(16);
list.Add(ps.gameObject);
}
return list;
}
/// <summary>节点在"根若激活"的前提下是否会被激活(检查自身到根之间每级的 activeSelf)</summary>
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;
}
/// <summary>逐帧分批点亮粒子节点(每帧约 8ms 预算:快机一帧多开、慢机一帧一个)</summary>
private static IEnumerator ChunkEnableParticles(List<GameObject> 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;
}
/// <summary>
/// ⭐ 进房早期(0~2%)预关闭"未激活座位"的多余 XJMH 炮台。
/// 原因:XJMH 座位预制体里 4 把炮台(SkeletonGraphic)默认激活,而座位首次 SetActive 时
/// 4 套骨架/材质/贴图会挤在同一帧全部首初始化(单帧尖峰);预关后 Awake 的 InitXJMHTurret
/// 只点亮默认那把 → 激活帧只初始化 1 套。其它玩家/bot 的座位在 100% 入座时才激活,
/// 同样受益(否则每个座位入座都要初始化 4 套)。
/// ⚠️ 只处理"尚未激活"的座位:已激活的座位 Awake 已跑过,会被 Admin 逻辑重新点亮,
/// 动它反而可能把炮台关掉回不来。
/// </summary>
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++) for (int i = 0; i < sit.xjmhTurretList.Count; i++)
{ {
var t = sit.xjmhTurretList[i]; var t = sit.xjmhTurretList[i];
if (t != null && t.gameObject.activeSelf) t.gameObject.SetActive(false); 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() private void RefreshGGKCount()
@@ -1379,6 +1509,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
// 90 ⭐确认点:走到这里时所有资源 await 都已完成 // 90 ⭐确认点:走到这里时所有资源 await 都已完成
// 90~100 平滑推进收尾(替代原来的 2 秒死等,条一直在动) // 90~100 平滑推进收尾(替代原来的 2 秒死等,条一直在动)
SetLoadProgress(0.01f); SetLoadProgress(0.01f);
LoadingStallWatchdog.Phase = "0~2 卸载大厅UI";
// ⭐ 卸载大厅 UI 资源(进游戏释放 HomeUIRes 内存,回大厅时由离房流程重新预载): // ⭐ 卸载大厅 UI 资源(进游戏释放 HomeUIRes 内存,回大厅时由离房流程重新预载):
// 大厅主面板此时已被关闭(实例销毁时 SubUIManager.ReleaseAll 已释放子面板句柄), // 大厅主面板此时已被关闭(实例销毁时 SubUIManager.ReleaseAll 已释放子面板句柄),
@@ -1391,7 +1522,13 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
// 不再把"打开小包 + 标签求交集"的耗时压在 78% 那一帧(进度条卡死点) // 不再把"打开小包 + 标签求交集"的耗时压在 78% 那一帧(进度条卡死点)
_ = LoadBulletClip(); _ = LoadBulletClip();
// ⭐ 预关"未激活座位"的多余 XJMH 炮台(4 把 SkeletonGraphic 默认全开):
// 座位首次 SetActive 才做骨架/材质首初始化——预关后激活那一刻只初始化默认那把(1 套);
// 其它玩家/bot 的座位在 100% 入座时才激活,4 套→1 套,大幅削掉入座瞬间的卡顿
PreCloseInactiveSitExtraTurrets();
// 路线:2~30(大包同步解压期间回调不刷新 → 预估进度保证条持续推进) // 路线:2~30(大包同步解压期间回调不刷新 → 预估进度保证条持续推进)
LoadingStallWatchdog.Phase = "2~30 路线";
StartCreepProgress(0.02f, 0.30f); StartCreepProgress(0.02f, 0.30f);
await MonsterMoveLine.LoadBezier(p => await MonsterMoveLine.LoadBezier(p =>
{ {
@@ -1400,6 +1537,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
if (flow != _resourceFlowToken) return; // ⭐ 期间已退出/切换,本轮作废 if (flow != _resourceFlowToken) return; // ⭐ 期间已退出/切换,本轮作废
// 鱼包:30~60,内部子进度实时上报(预估进度兜底 LZMA 解压阻塞段,不再卡在 30) // 鱼包:30~60,内部子进度实时上报(预估进度兜底 LZMA 解压阻塞段,不再卡在 30)
LoadingStallWatchdog.Phase = "30~60 鱼包";
StartCreepProgress(0.30f, 0.60f); StartCreepProgress(0.30f, 0.60f);
await FishResourcesData.LoadFishAssetsData(p => await FishResourcesData.LoadFishAssetsData(p =>
{ {
@@ -1408,7 +1546,16 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
if (flow != _resourceFlowToken) return; if (flow != _resourceFlowToken) return;
// 玩法音频:60~78 // 玩法音频:60~78
LoadingStallWatchdog.Phase = "60~78 音频";
StartCreepProgress(0.60f, 0.78f); StartCreepProgress(0.60f, 0.78f);
// ⭐ XJMH 座位/飞机预热:把"首次实例化"的活提前到加载期做(这些活反正躲不掉,不做就会
// 堆到 100% 入座/入场动画那一帧卡——已确认注释掉它 100% 会卡一下)。
// 放在音频段起点:本段是真实加载耗时段(大量小包 IO,本来就有加载卡顿),混在里面的小帧不显眼。
// 重活已全部按帧拆开:座位外壳 1 帧 / 炮台骨架 1 帧 / 座位·炮台·飞机粒子逐帧点亮(每帧 8ms 预算)。
// 如果你更愿意"加载顺滑、接受 100% 卡一下",把下面这行注释回去即可。
PrewarmXJMHForLocalSeat();
await AudioResourcesData.LoadAudioGame(p => await AudioResourcesData.LoadAudioGame(p =>
{ {
if (flow == _resourceFlowToken) SetLoadProgress(Mathf.Max(_loadProgress, Mathf.Lerp(0.60f, 0.78f, 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 双标签交集检索): // ⭐ 开火音效 PW_Shoot 在 Default Local Group(Audio+PTCBZS 双标签交集检索):
// 只命中 Default 小包,不把其它玩法的巨型音频包拉进内存; // 只命中 Default 小包,不把其它玩法的巨型音频包拉进内存;
// 资源在 0~2% 已后台 kick-off 且全局常驻,这里 await 基本瞬时通过(不再把打开包的耗时压在 78% 主线程上) // 资源在 0~2% 已后台 kick-off 且全局常驻,这里 await 基本瞬时通过(不再把打开包的耗时压在 78% 主线程上)
LoadingStallWatchdog.Phase = "78~84 开火音效";
StartCreepProgress(0.78f, 0.84f); StartCreepProgress(0.78f, 0.84f);
await LoadBulletClip(); await LoadBulletClip();
StopCreepProgress(); StopCreepProgress();
@@ -1436,6 +1584,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
} }
} }
LoadingStallWatchdog.Phase = "84~90 EnterRoom/初始鱼";
CreateFishManager.Instance.EnterRoom(gameType); CreateFishManager.Instance.EnterRoom(gameType);
CreateFishManager.Instance.isResources = true; CreateFishManager.Instance.isResources = true;
SetLoadProgress(0.87f); SetLoadProgress(0.87f);
@@ -1468,12 +1617,10 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
AudioManager.Instance.ChangeBG(GameBgmAudioId); AudioManager.Instance.ChangeBG(GameBgmAudioId);
// ⭐ 90% 确认点:走到这里所有资源加载都已完成(上面全是 await 顺序执行) // ⭐ 90% 确认点:走到这里所有资源加载都已完成(上面全是 await 顺序执行)
// (XJMH 座位/飞机预热已挪到 60% 音频段起点——这里是纯净的平滑收尾,不再有重活)
LoadingStallWatchdog.Phase = "90~100 收尾";
SetLoadProgress(0.9f); SetLoadProgress(0.9f);
// ⭐ XJMH 预热:借 90→100 的收尾窗口(进度条还在走)提前把本地座位/炮台 + 入场飞机点亮。
// 这些 XJMH 独有资产首次初始化(骨架/粒子/贴图)很重,堆到 100% 那帧玩家会看到卡一下
PrewarmXJMHForLocalSeat();
// ⭐ 90→100 平滑收尾:约 1 秒走完,进度条持续在动(替代原 2 秒死等; // ⭐ 90→100 平滑收尾:约 1 秒走完,进度条持续在动(替代原 2 秒死等;
// 保留这段缓冲是等出鱼首帧/场景渲染稳定,比原来快 1 秒+) // 保留这段缓冲是等出鱼首帧/场景渲染稳定,比原来快 1 秒+)
for (int i = 1; i <= 20; i++) for (int i = 1; i <= 20; i++)
@@ -1491,6 +1638,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
// ⭐ 加载到 100%(进度条已隐藏):标记完成 + 执行挂起的进房初始化(座位/房间信息)。 // ⭐ 加载到 100%(进度条已隐藏):标记完成 + 执行挂起的进房初始化(座位/房间信息)。
// 入场动画等表现由初始化链路在此之后触发——玩家可见、位置已就绪 // 入场动画等表现由初始化链路在此之后触发——玩家可见、位置已就绪
LoadingStallWatchdog.Phase = "100 首屏初始化";
IsGameLoadCompleted = true; IsGameLoadCompleted = true;
if (_pendingFirstShowInit) if (_pendingFirstShowInit)
{ {
@@ -1498,6 +1646,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
ApplyFirstShowInit(); ApplyFirstShowInit();
} }
// 加载与首屏初始化完成 → 关闭监测(阶段置空,不再打日志)
LoadingStallWatchdog.Phase = "";
// ⭐ 无操作自动退出:加载完成,从现在开始计无操作时间 // ⭐ 无操作自动退出:加载完成,从现在开始计无操作时间
ArmIdleAutoExit(); ArmIdleAutoExit();
} }
@@ -1868,6 +2019,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
private void SetLoadProgress(float p) private void SetLoadProgress(float p)
{ {
p = Mathf.Clamp01(p); p = Mathf.Clamp01(p);
// ⭐ 不回退:场景加载阶段的爬升/预估进度可能已把条推到前面,
// 后续固定值(0.01/0.02 等)不能把它拉回去(带上限取大值)
if (p < _loadProgress) p = _loadProgress;
_loadProgress = p; _loadProgress = p;
if (_changeRoomBar != null) if (_changeRoomBar != null)
_changeRoomBar.SetProgress(0.55f + p * 0.45f); _changeRoomBar.SetProgress(0.55f + p * 0.45f);
@@ -1875,6 +2029,22 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
JinDuTiao.SetProgress(p); JinDuTiao.SetProgress(p);
} }
/// <summary>
/// ⭐ 第一步(只加载了 UI 面板、游戏场景还在加载中):进度条从 0 缓慢爬升到约 3%。
/// 由 WebSocketReponseList 在"面板显示后、场景加载前"调用;真实资源加载开始时由
/// LoadResources 接管(SetLoadProgress 不回退,固定起点值不会把条拉回去)。
/// </summary>
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 同步解压期间主线程阻塞、回调不刷新 → 条卡死像没响应)---- // ---- 预估进度(大包 LZMA 同步解压期间主线程阻塞、回调不刷新 → 条卡死像没响应)----
/// <summary>加载进度当前值(真实或预估),预估推进时判断不回退</summary> /// <summary>加载进度当前值(真实或预估),预估推进时判断不回退</summary>
private float _loadProgress = 0f; private float _loadProgress = 0f;
@@ -6767,3 +6937,37 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
#endregion #endregion
} }
/// <summary>
/// ⭐ 加载期卡顿监测(诊断用,挂面板对象、随面板销毁):
/// 加载全程 Update 检查单帧耗时,凡单帧 ≥120ms 就打一条日志并带"当前阶段"——
/// 用于精确定位是哪个环节顶住了主线程(进度条/界面动画卡顿的根因)。
/// 阶段名由 GameMainPanel_FishBase / FlyTurretData 在流程中随时更新;加载完成后置空即静默。
/// 排查完成后可整体删除本类及各 Phase 赋值。
/// </summary>
public class LoadingStallWatchdog : MonoBehaviour
{
/// <summary>当前阶段(由加载流程更新;为空=不监测)</summary>
public static string Phase = "";
private static int _logged;
/// <summary>在面板下挂一个监测器(存在则不重复创建)</summary>
public static void Attach(MonoBehaviour host)
{
if (host == null || host.GetComponentInChildren<LoadingStallWatchdog>(true) != null) return;
var go = new GameObject("LoadingStallWatchdog");
go.transform.SetParent(host.transform, false);
go.AddComponent<LoadingStallWatchdog>();
}
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} 条)");
}
}
}
@@ -4308,6 +4308,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 6dec7b077ca94844c8c1360f71e7aa58, type: 3} m_Script: {fileID: 11500000, guid: 6dec7b077ca94844c8c1360f71e7aa58, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
buttonClickAudioID: 60009
panelAddressConfig: {fileID: 11400000, guid: 21aa1c64fb06c5d4c9769a8650a3dbc1, type: 2} panelAddressConfig: {fileID: 11400000, guid: 21aa1c64fb06c5d4c9769a8650a3dbc1, type: 2}
subPanelParent: {fileID: 1078076760308578925} subPanelParent: {fileID: 1078076760308578925}
textMeshProUGUI_RoomID: {fileID: 222610915537559086} textMeshProUGUI_RoomID: {fileID: 222610915537559086}
@@ -222,6 +222,17 @@ public class UIHome_BY_SelectRoomLevelPanel : SubUIBase
} }
if (currGameType!= GameType.JQQD) 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; GameObject LV1 = SelectRoom1.transform.GetChild(((int)currGameType)).gameObject;
LV1.SetActive(true); LV1.SetActive(true);
var Lv1Item = LV1.GetComponent<SeccionItem>(); var Lv1Item = LV1.GetComponent<SeccionItem>();