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
}