Files
JJBB/Assets/Project/Script/Common/Utilities/Editor/MecanimTransitionSet.cs

356 lines
19 KiB
C#
Raw Normal View History

2024-08-23 15:49:34 +08:00
// Author: Blastom
// 按照Animation.txt表重置动画机全部动画转换效果
// 新版动画系统将会完全使用动画机内部参数进行转换
using Games.AnimationModule;
using Games.LogicObj;
using Games.SkillModle;
using GCGame.Table;
using System;
using System.Collections.Generic;
using Module.Log;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
public class MecanimTransitionSet
{
private readonly List<Tab_Animation> _animationData;
// 连段普攻动画记录
private readonly List<AttackComboData> _comboList;
private readonly string[] _controllers;
private readonly List<string> _errorList;
private string _controllerPath;
private int _index;
private MecanimTransitionSet()
{
// 获得需要处理的动画机
var paths = EditorCommonUtility.GetPathsFromSelectByExtension(".controller");
if (paths.Count > 0)
{
_controllers = paths.ToArray();
_animationData = EditorTableManager.GetTable<Tab_Animation>();
// 记录所有可以跳过前摇的普攻连段配置 - 普攻将额外配置一条带Offset的Transition
var skillExData = EditorTableManager.GetTable<Tab_SkillEx>();
var skillBaseData = EditorTableManager.GetTable<Tab_SkillBase>();
_comboList = new List<AttackComboData>();
for (var i = 0; i < skillExData.Count; i++)
{
var skillEx = skillExData[i];
var skillBase = skillBaseData.Find(a => a.Id == skillEx.BaseId);
if (skillBase != null && skillBase.SkillClass.ContainFlag((int) SKILLCLASS.SIMPLEATTACK))
{
if (_comboList.Find(a => a.Contains(skillEx)) == null)
{
var combo = new AttackComboData(skillEx, skillExData, _animationData);
if (combo.attackComboList.Count > 1 && combo.NoEmptyAnimation())
_comboList.Add(combo);
}
}
}
_errorList = new List<string>();
}
}
// 丢失动画的状态,会试图修复状态;动画名称不对应动画状态,会试图修改动画名称;
[MenuItem("Assets/Animation/配置Mecanim动画机转换")]
public static void RemovAnimatoreTransitions()
{
var animatorFix = new MecanimTransitionSet();
animatorFix.Start();
}
private void Start()
{
if (_controllers.Length > 0 && _animationData != null)
EditorApplication.update = Update;
}
private void Stop()
{
EditorUtility.ClearProgressBar();
EditorApplication.update = null;
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
EditorUtility.UnloadUnusedAssetsImmediate(true);
for (var i = 0; i < _errorList.Count; i++)
LogModule.ErrorLog(_errorList[i]);
}
private void Update()
{
var cancel = false;
// 每一帧处理一个State使移动后的路径能够正常赋值
if (_index > _controllers.Length - 1)
{
cancel = true;
LogModule.WarningLog("******** 动画机转换全部完成 ********");
}
else
_controllerPath = _controllers[_index];
_index++;
if (!cancel)
cancel = EditorUtility.DisplayCancelableProgressBar("自动修改动画机转换", _controllerPath,
_index / (float) _controllers.Length);
if (cancel)
Stop();
else
{
var animatorController = AssetDatabase.LoadAssetAtPath<AnimatorController>(_controllerPath);
if (animatorController != null)
{
// 清空当前Controller的Parameters然后重建
animatorController.parameters = new AnimatorControllerParameter[0];
for (var i = 0; i < animatorController.layers.Length; i++)
{
// AnyState跳转到新状态的功能
var anyTransitionList = new List<AnimatorStateTransition>();
// 状态机的状态列表
var layer = animatorController.layers[i];
var states = layer.stateMachine.states;
if (layer.stateMachine.behaviours.Find(a => a is AnimationStateEvent) == null)
layer.stateMachine.AddStateMachineBehaviour<AnimationStateEvent>();
// 构造各状态的特征
for (var j = 0; j < states.Length; j++)
{
var state = states[j];
if (state.state.motion == null)
_errorList.Add(string.Format("动画机{0}上状态{1}没有动画!", _controllerPath, state.state.name));
else
{
// 动画机状态统一使用小写格式
state.state.name = state.state.name.ToLower();
var currentAnim = _animationData.Find(a =>
string.Equals(a.AnimName, state.state.name, StringComparison.CurrentCultureIgnoreCase));
if (currentAnim != null)
{
state.state.transitions = new AnimatorStateTransition[0];
//var stateTransitionList = new List<AnimatorStateTransition>();
// 如果当前状态能够在连段列表中被找到,就试图创建额外的连段转换
var nextAttack = GetNextAnim(state.state.name);
if (nextAttack != null)
{
var nextState = states.Find(a => string.Equals(a.state.name,
nextAttack.second.AnimName,
StringComparison.CurrentCultureIgnoreCase));
if (nextState.state != null)
{
// 动画系统一律使用小写传递,防止出现错误
var nextTransitionName =
AnimationLogic.GetTransitionName(currentAnim, nextAttack.second);
animatorController.AddParameter(nextTransitionName,
AnimatorControllerParameterType.Trigger);
var animClip = nextState.state.motion as AnimationClip;
var duration = animClip == null ? nextState.state.motion.averageDuration : animClip.length;
var attackTransition = new AnimatorStateTransition
{
destinationState = nextState.state,
interruptionSource = TransitionInterruptionSource.Destination,
duration = 0f,//currentAnim.SwitchFromTime,
canTransitionToSelf = false,
exitTime = 1f,
hasExitTime = false,
orderedInterruption = false,
offset = nextAttack.first.CradleTime.ToClientTime() /
duration
};
attackTransition.AddCondition(AnimatorConditionMode.If, default(float),
nextTransitionName);
anyTransitionList.Add(attackTransition);
}
else
nextAttack = null;
}
if (nextAttack == null)
state.state.transitions = new AnimatorStateTransition[0];
// 建立任意状态到当前状态的转换
var anyTransitionName = state.state.name.ToLower();
animatorController.AddParameter(anyTransitionName,
AnimatorControllerParameterType.Trigger);
var anyTransistion = new AnimatorStateTransition
{
destinationState = state.state,
// 统一允许重置 - 出现问题可以正常清掉Trigger
canTransitionToSelf = true,
hasExitTime = false,
orderedInterruption = false,
interruptionSource = TransitionInterruptionSource.Destination,
duration = currentAnim.SwitchToTime,
};
anyTransistion.AddCondition(AnimatorConditionMode.If, 0f, anyTransitionName);
anyTransitionList.Add(anyTransistion);
// 建立自己到ExitTime动画的连接
if (currentAnim.NextAnimID > -1)
{
var nextAnim = _animationData.Find(a => a.AnimID == currentAnim.NextAnimID);
if (nextAnim != null)
{
var nextState = states.Find(a => string.Equals(a.state.name, nextAnim.AnimName,
StringComparison.CurrentCultureIgnoreCase));
// 特殊如果目标动画不存在回滚到Idle动画
if (nextState.state == null)
{
nextAnim = _animationData.Find(a => a.AnimID == Obj_Character.idleAnimId);
nextState = states.Find(a => string.Equals(a.state.name, nextAnim.AnimName,
StringComparison.CurrentCultureIgnoreCase));
}
if (nextState.state == null)
_errorList.Add(string.Format("动画机{0}中没有默认id={1}的动画!", _controllerPath,
Obj_Character.idleAnimId));
else
{
var transition = state.state.AddTransition(nextState.state);
transition.interruptionSource = TransitionInterruptionSource.Destination;
transition.duration = currentAnim.SwitchFromTime;
transition.canTransitionToSelf = true;
transition.hasExitTime = true;
transition.orderedInterruption = true;
transition.exitTime = 1f - currentAnim.SwitchFromTime;
//var nextTransition = new AnimatorStateTransition
//{
// destinationState = nextState.state,
// interruptionSource = TransitionInterruptionSource.Destination,
// duration = currentAnim.SwitchFromTime,
// canTransitionToSelf = true,
// hasExitTime = true,
// orderedInterruption = true,
// exitTime = 1f - currentAnim.SwitchFromTime
//};
//stateTransitionList.Add(nextTransition);
}
}
else
_errorList.Add(string.Format("动画数据 {0}关联了不存在的NextAnimId {1}",
currentAnim.AnimID,
currentAnim.NextAnimID));
}
// 特殊处理尸体动画
if (currentAnim.AnimID == Obj_Character.corpseAnimId)
{
animatorController.AddParameter(Obj_Character.corpseTransition,
AnimatorControllerParameterType.Trigger);
var corpseTransition = new AnimatorStateTransition
{
destinationState = state.state,
interruptionSource = TransitionInterruptionSource.None,
duration = 0f,
offset = 1f,
canTransitionToSelf = true,
hasExitTime = false,
orderedInterruption = false
};
corpseTransition.AddCondition(AnimatorConditionMode.If, 0f,
Obj_Character.corpseTransition);
anyTransitionList.Add(corpseTransition);
}
//var oldStateTransitions = state.state.transitions;
//for (var t = 0; t < oldStateTransitions.Length; t++)
// state.state.RemoveTransition(oldStateTransitions[i]);
//state.state.transitions = new AnimatorStateTransition[0];
//for (var t = 0; t < stateTransitionList.Count; t++)
// state.state.AddTransition(stateTransitionList[i]);
}
else
_errorList.Add(string.Format("动画机{0}中名为{1}的动画状态无法找到对应动画数据!", _controllerPath,
state.state.name));
}
//var oldAnyTransitions = layer.stateMachine.anyStateTransitions;
//for (var t = 0; t < oldAnyTransitions.Length; t++)
// layer.stateMachine.RemoveAnyStateTransition(oldAnyTransitions[t]);
layer.stateMachine.anyStateTransitions = new AnimatorStateTransition[0];
for (var t = 0; t < anyTransitionList.Count; t++)
{
var anyTransition = layer.stateMachine.AddAnyStateTransition(anyTransitionList[t].destinationState);
anyTransition.canTransitionToSelf = anyTransitionList[t].canTransitionToSelf;
anyTransition.duration = anyTransitionList[t].duration;
anyTransition.hasExitTime = anyTransitionList[t].hasExitTime;
anyTransition.orderedInterruption = anyTransitionList[t].orderedInterruption;
anyTransition.interruptionSource = anyTransitionList[t].interruptionSource;
anyTransition.offset = anyTransitionList[t].offset;
anyTransition.exitTime = anyTransitionList[t].exitTime;
anyTransition.conditions = anyTransitionList[t].conditions;
}
}
}
//EditorUtility.SetDirty(animatorController);
}
}
}
private MyTuple<Tab_SkillEx, Tab_Animation> GetNextAnim(string motionName)
{
MyTuple<Tab_SkillEx, Tab_Animation> result = null;
for (var i = 0; i < _comboList.Count; i++)
{
result = _comboList[i].GetNextAnim(motionName);
if (result != null)
break;
}
return result;
}
/// <summary>
/// 连段普攻数据
/// </summary>
private class AttackComboData
{
public readonly List<MyTuple<Tab_SkillEx, Tab_Animation>> attackComboList;
public AttackComboData(Tab_SkillEx firstAttack, List<Tab_SkillEx> skillList, List<Tab_Animation> animList)
{
attackComboList = new List<MyTuple<Tab_SkillEx, Tab_Animation>>();
var current = firstAttack;
// 获得firstAttack之前的连段
while (current != null)
{
attackComboList.Insert(0, new MyTuple<Tab_SkillEx, Tab_Animation>{ first = current });
current = skillList.Find(a => a.NextSkillId == current.SkillExID);
}
// 获得firstAttack之后的连段
current = firstAttack;
while (current != null)
{
current = skillList.Find(a => a.SkillExID == current.NextSkillId);
if (current != null)
attackComboList.Add(new MyTuple<Tab_SkillEx, Tab_Animation>{ first = current });
}
for (var i = 0; i < attackComboList.Count; i++)
attackComboList[i].second = animList.Find(a => a.AnimID == attackComboList[i].first.SatrtMotionId);
}
public bool NoEmptyAnimation()
{
var result = true;
for (var i = 0; i < attackComboList.Count; i++)
if (attackComboList[i].second == null)
{
result = false;
// 过滤掉对id小于等于0情况的报错。
if (attackComboList[i].first.SatrtMotionId > 0)
LogModule.WarningLog(string.Format("技能{0}没有对应的动画id={1}的配置!", attackComboList[i].first.SkillExID, attackComboList[i].first.SatrtMotionId));
}
return result;
}
public bool Contains(Tab_SkillEx skillEx)
{
var result = false;
for (var i = 0; i < attackComboList.Count; i++)
if (skillEx.SkillExID == attackComboList[i].first.SkillExID)
{
result = true;
break;
}
return result;
}
public MyTuple<Tab_SkillEx, Tab_Animation> GetNextAnim(string motionName)
{
MyTuple<Tab_SkillEx, Tab_Animation> result = null;
var index = attackComboList.FindIndex(a => string.Equals(a.second.AnimName, motionName, StringComparison.CurrentCultureIgnoreCase));
if (index >= 0 && index < attackComboList.Count - 1)
result = attackComboList[index + 1];
return result;
}
}
}