using System.Collections.Generic; using UnityEngine; using UnityEditor; using UnityEditor.Animations; public static class SetupPlayerAnimatorLayers { private const string ControllerPath = "Assets/mainhero/PlayerAnimator.controller"; private const string UpperMaskPath = "Assets/Kevin Iglesias/Human Animations/Models/Avatar Masks/Human Body Upper Mask.mask"; private const string IdleFbxPath = "Assets/Kevin Iglesias/Human Animations/Animations/Male/Idles/HumanM@Idle01.fbx"; private const string WalkFbxPath = "Assets/Kevin Iglesias/Human Animations/Animations/Male/Movement/Walk/HumanM@Walk01_Forward.fbx"; private const string SprintFbxPath = "Assets/Kevin Iglesias/Human Animations/Animations/Male/Movement/Sprint/HumanM@Sprint01_Forward.fbx"; private const string AttackFbxPath = "Assets/Brawler Animations Sample/Animations/Atk_P_1.fbx"; private const string GunAimFbxPath = "Assets/Kevin Iglesias/Human Animations/Animations/Male/Combat/Gun/HumanM@Gun_Aim01.fbx"; private const string GunShootFbxPath = "Assets/Kevin Iglesias/Human Animations/Animations/Male/Combat/Gun/HumanM@Gun_Aim01_Shoot01.fbx"; private const string GunReloadFbxPath = "Assets/Kevin Iglesias/Human Animations/Animations/Male/Combat/Gun/HumanM@Gun_Reload01.fbx"; public static AnimationClip FindClip(string fbxPath) { var all = AssetDatabase.LoadAllAssetsAtPath(fbxPath); foreach (var a in all) { if (a is AnimationClip c && !c.name.StartsWith("__preview")) { return c; } } return null; } [MenuItem("Tools/Rebuild Player Animator Controller")] public static string Rebuild() { var controller = AssetDatabase.LoadAssetAtPath(ControllerPath); if (controller == null) { controller = AnimatorController.CreateAnimatorControllerAtPath(ControllerPath); } // 1. Параметры AddOrUpdateParam(controller, "Speed", AnimatorControllerParameterType.Float, 0f); AddOrUpdateParam(controller, "IsMoving", AnimatorControllerParameterType.Bool, false); AddOrUpdateParam(controller, "Attack", AnimatorControllerParameterType.Trigger); AddOrUpdateParam(controller, "HasGun", AnimatorControllerParameterType.Bool, false); AddOrUpdateParam(controller, "Shoot", AnimatorControllerParameterType.Trigger); AddOrUpdateParam(controller, "Reload", AnimatorControllerParameterType.Trigger); // 2. Base Layer var baseSm = controller.layers[0].stateMachine; // Очищаем существующие состояния for (int i = baseSm.states.Length - 1; i >= 0; i--) { baseSm.RemoveState(baseSm.states[i].state); } for (int i = baseSm.anyStateTransitions.Length - 1; i >= 0; i--) { baseSm.RemoveAnyStateTransition(baseSm.anyStateTransitions[i]); } // Создаем BlendTree Locomotion var locomotionState = baseSm.AddState("Locomotion", new Vector3(250, 50, 0)); var blendTree = new BlendTree { name = "Locomotion", blendType = BlendTreeType.Simple1D, blendParameter = "Speed", useAutomaticThresholds = false }; AssetDatabase.AddObjectToAsset(blendTree, controller); var idleClip = FindClip(IdleFbxPath); var walkClip = FindClip(WalkFbxPath); var sprintClip = FindClip(SprintFbxPath); blendTree.AddChild(idleClip, 0f); blendTree.AddChild(walkClip, 1.5f); blendTree.AddChild(walkClip, 3.0f); blendTree.AddChild(sprintClip, 6.0f); locomotionState.motion = blendTree; baseSm.defaultState = locomotionState; // Создаем Melee Attack State в Base Layer var attackClip = FindClip(AttackFbxPath); if (attackClip != null) { var attackState = baseSm.AddState("Attack", new Vector3(250, 150, 0)); attackState.motion = attackClip; attackState.speed = 1.15f; var toAttack = baseSm.AddAnyStateTransition(attackState); toAttack.AddCondition(AnimatorConditionMode.If, 0f, "Attack"); toAttack.duration = 0.08f; toAttack.canTransitionToSelf = false; var fromAttack = attackState.AddTransition(locomotionState); fromAttack.hasExitTime = true; fromAttack.exitTime = 0.85f; fromAttack.duration = 0.15f; } else { Debug.LogError($"[SetupPlayerAnimatorLayers] Attack clip not found at path: {AttackFbxPath}!"); } // 3. UpperBody Layer (для удержания оружия, выстрела, перезарядки и рукопашного удара при оружии) var upperMask = AssetDatabase.LoadAssetAtPath(UpperMaskPath); if (upperMask == null) { Debug.LogError("[SetupPlayerAnimatorLayers] Upper body mask not found!"); } // Удаляем слой UpperBody если уже существовал for (int i = controller.layers.Length - 1; i >= 1; i--) { controller.RemoveLayer(i); } controller.AddLayer("UpperBody Layer"); var layers = controller.layers; var upperLayer = layers[1]; upperLayer.defaultWeight = 0f; // Управляется PlayerGun скриптом upperLayer.blendingMode = AnimatorLayerBlendingMode.Override; upperLayer.avatarMask = upperMask; var upperSm = upperLayer.stateMachine; var emptyState = upperSm.AddState("Empty", new Vector3(250, 50, 0)); emptyState.motion = null; upperSm.defaultState = emptyState; var gunAimClip = FindClip(GunAimFbxPath); var gunShootClip = FindClip(GunShootFbxPath); var gunReloadClip = FindClip(GunReloadFbxPath); AnimatorState gunAimState = null; if (gunAimClip != null) { gunAimState = upperSm.AddState("Gun_Aim", new Vector3(250, 150, 0)); gunAimState.motion = gunAimClip; // Empty <-> Gun_Aim var toAim = emptyState.AddTransition(gunAimState); toAim.AddCondition(AnimatorConditionMode.If, 0f, "HasGun"); toAim.duration = 0.15f; toAim.hasExitTime = false; var toEmpty = gunAimState.AddTransition(emptyState); toEmpty.AddCondition(AnimatorConditionMode.IfNot, 0f, "HasGun"); toEmpty.duration = 0.15f; toEmpty.hasExitTime = false; // Gun_Aim <-> Shoot if (gunShootClip != null) { var shootState = upperSm.AddState("Shoot", new Vector3(500, 100, 0)); shootState.motion = gunShootClip; var toShoot = gunAimState.AddTransition(shootState); toShoot.AddCondition(AnimatorConditionMode.If, 0f, "Shoot"); toShoot.duration = 0.05f; toShoot.hasExitTime = false; var fromShoot = shootState.AddTransition(gunAimState); fromShoot.hasExitTime = true; fromShoot.exitTime = 0.8f; fromShoot.duration = 0.1f; } // Gun_Aim <-> Reload if (gunReloadClip != null) { var reloadState = upperSm.AddState("Reload", new Vector3(500, 200, 0)); reloadState.motion = gunReloadClip; var toReload = gunAimState.AddTransition(reloadState); toReload.AddCondition(AnimatorConditionMode.If, 0f, "Reload"); toReload.duration = 0.1f; toReload.hasExitTime = false; var fromReload = reloadState.AddTransition(gunAimState); fromReload.hasExitTime = true; fromReload.exitTime = 0.9f; fromReload.duration = 0.15f; } } // Также добавляем Attack в UpperBody Layer, чтобы при удержании оружия (вес UpperBody=1) анимация удара не блокировалась if (attackClip != null) { var upperAttackState = upperSm.AddState("Attack", new Vector3(500, 0, 0)); upperAttackState.motion = attackClip; upperAttackState.speed = 1.15f; var toUpperAttack = upperSm.AddAnyStateTransition(upperAttackState); toUpperAttack.AddCondition(AnimatorConditionMode.If, 0f, "Attack"); toUpperAttack.duration = 0.08f; toUpperAttack.canTransitionToSelf = false; if (gunAimState != null) { var toAimAfterAttack = upperAttackState.AddTransition(gunAimState); toAimAfterAttack.AddCondition(AnimatorConditionMode.If, 0f, "HasGun"); toAimAfterAttack.hasExitTime = true; toAimAfterAttack.exitTime = 0.85f; toAimAfterAttack.duration = 0.15f; } var toEmptyAfterAttack = upperAttackState.AddTransition(emptyState); toEmptyAfterAttack.AddCondition(AnimatorConditionMode.IfNot, 0f, "HasGun"); toEmptyAfterAttack.hasExitTime = true; toEmptyAfterAttack.exitTime = 0.85f; toEmptyAfterAttack.duration = 0.15f; } controller.layers = layers; EditorUtility.SetDirty(controller); AssetDatabase.SaveAssets(); Debug.Log("[SetupPlayerAnimatorLayers] Controller successfully rebuilt with Base and UpperBody layers!"); return "SUCCESS"; } private static void AddOrUpdateParam(AnimatorController ctrl, string name, AnimatorControllerParameterType type, object defVal = null) { foreach (var p in ctrl.parameters) { if (p.name == name) return; } ctrl.AddParameter(name, type); if (defVal != null) { for (int i = 0; i < ctrl.parameters.Length; i++) { if (ctrl.parameters[i].name == name) { if (defVal is float f) ctrl.parameters[i].defaultFloat = f; else if (defVal is bool b) ctrl.parameters[i].defaultBool = b; break; } } } } }