(Feat) Add Network Body
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7bcd5cb7e1311614a8607e68424e119f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,185 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ashwild.Player
|
||||
{
|
||||
/// <summary>
|
||||
/// Drives a humanoid Animator from the local player's PlayerEvents bus so a visible rig
|
||||
/// reflects what the player is doing. It is a pure consumer of the bus — it never simulates
|
||||
/// anything itself — which lets the exact same component drive two independent rigs from one
|
||||
/// shared source of truth: the owner's first-person arms (seen only by the owner, never
|
||||
/// networked) and his third-person body (replicated to other players by a sibling FishNet
|
||||
/// NetworkAnimator). Because both rigs read the same events at the same frame, the arms the
|
||||
/// owner sees and the body everyone else sees stay in lockstep by construction.
|
||||
/// This is owner-only local simulation: add every instance to
|
||||
/// PlayerNetworkController.ownerOnlyBehaviours so remote puppets never run it — a remote body
|
||||
/// is animated by its replicated NetworkAnimator instead. Every parameter name is optional;
|
||||
/// leave a field blank to skip that parameter, so a minimal arms rig can ignore the full
|
||||
/// locomotion blend that the third-person body uses.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class PlayerAnimatorDriver : MonoBehaviour
|
||||
{
|
||||
#region Serialized Fields
|
||||
|
||||
[Header("Target")]
|
||||
[SerializeField] private Animator animator;
|
||||
[Tooltip("Transform whose facing defines local move direction. Defaults to the Animator's transform when left empty.")]
|
||||
[SerializeField] private Transform orientation;
|
||||
|
||||
[Header("Float parameters (leave blank to skip)")]
|
||||
[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("Damping applied to float parameters for smooth blends.")]
|
||||
[SerializeField] private float floatDampTime = 0.1f;
|
||||
|
||||
[Header("Bool parameters (leave blank to skip)")]
|
||||
[SerializeField] private string groundedParam = "Grounded";
|
||||
[SerializeField] private string crouchingParam = "Crouching";
|
||||
[SerializeField] private string sprintingParam = "Sprinting";
|
||||
|
||||
[Header("Trigger parameters (leave blank to skip)")]
|
||||
[SerializeField] private string jumpParam = "Jump";
|
||||
[SerializeField] private string attackParam = "Attack";
|
||||
[SerializeField] private string landParam = "Land";
|
||||
|
||||
#endregion
|
||||
|
||||
#region State
|
||||
|
||||
private Vector3 planarVelocity;
|
||||
|
||||
private int speedId, moveXId, moveYId;
|
||||
private int groundedId, crouchingId, sprintingId;
|
||||
private int jumpId, attackId, landId;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity Lifecycle
|
||||
|
||||
/// <summary>
|
||||
/// Caches parameter hashes (0 for blank names, which are then skipped) and defaults the
|
||||
/// orientation reference so local move direction works without extra wiring.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
speedId = Hash(speedParam);
|
||||
moveXId = Hash(moveXParam);
|
||||
moveYId = Hash(moveYParam);
|
||||
groundedId = Hash(groundedParam);
|
||||
crouchingId = Hash(crouchingParam);
|
||||
sprintingId = Hash(sprintingParam);
|
||||
jumpId = Hash(jumpParam);
|
||||
attackId = Hash(attackParam);
|
||||
landId = Hash(landParam);
|
||||
|
||||
if (orientation == null && animator != null)
|
||||
orientation = animator.transform;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to the locomotion/tool events that map onto animation, and seeds the bool
|
||||
/// parameters from the bus's current state so a freshly enabled rig starts in the right pose.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
PlayerEvents.VelocityChanged += HandleVelocityChanged;
|
||||
PlayerEvents.GroundedChanged += HandleGroundedChanged;
|
||||
PlayerEvents.CrouchChanged += HandleCrouchChanged;
|
||||
PlayerEvents.SprintChanged += HandleSprintChanged;
|
||||
PlayerEvents.Jumped += HandleJumped;
|
||||
PlayerEvents.AttackSwung += HandleAttackSwung;
|
||||
PlayerEvents.LandingStunStarted += HandleLandingStunStarted;
|
||||
|
||||
SetBool(groundedId, PlayerEvents.IsGrounded);
|
||||
SetBool(crouchingId, PlayerEvents.IsCrouching);
|
||||
SetBool(sprintingId, PlayerEvents.IsSprinting);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes — mirrors OnEnable exactly.
|
||||
/// </summary>
|
||||
private void OnDisable()
|
||||
{
|
||||
PlayerEvents.VelocityChanged -= HandleVelocityChanged;
|
||||
PlayerEvents.GroundedChanged -= HandleGroundedChanged;
|
||||
PlayerEvents.CrouchChanged -= HandleCrouchChanged;
|
||||
PlayerEvents.SprintChanged -= HandleSprintChanged;
|
||||
PlayerEvents.Jumped -= HandleJumped;
|
||||
PlayerEvents.AttackSwung -= HandleAttackSwung;
|
||||
PlayerEvents.LandingStunStarted -= HandleLandingStunStarted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes the smoothed movement floats every frame so blend trees ease between poses
|
||||
/// instead of snapping when velocity changes.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
if (animator == null) return;
|
||||
|
||||
float scale = normalizeSpeed > 0f ? 1f / normalizeSpeed : 1f;
|
||||
SetFloat(speedId, planarVelocity.magnitude * scale);
|
||||
|
||||
if (moveXId != 0 || moveYId != 0)
|
||||
{
|
||||
Vector3 local = orientation != null
|
||||
? orientation.InverseTransformDirection(planarVelocity)
|
||||
: planarVelocity;
|
||||
SetFloat(moveXId, local.x * scale);
|
||||
SetFloat(moveYId, local.z * scale);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
/// <summary>
|
||||
/// Caches the planar (horizontal) velocity that Update turns into blend-tree floats.
|
||||
/// </summary>
|
||||
private void HandleVelocityChanged(Vector3 velocity)
|
||||
{
|
||||
planarVelocity = new Vector3(velocity.x, 0f, velocity.z);
|
||||
}
|
||||
|
||||
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);
|
||||
private void HandleJumped() => SetTrigger(jumpId);
|
||||
private void HandleAttackSwung() => 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);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Hashes a parameter name, returning 0 for a blank name so the setters can skip it.
|
||||
/// </summary>
|
||||
private int Hash(string paramName) => string.IsNullOrEmpty(paramName) ? 0 : Animator.StringToHash(paramName);
|
||||
|
||||
private void SetFloat(int id, float value)
|
||||
{
|
||||
if (id != 0) animator.SetFloat(id, value, floatDampTime, Time.deltaTime);
|
||||
}
|
||||
|
||||
private void SetBool(int id, bool value)
|
||||
{
|
||||
if (id != 0 && animator != null) animator.SetBool(id, value);
|
||||
}
|
||||
|
||||
private void SetTrigger(int id)
|
||||
{
|
||||
if (id != 0 && animator != null) animator.SetTrigger(id);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c1abd96334ddec4f8abf1ccdde057f9
|
||||
@@ -47,6 +47,7 @@ namespace Ashwild.Player
|
||||
public static event Action<bool> SprintHeld;
|
||||
public static event Action CrouchPressed;
|
||||
public static event Action AttackPressed;
|
||||
public static event Action SecondaryUsePressed; // right-click / secondary use (build menu, aim, block, ...)
|
||||
public static event Action InteractPressed;
|
||||
public static event Action DropPressed;
|
||||
public static event Action InventoryTogglePressed;
|
||||
@@ -115,6 +116,12 @@ namespace Ashwild.Player
|
||||
public static event Action EquipAnimComplete;
|
||||
public static event Action UnequipAnimComplete;
|
||||
|
||||
// ============================================================
|
||||
// Building events
|
||||
// ============================================================
|
||||
|
||||
public static event Action BuildMenuToggleRequested; // the build hammer asks to open/close the construction menu
|
||||
|
||||
// ============================================================
|
||||
// Cooking events
|
||||
// ============================================================
|
||||
@@ -166,6 +173,7 @@ namespace Ashwild.Player
|
||||
public static void RaiseSprintHeld(bool b) { Log(nameof(SprintHeld)); SprintHeld?.Invoke(b); }
|
||||
public static void RaiseCrouchPressed() { Log(nameof(CrouchPressed)); CrouchPressed?.Invoke(); }
|
||||
public static void RaiseAttackPressed() { Log(nameof(AttackPressed)); AttackPressed?.Invoke(); }
|
||||
public static void RaiseSecondaryUsePressed() { Log(nameof(SecondaryUsePressed)); SecondaryUsePressed?.Invoke(); }
|
||||
public static void RaiseInteractPressed() { Log(nameof(InteractPressed)); InteractPressed?.Invoke(); }
|
||||
public static void RaiseDropPressed() { Log(nameof(DropPressed)); DropPressed?.Invoke(); }
|
||||
public static void RaiseInventoryTogglePressed() { Log(nameof(InventoryTogglePressed)); InventoryTogglePressed?.Invoke(); }
|
||||
@@ -258,6 +266,8 @@ namespace Ashwild.Player
|
||||
public static void RaiseEquipAnimComplete() { EquipAnimComplete?.Invoke(); }
|
||||
public static void RaiseUnequipAnimComplete() { UnequipAnimComplete?.Invoke(); }
|
||||
|
||||
public static void RaiseBuildMenuToggleRequested() { Log(nameof(BuildMenuToggleRequested)); BuildMenuToggleRequested?.Invoke(); }
|
||||
|
||||
public static void RaiseFoodPlacedToCook(ItemData raw) { Log(nameof(FoodPlacedToCook)); FoodPlacedToCook?.Invoke(raw); }
|
||||
public static void RaiseFoodCookedReady(ItemData cooked) { Log(nameof(FoodCookedReady)); FoodCookedReady?.Invoke(cooked); }
|
||||
public static void RaiseFuelAdded(ItemData fuel) { Log(nameof(FuelAdded)); FuelAdded?.Invoke(fuel); }
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
using UnityEngine;
|
||||
using Ashwild.Inventory;
|
||||
|
||||
namespace Ashwild.Player
|
||||
{
|
||||
/// <summary>
|
||||
/// Held-item logic for the build hammer, placed on its hand prefab. While equipped it
|
||||
/// listens to the secondary-use input (right-click) and asks the construction UI to
|
||||
/// open/close through the bus — it never reaches for the menu itself, so the hammer
|
||||
/// stays a pure input source and the UI owns its own state. Left-click placement will
|
||||
/// be added on top of this once the build menu exists.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class BuildHammerBehaviour : MonoBehaviour, IHeldItemBehaviour
|
||||
{
|
||||
#region Serialized Fields
|
||||
|
||||
[Header("Cooldown")]
|
||||
/// <summary>
|
||||
/// Minimum time, in seconds, between two menu toggles so a held right-click
|
||||
/// does not flicker the menu open and shut.
|
||||
/// </summary>
|
||||
[SerializeField] private float toggleCooldown = 0.25f;
|
||||
|
||||
#endregion
|
||||
|
||||
#region State
|
||||
|
||||
/// <summary>
|
||||
/// Earliest time, in seconds, the next menu toggle is allowed.
|
||||
/// </summary>
|
||||
private float nextToggleTime;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity Lifecycle
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to the secondary-use input for as long as the hammer is held.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
PlayerEvents.SecondaryUsePressed += HandleSecondaryUse;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes when the hammer is put away (the prefab is destroyed).
|
||||
/// </summary>
|
||||
private void OnDisable()
|
||||
{
|
||||
PlayerEvents.SecondaryUsePressed -= HandleSecondaryUse;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IHeldItemBehaviour
|
||||
|
||||
/// <summary>
|
||||
/// Nothing to link yet: toggling the menu needs no player refs. Present to satisfy
|
||||
/// the held-item contract; placement logic added later will use the context.
|
||||
/// </summary>
|
||||
public void Setup(HeldItemContext context, ItemData item)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
/// <summary>
|
||||
/// Requests the construction menu to open/close on right-click, gated by lock and
|
||||
/// cooldown. The UI decides whether that opens or closes it.
|
||||
/// </summary>
|
||||
private void HandleSecondaryUse()
|
||||
{
|
||||
if (PlayerEvents.InputLocked) return;
|
||||
if (Time.time < nextToggleTime) return;
|
||||
|
||||
nextToggleTime = Time.time + toggleCooldown;
|
||||
PlayerEvents.RaiseBuildMenuToggleRequested();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 59ffa82caa5b57841a87a6f1e5d8839d
|
||||
@@ -199,6 +199,7 @@ namespace Ashwild.Player
|
||||
WireHold(playerMap, "Sprint", PlayerEvents.RaiseSprintHeld);
|
||||
WireButton(playerMap, "Crouch", PlayerEvents.RaiseCrouchPressed);
|
||||
WireButton(playerMap, "Attack", PlayerEvents.RaiseAttackPressed);
|
||||
WireButton(playerMap, "SecondaryUse", PlayerEvents.RaiseSecondaryUsePressed);
|
||||
WireButton(playerMap, "Interact", PlayerEvents.RaiseInteractPressed);
|
||||
WireButton(playerMap, "Drop", PlayerEvents.RaiseDropPressed);
|
||||
WireButton(playerMap, "Inventory", PlayerEvents.RaiseInventoryTogglePressed);
|
||||
|
||||
Reference in New Issue
Block a user