PlayerAIMovement.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. using UnityEngine;
  2. using System.Linq;
  3. /// <summary>
  4. /// AI-controlled movement for hockey players using physics-based forces.
  5. /// Implements realistic hockey skating with momentum and inertia.
  6. /// </summary>
  7. public class PlayerAIMovement : MonoBehaviour
  8. {
  9. [Header("Movement Settings")]
  10. [SerializeField] private float maxSpeed = 8f;
  11. [SerializeField] private float acceleration = 15f;
  12. [SerializeField] private float deceleration = 10f;
  13. [SerializeField] private float turnSpeed = 5f;
  14. [SerializeField] private float stoppingForce = 20f;
  15. [Header("AI Behavior Settings")]
  16. [SerializeField] private MovementSchema movementSchema;
  17. [SerializeField] private float decisionUpdateInterval = 0.2f;
  18. [SerializeField] private float minOpponentDistance = 5f;
  19. [SerializeField] private float passSearchRadius = 20f;
  20. [SerializeField] private float shootingDistance = 15f;
  21. [SerializeField] private float interceptionRadius = 2f;
  22. [Header("Zone Positions")]
  23. [SerializeField] private float defensiveBlueLineZ = -18f;
  24. [SerializeField] private float offensiveBlueLineZ = 18f;
  25. [SerializeField] private float neutralZoneCenter = 0f;
  26. [Header("Faceoff Control")]
  27. [Tooltip("Disable AI movement during faceoffs")]
  28. public bool freezeDuringFaceoff = true;
  29. private Vector3 targetPosition;
  30. private Vector3 desiredVelocity;
  31. private float nextDecisionTime;
  32. private bool isAIControlled = true;
  33. private Rigidbody rb;
  34. private PlayerController playerController;
  35. private PuckController puck;
  36. private FaceoffManager faceoffManager;
  37. public bool IsAIControlled
  38. {
  39. get => isAIControlled;
  40. set => isAIControlled = value;
  41. }
  42. void Awake()
  43. {
  44. // Get components from same GameObject
  45. playerController = GetComponent<PlayerController>();
  46. rb = GetComponent<Rigidbody>();
  47. // Find puck by tag
  48. GameObject puckObject = GameObject.FindGameObjectWithTag("Puck");
  49. if (puckObject != null)
  50. {
  51. puck = puckObject.GetComponent<PuckController>();
  52. }
  53. // Find faceoff manager
  54. faceoffManager = FindAnyObjectByType<FaceoffManager>();
  55. }
  56. void Start()
  57. {
  58. // Set rigidbody constraints for hockey movement
  59. if (rb != null)
  60. {
  61. rb.constraints = RigidbodyConstraints.FreezeRotationX |
  62. RigidbodyConstraints.FreezeRotationZ |
  63. RigidbodyConstraints.FreezePositionY;
  64. rb.linearDamping = 1f;
  65. }
  66. }
  67. void Update()
  68. {
  69. if (!isAIControlled) return;
  70. // Don't make decisions if frozen (during faceoff)
  71. if (playerController != null && playerController.IsFrozen())
  72. {
  73. return;
  74. }
  75. // Don't make decisions if puck is not in play
  76. if (GameManager.Instance != null && !GameManager.Instance.IsPuckInPlay)
  77. {
  78. return;
  79. }
  80. // Make decisions periodically
  81. if (Time.time >= nextDecisionTime)
  82. {
  83. MakeMovementDecision();
  84. nextDecisionTime = Time.time + decisionUpdateInterval;
  85. }
  86. }
  87. void FixedUpdate()
  88. {
  89. if (!isAIControlled || rb == null) return;
  90. // Stop movement if frozen (during faceoff)
  91. if (playerController != null && playerController.IsFrozen())
  92. {
  93. rb.linearVelocity = Vector3.zero;
  94. rb.angularVelocity = Vector3.zero;
  95. return;
  96. }
  97. // Stop movement if puck is not in play
  98. if (GameManager.Instance != null && !GameManager.Instance.IsPuckInPlay)
  99. {
  100. rb.linearVelocity = Vector3.Lerp(rb.linearVelocity, Vector3.zero, deceleration * Time.fixedDeltaTime);
  101. return;
  102. }
  103. ApplyMovementForces();
  104. ApplyRotation();
  105. }
  106. bool IsFaceoffActive()
  107. {
  108. // Check if faceoff manager exists and has a faceoff in progress
  109. // You can also check if players are frozen via FaceoffManager
  110. if (faceoffManager != null)
  111. {
  112. // Access the private field through reflection or add a public property to FaceoffManager
  113. // For now, we'll assume if faceoffManager exists and players might be frozen
  114. return false; // Placeholder - you'll need to add public property to FaceoffManager
  115. }
  116. return false;
  117. }
  118. void MakeMovementDecision()
  119. {
  120. if (playerController == null || puck == null) return;
  121. bool hasPuck = playerController.HasPuck();
  122. bool teammateHasPuck = IsTeammateCarryingPuck(out PlayerController puckCarrier);
  123. bool opponentHasPuck = puck.CurrentCarrier != null && IsOpponent(puck.CurrentCarrier);
  124. if (hasPuck)
  125. {
  126. DecideMovementWithPuck();
  127. }
  128. else if (teammateHasPuck)
  129. {
  130. DecideMovementTeammateHasPuck(puckCarrier);
  131. }
  132. else if (opponentHasPuck)
  133. {
  134. DecideMovementOpponentHasPuck();
  135. }
  136. else
  137. {
  138. DecideMovementLoosePuck();
  139. }
  140. }
  141. #region Movement Behaviors
  142. void DecideMovementWithPuck()
  143. {
  144. Vector3 avoidanceVector = CalculateOpponentAvoidance();
  145. bool inOffensiveZone = IsInOffensiveZone();
  146. Vector3 goalPosition = GetOpponentGoalPosition();
  147. float distanceToGoal = Vector3.Distance(transform.position, goalPosition);
  148. if (inOffensiveZone && distanceToGoal < shootingDistance)
  149. {
  150. Vector3 shootingPosition = FindBestShootingPosition();
  151. targetPosition = shootingPosition + avoidanceVector;
  152. }
  153. else
  154. {
  155. Vector3 offensiveDirection = (goalPosition - transform.position).normalized;
  156. targetPosition = transform.position + offensiveDirection * 3f + avoidanceVector;
  157. }
  158. float moveDistance = Vector3.Distance(transform.position, targetPosition);
  159. if (moveDistance > 5f)
  160. {
  161. targetPosition = transform.position + (targetPosition - transform.position).normalized * 5f;
  162. }
  163. }
  164. void DecideMovementTeammateHasPuck(PlayerController puckCarrier)
  165. {
  166. if (puckCarrier == null) return;
  167. if (movementSchema == null)
  168. {
  169. DefaultTeammateHasPuckBehavior(puckCarrier);
  170. return;
  171. }
  172. bool puckCarrierIsDefender = IsDefender(puckCarrier.stats.position);
  173. bool thisPlayerIsDefender = IsDefender(playerController.stats.position);
  174. if (puckCarrierIsDefender && thisPlayerIsDefender)
  175. {
  176. targetPosition = FindOpenPassReceivingPosition(puckCarrier);
  177. }
  178. else if (!thisPlayerIsDefender)
  179. {
  180. targetPosition = FindForwardSupportPosition(puckCarrier);
  181. }
  182. else
  183. {
  184. DefaultTeammateHasPuckBehavior(puckCarrier);
  185. }
  186. }
  187. void DecideMovementOpponentHasPuck()
  188. {
  189. PlayerController opponentWithPuck = puck.CurrentCarrier;
  190. if (opponentWithPuck == null) return;
  191. bool isClosest = IsClosestTeammateToOpponent(opponentWithPuck);
  192. if (isClosest)
  193. {
  194. targetPosition = opponentWithPuck.transform.position;
  195. Vector3 toPuck = (opponentWithPuck.transform.position - transform.position).normalized;
  196. targetPosition = opponentWithPuck.transform.position - toPuck * interceptionRadius;
  197. }
  198. else
  199. {
  200. if (IsDefender(playerController.stats.position))
  201. {
  202. targetPosition = GetDefensiveFormationPosition(opponentWithPuck);
  203. }
  204. else
  205. {
  206. targetPosition = GetBackcheckPosition(opponentWithPuck);
  207. }
  208. }
  209. }
  210. void DecideMovementLoosePuck()
  211. {
  212. if (puck == null) return;
  213. // Only closest player chases loose puck
  214. PlayerController closestTeammate = GetClosestTeammateToPuck();
  215. if (closestTeammate == playerController)
  216. {
  217. targetPosition = puck.transform.position;
  218. }
  219. else
  220. {
  221. // Maintain positional structure
  222. targetPosition = GetDefaultPosition();
  223. }
  224. }
  225. #endregion
  226. #region Position Finding
  227. Vector3 CalculateOpponentAvoidance()
  228. {
  229. PlayerController[] opponents = FindOpponents();
  230. Vector3 avoidanceVector = Vector3.zero;
  231. foreach (PlayerController opponent in opponents)
  232. {
  233. float distance = Vector3.Distance(transform.position, opponent.transform.position);
  234. if (distance < minOpponentDistance)
  235. {
  236. Vector3 awayFromOpponent = (transform.position - opponent.transform.position).normalized;
  237. float strength = 1f - (distance / minOpponentDistance);
  238. avoidanceVector += awayFromOpponent * strength * 3f;
  239. }
  240. }
  241. return avoidanceVector;
  242. }
  243. Vector3 FindBestShootingPosition()
  244. {
  245. Vector3 goalPosition = GetOpponentGoalPosition();
  246. Vector3 directionToGoal = (goalPosition - transform.position).normalized;
  247. Vector3 slotPosition = goalPosition - directionToGoal * 8f;
  248. slotPosition.x = Mathf.Clamp(slotPosition.x, -3f, 3f);
  249. return slotPosition;
  250. }
  251. void DefaultTeammateHasPuckBehavior(PlayerController puckCarrier)
  252. {
  253. if (IsDefender(playerController.stats.position))
  254. {
  255. targetPosition = GetDefensivePosition();
  256. }
  257. else
  258. {
  259. targetPosition = FindForwardSupportPosition(puckCarrier);
  260. }
  261. }
  262. Vector3 FindOpenPassReceivingPosition(PlayerController passer)
  263. {
  264. Vector3 passerPosition = passer.transform.position;
  265. Vector3 goalDirection = (GetOpponentGoalPosition() - passerPosition).normalized;
  266. float lateralOffset = (playerController.stats.position == PlayerPosition.LD ||
  267. playerController.stats.position == PlayerPosition.LW) ? -4f : 4f;
  268. Vector3 targetPos = passerPosition + goalDirection * 5f + Vector3.right * lateralOffset;
  269. float distance = Vector3.Distance(passerPosition, targetPos);
  270. if (distance > passSearchRadius * 0.7f)
  271. {
  272. targetPos = passerPosition + (targetPos - passerPosition).normalized * passSearchRadius * 0.7f;
  273. }
  274. return targetPos;
  275. }
  276. Vector3 FindForwardSupportPosition(PlayerController puckCarrier)
  277. {
  278. if (movementSchema == null) return transform.position;
  279. Vector3 goalPosition = GetOpponentGoalPosition();
  280. Vector3 puckPosition = puckCarrier.transform.position;
  281. Vector3 offensiveDirection = (goalPosition - puckPosition).normalized;
  282. float lateralSpread = movementSchema.forwardLateralSpread;
  283. float lateralOffset = 0f;
  284. switch (playerController.stats.position)
  285. {
  286. case PlayerPosition.LW:
  287. lateralOffset = -lateralSpread;
  288. break;
  289. case PlayerPosition.RW:
  290. lateralOffset = lateralSpread;
  291. break;
  292. case PlayerPosition.C:
  293. lateralOffset = 0f;
  294. break;
  295. }
  296. Vector3 targetPos = puckPosition + offensiveDirection * movementSchema.forwardPushDistance + Vector3.right * lateralOffset;
  297. if (targetPos.z > offensiveBlueLineZ + 5f)
  298. {
  299. targetPos.z = offensiveBlueLineZ + 5f;
  300. }
  301. return targetPos;
  302. }
  303. Vector3 GetDefensiveFormationPosition(PlayerController opponentWithPuck)
  304. {
  305. Vector3 ownGoal = GetOwnGoalPosition();
  306. Vector3 puckPosition = opponentWithPuck.transform.position;
  307. PlayerController[] teammates = FindTeammates();
  308. PlayerController otherDefender = teammates.FirstOrDefault(t =>
  309. IsDefender(t.stats.position) && t != playerController);
  310. if (otherDefender != null)
  311. {
  312. if (playerController.stats.position == PlayerPosition.LD)
  313. {
  314. return new Vector3(-3f, 0f, defensiveBlueLineZ);
  315. }
  316. else
  317. {
  318. Vector3 targetPos = ownGoal + (puckPosition - ownGoal).normalized * 10f;
  319. targetPos.x = 3f;
  320. return targetPos;
  321. }
  322. }
  323. else
  324. {
  325. return ownGoal + (puckPosition - ownGoal).normalized * 12f;
  326. }
  327. }
  328. Vector3 GetBackcheckPosition(PlayerController opponentWithPuck)
  329. {
  330. Vector3 ownGoal = GetOwnGoalPosition();
  331. Vector3 puckPosition = opponentWithPuck.transform.position;
  332. Vector3 targetPos = puckPosition + (ownGoal - puckPosition).normalized * 3f;
  333. float lateralOffset = 0f;
  334. switch (playerController.stats.position)
  335. {
  336. case PlayerPosition.LW:
  337. lateralOffset = -4f;
  338. break;
  339. case PlayerPosition.RW:
  340. lateralOffset = 4f;
  341. break;
  342. }
  343. targetPos.x += lateralOffset;
  344. return targetPos;
  345. }
  346. Vector3 GetDefensivePosition()
  347. {
  348. float sideOffset = (playerController.stats.position == PlayerPosition.LD) ? -3f : 3f;
  349. bool isHomeTeam = CompareTag("HomeTeam");
  350. float zPosition = isHomeTeam ? defensiveBlueLineZ : -defensiveBlueLineZ;
  351. return new Vector3(sideOffset, 0, zPosition);
  352. }
  353. Vector3 GetDefaultPosition()
  354. {
  355. // Return a position based on player position and zone
  356. float xPos = 0f;
  357. float zPos = 0f;
  358. switch (playerController.stats.position)
  359. {
  360. case PlayerPosition.LW:
  361. xPos = -6f;
  362. zPos = offensiveBlueLineZ * 0.5f;
  363. break;
  364. case PlayerPosition.RW:
  365. xPos = 6f;
  366. zPos = offensiveBlueLineZ * 0.5f;
  367. break;
  368. case PlayerPosition.C:
  369. xPos = 0f;
  370. zPos = neutralZoneCenter;
  371. break;
  372. case PlayerPosition.LD:
  373. xPos = -3f;
  374. zPos = defensiveBlueLineZ;
  375. break;
  376. case PlayerPosition.RD:
  377. xPos = 3f;
  378. zPos = defensiveBlueLineZ;
  379. break;
  380. }
  381. return new Vector3(xPos, 0, zPos);
  382. }
  383. #endregion
  384. #region Physics Movement
  385. void ApplyMovementForces()
  386. {
  387. if (rb == null) return;
  388. Vector3 currentVelocity = rb.linearVelocity;
  389. Vector3 directionToTarget = (targetPosition - transform.position).normalized;
  390. float distanceToTarget = Vector3.Distance(transform.position, targetPosition);
  391. float desiredSpeed = Mathf.Min(maxSpeed, distanceToTarget * 2f);
  392. desiredVelocity = directionToTarget * desiredSpeed;
  393. Vector3 velocityDifference = desiredVelocity - currentVelocity;
  394. float forceMultiplier = velocityDifference.magnitude > 0.1f ? acceleration : deceleration;
  395. float currentSpeed = currentVelocity.magnitude;
  396. float dot = Vector3.Dot(currentVelocity.normalized, directionToTarget);
  397. if (dot < 0 && currentSpeed > 1f)
  398. {
  399. Vector3 stoppingForceVector = -currentVelocity.normalized * stoppingForce;
  400. rb.AddForce(stoppingForceVector, ForceMode.Acceleration);
  401. }
  402. else
  403. {
  404. Vector3 force = velocityDifference * forceMultiplier;
  405. rb.AddForce(force, ForceMode.Acceleration);
  406. }
  407. if (rb.linearVelocity.magnitude > maxSpeed)
  408. {
  409. rb.linearVelocity = rb.linearVelocity.normalized * maxSpeed;
  410. }
  411. if (distanceToTarget < 0.5f && currentSpeed < 1f)
  412. {
  413. rb.linearVelocity = Vector3.zero;
  414. }
  415. }
  416. void ApplyRotation()
  417. {
  418. if (rb == null) return;
  419. Vector3 velocity = rb.linearVelocity;
  420. if (velocity.magnitude > 0.5f)
  421. {
  422. Vector3 lookDirection = new Vector3(velocity.x, 0, velocity.z);
  423. Quaternion targetRotation = Quaternion.LookRotation(lookDirection);
  424. transform.rotation = Quaternion.Slerp(
  425. transform.rotation,
  426. targetRotation,
  427. turnSpeed * Time.fixedDeltaTime
  428. );
  429. }
  430. }
  431. #endregion
  432. #region Helper Methods
  433. bool IsTeammateCarryingPuck(out PlayerController carrier)
  434. {
  435. carrier = puck?.CurrentCarrier;
  436. if (carrier == null) return false;
  437. return !IsOpponent(carrier) && carrier != playerController;
  438. }
  439. bool IsOpponent(PlayerController other)
  440. {
  441. if (other == null || playerController == null) return false;
  442. return other.CompareTag("HomeTeam") != CompareTag("HomeTeam");
  443. }
  444. bool IsDefender(PlayerPosition position)
  445. {
  446. return position == PlayerPosition.LD || position == PlayerPosition.RD;
  447. }
  448. bool IsInOffensiveZone()
  449. {
  450. bool isHomeTeam = CompareTag("HomeTeam");
  451. return isHomeTeam ? transform.position.z > offensiveBlueLineZ : transform.position.z < -offensiveBlueLineZ;
  452. }
  453. Vector3 GetOpponentGoalPosition()
  454. {
  455. bool isHomeTeam = CompareTag("HomeTeam");
  456. return isHomeTeam ? new Vector3(0, 1, 30f) : new Vector3(0, 1, -30f);
  457. }
  458. Vector3 GetOwnGoalPosition()
  459. {
  460. bool isHomeTeam = CompareTag("HomeTeam");
  461. return isHomeTeam ? new Vector3(0, 1, -30f) : new Vector3(0, 1, 30f);
  462. }
  463. PlayerController[] FindOpponents()
  464. {
  465. return FindObjectsByType<PlayerController>(FindObjectsSortMode.None)
  466. .Where(p => IsOpponent(p))
  467. .ToArray();
  468. }
  469. PlayerController[] FindTeammates()
  470. {
  471. return FindObjectsByType<PlayerController>(FindObjectsSortMode.None)
  472. .Where(p => !IsOpponent(p) && p != playerController)
  473. .ToArray();
  474. }
  475. bool IsClosestTeammateToOpponent(PlayerController opponent)
  476. {
  477. PlayerController[] teammates = FindTeammates();
  478. float myDistance = Vector3.Distance(transform.position, opponent.transform.position);
  479. foreach (PlayerController teammate in teammates)
  480. {
  481. float teammateDistance = Vector3.Distance(teammate.transform.position, opponent.transform.position);
  482. if (teammateDistance < myDistance)
  483. {
  484. return false;
  485. }
  486. }
  487. return true;
  488. }
  489. PlayerController GetClosestTeammateToPuck()
  490. {
  491. if (puck == null) return playerController;
  492. PlayerController[] teammates = FindObjectsByType<PlayerController>(FindObjectsSortMode.None)
  493. .Where(p => !IsOpponent(p))
  494. .ToArray();
  495. PlayerController closest = playerController;
  496. float closestDistance = Vector3.Distance(transform.position, puck.transform.position);
  497. foreach (PlayerController teammate in teammates)
  498. {
  499. float distance = Vector3.Distance(teammate.transform.position, puck.transform.position);
  500. if (distance < closestDistance)
  501. {
  502. closestDistance = distance;
  503. closest = teammate;
  504. }
  505. }
  506. return closest;
  507. }
  508. #endregion
  509. #region Debug Visualization
  510. void OnDrawGizmos()
  511. {
  512. if (!isAIControlled) return;
  513. // Draw target position
  514. Gizmos.color = Color.yellow;
  515. Gizmos.DrawWireSphere(targetPosition, 0.5f);
  516. Gizmos.DrawLine(transform.position, targetPosition);
  517. // Draw desired velocity
  518. Gizmos.color = Color.green;
  519. Gizmos.DrawRay(transform.position, desiredVelocity);
  520. // Draw current velocity
  521. if (rb != null)
  522. {
  523. Gizmos.color = Color.blue;
  524. Gizmos.DrawRay(transform.position, rb.linearVelocity);
  525. }
  526. }
  527. #endregion
  528. }