Kursach_StealthAction/Assets/Scripts/Editor/SetupPlayerAnimatorLayers.cs

246 lines
10 KiB
C#

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<AnimatorController>(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<AvatarMask>(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;
}
}
}
}
}