// 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 _animationData; // 连段普攻动画记录 private readonly List _comboList; private readonly string[] _controllers; private readonly List _errorList; private string _controllerPath; private int _index; private MecanimTransitionSet() { // 获得需要处理的动画机 var paths = EditorCommonUtility.GetPathsFromSelectByExtension(".controller"); if (paths.Count > 0) { _controllers = paths.ToArray(); _animationData = EditorTableManager.GetTable(); // 记录所有可以跳过前摇的普攻连段配置 - 普攻将额外配置一条带Offset的Transition var skillExData = EditorTableManager.GetTable(); var skillBaseData = EditorTableManager.GetTable(); _comboList = new List(); 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(); } } // 丢失动画的状态,会试图修复状态;动画名称不对应动画状态,会试图修改动画名称; [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(_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(); // 状态机的状态列表 var layer = animatorController.layers[i]; var states = layer.stateMachine.states; if (layer.stateMachine.behaviours.Find(a => a is AnimationStateEvent) == null) layer.stateMachine.AddStateMachineBehaviour(); // 构造各状态的特征 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(); // 如果当前状态能够在连段列表中被找到,就试图创建额外的连段转换 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 GetNextAnim(string motionName) { MyTuple result = null; for (var i = 0; i < _comboList.Count; i++) { result = _comboList[i].GetNextAnim(motionName); if (result != null) break; } return result; } /// /// 连段普攻数据 /// private class AttackComboData { public readonly List> attackComboList; public AttackComboData(Tab_SkillEx firstAttack, List skillList, List animList) { attackComboList = new List>(); var current = firstAttack; // 获得firstAttack之前的连段 while (current != null) { attackComboList.Insert(0, new MyTuple{ 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{ 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 GetNextAnim(string motionName) { MyTuple 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; } } }