using UnityEngine; public class PuckController : MonoBehaviour { [Header("Physics Settings")] [SerializeField] private float mass = 0.17f; // ~170g (regulation puck) [SerializeField] private float drag = 0.5f; [SerializeField] private float angularDrag = 0.5f; [SerializeField] private float friction = 0.3f; [Header("Possession Settings")] [SerializeField] private float possessionRadius = 1.5f; // Distance to consider "controlling" the puck [SerializeField] private float carrierOffset = 1.0f; // How far in front of player when carrying [Header("Visual Feedback")] [SerializeField] private Material normalMaterial; [SerializeField] private Material possessedMaterial; private Rigidbody rb; private MeshRenderer meshRenderer; private PlayerController currentCarrier; private bool isBeingCarried = false; private Vector3 targetCarryPosition; public PlayerController CurrentCarrier => currentCarrier; public bool IsLoose => currentCarrier == null; void Awake() { SetupPhysics(); SetupVisuals(); } void SetupPhysics() { rb = GetComponent(); if (rb == null) { rb = gameObject.AddComponent(); } rb.mass = mass; rb.linearDamping = drag; rb.angularDamping = angularDrag; rb.collisionDetectionMode = CollisionDetectionMode.Continuous; rb.interpolation = RigidbodyInterpolation.Interpolate; // Set up collider if not present SphereCollider collider = GetComponent(); if (collider == null) { collider = gameObject.AddComponent(); collider.radius = 0.038f; // ~76mm diameter regulation puck } // Create physics material for ice-like behavior PhysicsMaterial puckPhysicsMaterial = new PhysicsMaterial("PuckPhysics"); puckPhysicsMaterial.dynamicFriction = friction; puckPhysicsMaterial.staticFriction = friction; puckPhysicsMaterial.bounciness = 0.2f; puckPhysicsMaterial.frictionCombine = PhysicsMaterialCombine.Minimum; puckPhysicsMaterial.bounceCombine = PhysicsMaterialCombine.Average; collider.material = puckPhysicsMaterial; } void SetupVisuals() { meshRenderer = GetComponent(); if (meshRenderer == null) { // Create a basic cylinder for the puck GameObject cylinder = GameObject.CreatePrimitive(PrimitiveType.Cylinder); cylinder.transform.SetParent(transform); cylinder.transform.localPosition = Vector3.zero; cylinder.transform.localScale = new Vector3(0.076f, 0.0125f, 0.076f); // Regulation size meshRenderer = cylinder.GetComponent(); // Destroy the collider from the primitive (we have our own) Destroy(cylinder.GetComponent()); } } void FixedUpdate() { if (isBeingCarried && currentCarrier != null) { // Keep puck attached to player Vector3 carrierForward = currentCarrier.transform.forward; targetCarryPosition = currentCarrier.transform.position + carrierForward * carrierOffset; targetCarryPosition.y = 0.025f; // Instant positioning while carried (no lerp to prevent lag) transform.position = targetCarryPosition; // Make absolutely sure no velocity while carried if (rb != null) { rb.linearVelocity = Vector3.zero; rb.angularVelocity = Vector3.zero; } } else { // Keep puck at proper height on ice when loose if (transform.position.y < 0.025f) { Vector3 pos = transform.position; pos.y = 0.025f; transform.position = pos; // Dampen vertical velocity if (rb != null) { Vector3 vel = rb.linearVelocity; if (vel.y < 0) { vel.y = 0; rb.linearVelocity = vel; } // Dampen excessive spinning if (rb.angularVelocity.magnitude > 10f) { rb.angularVelocity *= 0.9f; } } } } } public void AssignToPlayer(PlayerController player) { if (currentCarrier != null) { currentCarrier.ReleasePuck(); } currentCarrier = player; isBeingCarried = true; if (player != null) { // IMPORTANT: Stop all physics movement immediately if (rb != null) { rb.linearVelocity = Vector3.zero; rb.angularVelocity = Vector3.zero; rb.isKinematic = true; // Make kinematic while carried } // Position puck at player's feet immediately to prevent bouncing Vector3 playerForward = player.transform.forward; Vector3 targetPos = player.transform.position + playerForward * carrierOffset; targetPos.y = 0.025f; // Puck height transform.position = targetPos; player.GainPuck(this); UpdateVisualState(true); } } public void Release() { if (currentCarrier != null) { currentCarrier.ReleasePuck(); } currentCarrier = null; isBeingCarried = false; // Re-enable physics if (rb != null) { rb.isKinematic = false; } UpdateVisualState(false); } public void ApplyForce(Vector3 force, ForceMode mode = ForceMode.Impulse) { Release(); // Free the puck first rb.AddForce(force, mode); } public void SetVelocity(Vector3 velocity) { Release(); rb.linearVelocity = velocity; } public bool IsWithinPossessionRange(Vector3 playerPosition) { return Vector3.Distance(transform.position, playerPosition) <= possessionRadius; } private void UpdateVisualState(bool possessed) { if (meshRenderer != null) { if (possessed && possessedMaterial != null) { meshRenderer.material = possessedMaterial; } else if (normalMaterial != null) { meshRenderer.material = normalMaterial; } } } // Physics collision - check if any player can pick up the puck void OnTriggerStay(Collider other) { // Only auto-pickup if puck is moving slowly (not during passes) if (IsLoose && rb != null && rb.linearVelocity.magnitude < 2f) { PlayerController player = other.GetComponent(); if (player != null && player.CanPickupPuck()) { AssignToPlayer(player); } } } public float GetSpeed() { return rb.linearVelocity.magnitude; } public Vector3 GetVelocity() { return rb.linearVelocity; } }