| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634 |
- using UnityEngine;
- using System.Linq;
- /// <summary>
- /// AI-controlled movement for hockey players using physics-based forces.
- /// Implements realistic hockey skating with momentum and inertia.
- /// </summary>
- 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<PlayerController>();
- rb = GetComponent<Rigidbody>();
- // Find puck by tag
- GameObject puckObject = GameObject.FindGameObjectWithTag("Puck");
- if (puckObject != null)
- {
- puck = puckObject.GetComponent<PuckController>();
- }
- // Find faceoff manager
- faceoffManager = FindAnyObjectByType<FaceoffManager>();
- }
- 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<PlayerController>(FindObjectsSortMode.None)
- .Where(p => IsOpponent(p))
- .ToArray();
- }
- PlayerController[] FindTeammates()
- {
- return FindObjectsByType<PlayerController>(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<PlayerController>(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
- }
|