356 lines
19 KiB
C#
356 lines
19 KiB
C#
// 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;
|
||
}
|
||
}
|
||
} |