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:
@@ -30,8 +30,8 @@ namespace Ashwild.Player
|
||||
[SerializeField] private string speedParam = "Speed";
|
||||
[SerializeField] private string moveXParam = "MoveX";
|
||||
[SerializeField] private string moveYParam = "MoveY";
|
||||
[Tooltip("Planar speed (m/s) that maps to 1 in the blend tree. 0 feeds raw speed unscaled.")]
|
||||
[SerializeField] private float normalizeSpeed = 6f;
|
||||
[Tooltip("Planar speed (m/s) above which the player counts as moving rather than idle.")]
|
||||
[SerializeField] private float moveThreshold = 0.15f;
|
||||
[Tooltip("Damping applied to float parameters for smooth blends.")]
|
||||
[SerializeField] private float floatDampTime = 0.1f;
|
||||
|
||||
@@ -40,20 +40,46 @@ namespace Ashwild.Player
|
||||
[SerializeField] private string crouchingParam = "Crouching";
|
||||
[SerializeField] private string sprintingParam = "Sprinting";
|
||||
|
||||
[Header("Int parameters (leave blank to skip)")]
|
||||
[Tooltip("Selects which swing variant the attack state plays — 1-based, matching the Attack1/Attack2… slots.")]
|
||||
[SerializeField] private string attackIndexParam = "AttackIndex";
|
||||
|
||||
[Header("Trigger parameters (leave blank to skip)")]
|
||||
[SerializeField] private string jumpParam = "Jump";
|
||||
[SerializeField] private string attackParam = "Attack";
|
||||
[SerializeField] private string landParam = "Land";
|
||||
[SerializeField] private string grabParam = "Grab";
|
||||
[SerializeField] private string equipParam = "Equip";
|
||||
[SerializeField] private string unequipParam = "Unequip";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Locomotion Levels
|
||||
|
||||
/// <summary>
|
||||
/// The discrete gait the Speed parameter reports, and the thresholds the blend tree is built on.
|
||||
/// Deliberately not a measured velocity: a metre-per-second reading makes the animation hostage to
|
||||
/// movement tuning (retune sprintSpeed and every rig silently blends wrong), and it lies whenever
|
||||
/// the player's actual speed drops without his intent changing — pushing uphill, against a wall,
|
||||
/// through a slowing effect — where a sprint would visibly decay into a walk. Reporting intent
|
||||
/// instead keeps the gait stable, and the parameter damping still eases each change.
|
||||
/// </summary>
|
||||
private const float IdleLevel = 0f;
|
||||
private const float WalkLevel = 1f;
|
||||
private const float RunLevel = 2f;
|
||||
|
||||
#endregion
|
||||
|
||||
#region State
|
||||
|
||||
private Vector3 planarVelocity;
|
||||
private bool isSprinting;
|
||||
|
||||
private int speedId, moveXId, moveYId;
|
||||
private int groundedId, crouchingId, sprintingId;
|
||||
private int attackIndexId;
|
||||
private int jumpId, attackId, landId;
|
||||
private int grabId, equipId, unequipId;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -71,9 +97,13 @@ namespace Ashwild.Player
|
||||
groundedId = Hash(groundedParam);
|
||||
crouchingId = Hash(crouchingParam);
|
||||
sprintingId = Hash(sprintingParam);
|
||||
attackIndexId = Hash(attackIndexParam);
|
||||
jumpId = Hash(jumpParam);
|
||||
attackId = Hash(attackParam);
|
||||
landId = Hash(landParam);
|
||||
grabId = Hash(grabParam);
|
||||
equipId = Hash(equipParam);
|
||||
unequipId = Hash(unequipParam);
|
||||
|
||||
if (orientation == null && animator != null)
|
||||
orientation = animator.transform;
|
||||
@@ -92,10 +122,14 @@ namespace Ashwild.Player
|
||||
PlayerEvents.Jumped += HandleJumped;
|
||||
PlayerEvents.AttackSwung += HandleAttackSwung;
|
||||
PlayerEvents.LandingStunStarted += HandleLandingStunStarted;
|
||||
PlayerEvents.GrabPerformed += HandleGrabPerformed;
|
||||
PlayerEvents.EquipStarted += HandleEquipStarted;
|
||||
PlayerEvents.UnequipStarted += HandleUnequipStarted;
|
||||
|
||||
isSprinting = PlayerEvents.IsSprinting;
|
||||
SetBool(groundedId, PlayerEvents.IsGrounded);
|
||||
SetBool(crouchingId, PlayerEvents.IsCrouching);
|
||||
SetBool(sprintingId, PlayerEvents.IsSprinting);
|
||||
SetBool(sprintingId, isSprinting);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -110,26 +144,31 @@ namespace Ashwild.Player
|
||||
PlayerEvents.Jumped -= HandleJumped;
|
||||
PlayerEvents.AttackSwung -= HandleAttackSwung;
|
||||
PlayerEvents.LandingStunStarted -= HandleLandingStunStarted;
|
||||
PlayerEvents.GrabPerformed -= HandleGrabPerformed;
|
||||
PlayerEvents.EquipStarted -= HandleEquipStarted;
|
||||
PlayerEvents.UnequipStarted -= HandleUnequipStarted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes the smoothed movement floats every frame so blend trees ease between poses
|
||||
/// instead of snapping when velocity changes.
|
||||
/// Pushes the smoothed movement floats every frame. The gait itself is a step function, but the
|
||||
/// damping on the way to the Animator is what turns idle → walk → run into a blend instead of a
|
||||
/// snap, so the discrete parameter still reads as continuous motion.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
if (animator == null) return;
|
||||
|
||||
float scale = normalizeSpeed > 0f ? 1f / normalizeSpeed : 1f;
|
||||
SetFloat(speedId, planarVelocity.magnitude * scale);
|
||||
float level = ComputeLocomotionLevel();
|
||||
SetFloat(speedId, level);
|
||||
|
||||
if (moveXId != 0 || moveYId != 0)
|
||||
{
|
||||
Vector3 direction = planarVelocity.sqrMagnitude > 0.0001f ? planarVelocity.normalized : Vector3.zero;
|
||||
Vector3 local = orientation != null
|
||||
? orientation.InverseTransformDirection(planarVelocity)
|
||||
: planarVelocity;
|
||||
SetFloat(moveXId, local.x * scale);
|
||||
SetFloat(moveYId, local.z * scale);
|
||||
? orientation.InverseTransformDirection(direction)
|
||||
: direction;
|
||||
SetFloat(moveXId, local.x * level);
|
||||
SetFloat(moveYId, local.z * level);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,19 +186,54 @@ namespace Ashwild.Player
|
||||
|
||||
private void HandleGroundedChanged(bool grounded, float impactSpeed) => SetBool(groundedId, grounded);
|
||||
private void HandleCrouchChanged(bool crouching) => SetBool(crouchingId, crouching);
|
||||
private void HandleSprintChanged(bool sprinting) => SetBool(sprintingId, sprinting);
|
||||
|
||||
/// <summary>
|
||||
/// Caches the sprint intent as well as setting the bool, because the gait reported through Speed
|
||||
/// is chosen from the player's intent rather than from how fast he happens to be travelling.
|
||||
/// </summary>
|
||||
private void HandleSprintChanged(bool sprinting)
|
||||
{
|
||||
isSprinting = sprinting;
|
||||
SetBool(sprintingId, sprinting);
|
||||
}
|
||||
private void HandleJumped() => SetTrigger(jumpId);
|
||||
private void HandleAttackSwung() => SetTrigger(attackId);
|
||||
|
||||
/// <summary>
|
||||
/// Selects the combo variant before firing the swing, so the attack state reads an index that is
|
||||
/// already correct on the frame it is entered. The parameter is 1-based to match the Attack1/
|
||||
/// Attack2… clip slots a designer sees in the controller.
|
||||
/// </summary>
|
||||
private void HandleAttackSwung(int variant)
|
||||
{
|
||||
SetInt(attackIndexId, variant + 1);
|
||||
SetTrigger(attackId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A landing stun begins on a hard touchdown — the cue for a landing animation.
|
||||
/// </summary>
|
||||
private void HandleLandingStunStarted(float duration) => SetTrigger(landId);
|
||||
|
||||
private void HandleGrabPerformed() => SetTrigger(grabId);
|
||||
private void HandleEquipStarted() => SetTrigger(equipId);
|
||||
private void HandleUnequipStarted() => SetTrigger(unequipId);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Picks the gait to report. Velocity is consulted only to answer "is he moving at all" — the
|
||||
/// distinction between walking and running comes from the sprint intent, never from a speed
|
||||
/// reading, so the two never disagree. Crouching is not a level of its own: it is a separate bool
|
||||
/// so a crouch tree can reuse the same idle/walk distinction underneath it.
|
||||
/// </summary>
|
||||
private float ComputeLocomotionLevel()
|
||||
{
|
||||
if (planarVelocity.magnitude < moveThreshold) return IdleLevel;
|
||||
return isSprinting ? RunLevel : WalkLevel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hashes a parameter name, returning 0 for a blank name so the setters can skip it.
|
||||
/// </summary>
|
||||
@@ -175,6 +249,11 @@ namespace Ashwild.Player
|
||||
if (id != 0 && animator != null) animator.SetBool(id, value);
|
||||
}
|
||||
|
||||
private void SetInt(int id, int value)
|
||||
{
|
||||
if (id != 0 && animator != null) animator.SetInteger(id, value);
|
||||
}
|
||||
|
||||
private void SetTrigger(int id)
|
||||
{
|
||||
if (id != 0 && animator != null) animator.SetTrigger(id);
|
||||
|
||||
Reference in New Issue
Block a user