using UnityEngine; namespace Ashwild.Player { /// /// Which of the player's two rigs a set of clips is authored for. The same item needs two /// different sets: the owner's first-person arms play hand-only clips, while the third-person /// body everyone else sees plays full-body clips with legs and torso. /// public enum PlayerAnimationRig { Arms, Body } /// /// The clips one rig plays for one family of held item (bare hands, axe, pickaxe, sword…). /// This is the asset that makes the animation system extensible without code: the master /// Animator Controller authors the state machine once with placeholder clips, and each set /// merely swaps which clip sits in each slot. Adding a new weapon is two assets and one field /// on its ItemData — never a new controller, never a new state, never a new script. /// /// Slots are matched by the *name* of the placeholder clip in the master controller, so an /// authored state named "Run" pulls this set's run clip. A slot left empty falls back to the /// binder's default set and then to the master's own placeholder, which is why a minimal set /// (a torch with only an idle and a run) is perfectly valid and never leaves a rig in T-pose. /// [CreateAssetMenu(fileName = "NewAnimationSet", menuName = "Ashwild/Player Animation Set")] public class PlayerAnimationSet : ScriptableObject { #region Slot Names public const string SlotIdle = "Idle"; public const string SlotWalk = "Walk"; public const string SlotRun = "Run"; public const string SlotCrouchIdle = "CrouchIdle"; public const string SlotCrouchWalk = "CrouchWalk"; public const string SlotJumpStart = "JumpStart"; public const string SlotJumpLoop = "JumpLoop"; public const string SlotJumpLand = "JumpLand"; public const string SlotGrab = "Grab"; public const string SlotEquip = "Equip"; public const string SlotUnequip = "Unequip"; /// /// Prefix of the attack slots ("Attack1", "Attack2", …). Numbered rather than enumerated so a /// three-hit sword combo and a single pickaxe swing share the exact same master controller. /// public const string SlotAttackPrefix = "Attack"; #endregion #region Serialized Fields [Header("Locomotion")] [SerializeField] private AnimationClip idle; [SerializeField] private AnimationClip walk; [SerializeField] private AnimationClip run; [SerializeField] private AnimationClip crouchIdle; [SerializeField] private AnimationClip crouchWalk; [Header("Air")] [SerializeField] private AnimationClip jumpStart; [SerializeField] private AnimationClip jumpLoop; [SerializeField] private AnimationClip jumpLand; [Header("Actions")] [SerializeField] private AnimationClip grab; [SerializeField] private AnimationClip equip; [SerializeField] private AnimationClip unequip; [Header("Attack")] [Tooltip("Swing variants, in combo order. One entry for a plain tool, several for a weapon combo.")] [SerializeField] private AnimationClip[] attackVariants; #endregion #region Public API /// /// How many swing variants this set offers, so the swinging item can cycle through a combo /// without hard-coding a count it cannot see. /// public int AttackVariantCount => attackVariants != null ? attackVariants.Length : 0; /// /// Resolves the clip authored for a slot, or null when this set does not cover it (the caller /// then falls back). Attack slots are resolved by their trailing number, which is 1-based in the /// controller ("Attack1" is the first variant) so designers read the states in combo order. /// public AnimationClip GetClip(string slotName) { switch (slotName) { case SlotIdle: return idle; case SlotWalk: return walk; case SlotRun: return run; case SlotCrouchIdle: return crouchIdle; case SlotCrouchWalk: return crouchWalk; case SlotJumpStart: return jumpStart; case SlotJumpLoop: return jumpLoop; case SlotJumpLand: return jumpLand; case SlotGrab: return grab; case SlotEquip: return equip; case SlotUnequip: return unequip; } if (slotName != null && slotName.StartsWith(SlotAttackPrefix) && int.TryParse(slotName.Substring(SlotAttackPrefix.Length), out int variant)) return GetAttackVariant(variant - 1); return null; } /// /// The swing clip at a combo index, or null when out of range — an unauthored variant simply /// falls back instead of throwing mid-fight. /// public AnimationClip GetAttackVariant(int index) { if (attackVariants == null || index < 0 || index >= attackVariants.Length) return null; return attackVariants[index]; } #endregion } }