| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 |
- 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;
- [Header("Interception Settings")]
- [SerializeField] private bool canBeIntercepted = true;
- [SerializeField] private float interceptionCheckRadius = 0.5f;
- [SerializeField] private LayerMask playerLayer;
- private bool isPassActive = false;
- private float passStartTime = 0f;
- private float passProtectionTime = 0.1f; // Short window where pass can't be intercepted
- 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<Rigidbody>();
- if (rb == null)
- {
- rb = gameObject.AddComponent<Rigidbody>();
- }
- rb.mass = mass;
- rb.linearDamping = drag;
- rb.angularDamping = angularDrag;
- rb.collisionDetectionMode = CollisionDetectionMode.Continuous;
- rb.interpolation = RigidbodyInterpolation.Interpolate;
- // IMPORTANT: Make sure puck is NOT kinematic at start
- rb.isKinematic = false;
- // Freeze rotation on X and Z to keep puck flat
- rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ;
- // Set up collider if not present
- SphereCollider collider = GetComponent<SphereCollider>();
- if (collider == null)
- {
- collider = gameObject.AddComponent<SphereCollider>();
- }
- collider.radius = 0.038f; // ~76mm diameter regulation puck
- collider.isTrigger = false; // MUST be false for physics collisions
- // Create physics material for ice-like behavior
- PhysicsMaterial puckPhysicsMaterial = new PhysicsMaterial("PuckPhysics");
- puckPhysicsMaterial.dynamicFriction = friction;
- puckPhysicsMaterial.staticFriction = friction;
- puckPhysicsMaterial.bounciness = 0.1f; // Reduced bounce
- puckPhysicsMaterial.frictionCombine = PhysicsMaterialCombine.Minimum;
- puckPhysicsMaterial.bounceCombine = PhysicsMaterialCombine.Minimum; // Changed to Minimum
- collider.material = puckPhysicsMaterial;
- }
- void SetupVisuals()
- {
- meshRenderer = GetComponent<MeshRenderer>();
- 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<MeshRenderer>();
- // Destroy the collider from the primitive (we have our own)
- Destroy(cylinder.GetComponent<Collider>());
- }
- }
- 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;
- transform.position = targetCarryPosition;
- if (rb != null)
- {
- rb.linearVelocity = Vector3.zero;
- rb.angularVelocity = Vector3.zero;
- }
- }
- else
- {
- // Check for interceptions during passes
- if (isPassActive && canBeIntercepted)
- {
- CheckForInterception();
- }
- // 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;
- if (rb != null)
- {
- Vector3 vel = rb.linearVelocity;
- if (vel.y < 0)
- {
- vel.y = 0;
- rb.linearVelocity = vel;
- }
- if (rb.angularVelocity.magnitude > 10f)
- {
- rb.angularVelocity *= 0.9f;
- }
- }
- }
- // End pass if puck has slowed down
- if (isPassActive && rb != null && rb.linearVelocity.magnitude < 1f)
- {
- isPassActive = false;
- }
- }
- }
- private void CheckForInterception()
- {
- // Skip interception check during protection window
- if (Time.time - passStartTime < passProtectionTime)
- return;
- // Find nearby players
- Collider[] nearbyColliders = Physics.OverlapSphere(transform.position, interceptionCheckRadius, playerLayer);
- foreach (var col in nearbyColliders)
- {
- PlayerController player = col.GetComponent<PlayerController>();
- if (player != null && player.CanPickupPuck())
- {
- // Calculate interception chance based on player stats and positioning
- float interceptChance = CalculateInterceptionChance(player);
- if (Random.value < interceptChance)
- {
- Debug.Log($"{player.stats.playerName} intercepts the pass!");
- AssignToPlayer(player);
- isPassActive = false;
- break;
- }
- }
- }
- }
- private float CalculateInterceptionChance(PlayerController player)
- {
- // Base chance very low (5%)
- float baseChance = 0.05f;
- // Increase based on awareness and stick handling
- float statBonus = (player.stats.awareness + player.stats.stickHandling) / 200f * 0.1f;
- // Increase if player is facing the puck
- Vector3 toPuck = (transform.position - player.transform.position).normalized;
- float facingBonus = Vector3.Dot(player.transform.forward, toPuck) * 0.05f;
- return Mathf.Clamp01(baseChance + statBonus + facingBonus);
- }
- 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();
- rb.AddForce(force, mode);
- // Mark as active pass
- isPassActive = true;
- passStartTime = Time.time;
- }
- 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 NOT in active pass and moving slowly
- if (IsLoose && !isPassActive && rb != null && rb.linearVelocity.magnitude < 2f)
- {
- PlayerController player = other.GetComponent<PlayerController>();
- if (player != null && player.CanPickupPuck())
- {
- AssignToPlayer(player);
- }
- }
- }
- public float GetSpeed()
- {
- return rb.linearVelocity.magnitude;
- }
- public Vector3 GetVelocity()
- {
- return rb.linearVelocity;
- }
- }
|