diff --git a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs index b77c2e87c..e0101c8f3 100644 --- a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs +++ b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs @@ -223,6 +223,18 @@ public class GameMainPanel_FishBase : GameMainPanel_Base public float currBingFengTime = 0f; private Dictionary _frozenAnimSpeeds = new Dictionary(); + /// + /// 冰冻中“受 Timeline 驱动”的 Animator(其所属鱼体内存在 PlayableDirector)。 + /// ⭐ 这些 Animator 的动画由 Timeline 驱动,冰冻时绝对不能把 speed 设 0: + /// speed=0 会让 Timeline 的动画输出失效 → 模型瞬间回退到默认姿态。 + /// 它们的定格交给 director(根 Playable 速度归零)处理。 + /// + private readonly HashSet _timelineDrivenAnimators = new HashSet(); + + /// 冰冻中被定格的 Timeline:记录根 Playable 的原始速度,解冻时还原 + private readonly Dictionary _frozenDirectors + = new Dictionary(); + // ⭐ 暴走/冰封本地数量:因为暂无扣减 API,激活时本地 -1,避免被 OnUserDataChanged 覆盖回服务端旧值 private int _localBaoZouCount = -1; // -1 表示尚未初始化 private int _localBingFengCount = -1; @@ -947,6 +959,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base _bingFengInstance = null; } _frozenAnimSpeeds.Clear(); + // ⭐ 冰冻中面板被销毁(例如切房):把被定格的 Timeline 速度还原,避免残留 0 速导致时间轴永远不动 + RestoreFrozenDirectors(); } public virtual void Start() @@ -4672,11 +4686,15 @@ public class GameMainPanel_FishBase : GameMainPanel_Base // ⭐ 守卫:已暂停时直接返回,避免重复调用覆盖已记录的速度 // (其他客户端会先后被 OnUsePropNotice 和 SyncGameData_FreezeFish 触发两次, // 第二次会记录 speed=0,解冻时恢复成 0 → 动画永远暂停) - if (_frozenAnimSpeeds.Count > 0) return; + if (_frozenAnimSpeeds.Count > 0 || _frozenDirectors.Count > 0) return; var root = CreateFishManager.Instance.fishRoot; if (root == null) return; _frozenAnimSpeeds.Clear(); + _frozenDirectors.Clear(); + + // ⭐ 先收集“受 Timeline 驱动”的 Animator:它们必须跳过 speed=0 的处理 + CollectTimelineDrivenAnimators(root); foreach (var s in root.GetComponentsInChildren(true)) { @@ -4687,6 +4705,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base foreach (var a in root.GetComponentsInChildren(true)) { if (!a.gameObject.activeInHierarchy) continue; + // ⭐ Timeline 驱动的 Animator 不能设 speed=0:会让 Timeline 动画输出失效 → + // 模型回退到默认姿态(雷霆之主冰冻后显示默认状态的根因)。由下面的 director 定格 + if (_timelineDrivenAnimators.Contains(a)) continue; _frozenAnimSpeeds[a] = a.speed; a.speed = 0f; } @@ -4704,14 +4725,56 @@ public class GameMainPanel_FishBase : GameMainPanel_Base } foreach (var pd in root.GetComponentsInChildren(true)) { - if (!pd.gameObject.activeInHierarchy || pd.state != PlayState.Playing) continue; - _frozenAnimSpeeds[pd] = 1f; - pd.Pause(); + if (!pd.gameObject.activeInHierarchy || pd.playableAsset == null) continue; + + // ⭐ Timeline 鱼/Boss(雷霆之主 4052 等)必须定格在“当前帧”: + // ⛔ 不能用 pd.Pause() —— 暂停会把 Timeline 的动画输出交还给 Animator, + // 模型会瞬间回退到默认姿态(而不是暂停那一刻的姿态),且 Evaluate 也救不回来。 + // ✅ 正确做法:保持 director 继续播放,只把根 Playable 的速度设为 0。 + // 图仍在每帧求值同一帧 → 姿态定格在暂停瞬间,动画输出始终不脱离 Timeline。 + var graph = pd.playableGraph; + if (!graph.IsValid()) continue; // 未播放过/已 Stop(图已销毁)无法定格 + + int rootPlayableCount = graph.GetRootPlayableCount(); + _frozenDirectors[pd] = rootPlayableCount > 0 ? graph.GetRootPlayable(0).GetSpeed() : 1d; + for (int i = 0; i < rootPlayableCount; i++) + graph.GetRootPlayable(i).SetSpeed(0d); + + // 立即用定格帧刷新一次(delta=0 只应用姿态,不推进时间) + graph.Evaluate(); + } + + Debug.Log($"[冰冻] 暂停动画完成:Animator定格={_frozenAnimSpeeds.Count} Timeline定格={_frozenDirectors.Count} " + + $"Timeline驱动Animator(跳过speed=0)={_timelineDrivenAnimators.Count}"); + } + + /// + /// 收集“受 Timeline 驱动”的 Animator:所属鱼体(fishRoot 的直接子节点)内存在 PlayableDirector。 + /// 冰冻时这些 Animator 不能设 speed=0(见 PauseAllFishAnimations 注释),由 director 定格。 + /// + private void CollectTimelineDrivenAnimators(Transform root) + { + _timelineDrivenAnimators.Clear(); + foreach (var pd in root.GetComponentsInChildren(true)) + { + if (pd.playableAsset == null) continue; + + // 找到该 director 所属的鱼节点;挂在 fishRoot 本身的不处理(避免波及全部鱼) + Transform fish = pd.transform; + while (fish != root && fish.parent != null && fish.parent != root) + fish = fish.parent; + if (fish == root || fish.parent != root) continue; + + foreach (var a in fish.GetComponentsInChildren(true)) + _timelineDrivenAnimators.Add(a); } } private void ResumeAllFishAnimations() { + // ⭐ 先还原被定格的 Timeline(根 Playable 速度恢复),Timeline 从未暂停过,无需 Resume + RestoreFrozenDirectors(); + if (_frozenAnimSpeeds.Count == 0) return; foreach (var kv in _frozenAnimSpeeds) @@ -4734,12 +4797,29 @@ public class GameMainPanel_FishBase : GameMainPanel_Base } } break; - case UnityEngine.Playables.PlayableDirector pd: pd.Resume(); break; } } _frozenAnimSpeeds.Clear(); } + /// + /// 还原冰冻期间被定格的 Timeline:恢复根 Playable 的原始速度(重新开始推进)。 + /// + private void RestoreFrozenDirectors() + { + foreach (var kv in _frozenDirectors) + { + var pd = kv.Key; + if (pd == null) continue; + var graph = pd.playableGraph; + if (!graph.IsValid()) continue; + for (int i = 0; i < graph.GetRootPlayableCount(); i++) + graph.GetRootPlayable(i).SetSpeed(kv.Value); + } + _frozenDirectors.Clear(); + _timelineDrivenAnimators.Clear(); + } + #endregion #region 玩家加入/退出(PlayerJoinModule)