Files
JJBB/Assets/Project/Script/Player/Animation/AnimationLogic.cs
2024-08-23 15:49:34 +08:00

520 lines
20 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}
}
}