1
This commit is contained in:
@@ -418,7 +418,10 @@ public class FlyTurretData : TurretData
|
||||
SetPlaneChildActive("FreeEffect_Crit", false);
|
||||
SetPlaneChildActive("FreeEffect_Speedup", false);
|
||||
|
||||
// 诊断:飞机首次激活(骨架 + 护盾/尾迹粒子首初始化)的真实耗时
|
||||
var swPlane = System.Diagnostics.Stopwatch.StartNew();
|
||||
_enterPlaneRoot.gameObject.SetActive(true);
|
||||
Debug.Log($"[XJMH首屏] 入场飞机激活(SetActive) {swPlane.ElapsedMilliseconds}ms");
|
||||
_enterPlaneRoot.localPosition = start;
|
||||
if (_enterPlaneSg != null && _enterPlaneSg.AnimationState != null)
|
||||
{
|
||||
@@ -591,12 +594,13 @@ public class FlyTurretData : TurretData
|
||||
if (t != null) t.gameObject.SetActive(active);
|
||||
}
|
||||
|
||||
// ==================== 入场飞机预热(进房加载收尾时由面板调用,卡顿被进度条遮住) ====================
|
||||
// XJMH 进入房间时"命中"的那架完整飞机 PW_Plane(骨架 + 41 个粒子系统、2.2MB 预制体、52 个节点):
|
||||
// 整根激活会让 41 套粒子在同一帧全部首初始化(分配缓冲/上传网格贴图)——单帧几百 ms 主线程尖峰。
|
||||
// 这就是"只有星际迷航进房卡、其它玩法不卡"的原因(其他玩法没有这套 XJMH 独有资产)。
|
||||
// 预热 = 进度条还在走时把飞机临时挂到激活父节点下、缩到 0 隐藏;骨架 1 帧 + 粒子按每帧 8ms 预算
|
||||
// 逐帧点亮完成初始化,再原样还原(玩家全程不可见)。
|
||||
// ==================== 入场飞机预热(90% 确认点调用,被加载页遮住) ====================
|
||||
// XJMH 入场飞机 PW_Plane(骨架 + 41 粒子、2.2MB 预制体):整根激活 = 单帧大尖峰。
|
||||
// ⚠️ 必须一次性激活,不能分帧:每次 SetActive 都会把所属大 Canvas 标脏 → 每帧一次
|
||||
// 全量 Rebuild(Unity 官方文档:重建成本 ∝ Canvas 元素数)——分帧 = 把一次大卡
|
||||
// 变成几十次中卡,总时长反而暴涨(61% 卡好久好久的实测教训)。
|
||||
// 预热 = 加载页还开着时:一次性激活(缩到 0 不可见)→ 初始化 → 原样还原;
|
||||
// 100% 入场动画再激活它就是"重启用",零首初始化成本。
|
||||
|
||||
private Coroutine _prewarmRoutine;
|
||||
private Transform _prewarmPlane; // 预热中的飞机(被打断时还原用)
|
||||
@@ -606,10 +610,8 @@ public class FlyTurretData : TurretData
|
||||
private Quaternion _prewarmOrigRot;
|
||||
private Vector3 _prewarmOrigScale;
|
||||
private bool _prewarmOrigActive;
|
||||
/// <summary>粒子分帧点亮的帧内计时(Stopwatch 不受 Time.realtimeSinceStartup 帧内不刷新影响)</summary>
|
||||
private static readonly System.Diagnostics.Stopwatch _prewarmFrameSw = new System.Diagnostics.Stopwatch();
|
||||
|
||||
/// <summary>入场飞机预热(面板在加载收尾阶段调用;座位/炮台需已激活)。幂等:重复调用只跑一次。</summary>
|
||||
/// <summary>入场飞机预热(面板在 90% 确认点调用;座位/炮台需已激活)。幂等:重复调用只跑一次。</summary>
|
||||
public void PrewarmEnterPlane()
|
||||
{
|
||||
if (!isActiveAndEnabled) return;
|
||||
@@ -637,60 +639,27 @@ public class FlyTurretData : TurretData
|
||||
SetPlaneChildActive(plane, "FreeEffect_Crit", 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:既能触发初始化,又完全不可见
|
||||
Transform holder = transform.root;
|
||||
if (holder != null && holder != _prewarmOrigParent)
|
||||
plane.SetParent(holder, true);
|
||||
plane.localScale = Vector3.zero;
|
||||
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; // 首帧网格上传铺开
|
||||
// ⭐ 一次性整根激活:骨架 + 全部入场粒子在一帧完成首初始化(单帧尖峰,被加载页遮住)
|
||||
LoadingStallWatchdog.Phase = "90 首屏初始化·飞机一次性激活";
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
plane.gameObject.SetActive(true);
|
||||
Debug.Log($"[XJMH入场] 飞机一次性预热激活 {sw.ElapsedMilliseconds}ms");
|
||||
|
||||
yield return null; // 骨架首帧网格
|
||||
yield return null; // 粒子首帧模拟
|
||||
yield return null; // 网格/贴图上传铺开
|
||||
|
||||
FinishPrewarmRestore();
|
||||
Debug.Log($"[XJMH入场] 飞机预热完成({plane.name},粒子分帧点亮 {psNodes.Count} 个)");
|
||||
Debug.Log($"[XJMH入场] 飞机预热完成并还原({plane.name})");
|
||||
_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;
|
||||
// 诊断:预热结束,阶段还给收尾段(后续卡顿归因不错位)
|
||||
LoadingStallWatchdog.Phase = "90~100 收尾";
|
||||
}
|
||||
|
||||
/// <summary>预热还原:飞机隐藏并恢复到预热前的挂载/变换/激活状态(协程正常收尾或中途被打断都用它)</summary>
|
||||
|
||||
@@ -1199,6 +1199,16 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
|
||||
{
|
||||
if (_xjmhEnterPlayed || _xjmhEnterPending) return;
|
||||
|
||||
// ⭐ 加载完成门卫:座位激活已提前到 90%(加载页还开着)——此时播入场动画
|
||||
// 玩家看不到、飞机升起也会被进度条挡住白播。挂起等 100% 加载完成,
|
||||
// 由 UpdateXJMHEnterPending 补播(此时画面已可见、飞机已预热,开播即满速)
|
||||
var loadPanel = CreateFishManager.Instance?.currGameType as GameMainPanel_FishBase;
|
||||
if (loadPanel == null || !loadPanel.IsGameLoadCompleted)
|
||||
{
|
||||
_xjmhEnterPending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var turret = currentTurret;
|
||||
if (turret == null && xjmhTurretList != null && xjmhTurretList.Count > 0)
|
||||
turret = xjmhTurretList[0];
|
||||
|
||||
@@ -165,8 +165,16 @@ public class TurretData : MonoBehaviour
|
||||
currState = TurretState.Idle;
|
||||
PlayState(TurretState.Idle);
|
||||
BindAddDownButtons(); // ⭐ XJMH 加减炮倍:炮台上的 +/- 绑定点击(每次激活重绑,幂等)
|
||||
OnTurretEnable(); // ⭐ 子类扩展点:出现/重新激活时的额外逻辑(必须走基类 OnEnable,否则子类自建 OnEnable 会顶掉上面三件事)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 子类覆写:炮台出现/重新激活时的额外初始化(基类 OnEnable 末尾调用)。
|
||||
/// ⚠ 不要在子类里另写 OnEnable——Unity 的 OnEnable 只调最派生的那一个,
|
||||
/// 子类写了就会把基类的"状态复位 + 播 Idle + 绑定按钮"整段顶掉。
|
||||
/// </summary>
|
||||
protected virtual void OnTurretEnable() { }
|
||||
|
||||
/// <summary>
|
||||
/// 当前动画状态(只读)。调用方(GameMainPanel)用它在"这一帧没开火"时判断要不要补发 Idle,
|
||||
/// 避免每帧重复广播状态。注意:状态由 OnStateChange 指向的那个 TurretData 接收
|
||||
|
||||
Reference in New Issue
Block a user