GameManager.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using Unity.VisualScripting;
  6. using UnityEngine;
  7. using UnityEngine.AI;
  8. using UnityEngine.Rendering.UI;
  9. public class GameManager : MonoBehaviour
  10. {
  11. public static GameManager Instance { get; private set; }
  12. public List<GameObject> playerCharacters = new List<GameObject>();
  13. public List<GameObject> enemyCharacters = new List<GameObject>();
  14. [Header("Decision Controller")]
  15. public PlayerDecisionController playerDecisionController;
  16. private Dictionary<GameObject, GameObject> enemySelectedTargets = new Dictionary<GameObject, GameObject>();
  17. public bool isContinuousRunActive = false; // Indicates if the game is in continuous run mode
  18. private const string ENEMY_LAYER_NAME = "Enemies";
  19. private int enemyLayerMask;
  20. private bool isActionExecutionPhaseActive;
  21. private bool waitingForAdvanceInput = false;
  22. private void Awake()
  23. {
  24. if (Instance == null)
  25. {
  26. Instance = this;
  27. DontDestroyOnLoad(gameObject);
  28. // Initialize PlayerDecisionController if not set
  29. if (playerDecisionController == null)
  30. {
  31. playerDecisionController = FindFirstObjectByType<PlayerDecisionController>();
  32. if (playerDecisionController == null)
  33. {
  34. GameObject pdcGO = new GameObject("PlayerDecisionController");
  35. playerDecisionController = pdcGO.AddComponent<PlayerDecisionController>();
  36. }
  37. }
  38. int enemyLayer = LayerMask.NameToLayer(ENEMY_LAYER_NAME);
  39. if (enemyLayer == -1)
  40. {
  41. Debug.LogError($"Layer '{ENEMY_LAYER_NAME}' not found. Please create it in Project Settings > Tags and Layers, and assign your enemy prefabs to it.");
  42. // Disable functionality or handle error appropriately
  43. }
  44. else
  45. {
  46. enemyLayerMask = 1 << enemyLayer;
  47. }
  48. }
  49. else
  50. {
  51. Destroy(gameObject);
  52. return; // Avoid further initialization if this is not the singleton instance
  53. }
  54. }
  55. internal void StartBattle(List<GameObject> placedPlayerCharacters, List<GameObject> placedEnemyCharacters)
  56. {
  57. playerCharacters = placedPlayerCharacters;
  58. enemyCharacters = placedEnemyCharacters;
  59. // Refresh PlayerDecisionController with current player characters
  60. if (playerDecisionController != null)
  61. {
  62. playerDecisionController.RefreshPlayerCharacters();
  63. playerDecisionController.SetEnabled(false); // Disable initially until decision phase
  64. }
  65. // Start the battle logic here
  66. EnemiesDecideAction();
  67. StartCoroutine(MainBattleLoopCoroutine());
  68. }
  69. private IEnumerator MainBattleLoopCoroutine()
  70. {
  71. while (true) // Main battle loop
  72. {
  73. // --- Player Decision Phase ---
  74. Time.timeScale = 1f; // Ensure normal time scale for player decision phase
  75. // Check for completed movements before turn starts
  76. CheckAndResetCompletedMovements();
  77. // Keep existing actions, only allow new decisions for characters with no action
  78. playerDecisionController.UpdateVisualStates(); // Update colors: pink for no action, green for incomplete
  79. playerDecisionController.SetEnabled(true);
  80. // Show lines for any existing incomplete actions
  81. playerDecisionController.ShowActiveActionLines();
  82. // Wait for all players to make their initial decisions (only needed at turn start)
  83. while (!playerDecisionController.AllCharactersHaveActions())
  84. {
  85. yield return null; // Wait for player input through PlayerDecisionController
  86. }
  87. // Disable input during execution phase and hide action lines
  88. playerDecisionController.SetEnabled(false);
  89. playerDecisionController.HideActiveActionLines();
  90. // --- Pause point after decision (if not in continuous run mode) ---
  91. if (!isContinuousRunActive)
  92. {
  93. PauseGame();
  94. yield return new WaitUntil(() => Time.timeScale > 0 || isContinuousRunActive);
  95. }
  96. if (Time.timeScale == 0 && isContinuousRunActive)
  97. {
  98. ResumeGame();
  99. }
  100. // --- Action execution phase ---
  101. // yield return StartCoroutine(ExecutePlayerActionsCoroutine());
  102. yield return StartCoroutine(ExecuteAllActionsSimultaneously());
  103. // TODO: add ement action execution here for a full turn
  104. if (BattleUIManager.Instance != null)
  105. {
  106. BattleUIManager.Instance.SetButtonsInteractable(true);
  107. }
  108. EnemiesDecideAction(); // Enemies decide their actions after players have executed their actions
  109. // --- Post-Actions: Loop or Pause ---
  110. if (isContinuousRunActive)
  111. {
  112. if (Time.timeScale > 0)
  113. {
  114. yield return new WaitForSeconds(0.5f); // Brief delay if running
  115. }
  116. }
  117. else
  118. {
  119. PauseGame();
  120. }
  121. // Check for battle end conditions
  122. if (playerCharacters.Count == 0 || enemyCharacters.Count == 0)
  123. {
  124. PauseGame();
  125. if (BattleUIManager.Instance != null)
  126. {
  127. BattleUIManager.Instance.DisableBattleControls(); // Disable UI controls
  128. }
  129. // Handle battle end logic here (e.g., show results, reset game state, etc.)
  130. break; // Exit the loop if battle is over
  131. }
  132. }
  133. }
  134. public List<GameObject> GetAllCharacters()
  135. {
  136. return playerCharacters.Concat(enemyCharacters).ToList();
  137. }
  138. private IEnumerator ExecuteAllActionsSimultaneously()
  139. {
  140. isActionExecutionPhaseActive = true;
  141. var allActions = new Dictionary<GameObject, GameObject>();
  142. var movementActions = new Dictionary<GameObject, Vector3>();
  143. // Collect player actions from Character components
  144. foreach (GameObject playerGO in playerCharacters)
  145. {
  146. Character character = playerGO.GetComponent<Character>();
  147. if (character != null && character.actionData.hasTarget)
  148. {
  149. if (character.actionData.targetEnemy != null)
  150. {
  151. allActions[playerGO] = character.actionData.targetEnemy;
  152. }
  153. else if (character.actionData.targetPosition != Vector3.zero)
  154. {
  155. movementActions[playerGO] = character.actionData.targetPosition;
  156. }
  157. }
  158. }
  159. foreach (var action in enemySelectedTargets)
  160. {
  161. allActions[action.Key] = action.Value;
  162. }
  163. int playerActionsCount = allActions.Count - enemySelectedTargets.Count + movementActions.Count;
  164. enemySelectedTargets.Clear(); // Clear enemy targets as well
  165. if (allActions.Count == 0 && movementActions.Count == 0)
  166. {
  167. isActionExecutionPhaseActive = false;
  168. yield break;
  169. }
  170. // Configure All NavMeshAgents before starting movement
  171. foreach (var action in allActions)
  172. {
  173. GameObject actor = action.Key;
  174. if (actor == null)
  175. {
  176. continue;
  177. }
  178. Character character = actor.GetComponent<Character>();
  179. float attackRange = character != null ? character.GetAttackRange() : 2.0f;
  180. NavMeshAgent agent = actor.GetComponent<NavMeshAgent>();
  181. if (agent != null)
  182. {
  183. // For melee weapons (range <= 2), get much closer. For ranged, stay back more.
  184. float stoppingDistance = attackRange <= 2 ? 0.2f : Mathf.Max(0.5f, attackRange - 1.0f);
  185. agent.stoppingDistance = stoppingDistance;
  186. //
  187. agent.obstacleAvoidanceType = ObstacleAvoidanceType.HighQualityObstacleAvoidance;
  188. agent.avoidancePriority = UnityEngine.Random.Range(0, 100);
  189. // Start movement
  190. GameObject target = action.Value;
  191. if (target != null)
  192. {
  193. agent.SetDestination(target.transform.position);
  194. agent.isStopped = false;
  195. }
  196. }
  197. else
  198. {
  199. Debug.LogWarning($"NavMeshAgent not found or disabled on {actor.name}. Cannot move towards target.");
  200. }
  201. }
  202. // Handle pure movement actions (no enemy target)
  203. foreach (var movement in movementActions)
  204. {
  205. GameObject playerGO = movement.Key;
  206. Vector3 targetPosition = movement.Value;
  207. NavMeshAgent agent = playerGO.GetComponent<NavMeshAgent>();
  208. if (agent != null)
  209. {
  210. agent.SetDestination(targetPosition);
  211. agent.isStopped = false;
  212. }
  213. }
  214. // handle movement duration based on mode
  215. //
  216. if (allActions.Count > 0 || movementActions.Count > 0)
  217. {
  218. yield return StartCoroutine(MovementPhaseWithTargetTracking(allActions, movementActions, 1.0f));
  219. }
  220. // else
  221. // {
  222. // // If there are only attacks (no movement), still wait for 1 second
  223. // yield return new WaitForSeconds(1.0f);
  224. // }
  225. if (!isContinuousRunActive)
  226. {
  227. //
  228. //
  229. // Check for completed movement actions before showing lines
  230. CheckAndResetCompletedMovements();
  231. // Enable input and show lines for actions during pause - allow re-picking actions
  232. playerDecisionController.UpdateVisualStates(); // Update colors
  233. playerDecisionController.SetEnabled(true); // Allow action changes
  234. playerDecisionController.ShowActiveActionLines();
  235. PauseGame();
  236. waitingForAdvanceInput = true;
  237. yield return new WaitUntil(() => !waitingForAdvanceInput);
  238. // Disable input when advancing
  239. playerDecisionController.SetEnabled(false);
  240. ResumeGame();
  241. yield return new WaitForSeconds(1.0f);
  242. }
  243. foreach (var action in allActions)
  244. {
  245. GameObject actor = action.Key;
  246. GameObject target = action.Value;
  247. if (actor == null || target == null) continue;
  248. Character character = actor.GetComponent<Character>();
  249. float attackRange = character != null ? character.GetAttackRange() : 2.0f;
  250. float distance = Vector3.Distance(actor.transform.position, target.transform.position);
  251. if (distance <= attackRange)
  252. {
  253. // Execute the character's attack action
  254. character.ExecuteAction();
  255. }
  256. else
  257. {
  258. }
  259. }
  260. // Check movement completion and reset action if reached (movement actions are one-time)
  261. foreach (var movement in movementActions)
  262. {
  263. GameObject actor = movement.Key;
  264. Vector3 targetPosition = movement.Value;
  265. if (actor == null) continue;
  266. Character character = actor.GetComponent<Character>();
  267. if (character != null)
  268. {
  269. // Use horizontal distance only, ignore Y differences
  270. Vector3 actorPos = actor.transform.position;
  271. Vector2 actorPos2D = new Vector2(actorPos.x, actorPos.z);
  272. Vector2 targetPos2D = new Vector2(targetPosition.x, targetPosition.z);
  273. float horizontalDistance = Vector2.Distance(actorPos2D, targetPos2D);
  274. if (horizontalDistance <= 1.2f) // Movement completion threshold
  275. {
  276. character.actionData.Reset();
  277. character.SetVisualState(ActionDecisionState.NoAction);
  278. }
  279. }
  280. }
  281. isActionExecutionPhaseActive = false;
  282. BattleUIManager.Instance.SetButtonsInteractable(true);
  283. }
  284. private IEnumerator MovementPhaseWithTargetTracking(Dictionary<GameObject, GameObject> allActions, Dictionary<GameObject, Vector3> movementActions, float movementDuration)
  285. {
  286. float elapsedTime = 0f;
  287. // Store witch agents are still moving (not in attack range)
  288. Dictionary<NavMeshAgent, GameObject> activeMovements = new Dictionary<NavMeshAgent, GameObject>();
  289. Dictionary<NavMeshAgent, float> agentAttackRanges = new Dictionary<NavMeshAgent, float>();
  290. // Initialize active movements for attack actions
  291. foreach (var action in allActions)
  292. {
  293. GameObject actor = action.Key;
  294. GameObject target = action.Value;
  295. if (actor == null || target == null) continue;
  296. NavMeshAgent agent = actor.GetComponent<NavMeshAgent>();
  297. if (agent != null && agent.enabled)
  298. {
  299. // Get the character's attack range
  300. Character character = actor.GetComponent<Character>();
  301. float attackRange = character != null ? character.GetAttackRange() : 2.0f;
  302. // Configure agent for proper pathfinding around obstacles
  303. agent.obstacleAvoidanceType = ObstacleAvoidanceType.HighQualityObstacleAvoidance;
  304. agent.avoidancePriority = UnityEngine.Random.Range(0, 100); // Random priority to avoid clustering
  305. activeMovements[agent] = target;
  306. agentAttackRanges[agent] = attackRange;
  307. }
  308. }
  309. // Initialize active movements for pure movement actions
  310. foreach (var movement in movementActions)
  311. {
  312. GameObject actor = movement.Key;
  313. Vector3 targetPosition = movement.Value;
  314. if (actor == null) continue;
  315. NavMeshAgent agent = actor.GetComponent<NavMeshAgent>();
  316. if (agent != null && agent.enabled)
  317. {
  318. // For movement actions, use a small stopping distance to get closer
  319. float arrivalRange = 1.2f;
  320. // Configure agent for proper pathfinding around obstacles
  321. agent.obstacleAvoidanceType = ObstacleAvoidanceType.HighQualityObstacleAvoidance;
  322. agent.avoidancePriority = UnityEngine.Random.Range(0, 100);
  323. agent.stoppingDistance = 0.1f; // Get very close to the target
  324. // Create a dummy target GameObject at the position for consistency with the tracking system
  325. GameObject dummyTarget = new GameObject("MovementTarget");
  326. dummyTarget.transform.position = targetPosition;
  327. activeMovements[agent] = dummyTarget;
  328. agentAttackRanges[agent] = arrivalRange;
  329. }
  330. }
  331. while (elapsedTime < movementDuration && Time.timeScale > 0 && activeMovements.Count > 0)
  332. {
  333. var agentsToRemove = new List<NavMeshAgent>();
  334. foreach (var movement in activeMovements)
  335. {
  336. NavMeshAgent agent = movement.Key;
  337. GameObject target = movement.Value;
  338. float attackRange = agentAttackRanges[agent];
  339. if (agent == null || target == null || agent.gameObject == null)
  340. {
  341. agentsToRemove.Add(agent);
  342. continue;
  343. }
  344. float distanceToTarget = Vector3.Distance(agent.transform.position, target.transform.position);
  345. if (distanceToTarget <= attackRange)
  346. {
  347. agent.isStopped = true;
  348. agent.velocity = Vector3.zero;
  349. agentsToRemove.Add(agent);
  350. // If this is a movement target (dummy), check if we should reset the action
  351. if (target.name == "MovementTarget")
  352. {
  353. Character character = agent.GetComponent<Character>();
  354. if (character != null && character.actionData.targetPosition != Vector3.zero)
  355. {
  356. character.actionData.Reset();
  357. character.SetVisualState(ActionDecisionState.NoAction);
  358. }
  359. }
  360. }
  361. else if (!agent.isStopped)
  362. {
  363. // For movement targets, go directly to the position
  364. if (target.name == "MovementTarget")
  365. {
  366. agent.SetDestination(target.transform.position);
  367. // Check if agent has reached destination or is very close but stopped moving
  368. if (agent.pathStatus == NavMeshPathStatus.PathComplete &&
  369. (!agent.hasPath || agent.remainingDistance < 0.1f))
  370. {
  371. Character character = agent.GetComponent<Character>();
  372. if (character != null)
  373. {
  374. Vector3 actorPos = agent.transform.position;
  375. Vector3 targetPos = character.actionData.targetPosition;
  376. Vector2 actorPos2D = new Vector2(actorPos.x, actorPos.z);
  377. Vector2 targetPos2D = new Vector2(targetPos.x, targetPos.z);
  378. float horizontalDistance = Vector2.Distance(actorPos2D, targetPos2D);
  379. if (horizontalDistance <= 1.2f)
  380. {
  381. agent.isStopped = true;
  382. agentsToRemove.Add(agent);
  383. character.actionData.Reset();
  384. character.SetVisualState(ActionDecisionState.NoAction);
  385. }
  386. }
  387. }
  388. }
  389. else
  390. {
  391. // Update destination to track moving target, but stop just outside attack range
  392. Vector3 directionToTarget = (target.transform.position - agent.transform.position).normalized;
  393. Vector3 stoppingPosition = target.transform.position - directionToTarget * (attackRange * 0.9f);
  394. agent.SetDestination(stoppingPosition);
  395. }
  396. // Check if agent is stuck or can't reach destination
  397. if (agent.pathStatus == NavMeshPathStatus.PathInvalid)
  398. {
  399. Debug.LogWarning($"{agent.gameObject.name} cannot find path to {target.name}");
  400. agent.SetDestination(target.transform.position);
  401. }
  402. }
  403. }
  404. foreach (var agent in agentsToRemove)
  405. {
  406. activeMovements.Remove(agent);
  407. agentAttackRanges.Remove(agent);
  408. }
  409. yield return null; // wait one frame
  410. elapsedTime += Time.deltaTime;
  411. }
  412. // Stop any remaining moving agents and clean up dummy targets
  413. foreach (var movement in activeMovements)
  414. {
  415. if (movement.Key != null && movement.Key.enabled)
  416. {
  417. movement.Key.isStopped = true;
  418. movement.Key.velocity = Vector3.zero;
  419. }
  420. // Clean up dummy movement targets
  421. if (movement.Value != null && movement.Value.name == "MovementTarget")
  422. {
  423. Destroy(movement.Value);
  424. }
  425. }
  426. }
  427. public void AdvanceAction()
  428. {
  429. // Hide action lines when advancing
  430. if (playerDecisionController != null)
  431. {
  432. playerDecisionController.HideActiveActionLines();
  433. }
  434. if (waitingForAdvanceInput)
  435. {
  436. waitingForAdvanceInput = false;
  437. }
  438. else if (Time.timeScale == 0 && !isActionExecutionPhaseActive)
  439. {
  440. ResumeGame();
  441. }
  442. else
  443. {
  444. }
  445. }
  446. public void PauseGame()
  447. {
  448. Time.timeScale = 0f; // Pause the game
  449. BattleUIManager.Instance.SetButtonsInteractable(true);
  450. }
  451. public void ResumeGame()
  452. {
  453. Time.timeScale = 1f; // Resume the game
  454. }
  455. private void EnemiesDecideAction()
  456. {
  457. enemySelectedTargets.Clear();
  458. foreach (var enemy in enemyCharacters)
  459. {
  460. if (enemy == null)
  461. {
  462. Debug.LogWarning("Found null enemy in enemyCharacters list");
  463. continue;
  464. }
  465. if (playerCharacters.Count == 0)
  466. {
  467. Debug.LogWarning("No player characters available for enemies to target");
  468. continue;
  469. }
  470. // Logic for enemy action decision
  471. GameObject closestPlayer = null;
  472. float minDistance = float.MaxValue;
  473. List<GameObject> alivePlayers = playerCharacters.Where(player => !player.GetComponent<Character>().IsDead).ToList();
  474. foreach (var player in alivePlayers)
  475. {
  476. if (player == null) continue; // Safety check
  477. float distance = Vector3.Distance(enemy.transform.position, player.transform.position);
  478. if (distance < minDistance)
  479. {
  480. minDistance = distance;
  481. closestPlayer = player;
  482. }
  483. }
  484. if (closestPlayer != null)
  485. {
  486. // Set the enemy's action data to attack the target
  487. Character enemyCharacter = enemy.GetComponent<Character>();
  488. if (enemyCharacter != null)
  489. {
  490. enemyCharacter.actionData.SetAttackTarget(closestPlayer);
  491. }
  492. enemySelectedTargets[enemy] = closestPlayer; // Assign the closest player as the target
  493. }
  494. else
  495. {
  496. Debug.LogWarning($"{enemy.name} could not find a valid target");
  497. }
  498. enemy.GetComponent<Character>().SetVisualState(ActionDecisionState.EnemyAction);
  499. }
  500. }
  501. private void CheckAndResetCompletedMovements()
  502. {
  503. foreach (GameObject playerGO in playerCharacters)
  504. {
  505. Character character = playerGO.GetComponent<Character>();
  506. if (character != null && character.actionData.targetPosition != Vector3.zero && character.actionData.targetEnemy == null)
  507. {
  508. // This is a movement action, check if completed using horizontal distance only
  509. Vector3 playerPos = playerGO.transform.position;
  510. Vector3 targetPos = character.actionData.targetPosition;
  511. // Use only X and Z coordinates (ignore Y differences)
  512. Vector2 playerPos2D = new Vector2(playerPos.x, playerPos.z);
  513. Vector2 targetPos2D = new Vector2(targetPos.x, targetPos.z);
  514. float horizontalDistance = Vector2.Distance(playerPos2D, targetPos2D);
  515. if (horizontalDistance <= 1.2f) // Threshold for "close enough"
  516. {
  517. character.actionData.Reset();
  518. character.SetVisualState(ActionDecisionState.NoAction);
  519. }
  520. }
  521. }
  522. }
  523. }