PuckController.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  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. [Header("Interception Settings")]
  16. [SerializeField] private bool canBeIntercepted = true;
  17. [SerializeField] private float interceptionCheckRadius = 0.5f;
  18. [SerializeField] private LayerMask playerLayer;
  19. private bool isPassActive = false;
  20. private float passStartTime = 0f;
  21. private float passProtectionTime = 0.1f; // Short window where pass can't be intercepted
  22. private Rigidbody rb;
  23. private MeshRenderer meshRenderer;
  24. private PlayerController currentCarrier;
  25. private bool isBeingCarried = false;
  26. private Vector3 targetCarryPosition;
  27. public PlayerController CurrentCarrier => currentCarrier;
  28. public bool IsLoose => currentCarrier == null;
  29. void Awake()
  30. {
  31. SetupPhysics();
  32. SetupVisuals();
  33. }
  34. void SetupPhysics()
  35. {
  36. rb = GetComponent<Rigidbody>();
  37. if (rb == null)
  38. {
  39. rb = gameObject.AddComponent<Rigidbody>();
  40. }
  41. rb.mass = mass;
  42. rb.linearDamping = drag;
  43. rb.angularDamping = angularDrag;
  44. rb.collisionDetectionMode = CollisionDetectionMode.Continuous;
  45. rb.interpolation = RigidbodyInterpolation.Interpolate;
  46. // IMPORTANT: Make sure puck is NOT kinematic at start
  47. rb.isKinematic = false;
  48. // Freeze rotation on X and Z to keep puck flat
  49. rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ;
  50. // Set up collider if not present
  51. SphereCollider collider = GetComponent<SphereCollider>();
  52. if (collider == null)
  53. {
  54. collider = gameObject.AddComponent<SphereCollider>();
  55. }
  56. collider.radius = 0.038f; // ~76mm diameter regulation puck
  57. collider.isTrigger = false; // MUST be false for physics collisions
  58. // Create physics material for ice-like behavior
  59. PhysicsMaterial puckPhysicsMaterial = new PhysicsMaterial("PuckPhysics");
  60. puckPhysicsMaterial.dynamicFriction = friction;
  61. puckPhysicsMaterial.staticFriction = friction;
  62. puckPhysicsMaterial.bounciness = 0.1f; // Reduced bounce
  63. puckPhysicsMaterial.frictionCombine = PhysicsMaterialCombine.Minimum;
  64. puckPhysicsMaterial.bounceCombine = PhysicsMaterialCombine.Minimum; // Changed to Minimum
  65. collider.material = puckPhysicsMaterial;
  66. }
  67. void SetupVisuals()
  68. {
  69. meshRenderer = GetComponent<MeshRenderer>();
  70. if (meshRenderer == null)
  71. {
  72. // Create a basic cylinder for the puck
  73. GameObject cylinder = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
  74. cylinder.transform.SetParent(transform);
  75. cylinder.transform.localPosition = Vector3.zero;
  76. cylinder.transform.localScale = new Vector3(0.076f, 0.0125f, 0.076f); // Regulation size
  77. meshRenderer = cylinder.GetComponent<MeshRenderer>();
  78. // Destroy the collider from the primitive (we have our own)
  79. Destroy(cylinder.GetComponent<Collider>());
  80. }
  81. }
  82. void FixedUpdate()
  83. {
  84. if (isBeingCarried && currentCarrier != null)
  85. {
  86. // Keep puck attached to player
  87. Vector3 carrierForward = currentCarrier.transform.forward;
  88. targetCarryPosition = currentCarrier.transform.position + carrierForward * carrierOffset;
  89. targetCarryPosition.y = 0.025f;
  90. transform.position = targetCarryPosition;
  91. // Only set velocities if NOT kinematic
  92. if (rb != null && !rb.isKinematic)
  93. {
  94. rb.linearVelocity = Vector3.zero;
  95. rb.angularVelocity = Vector3.zero;
  96. }
  97. }
  98. else
  99. {
  100. // Check for interceptions during passes
  101. if (isPassActive && canBeIntercepted)
  102. {
  103. CheckForInterception();
  104. }
  105. // Keep puck at proper height on ice when loose
  106. if (transform.position.y < 0.025f)
  107. {
  108. Vector3 pos = transform.position;
  109. pos.y = 0.025f;
  110. transform.position = pos;
  111. if (rb != null && !rb.isKinematic)
  112. {
  113. Vector3 vel = rb.linearVelocity;
  114. if (vel.y < 0)
  115. {
  116. vel.y = 0;
  117. rb.linearVelocity = vel;
  118. }
  119. if (rb.angularVelocity.magnitude > 10f)
  120. {
  121. rb.angularVelocity *= 0.9f;
  122. }
  123. }
  124. }
  125. // End pass if puck has slowed down
  126. if (isPassActive && rb != null && rb.linearVelocity.magnitude < 1f)
  127. {
  128. isPassActive = false;
  129. }
  130. }
  131. }
  132. private void CheckForInterception()
  133. {
  134. // Skip interception check during protection window
  135. if (Time.time - passStartTime < passProtectionTime)
  136. return;
  137. // Find nearby players
  138. Collider[] nearbyColliders = Physics.OverlapSphere(transform.position, interceptionCheckRadius, playerLayer);
  139. foreach (var col in nearbyColliders)
  140. {
  141. PlayerController player = col.GetComponent<PlayerController>();
  142. if (player != null && player.CanPickupPuck())
  143. {
  144. // Calculate interception chance based on player stats and positioning
  145. float interceptChance = CalculateInterceptionChance(player);
  146. if (Random.value < interceptChance)
  147. {
  148. Debug.Log($"{player.stats.playerName} intercepts the pass!");
  149. AssignToPlayer(player);
  150. isPassActive = false;
  151. break;
  152. }
  153. }
  154. }
  155. }
  156. private float CalculateInterceptionChance(PlayerController player)
  157. {
  158. // Base chance very low (5%)
  159. float baseChance = 0.05f;
  160. // Increase based on awareness and stick handling
  161. float statBonus = (player.stats.awareness + player.stats.stickHandling) / 200f * 0.1f;
  162. // Increase if player is facing the puck
  163. Vector3 toPuck = (transform.position - player.transform.position).normalized;
  164. float facingBonus = Vector3.Dot(player.transform.forward, toPuck) * 0.05f;
  165. return Mathf.Clamp01(baseChance + statBonus + facingBonus);
  166. }
  167. public void AssignToPlayer(PlayerController player)
  168. {
  169. if (currentCarrier != null)
  170. {
  171. currentCarrier.ReleasePuck();
  172. }
  173. currentCarrier = player;
  174. isBeingCarried = true;
  175. isPassActive = false; // Stop pass when picked up
  176. if (player != null)
  177. {
  178. // IMPORTANT: Stop all physics movement immediately
  179. if (rb != null)
  180. {
  181. // Stop velocities BEFORE making kinematic
  182. if (!rb.isKinematic)
  183. {
  184. rb.linearVelocity = Vector3.zero;
  185. rb.angularVelocity = Vector3.zero;
  186. }
  187. // Now make kinematic
  188. rb.isKinematic = true;
  189. }
  190. // Position puck at player's feet immediately to prevent bouncing
  191. Vector3 playerForward = player.transform.forward;
  192. Vector3 targetPos = player.transform.position + playerForward * carrierOffset;
  193. targetPos.y = 0.025f;
  194. transform.position = targetPos;
  195. player.GainPuck(this);
  196. UpdateVisualState(true);
  197. }
  198. }
  199. public void Release()
  200. {
  201. if (currentCarrier != null)
  202. {
  203. currentCarrier.ReleasePuck();
  204. }
  205. currentCarrier = null;
  206. isBeingCarried = false;
  207. // Re-enable physics
  208. if (rb != null && rb.isKinematic)
  209. {
  210. rb.isKinematic = false;
  211. // Velocities will be set by ApplyForce or naturally by physics
  212. }
  213. UpdateVisualState(false);
  214. }
  215. public void ApplyForce(Vector3 force, ForceMode mode = ForceMode.Impulse)
  216. {
  217. Release();
  218. rb.AddForce(force, mode);
  219. // Mark as active pass
  220. isPassActive = true;
  221. passStartTime = Time.time;
  222. }
  223. public void SetVelocity(Vector3 velocity)
  224. {
  225. Release();
  226. rb.linearVelocity = velocity;
  227. }
  228. public bool IsWithinPossessionRange(Vector3 playerPosition)
  229. {
  230. return Vector3.Distance(transform.position, playerPosition) <= possessionRadius;
  231. }
  232. private void UpdateVisualState(bool possessed)
  233. {
  234. if (meshRenderer != null)
  235. {
  236. if (possessed && possessedMaterial != null)
  237. {
  238. meshRenderer.material = possessedMaterial;
  239. }
  240. else if (normalMaterial != null)
  241. {
  242. meshRenderer.material = normalMaterial;
  243. }
  244. }
  245. }
  246. // Physics collision - check if any player can pick up the puck
  247. void OnTriggerStay(Collider other)
  248. {
  249. // Only auto-pickup if puck is NOT in active pass and moving slowly
  250. if (IsLoose && !isPassActive && rb != null && rb.linearVelocity.magnitude < 2f)
  251. {
  252. PlayerController player = other.GetComponent<PlayerController>();
  253. if (player != null && player.CanPickupPuck())
  254. {
  255. AssignToPlayer(player);
  256. }
  257. }
  258. }
  259. public float GetSpeed()
  260. {
  261. return rb.linearVelocity.magnitude;
  262. }
  263. public Vector3 GetVelocity()
  264. {
  265. return rb.linearVelocity;
  266. }
  267. }