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