using UnityEngine; using System.Linq; /// /// AI-controlled movement for hockey players using physics-based forces. /// Implements realistic hockey skating with momentum and inertia. /// public class PlayerAIMovement : MonoBehaviour { [Header("Movement Settings")] [SerializeField] private float maxSpeed = 8f; [SerializeField] private float acceleration = 15f; [SerializeField] private float deceleration = 10f; [SerializeField] private float turnSpeed = 5f; [SerializeField] private float stoppingForce = 20f; [Header("AI Behavior Settings")] [SerializeField] private MovementSchema movementSchema; [SerializeField] private float decisionUpdateInterval = 0.2f; [SerializeField] private float minOpponentDistance = 5f; [SerializeField] private float passSearchRadius = 20f; [SerializeField] private float shootingDistance = 15f; [SerializeField] private float interceptionRadius = 2f; [Header("Zone Positions")] [SerializeField] private float defensiveBlueLineZ = -18f; [SerializeField] private float offensiveBlueLineZ = 18f; [SerializeField] private float neutralZoneCenter = 0f; [Header("Faceoff Control")] [Tooltip("Disable AI movement during faceoffs")] public bool freezeDuringFaceoff = true; private Vector3 targetPosition; private Vector3 desiredVelocity; private float nextDecisionTime; private bool isAIControlled = true; private Rigidbody rb; private PlayerController playerController; private PuckController puck; private FaceoffManager faceoffManager; public bool IsAIControlled { get => isAIControlled; set => isAIControlled = value; } void Awake() { // Get components from same GameObject playerController = GetComponent(); rb = GetComponent(); // Find puck by tag GameObject puckObject = GameObject.FindGameObjectWithTag("Puck"); if (puckObject != null) { puck = puckObject.GetComponent(); } // Find faceoff manager faceoffManager = FindAnyObjectByType(); } void Start() { // Set rigidbody constraints for hockey movement if (rb != null) { rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ | RigidbodyConstraints.FreezePositionY; rb.linearDamping = 1f; } } void Update() { if (!isAIControlled) return; // Don't make decisions if frozen (during faceoff) if (playerController != null && playerController.IsFrozen()) { return; } // Don't make decisions if puck is not in play if (GameManager.Instance != null && !GameManager.Instance.IsPuckInPlay) { return; } // Make decisions periodically if (Time.time >= nextDecisionTime) { MakeMovementDecision(); nextDecisionTime = Time.time + decisionUpdateInterval; } } void FixedUpdate() { if (!isAIControlled || rb == null) return; // Stop movement if frozen (during faceoff) if (playerController != null && playerController.IsFrozen()) { rb.linearVelocity = Vector3.zero; rb.angularVelocity = Vector3.zero; return; } // Stop movement if puck is not in play if (GameManager.Instance != null && !GameManager.Instance.IsPuckInPlay) { rb.linearVelocity = Vector3.Lerp(rb.linearVelocity, Vector3.zero, deceleration * Time.fixedDeltaTime); return; } ApplyMovementForces(); ApplyRotation(); } bool IsFaceoffActive() { // Check if faceoff manager exists and has a faceoff in progress // You can also check if players are frozen via FaceoffManager if (faceoffManager != null) { // Access the private field through reflection or add a public property to FaceoffManager // For now, we'll assume if faceoffManager exists and players might be frozen return false; // Placeholder - you'll need to add public property to FaceoffManager } return false; } void MakeMovementDecision() { if (playerController == null || puck == null) return; bool hasPuck = playerController.HasPuck(); bool teammateHasPuck = IsTeammateCarryingPuck(out PlayerController puckCarrier); bool opponentHasPuck = puck.CurrentCarrier != null && IsOpponent(puck.CurrentCarrier); if (hasPuck) { DecideMovementWithPuck(); } else if (teammateHasPuck) { DecideMovementTeammateHasPuck(puckCarrier); } else if (opponentHasPuck) { DecideMovementOpponentHasPuck(); } else { DecideMovementLoosePuck(); } } #region Movement Behaviors void DecideMovementWithPuck() { Vector3 avoidanceVector = CalculateOpponentAvoidance(); bool inOffensiveZone = IsInOffensiveZone(); Vector3 goalPosition = GetOpponentGoalPosition(); float distanceToGoal = Vector3.Distance(transform.position, goalPosition); if (inOffensiveZone && distanceToGoal < shootingDistance) { Vector3 shootingPosition = FindBestShootingPosition(); targetPosition = shootingPosition + avoidanceVector; } else { Vector3 offensiveDirection = (goalPosition - transform.position).normalized; targetPosition = transform.position + offensiveDirection * 3f + avoidanceVector; } float moveDistance = Vector3.Distance(transform.position, targetPosition); if (moveDistance > 5f) { targetPosition = transform.position + (targetPosition - transform.position).normalized * 5f; } } void DecideMovementTeammateHasPuck(PlayerController puckCarrier) { if (puckCarrier == null) return; if (movementSchema == null) { DefaultTeammateHasPuckBehavior(puckCarrier); return; } bool puckCarrierIsDefender = IsDefender(puckCarrier.stats.position); bool thisPlayerIsDefender = IsDefender(playerController.stats.position); if (puckCarrierIsDefender && thisPlayerIsDefender) { targetPosition = FindOpenPassReceivingPosition(puckCarrier); } else if (!thisPlayerIsDefender) { targetPosition = FindForwardSupportPosition(puckCarrier); } else { DefaultTeammateHasPuckBehavior(puckCarrier); } } void DecideMovementOpponentHasPuck() { PlayerController opponentWithPuck = puck.CurrentCarrier; if (opponentWithPuck == null) return; bool isClosest = IsClosestTeammateToOpponent(opponentWithPuck); if (isClosest) { targetPosition = opponentWithPuck.transform.position; Vector3 toPuck = (opponentWithPuck.transform.position - transform.position).normalized; targetPosition = opponentWithPuck.transform.position - toPuck * interceptionRadius; } else { if (IsDefender(playerController.stats.position)) { targetPosition = GetDefensiveFormationPosition(opponentWithPuck); } else { targetPosition = GetBackcheckPosition(opponentWithPuck); } } } void DecideMovementLoosePuck() { if (puck == null) return; // Only closest player chases loose puck PlayerController closestTeammate = GetClosestTeammateToPuck(); if (closestTeammate == playerController) { targetPosition = puck.transform.position; } else { // Maintain positional structure targetPosition = GetDefaultPosition(); } } #endregion #region Position Finding Vector3 CalculateOpponentAvoidance() { PlayerController[] opponents = FindOpponents(); Vector3 avoidanceVector = Vector3.zero; foreach (PlayerController opponent in opponents) { float distance = Vector3.Distance(transform.position, opponent.transform.position); if (distance < minOpponentDistance) { Vector3 awayFromOpponent = (transform.position - opponent.transform.position).normalized; float strength = 1f - (distance / minOpponentDistance); avoidanceVector += awayFromOpponent * strength * 3f; } } return avoidanceVector; } Vector3 FindBestShootingPosition() { Vector3 goalPosition = GetOpponentGoalPosition(); Vector3 directionToGoal = (goalPosition - transform.position).normalized; Vector3 slotPosition = goalPosition - directionToGoal * 8f; slotPosition.x = Mathf.Clamp(slotPosition.x, -3f, 3f); return slotPosition; } void DefaultTeammateHasPuckBehavior(PlayerController puckCarrier) { if (IsDefender(playerController.stats.position)) { targetPosition = GetDefensivePosition(); } else { targetPosition = FindForwardSupportPosition(puckCarrier); } } Vector3 FindOpenPassReceivingPosition(PlayerController passer) { Vector3 passerPosition = passer.transform.position; Vector3 goalDirection = (GetOpponentGoalPosition() - passerPosition).normalized; float lateralOffset = (playerController.stats.position == PlayerPosition.LD || playerController.stats.position == PlayerPosition.LW) ? -4f : 4f; Vector3 targetPos = passerPosition + goalDirection * 5f + Vector3.right * lateralOffset; float distance = Vector3.Distance(passerPosition, targetPos); if (distance > passSearchRadius * 0.7f) { targetPos = passerPosition + (targetPos - passerPosition).normalized * passSearchRadius * 0.7f; } return targetPos; } Vector3 FindForwardSupportPosition(PlayerController puckCarrier) { if (movementSchema == null) return transform.position; Vector3 goalPosition = GetOpponentGoalPosition(); Vector3 puckPosition = puckCarrier.transform.position; Vector3 offensiveDirection = (goalPosition - puckPosition).normalized; float lateralSpread = movementSchema.forwardLateralSpread; float lateralOffset = 0f; switch (playerController.stats.position) { case PlayerPosition.LW: lateralOffset = -lateralSpread; break; case PlayerPosition.RW: lateralOffset = lateralSpread; break; case PlayerPosition.C: lateralOffset = 0f; break; } Vector3 targetPos = puckPosition + offensiveDirection * movementSchema.forwardPushDistance + Vector3.right * lateralOffset; if (targetPos.z > offensiveBlueLineZ + 5f) { targetPos.z = offensiveBlueLineZ + 5f; } return targetPos; } Vector3 GetDefensiveFormationPosition(PlayerController opponentWithPuck) { Vector3 ownGoal = GetOwnGoalPosition(); Vector3 puckPosition = opponentWithPuck.transform.position; PlayerController[] teammates = FindTeammates(); PlayerController otherDefender = teammates.FirstOrDefault(t => IsDefender(t.stats.position) && t != playerController); if (otherDefender != null) { if (playerController.stats.position == PlayerPosition.LD) { return new Vector3(-3f, 0f, defensiveBlueLineZ); } else { Vector3 targetPos = ownGoal + (puckPosition - ownGoal).normalized * 10f; targetPos.x = 3f; return targetPos; } } else { return ownGoal + (puckPosition - ownGoal).normalized * 12f; } } Vector3 GetBackcheckPosition(PlayerController opponentWithPuck) { Vector3 ownGoal = GetOwnGoalPosition(); Vector3 puckPosition = opponentWithPuck.transform.position; Vector3 targetPos = puckPosition + (ownGoal - puckPosition).normalized * 3f; float lateralOffset = 0f; switch (playerController.stats.position) { case PlayerPosition.LW: lateralOffset = -4f; break; case PlayerPosition.RW: lateralOffset = 4f; break; } targetPos.x += lateralOffset; return targetPos; } Vector3 GetDefensivePosition() { float sideOffset = (playerController.stats.position == PlayerPosition.LD) ? -3f : 3f; bool isHomeTeam = CompareTag("HomeTeam"); float zPosition = isHomeTeam ? defensiveBlueLineZ : -defensiveBlueLineZ; return new Vector3(sideOffset, 0, zPosition); } Vector3 GetDefaultPosition() { // Return a position based on player position and zone float xPos = 0f; float zPos = 0f; switch (playerController.stats.position) { case PlayerPosition.LW: xPos = -6f; zPos = offensiveBlueLineZ * 0.5f; break; case PlayerPosition.RW: xPos = 6f; zPos = offensiveBlueLineZ * 0.5f; break; case PlayerPosition.C: xPos = 0f; zPos = neutralZoneCenter; break; case PlayerPosition.LD: xPos = -3f; zPos = defensiveBlueLineZ; break; case PlayerPosition.RD: xPos = 3f; zPos = defensiveBlueLineZ; break; } return new Vector3(xPos, 0, zPos); } #endregion #region Physics Movement void ApplyMovementForces() { if (rb == null) return; Vector3 currentVelocity = rb.linearVelocity; Vector3 directionToTarget = (targetPosition - transform.position).normalized; float distanceToTarget = Vector3.Distance(transform.position, targetPosition); float desiredSpeed = Mathf.Min(maxSpeed, distanceToTarget * 2f); desiredVelocity = directionToTarget * desiredSpeed; Vector3 velocityDifference = desiredVelocity - currentVelocity; float forceMultiplier = velocityDifference.magnitude > 0.1f ? acceleration : deceleration; float currentSpeed = currentVelocity.magnitude; float dot = Vector3.Dot(currentVelocity.normalized, directionToTarget); if (dot < 0 && currentSpeed > 1f) { Vector3 stoppingForceVector = -currentVelocity.normalized * stoppingForce; rb.AddForce(stoppingForceVector, ForceMode.Acceleration); } else { Vector3 force = velocityDifference * forceMultiplier; rb.AddForce(force, ForceMode.Acceleration); } if (rb.linearVelocity.magnitude > maxSpeed) { rb.linearVelocity = rb.linearVelocity.normalized * maxSpeed; } if (distanceToTarget < 0.5f && currentSpeed < 1f) { rb.linearVelocity = Vector3.zero; } } void ApplyRotation() { if (rb == null) return; Vector3 velocity = rb.linearVelocity; if (velocity.magnitude > 0.5f) { Vector3 lookDirection = new Vector3(velocity.x, 0, velocity.z); Quaternion targetRotation = Quaternion.LookRotation(lookDirection); transform.rotation = Quaternion.Slerp( transform.rotation, targetRotation, turnSpeed * Time.fixedDeltaTime ); } } #endregion #region Helper Methods bool IsTeammateCarryingPuck(out PlayerController carrier) { carrier = puck?.CurrentCarrier; if (carrier == null) return false; return !IsOpponent(carrier) && carrier != playerController; } bool IsOpponent(PlayerController other) { if (other == null || playerController == null) return false; return other.CompareTag("HomeTeam") != CompareTag("HomeTeam"); } bool IsDefender(PlayerPosition position) { return position == PlayerPosition.LD || position == PlayerPosition.RD; } bool IsInOffensiveZone() { bool isHomeTeam = CompareTag("HomeTeam"); return isHomeTeam ? transform.position.z > offensiveBlueLineZ : transform.position.z < -offensiveBlueLineZ; } Vector3 GetOpponentGoalPosition() { bool isHomeTeam = CompareTag("HomeTeam"); return isHomeTeam ? new Vector3(0, 1, 30f) : new Vector3(0, 1, -30f); } Vector3 GetOwnGoalPosition() { bool isHomeTeam = CompareTag("HomeTeam"); return isHomeTeam ? new Vector3(0, 1, -30f) : new Vector3(0, 1, 30f); } PlayerController[] FindOpponents() { return FindObjectsByType(FindObjectsSortMode.None) .Where(p => IsOpponent(p)) .ToArray(); } PlayerController[] FindTeammates() { return FindObjectsByType(FindObjectsSortMode.None) .Where(p => !IsOpponent(p) && p != playerController) .ToArray(); } bool IsClosestTeammateToOpponent(PlayerController opponent) { PlayerController[] teammates = FindTeammates(); float myDistance = Vector3.Distance(transform.position, opponent.transform.position); foreach (PlayerController teammate in teammates) { float teammateDistance = Vector3.Distance(teammate.transform.position, opponent.transform.position); if (teammateDistance < myDistance) { return false; } } return true; } PlayerController GetClosestTeammateToPuck() { if (puck == null) return playerController; PlayerController[] teammates = FindObjectsByType(FindObjectsSortMode.None) .Where(p => !IsOpponent(p)) .ToArray(); PlayerController closest = playerController; float closestDistance = Vector3.Distance(transform.position, puck.transform.position); foreach (PlayerController teammate in teammates) { float distance = Vector3.Distance(teammate.transform.position, puck.transform.position); if (distance < closestDistance) { closestDistance = distance; closest = teammate; } } return closest; } #endregion #region Debug Visualization void OnDrawGizmos() { if (!isAIControlled) return; // Draw target position Gizmos.color = Color.yellow; Gizmos.DrawWireSphere(targetPosition, 0.5f); Gizmos.DrawLine(transform.position, targetPosition); // Draw desired velocity Gizmos.color = Color.green; Gizmos.DrawRay(transform.position, desiredVelocity); // Draw current velocity if (rb != null) { Gizmos.color = Color.blue; Gizmos.DrawRay(transform.position, rb.linearVelocity); } } #endregion }