PuckController.cs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. using UnityEngine;
  2. public class PuckController : MonoBehaviour
  3. {
  4. [Header("Physics Settings")]
  5. [SerializeField] private float mass = 0.17f; // ~170g (regulation puck)
  6. [SerializeField] private float drag = 0.5f;
  7. [SerializeField] private float angularDrag = 0.5f;
  8. [SerializeField] private float friction = 0.3f;
  9. [Header("Possession Settings")]
  10. [SerializeField] private float possessionRadius = 1.5f; // Distance to consider "controlling" the puck
  11. [SerializeField] private float carrierOffset = 1.0f; // How far in front of player when carrying
  12. [Header("Visual Feedback")]
  13. [SerializeField] private Material normalMaterial;
  14. [SerializeField] private Material possessedMaterial;
  15. private Rigidbody rb;
  16. private MeshRenderer meshRenderer;
  17. private PlayerController currentCarrier;
  18. private bool isBeingCarried = false;
  19. private Vector3 targetCarryPosition;
  20. public PlayerController CurrentCarrier => currentCarrier;
  21. public bool IsLoose => currentCarrier == null;
  22. void Awake()
  23. {
  24. SetupPhysics();
  25. SetupVisuals();
  26. }
  27. void SetupPhysics()
  28. {
  29. rb = GetComponent<Rigidbody>();
  30. if (rb == null)
  31. {
  32. rb = gameObject.AddComponent<Rigidbody>();
  33. }
  34. rb.mass = mass;
  35. rb.linearDamping = drag;
  36. rb.angularDamping = angularDrag;
  37. rb.collisionDetectionMode = CollisionDetectionMode.Continuous;
  38. rb.interpolation = RigidbodyInterpolation.Interpolate;
  39. // Set up collider if not present
  40. SphereCollider collider = GetComponent<SphereCollider>();
  41. if (collider == null)
  42. {
  43. collider = gameObject.AddComponent<SphereCollider>();
  44. collider.radius = 0.038f; // ~76mm diameter regulation puck
  45. }
  46. // Create physics material for ice-like behavior
  47. PhysicsMaterial puckPhysicsMaterial = new PhysicsMaterial("PuckPhysics");
  48. puckPhysicsMaterial.dynamicFriction = friction;
  49. puckPhysicsMaterial.staticFriction = friction;
  50. puckPhysicsMaterial.bounciness = 0.2f;
  51. puckPhysicsMaterial.frictionCombine = PhysicsMaterialCombine.Minimum;
  52. puckPhysicsMaterial.bounceCombine = PhysicsMaterialCombine.Average;
  53. collider.material = puckPhysicsMaterial;
  54. }
  55. void SetupVisuals()
  56. {
  57. meshRenderer = GetComponent<MeshRenderer>();
  58. if (meshRenderer == null)
  59. {
  60. // Create a basic cylinder for the puck
  61. GameObject cylinder = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
  62. cylinder.transform.SetParent(transform);
  63. cylinder.transform.localPosition = Vector3.zero;
  64. cylinder.transform.localScale = new Vector3(0.076f, 0.0125f, 0.076f); // Regulation size
  65. meshRenderer = cylinder.GetComponent<MeshRenderer>();
  66. // Destroy the collider from the primitive (we have our own)
  67. Destroy(cylinder.GetComponent<Collider>());
  68. }
  69. }
  70. void FixedUpdate()
  71. {
  72. if (isBeingCarried && currentCarrier != null)
  73. {
  74. // Keep puck attached to player
  75. Vector3 carrierForward = currentCarrier.transform.forward;
  76. targetCarryPosition = currentCarrier.transform.position + carrierForward * carrierOffset;
  77. targetCarryPosition.y = 0.025f;
  78. // Instant positioning while carried (no lerp to prevent lag)
  79. transform.position = targetCarryPosition;
  80. // Make absolutely sure no velocity while carried
  81. if (rb != null)
  82. {
  83. rb.linearVelocity = Vector3.zero;
  84. rb.angularVelocity = Vector3.zero;
  85. }
  86. }
  87. else
  88. {
  89. // Keep puck at proper height on ice when loose
  90. if (transform.position.y < 0.025f)
  91. {
  92. Vector3 pos = transform.position;
  93. pos.y = 0.025f;
  94. transform.position = pos;
  95. // Dampen vertical velocity
  96. if (rb != null)
  97. {
  98. Vector3 vel = rb.linearVelocity;
  99. if (vel.y < 0)
  100. {
  101. vel.y = 0;
  102. rb.linearVelocity = vel;
  103. }
  104. // Dampen excessive spinning
  105. if (rb.angularVelocity.magnitude > 10f)
  106. {
  107. rb.angularVelocity *= 0.9f;
  108. }
  109. }
  110. }
  111. }
  112. }
  113. public void AssignToPlayer(PlayerController player)
  114. {
  115. if (currentCarrier != null)
  116. {
  117. currentCarrier.ReleasePuck();
  118. }
  119. currentCarrier = player;
  120. isBeingCarried = true;
  121. if (player != null)
  122. {
  123. // IMPORTANT: Stop all physics movement immediately
  124. if (rb != null)
  125. {
  126. rb.linearVelocity = Vector3.zero;
  127. rb.angularVelocity = Vector3.zero;
  128. rb.isKinematic = true; // Make kinematic while carried
  129. }
  130. // Position puck at player's feet immediately to prevent bouncing
  131. Vector3 playerForward = player.transform.forward;
  132. Vector3 targetPos = player.transform.position + playerForward * carrierOffset;
  133. targetPos.y = 0.025f; // Puck height
  134. transform.position = targetPos;
  135. player.GainPuck(this);
  136. UpdateVisualState(true);
  137. }
  138. }
  139. public void Release()
  140. {
  141. if (currentCarrier != null)
  142. {
  143. currentCarrier.ReleasePuck();
  144. }
  145. currentCarrier = null;
  146. isBeingCarried = false;
  147. // Re-enable physics
  148. if (rb != null)
  149. {
  150. rb.isKinematic = false;
  151. }
  152. UpdateVisualState(false);
  153. }
  154. public void ApplyForce(Vector3 force, ForceMode mode = ForceMode.Impulse)
  155. {
  156. Release(); // Free the puck first
  157. rb.AddForce(force, mode);
  158. }
  159. public void SetVelocity(Vector3 velocity)
  160. {
  161. Release();
  162. rb.linearVelocity = velocity;
  163. }
  164. public bool IsWithinPossessionRange(Vector3 playerPosition)
  165. {
  166. return Vector3.Distance(transform.position, playerPosition) <= possessionRadius;
  167. }
  168. private void UpdateVisualState(bool possessed)
  169. {
  170. if (meshRenderer != null)
  171. {
  172. if (possessed && possessedMaterial != null)
  173. {
  174. meshRenderer.material = possessedMaterial;
  175. }
  176. else if (normalMaterial != null)
  177. {
  178. meshRenderer.material = normalMaterial;
  179. }
  180. }
  181. }
  182. // Physics collision - check if any player can pick up the puck
  183. void OnTriggerStay(Collider other)
  184. {
  185. // Only auto-pickup if puck is moving slowly (not during passes)
  186. if (IsLoose && rb != null && rb.linearVelocity.magnitude < 2f)
  187. {
  188. PlayerController player = other.GetComponent<PlayerController>();
  189. if (player != null && player.CanPickupPuck())
  190. {
  191. AssignToPlayer(player);
  192. }
  193. }
  194. }
  195. public float GetSpeed()
  196. {
  197. return rb.linearVelocity.magnitude;
  198. }
  199. public Vector3 GetVelocity()
  200. {
  201. return rb.linearVelocity;
  202. }
  203. }