520 lines
20 KiB
C#
520 lines
20 KiB
C#
using System.Collections.Generic;
|
||
using Games.LogicObj;
|
||
using GCGame.Table;
|
||
using Module.Log;
|
||
using UnityEngine;
|
||
using UnityEngine.Events;
|
||
|
||
namespace Games.AnimationModule
|
||
{
|
||
// 动画机修改为正常流程,使用Trigger推动动画机内部的转换
|
||
// AnimationLogic仅仅保证每一帧末尾将最后收到的动画触发到动画机
|
||
// 动画机本身已经不再具备随意跳帧的功能,普攻跳帧由动画机转换配置
|
||
public class AnimationLogic : MonoBehaviour
|
||
{
|
||
public bool cullAnimator
|
||
{
|
||
get { return _cullAnimator; }
|
||
set
|
||
{
|
||
if (_cullAnimator != value)
|
||
{
|
||
_cullAnimator = value;
|
||
RefreshCullAnimator();
|
||
}
|
||
}
|
||
}
|
||
private bool _cullAnimator = true;
|
||
private static readonly Queue<int> _effectBuffer = new Queue<int>();
|
||
private Animator _animationControl;
|
||
private List<BoneData> _boneList;
|
||
|
||
private Tab_Animation _inAnimator;
|
||
// 暂时只有冻结效果,更多效果就扩展这个参数
|
||
private AnimationFreezeData _freezeData;
|
||
|
||
// 当前Animator中,正在播放的动画真实Hash;
|
||
private int _inAnimatorHash;
|
||
|
||
// 三层动画状态 - 动画机状态/希望动画机状态/当前帧希望转入状态
|
||
// _nextInComponent搜集一帧内的转化需求,仅仅保留最终需求;
|
||
// _toAnimator记录当前转换需求,防止重复切入循环动画,额外叠加Trigger;此外负责处理动画结束;
|
||
// _inAnimator仅仅处理动画退出的情况。
|
||
private Tab_Animation _nextInComponent;
|
||
|
||
// 特殊跳转方式,跳转Trigger的名称;同_nextInComponent一个级别。
|
||
private string _nextTransitionName;
|
||
private Tab_Animation _toAnimator;
|
||
private int _visibleRendererCount;
|
||
public event UnityAction<Tab_Animation, string> onPlayAnim;
|
||
public event UnityAction<int> onAnimEnter;
|
||
|
||
public event UnityAction<int, float> onPlayEffect;
|
||
public event UnityAction<int> onRemoveEffect;
|
||
public event UnityAction<int> onPlaySound;
|
||
public event UnityAction<int> onStopSound;
|
||
|
||
public float animationSpeed { get; private set; }
|
||
public bool isFreeze { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 初始化动画逻辑数据
|
||
/// </summary>
|
||
public void InitAnimLogicData(Animator animator, List<RendererInfo> rendererList)
|
||
{
|
||
_animationControl = animator;
|
||
animationSpeed = 1f;
|
||
isFreeze = false;
|
||
RefreshCullAnimator();
|
||
// 破烂动画机,消耗高,需要做CullCompletely,然而Cull状态被推两个Trigger会导致执行顺序问题;
|
||
// 然后这破烂是否处于Cull状态还无法获得,只能自己抓全部Renderer的Visible事件来监视Animator的可视状态;
|
||
if (rendererList == null)
|
||
// 特殊的Bypass,给非ObjPartRoot用的
|
||
_visibleRendererCount = 1;
|
||
else
|
||
{
|
||
_visibleRendererCount = 0;
|
||
var unityAction = new UnityAction<bool>(OnBecomeVisible);
|
||
for (var i = 0; i < rendererList.Count; i++)
|
||
{
|
||
var itemRenderer = rendererList[i].CachedRenderer;
|
||
if (itemRenderer != null)
|
||
{
|
||
var listener = itemRenderer.gameObject.EnsureComponent<RendererVisibleListener>();
|
||
if (listener.isVisible)
|
||
_visibleRendererCount++;
|
||
// 注:即使Listener被摧毁也可以在最后一帧获得OnBecomeInvisible
|
||
listener.onBecameVisible += unityAction;
|
||
}
|
||
}
|
||
}
|
||
OnEnable();
|
||
}
|
||
|
||
public void SetAnimationSpeed(float animSpeed)
|
||
{
|
||
if (animationSpeed != animSpeed)
|
||
{
|
||
animationSpeed = animSpeed;
|
||
RefreshAnimationSpeed();
|
||
}
|
||
}
|
||
|
||
private void OnBecomeVisible(bool isVisible)
|
||
{
|
||
if (isVisible)
|
||
_visibleRendererCount++;
|
||
else
|
||
_visibleRendererCount--;
|
||
}
|
||
|
||
private void SetAnimFreeze(bool freeze)
|
||
{
|
||
if (isFreeze != freeze)
|
||
{
|
||
isFreeze = freeze;
|
||
RefreshAnimationSpeed();
|
||
}
|
||
}
|
||
|
||
private void RefreshAnimationSpeed()
|
||
{
|
||
if (_animationControl != null)
|
||
_animationControl.speed = isFreeze ? 0f : animationSpeed;
|
||
}
|
||
|
||
private void RefreshCullAnimator()
|
||
{
|
||
if (_animationControl != null)
|
||
_animationControl.cullingMode = _cullAnimator
|
||
? AnimatorCullingMode.CullCompletely
|
||
: AnimatorCullingMode.AlwaysAnimate;
|
||
}
|
||
|
||
// 注:物体初始化帧可能遭遇两次OnEnable。第一次由Unity触发,不存在_animationControl;第二次由InitAnimLogicData触发。
|
||
private void OnEnable()
|
||
{
|
||
if (_animationControl != null)
|
||
{
|
||
var animatorBehavior = _animationControl.GetBehaviour<AnimationStateEvent>();
|
||
if (animatorBehavior == null)
|
||
LogModule.WarningLog(string.Format("动画机没有配置状态转换事件,于物体{0}", transform.GetHierarchyName()));
|
||
else
|
||
{
|
||
animatorBehavior.OnAnimStateEnter -= OnAnimationEnter;
|
||
animatorBehavior.OnAnimStateEnter += OnAnimationEnter;
|
||
_toAnimator = TableManager.GetAnimationByID(Obj_Character.idleAnimId, 0);
|
||
_inAnimatorHash = GetNameHash(_toAnimator);
|
||
}
|
||
if (_boneList == null)
|
||
{
|
||
_boneList = new List<BoneData>();
|
||
var root = transform.Find("Bip001");
|
||
if (root != null)
|
||
{
|
||
var children = root.GetComponentsInChildren<Transform>();
|
||
for (var i = 0; i < children.Length; i++)
|
||
{
|
||
_boneList.Add(new BoneData(children[i]));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 防止动画速度在回收时被其他组件调整的情况
|
||
private void OnDisable()
|
||
{
|
||
if (!GameManager.applicationQuit)
|
||
{
|
||
if (_boneList != null)
|
||
{
|
||
for (var i = 0; i < _boneList.Count; i++)
|
||
_boneList[i].Reset();
|
||
}
|
||
animationSpeed = 1f;
|
||
isFreeze = false;
|
||
_freezeData = null;
|
||
RefreshAnimationSpeed();
|
||
cullAnimator = true;
|
||
}
|
||
}
|
||
|
||
// 注:动画自己转向自己时,实际会先开始状态,然后再退出状态,直接End会导致新开始的特效也消失;
|
||
// 因此特殊处理这类情况,自己切换到自己,将在开始时结束之前特效,然后开始新的;
|
||
private void OnAnimationEnter(AnimatorStateInfo stateInfo)
|
||
{
|
||
_inAnimatorHash = stateInfo.shortNameHash;
|
||
// 注:已知问题,有一些动画没有对应动画表,会导致状态错误;但是一般那些都是只有单一动画的友好NPC,所以不造成问题
|
||
//var nextAnimData = GetStateDataByHash(stateInfo.shortNameHash);
|
||
// 仅仅执行预期的动画转换,非预期转换直接跳过;防御上次动画因阻断未完全执行的诡异情况;
|
||
if (_toAnimator != null && GetNameHash(_toAnimator) == stateInfo.shortNameHash)
|
||
{
|
||
// 注:自己转入自己时,当前normalizedTime会为转入前播放位置,不能用作时间参考
|
||
// 自己转入自己并且跳过时间的情况没有处理
|
||
var skipTime = _toAnimator == _inAnimator
|
||
? 0f
|
||
: stateInfo.normalizedTime * stateInfo.length * stateInfo.speed;
|
||
PlayStartEffect(_toAnimator, skipTime);
|
||
ConsumeInAnimator();
|
||
_inAnimator = _toAnimator;
|
||
_toAnimator = null;
|
||
if (_inAnimator.FreezeStart >= 0f && _inAnimator.FreezeDuration > 0f)
|
||
{
|
||
var startTime = Time.time + _inAnimator.FreezeStart;
|
||
var endTime = startTime + _inAnimator.FreezeDuration;
|
||
_freezeData = new AnimationFreezeData(startTime, endTime);
|
||
}
|
||
else
|
||
_freezeData = null;
|
||
SetAnimFreeze(_inAnimator.FreezeStart == 0f);
|
||
}
|
||
else
|
||
// 每次转换都应该消耗掉InAnimator,只是toAnimator时需要在特殊时机消耗
|
||
{
|
||
ConsumeInAnimator();
|
||
}
|
||
if (onAnimEnter != null)
|
||
onAnimEnter(_inAnimatorHash);
|
||
}
|
||
|
||
public void PlayStartEffect(Tab_Animation animData, float skipTime)
|
||
{
|
||
if (onPlayEffect != null)
|
||
{
|
||
PushEffectsToBuffer(animData.StartEffect);
|
||
while (_effectBuffer.Count > 0)
|
||
onPlayEffect(_effectBuffer.Dequeue(), skipTime);
|
||
}
|
||
|
||
if (onPlaySound != null)
|
||
onPlaySound(animData.SoundID);
|
||
}
|
||
|
||
public void RemoveStartEffect(Tab_Animation animData)
|
||
{
|
||
if (onRemoveEffect != null)
|
||
{
|
||
PushEffectsToBuffer(animData.StartEffect);
|
||
while (_effectBuffer.Count > 0)
|
||
onRemoveEffect(_effectBuffer.Dequeue());
|
||
}
|
||
|
||
if (onStopSound != null)
|
||
onStopSound(animData.SoundID);
|
||
}
|
||
|
||
private static void PushEffectsToBuffer(string animText)
|
||
{
|
||
var animTexts = animText.Split(';');
|
||
for (var i = 0; i < animTexts.Length; i++)
|
||
{
|
||
int temp;
|
||
if (int.TryParse(animTexts[i], out temp) && temp >= 0)
|
||
_effectBuffer.Enqueue(temp);
|
||
}
|
||
}
|
||
|
||
private void ConsumeInAnimator()
|
||
{
|
||
if (_inAnimator != null)
|
||
{
|
||
RemoveStartEffect(_inAnimator);
|
||
PlayEndEffect(_inAnimator);
|
||
_inAnimator = null;
|
||
}
|
||
}
|
||
|
||
public void PlayEndEffect(Tab_Animation animData)
|
||
{
|
||
if (onPlayEffect != null)
|
||
{
|
||
PushEffectsToBuffer(animData.EndEffect);
|
||
while (_effectBuffer.Count > 0)
|
||
onPlayEffect(_effectBuffer.Dequeue(), 0f);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查当前播放的动画是否为需要动画
|
||
/// </summary>
|
||
public bool IsPlayAnim(int animId)
|
||
{
|
||
var animData = TableManager.GetAnimationByID(animId, 0);
|
||
return IsPlayAnim(animData);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查当前播放的动画是否为需要动画
|
||
/// </summary>
|
||
public bool IsPlayAnim(Tab_Animation animData)
|
||
{
|
||
var result = false;
|
||
if (AnimPlayable() && animData != null)
|
||
{
|
||
var nameHash = GetNameHash(animData);
|
||
result = nameHash ==
|
||
_animationControl.GetCurrentAnimatorStateInfo(0).shortNameHash;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
public bool AnimPlayable()
|
||
{
|
||
return _animationControl != null &&
|
||
_animationControl.runtimeAnimatorController != null;
|
||
}
|
||
|
||
// 防止一帧收到多个Play指令,导致Animator抽风,动画统一在LateUpdate更新
|
||
public void Play(int animId, string transitionName = default(string))
|
||
{
|
||
if (animId > -1)
|
||
{
|
||
var animData = GetAnimInfoById(animId);
|
||
if (animData != null)
|
||
Play(animData, transitionName);
|
||
}
|
||
}
|
||
|
||
public void Play(Tab_Animation animData, string transitionName = default(string))
|
||
{
|
||
// 不做过多判断 - 直接缓存NextAnim
|
||
if (AnimPlayable() && animData != null)
|
||
{
|
||
_nextInComponent = animData;
|
||
_nextTransitionName = transitionName;
|
||
if (onPlayAnim != null)
|
||
onPlayAnim(animData, transitionName);
|
||
}
|
||
}
|
||
|
||
public bool IsInTransition()
|
||
{
|
||
return AnimPlayable() && _animationControl.IsInTransition(0);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始动画专用流程,会立刻执行动画转换而不会搜集一帧内的情况
|
||
/// </summary>
|
||
// 注:滥用这个接口,可能导致Animator Trigger无法清空的问题
|
||
// 注:等待到LateUpdate的流程会实际使动画机慢一帧,但是找不到一个介于Update和动画机状态刷新之间的时间点
|
||
// 调整ExecuteOrder同热更新会有后患
|
||
public void PlayStartShow(int animId)
|
||
{
|
||
if (AnimPlayable() && animId > -1)
|
||
{
|
||
var animData = GetAnimInfoById(animId);
|
||
if (animData != null)
|
||
{
|
||
var transitionName = animData.AnimName.ToLower();
|
||
if (_animationControl.parameters.FindIndex(a => a.name == transitionName) > -1)
|
||
{
|
||
_nextInComponent = null;
|
||
_toAnimator = animData; //AddAnimLogicData(animData);
|
||
_animationControl.SetTrigger(transitionName);
|
||
}
|
||
// else
|
||
// {
|
||
// animData = GetAnimInfoById(0); //默认Idle
|
||
// if(animData != null)
|
||
// {
|
||
// transitionName = animData.AnimName.ToLower();
|
||
// if(_animationControl.parameters.FindIndex(a => (a.name) == transitionName) > -1)
|
||
// {
|
||
// _nextInComponent = null;
|
||
// _toAnimator = animData; //AddAnimLogicData(animData);
|
||
// _animationControl.SetTrigger(transitionName);
|
||
// }
|
||
// }
|
||
// }
|
||
}
|
||
}
|
||
}
|
||
|
||
private void LateUpdate()
|
||
{
|
||
// 注:不可见情况下,允许推进动画指令,但是不允许执行
|
||
if (AnimPlayable() && (_visibleRendererCount > 0 || _animationControl.cullingMode != AnimatorCullingMode.CullCompletely))
|
||
{
|
||
if (_nextInComponent != null)
|
||
{
|
||
var nextHash = GetNameHash(_nextInComponent);
|
||
// 特殊处理转换到同一动画状态,但是需要不同特效的稀有情况
|
||
if (_toAnimator != null && GetNameHash(_toAnimator) == nextHash)
|
||
{
|
||
_toAnimator = _nextInComponent;
|
||
}
|
||
// 特殊检测 - 循环动画不刷新自己;
|
||
// Hack:循环动画不会因为ExitTime结束,因此_currentAnim必然对应自己
|
||
else if (_nextInComponent.NextAnimID > -1 ||
|
||
_inAnimatorHash != GetNameHash(_nextInComponent))
|
||
{
|
||
// 特殊处理普攻接普攻,如果有SkipTim,使用衔接切换
|
||
var transitionName = string.IsNullOrEmpty(_nextTransitionName)
|
||
? _nextInComponent.AnimName.ToLower()
|
||
: _nextTransitionName;
|
||
// 如果动画机不存在对应的状态,跳过参数设置
|
||
if (_animationControl.parameters.FindIndex(a => a.name == transitionName) > -1)
|
||
{
|
||
_toAnimator = _nextInComponent;
|
||
_animationControl.SetTrigger(transitionName);
|
||
}
|
||
}
|
||
|
||
_nextInComponent = null;
|
||
}
|
||
if (_freezeData != null)
|
||
{
|
||
if (isFreeze)
|
||
{
|
||
if (_freezeData.freezeEnd < Time.time)
|
||
{
|
||
SetAnimFreeze(false);
|
||
_freezeData = null;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (_freezeData.freezeStart < Time.time)
|
||
SetAnimFreeze(true);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public static Tab_Animation GetAnimInfoById(int animId)
|
||
{
|
||
return CommonUtility.TryGetTable(animId, a => TableManager.GetAnimationByID(a, 0));
|
||
}
|
||
|
||
public void StartEffect(int effectId)
|
||
{
|
||
if (onPlayEffect != null)
|
||
onPlayEffect(effectId, 0f);
|
||
}
|
||
|
||
public void SetCurrAnimatorEnable(string state, bool isEnable = true)
|
||
{
|
||
if (AnimPlayable())
|
||
_animationControl.SetBool(state, isEnable);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 如果当前正播放targetAnimId并且没有下个动画,就切换到Idle动画
|
||
/// </summary>
|
||
/// <param name="targetAnimId">目标动画Id</param>
|
||
/// <param name="idleId">站立状态使用的通用动画</param>
|
||
public void SwitchLoopToIdle(int targetAnimId, int idleId)
|
||
{
|
||
if (AnimPlayable())
|
||
{
|
||
var animData = GetAnimInfoById(targetAnimId);
|
||
if (animData.NextAnimID < 0 && IsPlayAnim(animData))
|
||
Play(idleId);
|
||
}
|
||
}
|
||
|
||
public bool HasMotionState(string state, int layer = 0)
|
||
{
|
||
if (_animationControl == null || _animationControl.runtimeAnimatorController == null)
|
||
return false;
|
||
return _animationControl.HasState(layer, Animator.StringToHash(state.ToLower()));
|
||
}
|
||
|
||
public static string GetTransitionName(int fromId, int toId)
|
||
{
|
||
var fromAnim = GetAnimInfoById(fromId);
|
||
var toAnim = GetAnimInfoById(toId);
|
||
if (fromAnim != null && toAnim != null)
|
||
return GetTransitionName(fromAnim, toAnim);
|
||
return string.Empty;
|
||
}
|
||
|
||
public static string GetTransitionName(Tab_Animation from, Tab_Animation to)
|
||
{
|
||
return from.AnimName.ToLower() + "To" + to.AnimName.ToLower();
|
||
}
|
||
|
||
private static int GetNameHash(Tab_Animation data)
|
||
{
|
||
// 注:测试发现,使用StringToHash比查表速度更快。因此不搞StringToHash表。
|
||
return Animator.StringToHash(data.AnimName.ToLower());
|
||
}
|
||
|
||
public class AnimationFreezeData
|
||
{
|
||
public readonly float freezeStart;
|
||
public readonly float freezeEnd;
|
||
|
||
public AnimationFreezeData(float freezeStart, float freezeEnd)
|
||
{
|
||
this.freezeStart = freezeStart;
|
||
this.freezeEnd = freezeEnd;
|
||
}
|
||
}
|
||
|
||
private class BoneData
|
||
{
|
||
public readonly Transform bone;
|
||
public readonly Vector3 position;
|
||
public readonly Quaternion rotation;
|
||
public readonly Vector3 scale;
|
||
|
||
public BoneData(Transform root)
|
||
{
|
||
bone = root;
|
||
position = bone.localPosition;
|
||
rotation = bone.localRotation;
|
||
scale = bone.localScale;
|
||
}
|
||
|
||
public void Reset()
|
||
{
|
||
bone.localPosition = position;
|
||
bone.localRotation = rotation;
|
||
bone.localScale = scale;
|
||
}
|
||
}
|
||
}
|
||
} |