using System; using System.Collections.Generic; using System.Linq; using Cielonos.MainGame.Characters; using Sirenix.OdinInspector; using SLSUtilities.FunctionalAnimation; using UnityEngine; namespace Cielonos.MainGame { /// /// 角色(Player/Enemy)专属战斗动作子模块。 /// 继承通用播放器并实现 ISubmodule 接口,融合了战斗受击、硬直打断、预输入重置等复杂的动作决策机制。 /// public partial class FuncAnimSubmodule : FuncAnimPlayer, ISubmodule { /// 本模块的所有者(动作子控制器) public AnimationSubcontrollerBase Owner { get; set; } // 兼容原 owner(小写)访问 private AnimationSubcontrollerBase animationSc => Owner; private CharacterBase character => Owner.owner; private Player player => character as Player; private static readonly int ActionSpeed = Animator.StringToHash("ActionSpeed"); public FuncAnimSubmodule(AnimationSubcontrollerBase owner, string animatorLayerName) : base(owner.owner, animatorLayerName) { this.Owner = owner; } /// /// 注册战斗动作数据。如果是玩家控制的角色,还将注册动画剪辑到重载控制器中。 /// public override void Add(FuncAnimData animation) { base.Add(animation); if (player != null) { player.animationSc.SetOverride(animation.animInfo.stateName, animation.animationClip); } } /// /// 战斗特有:执行动作可播放性评估。 /// public bool CheckPlayability(RuntimeFuncAnim oldFuncAnim, RuntimeFuncAnim newFuncAnim) { if (oldFuncAnim != null) { var oldData = oldFuncAnim.funcAnimData; var newData = newFuncAnim.funcAnimData; // 检测是否配置了特殊动作打断区间(SpecifiedActionDisruption) FuncAnimInterval specifiedInterval = oldData.intervals.FirstOrDefault(interval => interval.intervalType == IntervalType.Custom && interval.intervalName == "SpecifiedActionDisruption"); if (specifiedInterval != null) { if (oldData.interactions.TryGetValue("SpecifiedActionDisruption", out List interactions)) { // 若新动作在当前动作的指定打断白名单内,只需判断是否处于该打断区间 if (interactions.Contains(newData.animInfo.animationName)) { return animationSc.CurrentIntervals.Contains(specifiedInterval); } } } } return CheckPlayability(newFuncAnim.funcAnimData.animInfo.disruptionType); } /// /// 战斗特有:评估当前角色的硬直状态与打断类型。 /// public bool CheckPlayability(DisruptionType disruptionType = DisruptionType.NormalAction) { if (character.statusSm.isDead) return false; // 无前动作、死亡动作或强制动作(Must)拥有绝对执行权 if (currentRuntimeFuncAnim == null || disruptionType == DisruptionType.Death || disruptionType == DisruptionType.Must) { return true; } if (disruptionType == DisruptionType.ForcedAction) { return animationSc.disruptionStatus[DisruptionType.ForcedAction] || animationSc.disruptionStatus[DisruptionType.NormalAction] || animationSc.disruptionStatus[DisruptionType.Movement]; } if (disruptionType == DisruptionType.NormalAction) { return animationSc.disruptionStatus[DisruptionType.NormalAction] || animationSc.disruptionStatus[DisruptionType.Movement]; } if (disruptionType == DisruptionType.Movement) { return animationSc.disruptionStatus[DisruptionType.Movement]; } throw new Exception("Invalid DisruptionType for checking playability."); } /// /// 战斗特有:评估当前状态机内是否存在允许打断该动作的标记。 /// public bool CheckDisruption(DisruptionType disruptionType) { return disruptionType switch { DisruptionType.None => false, DisruptionType.Must or DisruptionType.Death => true, _ => animationSc.disruptionStatus[disruptionType] }; } /// /// 覆写播放核心:融入战斗专属的可播放性检测、前置打断清理及事件控制。 /// public override bool Play(FuncAnimData funcAnimData, float animationSpeedMultiplier = 1f, float transitionDuration = 0.1f, bool isNormalizedTransition = false, float normalizedStartTime = -1f, List runtimeStartEvents = null) { var newRtFuncAnim = new RuntimeFuncAnim(funcAnimData, Executor); // 1. 战斗独有的前置条件与打断优先级检测 if (!CheckPlayability(currentRuntimeFuncAnim, newRtFuncAnim)) { return false; } // 2. 如果存在正在播放的动作,且还未打断,执行战斗特有的清理 if (currentRuntimeFuncAnim != null && !currentRuntimeFuncAnim.isDisrupted) { ResetPlayerPreinput(); animationSc.disruptionStatus[DisruptionType.NormalExternal] = false; animationSc.disruptionStatus[DisruptionType.NormalAction] = false; animationSc.disruptionStatus[DisruptionType.Movement] = false; } // 3. 计算实际应用的速度缩放(受 affectsBySpeedMultiplier 标记约束) float finalSpeedMultiplier = funcAnimData.animInfo.isAffectedBySpeedMultiplier ? animationSpeedMultiplier : 1f; // 4. 委派底层物理播放器执行 CrossFade 过渡与启动 bool playSuccess = base.Play(funcAnimData, finalSpeedMultiplier, transitionDuration, isNormalizedTransition, normalizedStartTime, runtimeStartEvents); if (playSuccess) { // 5. 重置运动阻断状态 animationSc.isDisablingMoveXZ = false; animationSc.isDisablingMoveY = false; // 6. 重置事件分发指针 currentRuntimeFuncAnim.dataAnimEventIndex = 0; currentRuntimeFuncAnim.runtimeAnimEventIndex = 0; currentRuntimeFuncAnim.SetUpdateUntilEventsStatus(); } return playSuccess; } /// /// 通过动作名停止播放动作(支持打断级别限制)。 /// public bool Stop(string animationName, DisruptionType disruptionType = DisruptionType.Must, float transitionDuration = 0.1f) { if (currentRuntimeFuncAnim == null) return true; if (currentRuntimeFuncAnim.funcAnimData.animInfo.animationName != animationName) return false; return Stop(disruptionType, transitionDuration); } /// /// 覆写停止动作:注入战斗受击与强制打断的校验逻辑。 /// public bool Stop(DisruptionType disruptionType, float transitionDuration = 0.1f) { if (disruptionType == DisruptionType.None) return false; if (currentRuntimeFuncAnim == null) return true; // 1. 校验是否允许打断 if (disruptionType != DisruptionType.Death && disruptionType != DisruptionType.Must) { if (!CheckDisruption(disruptionType)) { return false; } } // 2. 清理预输入缓存 if (!currentRuntimeFuncAnim.isDisrupted) { ResetPlayerPreinput(); } // 3. 调用底层物理播放器执行淡出到 Empty 状态 bool stopSuccess = base.Stop(transitionDuration); if (stopSuccess) { // 4. 重置子控制器的全局打断标识 animationSc.disruptionStatus[DisruptionType.NormalExternal] = false; animationSc.disruptionStatus[DisruptionType.NormalAction] = false; animationSc.disruptionStatus[DisruptionType.Movement] = false; } return stopSuccess; } /// /// 兼容通用物理停止(默认转换为 Must 强制打断)。 /// public override bool Stop(float transitionDuration = 0.1f) { return Stop(DisruptionType.Must, transitionDuration); } /// /// 驱动更新时间轴时钟(供 Subcontroller.Update 轮询)。 /// public void UpdateTime() { Update(character.selfTimeSm.DeltaTime); } /// /// 覆写时钟推进逻辑:包含动作自然播放完毕时,对输入缓存和打断标志的战斗退场清理。 /// public override void Update(float deltaTime) { if (currentRuntimeFuncAnim == null) { currentPlaySpeedMultiplier = 1f; return; } // 1. 推进播放时间 float step = deltaTime * currentData.animInfo.overridePlaySpeed * currentPlaySpeedMultiplier; currentRuntimeFuncAnim.currentPlayTime += step; currentRuntimeFuncAnim.currentTotalPlayTime += step; float clipLength = currentClip != null ? currentClip.length : 0f; // 2. 监测播放结束 if (currentPlayTime >= clipLength) { UpdateEvents(); if (currentClip != null && currentClip.isLooping) { // 循环复位 currentRuntimeFuncAnim.currentPlayTime = clipLength > 0f ? currentRuntimeFuncAnim.currentPlayTime % clipLength : 0f; currentRuntimeFuncAnim.dataAnimEventIndex = 0; currentRuntimeFuncAnim.runtimeAnimEventIndex = 0; currentRuntimeFuncAnim.ResetUpdateUntilEventsStatus(); currentRuntimeFuncAnim.InvokeStartEvents(); } else { // 非循环动作播放完毕:执行退场事件并注入战斗清理逻辑 if (!currentRuntimeFuncAnim.isDisrupted) { currentRuntimeFuncAnim.isDisrupted = true; currentRuntimeFuncAnim.InvokeDisruptionEvents(); currentRuntimeFuncAnim.InvokeEndEvents(); ResetPlayerPreinput(); } // 清空全局打断标志位 animationSc.disruptionStatus[DisruptionType.NormalExternal] = false; animationSc.disruptionStatus[DisruptionType.NormalAction] = false; animationSc.disruptionStatus[DisruptionType.Movement] = false; currentRuntimeFuncAnim = null; } } } private void ResetPlayerPreinput() => player?.inputSc.preinputSubmodule.Reset(); } public partial class FuncAnimSubmodule { /// /// 获取经播放速度缩放后的指定区间类型的持续时间。 /// 主要提供给武器及连招系统判断前摇/判定区间长度。 /// public float GetIntervalScaledDuration(IntervalType type) { if (currentData == null) return 0f; FuncAnimInterval interval = currentData.intervals.Find(i => i.intervalType == type); if (interval == null) return 0f; float baseDuration = interval.EndTime - interval.StartTime; float speed = currentData.animInfo.overridePlaySpeed * currentPlaySpeedMultiplier; return speed > 0f ? baseDuration / speed : 0f; } } }