This commit is contained in:
2026-10-12 15:32:36 +08:00
parent e4cdbd25ba
commit 1d7e30540d
14 changed files with 473 additions and 529 deletions
@@ -167,6 +167,15 @@ public class MonsterMoveLine
private static AsyncOperationHandle<IList<TextAsset>> _bezierHandles;
public static async Task LoadBezierTxtAsync(string path, Action<float> onProgress = null)
{
// ⭐ 资源加载超过 0.1 秒才显示"加载中"界面(引用计数,快速加载不显示)
UIManager.BeginLoading();
try { await LoadBezierTxtAsyncCore(path, onProgress); }
finally { UIManager.EndLoading(); }
}
/// <summary>LoadBezierTxtAsync 的实际逻辑(外层负责"加载中"界面)</summary>
private static async Task LoadBezierTxtAsyncCore(string path, Action<float> onProgress)
{
// ⭐ 加载前先全量清空上一场的路线缓存:
// 各玩法普通路线/鱼阵曲线名完全重名(曲线1~N、鱼阵N)但坐标不同,
@@ -589,9 +589,9 @@ public class FlyTurretData : TurretData
_prewarmOrigScale = plane.localScale;
_prewarmOrigActive = plane.gameObject.activeSelf;
// 只开入场要用的 Body+护盾、关掉免费三类——避免把五十来个粒子系统全部初始化(反而更重)
// 只开入场要用的 Body、关掉免费三类——避免把五十来个粒子系统全部初始化(反而更重);
// ⭐ 护盾粒子拆到下一帧再开:和 Body 骨架/粒子挤同一帧 = 单帧尖峰(90% 收尾窗口卡顿的来源之一)
SetPlaneChildActive(plane, "Body", true);
SetPlaneChildActive(plane, "PW_Effect_Defend", true);
SetPlaneChildActive(plane, "FreeEffect", false);
SetPlaneChildActive(plane, "FreeEffect_Crit", false);
SetPlaneChildActive(plane, "FreeEffect_Speedup", false);
@@ -602,8 +602,10 @@ public class FlyTurretData : TurretData
plane.SetParent(holder, true);
plane.localScale = Vector3.zero;
plane.gameObject.SetActive(true);
yield return null;
yield return null; // 让骨架首帧网格、粒子首帧模拟都跑到
yield return null; // Body 骨架首帧网格
SetPlaneChildActive(plane, "PW_Effect_Defend", true); // 护盾粒子单独一帧开
yield return null; // 护盾首帧模拟
yield return null; // 再等一帧让首帧网格上传铺开
FinishPrewarmRestore();
Debug.Log($"[XJMH入场] 飞机预热完成({plane.name})");
@@ -91,6 +91,15 @@ public class AudioResourcesData
/// onProgress:进度回调 0~1(按 LoadAssetsAsync 的 PercentComplete),供进房进度条使用;不传则行为不变。
/// </summary>
public static async Task LoadAudioGame(Action<float> onProgress = null)
{
// ⭐ 资源加载超过 0.1 秒才显示"加载中"界面(引用计数,快速加载不显示)
UIManager.BeginLoading();
try { await LoadAudioGameCore(onProgress); }
finally { UIManager.EndLoading(); }
}
/// <summary>LoadAudioGame 的实际逻辑(外层负责"加载中"界面)</summary>
private static async Task LoadAudioGameCore(Action<float> onProgress)
{
GameType gameType = GameManager.Instance.gameData.currGameType;
var keys = new List<string> { Audio_AALabel, gameType.ToString() };
@@ -92,6 +92,15 @@ public class FishResourcesData
/// 供进房进度条使用;不传则行为与原来一致。
/// </summary>
public static async Task LoadFishAssetsData(Action<float> onProgress = null)
{
// ⭐ 资源加载超过 0.1 秒才显示"加载中"界面(引用计数,快速加载不显示)
UIManager.BeginLoading();
try { await LoadFishAssetsDataCore(onProgress); }
finally { UIManager.EndLoading(); }
}
/// <summary>LoadFishAssetsData 的实际逻辑(外层负责"加载中"界面)</summary>
private static async Task LoadFishAssetsDataCore(Action<float> onProgress)
{
// 清空对象池
Pool.Instance.Clear();
@@ -76,6 +76,15 @@ public class TurretResourcesData
/// (调用方可回退 GetZhanShenPrefab 查全量字典,平滑迁移)。
/// </summary>
public static async Task<GameObject> AcquireZhanShenAsync(int turretNumber, ZhanShenUsage usage)
{
// ⭐ 资源加载超过 0.1 秒才显示"加载中"界面(缓存命中瞬间完成,不会显示)
UIManager.BeginLoading();
try { return await AcquireZhanShenAsyncCore(turretNumber, usage); }
finally { UIManager.EndLoading(); }
}
/// <summary>AcquireZhanShenAsync 的实际逻辑(外层负责"加载中"界面)</summary>
private static async Task<GameObject> AcquireZhanShenAsyncCore(int turretNumber, ZhanShenUsage usage)
{
var key = (turretNumber, usage);
_zsRefCount[key] = _zsRefCount.TryGetValue(key, out int c) ? c + 1 : 1;
@@ -179,6 +188,15 @@ public class TurretResourcesData
/// 与 ReleaseTurretPrefab 配对(切换/关闭时 -1,归零卸包)。
/// </summary>
public static async Task<GameObject> AcquireTurretPrefabAsync(int turretNumber)
{
// ⭐ 资源加载超过 0.1 秒才显示"加载中"界面(缓存命中瞬间完成,不会显示)
UIManager.BeginLoading();
try { return await AcquireTurretPrefabAsyncCore(turretNumber); }
finally { UIManager.EndLoading(); }
}
/// <summary>AcquireTurretPrefabAsync 的实际逻辑(外层负责"加载中"界面)</summary>
private static async Task<GameObject> AcquireTurretPrefabAsyncCore(int turretNumber)
{
_ptRefCount[turretNumber] = _ptRefCount.TryGetValue(turretNumber, out int c) ? c + 1 : 1;
if (_ptPrefabCache.TryGetValue(turretNumber, out var cached)) return cached;
@@ -368,6 +386,15 @@ public class TurretResourcesData
/// 字典中存的就是已销毁对象的引用(Editor 下因 AssetDatabase 模式不会暴露此问题)。
/// </summary>
public static async Task LoadTurretAssetsData()
{
// ⭐ 资源加载超过 0.1 秒才显示"加载中"界面(引用计数,快速加载不显示)
UIManager.BeginLoading();
try { await LoadTurretAssetsDataCore(); }
finally { UIManager.EndLoading(); }
}
/// <summary>LoadTurretAssetsData 的实际逻辑(外层负责"加载中"界面)</summary>
private static async Task LoadTurretAssetsDataCore()
{
turretPrefabDic = new Dictionary<int, GameObject>();
turretGamePrefabDic = new Dictionary<int, GameObject>();
@@ -5342,7 +5342,7 @@ PrefabInstance:
- target: {fileID: 3823345273878462180, guid: 1bcb895f3655d5548a9ffb4474b98605,
type: 3}
propertyPath: m_SceneBindings.Array.size
value: 26
value: 29
objectReference: {fileID: 0}
- target: {fileID: 3823345273878462180, guid: 1bcb895f3655d5548a9ffb4474b98605,
type: 3}
@@ -5356,6 +5356,24 @@ PrefabInstance:
value:
objectReference: {fileID: -4975399762532968560, guid: 97554428056b1ac4f9f32aface985316,
type: 2}
- target: {fileID: 3823345273878462180, guid: 1bcb895f3655d5548a9ffb4474b98605,
type: 3}
propertyPath: m_SceneBindings.Array.data[26].key
value:
objectReference: {fileID: -147752329279606508, guid: 97554428056b1ac4f9f32aface985316,
type: 2}
- target: {fileID: 3823345273878462180, guid: 1bcb895f3655d5548a9ffb4474b98605,
type: 3}
propertyPath: m_SceneBindings.Array.data[27].key
value:
objectReference: {fileID: 6124250156390186977, guid: 97554428056b1ac4f9f32aface985316,
type: 2}
- target: {fileID: 3823345273878462180, guid: 1bcb895f3655d5548a9ffb4474b98605,
type: 3}
propertyPath: m_SceneBindings.Array.data[28].key
value:
objectReference: {fileID: -6477884835058938809, guid: 97554428056b1ac4f9f32aface985316,
type: 2}
- target: {fileID: 3823345273878462180, guid: 1bcb895f3655d5548a9ffb4474b98605,
type: 3}
propertyPath: m_SceneBindings.Array.data[24].value
@@ -5366,6 +5384,21 @@ PrefabInstance:
propertyPath: m_SceneBindings.Array.data[25].value
value:
objectReference: {fileID: 3033651403988009196}
- target: {fileID: 3823345273878462180, guid: 1bcb895f3655d5548a9ffb4474b98605,
type: 3}
propertyPath: m_SceneBindings.Array.data[26].value
value:
objectReference: {fileID: 8282567264049790537}
- target: {fileID: 3823345273878462180, guid: 1bcb895f3655d5548a9ffb4474b98605,
type: 3}
propertyPath: m_SceneBindings.Array.data[27].value
value:
objectReference: {fileID: 7453087164729299865}
- target: {fileID: 3823345273878462180, guid: 1bcb895f3655d5548a9ffb4474b98605,
type: 3}
propertyPath: m_SceneBindings.Array.data[28].value
value:
objectReference: {fileID: 2215003876436266114}
- target: {fileID: 3824444864287102080, guid: 1bcb895f3655d5548a9ffb4474b98605,
type: 3}
propertyPath: m_Layer
@@ -13735,6 +13768,12 @@ CircleCollider2D:
m_Offset: {x: 0, y: 0}
serializedVersion: 2
m_Radius: 0.5
--- !u!95 &2215003876436266114 stripped
Animator:
m_CorrespondingSourceObject: {fileID: 4286368811891389748, guid: 1bcb895f3655d5548a9ffb4474b98605,
type: 3}
m_PrefabInstance: {fileID: 2720591934958713270}
m_PrefabAsset: {fileID: 0}
--- !u!1 &2283344592133234553 stripped
GameObject:
m_CorrespondingSourceObject: {fileID: 4211263933705135823, guid: 1bcb895f3655d5548a9ffb4474b98605,
@@ -15072,6 +15111,12 @@ CircleCollider2D:
m_Offset: {x: 0.018183004, y: -0.14367317}
serializedVersion: 2
m_Radius: 0.7301043
--- !u!95 &7453087164729299865 stripped
Animator:
m_CorrespondingSourceObject: {fileID: 4805293225234868783, guid: 1bcb895f3655d5548a9ffb4474b98605,
type: 3}
m_PrefabInstance: {fileID: 2720591934958713270}
m_PrefabAsset: {fileID: 0}
--- !u!1 &7511131309169197708 stripped
GameObject:
m_CorrespondingSourceObject: {fileID: 5619821435806153530, guid: 1bcb895f3655d5548a9ffb4474b98605,
@@ -15154,6 +15199,12 @@ CircleCollider2D:
m_Offset: {x: 0.0073856106, y: -0.08785672}
serializedVersion: 2
m_Radius: 0.6284307
--- !u!1 &8282567264049790537 stripped
GameObject:
m_CorrespondingSourceObject: {fileID: 6282782031822112767, guid: 1bcb895f3655d5548a9ffb4474b98605,
type: 3}
m_PrefabInstance: {fileID: 2720591934958713270}
m_PrefabAsset: {fileID: 0}
--- !u!1 &8394534036027403222 stripped
GameObject:
m_CorrespondingSourceObject: {fileID: 5890175915406086752, guid: 1bcb895f3655d5548a9ffb4474b98605,
@@ -387,7 +387,7 @@ MonoBehaviour:
- {fileID: 0}
needLockEvent: 0
createVideoClip: {fileID: 0}
rotationPY: 0
rotationPY: 60
useMirrorCompensate: 1
useRollSignFlip: 0
useRouteFlipX: 0
@@ -114,7 +114,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!224 &439111589277315844
RectTransform:
m_ObjectHideFlags: 0
@@ -311,7 +311,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
[Tooltip("鱼创建时播放的入场视频控件,由 Boss 预制体的 createVideoClip 指定视频片段。Inspector 拖拽场景中的 VideoPlayer 控件")]
public UnityEngine.Video.VideoPlayer createVideoPlayer;
/// <summary>开火音效(static:切房新面板实例直接复用,避免重复加载;离房时 UnLoadBulletClip 释放并置空)</summary>
/// <summary>开火音效(static:全局常驻,切房/重进房直接复用;见 LoadBulletClip 注释)</summary>
static AudioClip bulletClip;
public List<int> paoBeiList = new List<int>();
@@ -480,6 +480,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
/// XJMH 进房首帧预热(加载 90%→100% 收尾窗口调用,进度条还显示着玩家看不到):
/// 把本地座位(座位自带炮台随 Awake 一起初始化)与"入场要用的那架完整飞机"提前点亮——
/// 这些 XJMH 独有资产首次初始化(骨架/粒子/贴图首上传)很重,全堆在 100% 那一帧玩家会看到明显卡顿。
/// ⭐ 按帧拆散(避免"90% 卡死"):先让一帧(进度条先刷新)→ 只留默认炮台、关掉其余 3 把 →
/// 激活座位(1 套骨架) → 等两帧 → 飞机分两帧点亮(Body / 护盾粒子各一帧)。
/// 入场动画有加载完成门卫(IsGameLoadCompleted),预热不会导致动画提前播放。
/// </summary>
private void PrewarmXJMHForLocalSeat()
@@ -495,11 +497,34 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
private IEnumerator PrewarmXJMHCoroutine(SitPosData sit)
{
// ① 提前激活本地座位:Awake → InitXJMHTurret → 激活默认炮台(PW_Battery_x 首次初始化在这里完成,
// ⭐ 先等一帧:StartCoroutine 会同步执行到第一个 yield —— 原先座位激活的重活
// 和"90%"写入进度条是同一帧(数字还没刷新、主线程就被占住,看起来"90 卡死")。
// 先让一帧,进度条和收尾循环跑起来再干重活
yield return null;
bool willActivate = sit != null && !sit.gameObject.activeSelf;
// ⭐ 只有"座位从未激活、这次预热才激活"时才预关炮台:
// 预制体里 4 把 XJMH 炮台(SkeletonGraphic)默认激活,座位 SetActive 时 4 套骨架/材质/贴图
// 挤在同一帧全部首初始化(单帧尖峰巨大);先关掉后,这一帧只初始化 Awake 点亮的默认那把。
// ⚠️ 座位已在激活状态时不能动(Awake 不会再跑,关掉就回不来)
if (willActivate && 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);
}
}
// ① 提前激活本地座位:Awake → InitXJMHTurret → 激活默认炮台(首初始化在这里完成,
// 被加载界面遮住)。100% 时 JoinGame 再 SetActive(true) 是幂等的,不影响正常入座流程
if (sit != null && !sit.gameObject.activeSelf)
if (willActivate)
sit.gameObject.SetActive(true);
yield return null; // 让座位/炮台这帧的重活落在加载画面里
// 座位/炮台初始化(骨架首帧网格上传、材质铺开)多留两帧,再点飞机
yield return null;
yield return null;
// ② 飞机隐藏预热(座位激活后 currentTurret 已就绪 = 入场会用的那把)
var turret = sit != null ? sit.currentTurret as FlyTurretData : null;
@@ -1361,6 +1386,11 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
UIManager.Instance.ReleaseHomeUI();
SetLoadProgress(0.02f);
// ⭐ 提前后台加载开火音效 PW_Shoot(Audio+PTCBZS,独立小包):
// 与后面的路线/鱼包/音频加载并行跑,78% 处 await 时早已完成,
// 不再把"打开小包 + 标签求交集"的耗时压在 78% 那一帧(进度条卡死点)
_ = LoadBulletClip();
// 路线:2~30(大包同步解压期间回调不刷新 → 预估进度保证条持续推进)
StartCreepProgress(0.02f, 0.30f);
await MonsterMoveLine.LoadBezier(p =>
@@ -1386,7 +1416,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
if (flow != _resourceFlowToken) return;
// ⭐ 开火音效 PW_Shoot 在 Default Local Group(Audio+PTCBZS 双标签交集检索):
// 只命中 Default 小包,不把其它玩法的巨型音频包拉进内存;句柄由 LoadBulletClip 静态保存,离房释放
// 只命中 Default 小包,不把其它玩法的巨型音频包拉进内存;
// 资源在 0~2% 已后台 kick-off 且全局常驻,这里 await 基本瞬时通过(不再把打开包的耗时压在 78% 主线程上)
StartCreepProgress(0.78f, 0.84f);
await LoadBulletClip();
StopCreepProgress();
@@ -1484,7 +1515,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
if (flow != _resourceFlowToken) return; // ⭐ 期间已有新流程(新面板加载),剩余卸载作废
await AudioResourcesData.UnLoadAudioGame();
if (flow != _resourceFlowToken) return;
UnLoadBulletClip(); // ⭐ 释放开火音效句柄(引用计数归零)
// ⭐ 开火音效 PW_Shoot(Audio+PTCBZS)不释放:全局常驻(见 LoadBulletClip 注释,
// 独立小包反复开关只会把打开耗时反复压在进房 78% 那一帧,省不到内存)
TurretResourcesData.UnloadAllZhanShen(ZhanShenUsage.GamePlay); // ⭐ 卸载按需加载的战神(游戏内)
TurretResourcesData.UnloadAllTurretPrefabs(); // ⭐ 卸载按需加载的炮台/翅膀 prefab
await MonsterMoveLine.UnLoadBezier();
@@ -1510,29 +1542,27 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
/// <summary>
/// ⭐ 开火音效 PW_Shoot 的加载句柄(static:新老面板实例共享)。
/// PW_Shoot 在 Default Local Group(Audio+PTCBZS),该包因默认音频常驻本就不会卸载,
/// 但句柄仍要显式保存/释放,避免每次进房引用计数 +1 无限累加。
/// PW_Shoot 是全局开火音效、每局必用,且各组已是 PackSeparately(每个资源独立小包):
/// 旧实现"每次进房 Release+Load、离房再释放"省不到内存(下一局马上又加载回来),
/// 却把"打开小包 + Audio/PTCBZS 标签求交集"的主线程耗时全压在进房 78% 那一帧(进度条卡死点)。
/// 现在只加载一次、程序生命周期常驻;首次加载在进房 0~2%(后台)就 kick-off,78% 处直接通过。
/// </summary>
private static AsyncOperationHandle<IList<AudioClip>> _bulletClipHandle;
private static bool _bulletClipStarted;
/// <summary>加载开火音效(Audio+PTCBZS 交集,只命中 Default Local Group 里的 PW_Shoot)</summary>
/// <summary>加载开火音效(Audio+PTCBZS 交集,只命中 Default Local Group 里的 PW_Shoot)。
/// 幂等:已启动过则复用同一句柄(通常早已完成,await 直接通过)。</summary>
private static async Task LoadBulletClip()
{
// 防御:上一轮句柄未释放时先释放
if (_bulletClipHandle.IsValid())
Addressables.Release(_bulletClipHandle);
_bulletClipHandle = Addressables.LoadAssetsAsync<AudioClip>(new List<string> { "Audio", "PTCBZS" }, null,
Addressables.MergeMode.Intersection);
await _bulletClipHandle.Task;
}
/// <summary>释放开火音效句柄(离房卸载流程调用;static 引用一并置空防悬空)</summary>
private static void UnLoadBulletClip()
{
if (_bulletClipHandle.IsValid())
Addressables.Release(_bulletClipHandle);
bulletClip = null;
if (!_bulletClipStarted)
{
_bulletClipStarted = true;
_bulletClipHandle = Addressables.LoadAssetsAsync<AudioClip>(new List<string> { "Audio", "PTCBZS" }, null,
Addressables.MergeMode.Intersection);
}
if (!_bulletClipHandle.IsValid()) return;
try { await _bulletClipHandle.Task; }
catch (Exception e) { Debug.LogError($"开火音效加载失败: {e.Message}"); }
}
/// <summary>
@@ -1545,7 +1575,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
{
await FishResourcesData.UnLoadFishAssetsData();
await AudioResourcesData.UnLoadAudioGame();
UnLoadBulletClip();
// ⭐ 开火音效 PW_Shoot 常驻不释放(见 LoadBulletClip 注释)
TurretResourcesData.UnloadAllZhanShen(ZhanShenUsage.GamePlay);
TurretResourcesData.UnloadAllTurretPrefabs();
await MonsterMoveLine.UnLoadBezier();
@@ -1632,7 +1662,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
SetUnloadProgress(0.4f);
await AudioResourcesData.UnLoadAudioGame();
UnLoadBulletClip(); // ⭐ 释放开火音效句柄(引用计数归零)
// ⭐ 开火音效 PW_Shoot(Audio+PTCBZS)不释放:全局常驻(见 LoadBulletClip 注释)
TurretResourcesData.UnloadAllZhanShen(ZhanShenUsage.GamePlay); // ⭐ 卸载按需加载的战神(游戏内)
TurretResourcesData.UnloadAllTurretPrefabs(); // ⭐ 卸载按需加载的炮台/翅膀 prefab
+2 -2
View File
@@ -160,7 +160,7 @@ MonoBehaviour:
m_MinValue: 0
m_MaxValue: 1
m_WholeNumbers: 0
m_Value: 1
m_Value: 0
m_OnValueChanged:
m_PersistentCalls:
m_Calls: []
@@ -5849,7 +5849,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: LV:211
m_text: LV:0
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 5d4a713fdd931db4c9ce49f068b54e0a, type: 2}
m_sharedMaterial: {fileID: 3593458263369068374, guid: 5d4a713fdd931db4c9ce49f068b54e0a,