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
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user