Files
Emberwild/Assets/GAME/Script/Player/Animation/PlayerAnimationSet.cs
Mathew 78bfdf2828 Merge remote-tracking branch 'origin/feat/inport-props' into feat/craft-discovery
# Conflicts:
#	Assets/External/Animated PBR Chest Demo/Materials/WoodChest.mat
#	Packages/com.distantlands.cozy.core/Content/Integration/Import for BiRP.unitypackage.meta
#	Packages/com.distantlands.cozy.core/Content/Integration/Import for HDRP.unitypackage.meta
#	Packages/com.distantlands.cozy.core/Content/Integration/Import for URP.unitypackage.meta
2026-07-25 19:42:26 +02:00

128 lines
5.3 KiB
C#

using UnityEngine;
namespace Ashwild.Player
{
/// <summary>
/// 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.
/// </summary>
public enum PlayerAnimationRig
{
Arms,
Body
}
/// <summary>
/// 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.
/// </summary>
[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";
/// <summary>
/// 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.
/// </summary>
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
/// <summary>
/// How many swing variants this set offers, so the swinging item can cycle through a combo
/// without hard-coding a count it cannot see.
/// </summary>
public int AttackVariantCount => attackVariants != null ? attackVariants.Length : 0;
/// <summary>
/// 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.
/// </summary>
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;
}
/// <summary>
/// The swing clip at a combo index, or null when out of range — an unauthored variant simply
/// falls back instead of throwing mid-fight.
/// </summary>
public AnimationClip GetAttackVariant(int index)
{
if (attackVariants == null || index < 0 || index >= attackVariants.Length) return null;
return attackVariants[index];
}
#endregion
}
}