300 lines
13 KiB
C#
300 lines
13 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using Cielonos.MainGame.Characters;
|
||
using Sirenix.OdinInspector;
|
||
using SLSUtilities.FunctionalAnimation;
|
||
using UnityEngine;
|
||
|
||
namespace Cielonos.MainGame
|
||
{
|
||
/// <summary>
|
||
/// 角色(Player/Enemy)专属战斗动作子模块。
|
||
/// 继承通用播放器并实现 ISubmodule 接口,融合了战斗受击、硬直打断、预输入重置等复杂的动作决策机制。
|
||
/// </summary>
|
||
public partial class FuncAnimSubmodule : FuncAnimPlayer, ISubmodule<AnimationSubcontrollerBase>
|
||
{
|
||
/// <summary>本模块的所有者(动作子控制器)</summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 注册战斗动作数据。如果是玩家控制的角色,还将注册动画剪辑到重载控制器中。
|
||
/// </summary>
|
||
public override void Add(FuncAnimData animation)
|
||
{
|
||
base.Add(animation);
|
||
if (player != null)
|
||
{
|
||
player.animationSc.SetOverride(animation.animInfo.stateName, animation.animationClip);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 战斗特有:执行动作可播放性评估。
|
||
/// </summary>
|
||
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<string> interactions))
|
||
{
|
||
// 若新动作在当前动作的指定打断白名单内,只需判断是否处于该打断区间
|
||
if (interactions.Contains(newData.animInfo.animationName))
|
||
{
|
||
return animationSc.CurrentIntervals.Contains(specifiedInterval);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return CheckPlayability(newFuncAnim.funcAnimData.animInfo.disruptionType);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 战斗特有:评估当前角色的硬直状态与打断类型。
|
||
/// </summary>
|
||
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.");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 战斗特有:评估当前状态机内是否存在允许打断该动作的标记。
|
||
/// </summary>
|
||
public bool CheckDisruption(DisruptionType disruptionType)
|
||
{
|
||
return disruptionType switch
|
||
{
|
||
DisruptionType.None => false,
|
||
DisruptionType.Must or DisruptionType.Death => true,
|
||
_ => animationSc.disruptionStatus[disruptionType]
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 覆写播放核心:融入战斗专属的可播放性检测、前置打断清理及事件控制。
|
||
/// </summary>
|
||
public override bool Play(FuncAnimData funcAnimData, float animationSpeedMultiplier = 1f, float transitionDuration = 0.1f,
|
||
bool isNormalizedTransition = false, float normalizedStartTime = -1f, List<FuncAnimPayloadBase> 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通过动作名停止播放动作(支持打断级别限制)。
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 覆写停止动作:注入战斗受击与强制打断的校验逻辑。
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 兼容通用物理停止(默认转换为 Must 强制打断)。
|
||
/// </summary>
|
||
public override bool Stop(float transitionDuration = 0.1f)
|
||
{
|
||
return Stop(DisruptionType.Must, transitionDuration);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 驱动更新时间轴时钟(供 Subcontroller.Update 轮询)。
|
||
/// </summary>
|
||
public void UpdateTime()
|
||
{
|
||
Update(character.selfTimeSm.DeltaTime);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 覆写时钟推进逻辑:包含动作自然播放完毕时,对输入缓存和打断标志的战斗退场清理。
|
||
/// </summary>
|
||
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
|
||
{
|
||
/// <summary>
|
||
/// 获取经播放速度缩放后的指定区间类型的持续时间。
|
||
/// 主要提供给武器及连招系统判断前摇/判定区间长度。
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
} |