This commit is contained in:
2026-09-28 22:54:30 +08:00
parent 106c48cf82
commit f05f1580bd
@@ -223,6 +223,18 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
public float currBingFengTime = 0f;
private Dictionary<Component, object> _frozenAnimSpeeds = new Dictionary<Component, object>();
/// <summary>
/// 冰冻中“受 Timeline 驱动”的 Animator(其所属鱼体内存在 PlayableDirector)。
/// ⭐ 这些 Animator 的动画由 Timeline 驱动,冰冻时绝对不能把 speed 设 0:
/// speed=0 会让 Timeline 的动画输出失效 → 模型瞬间回退到默认姿态。
/// 它们的定格交给 director(根 Playable 速度归零)处理。
/// </summary>
private readonly HashSet<Animator> _timelineDrivenAnimators = new HashSet<Animator>();
/// <summary>冰冻中被定格的 Timeline:记录根 Playable 的原始速度,解冻时还原</summary>
private readonly Dictionary<UnityEngine.Playables.PlayableDirector, double> _frozenDirectors
= new Dictionary<UnityEngine.Playables.PlayableDirector, double>();
// ⭐ 暴走/冰封本地数量:因为暂无扣减 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<Spine.Unity.SkeletonAnimation>(true))
{
@@ -4687,6 +4705,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
foreach (var a in root.GetComponentsInChildren<Animator>(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<UnityEngine.Playables.PlayableDirector>(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}");
}
/// <summary>
/// 收集“受 Timeline 驱动”的 Animator:所属鱼体(fishRoot 的直接子节点)内存在 PlayableDirector。
/// 冰冻时这些 Animator 不能设 speed=0(见 PauseAllFishAnimations 注释),由 director 定格。
/// </summary>
private void CollectTimelineDrivenAnimators(Transform root)
{
_timelineDrivenAnimators.Clear();
foreach (var pd in root.GetComponentsInChildren<UnityEngine.Playables.PlayableDirector>(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<Animator>(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();
}
/// <summary>
/// 还原冰冻期间被定格的 Timeline:恢复根 Playable 的原始速度(重新开始推进)。
/// </summary>
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)