using TMPro; using UnityEngine; using UnityEngine.SceneManagement; namespace GameEngineI.Week07 { public enum RoundState { Ready, Playing, Won, Lost } // Week 5's collect -> goal / hazard -> start rules, with Week 7 screen states. [DefaultExecutionOrder(-200)] public sealed class Week07GameManager : MonoBehaviour { [Header("Rules")] [SerializeField, Min(1)] int targetScore = 3; [SerializeField, Min(1)] float roundDuration = 30f; [Header("Player")] [SerializeField] Rigidbody2D playerBody; [SerializeField] PlayerController movement; [SerializeField] PlayerDash dash; [SerializeField] PlayerAnimation animationDriver; [Header("UI")] [SerializeField] GameObject hud; [SerializeField] GameObject startPanel; [SerializeField] GameObject resultPanel; [SerializeField] TMP_Text scoreText; [SerializeField] TMP_Text timerText; [SerializeField] TMP_Text hintText; [SerializeField] TMP_Text resultText; [SerializeField] TMP_Text resultDetail; public RoundState CurrentState { get; private set; } public int Score { get; private set; } public int TargetScore => targetScore; public float TimeRemaining { get; private set; } public Vector2 SpawnPosition { get; private set; } float hintUntil; bool restarting; void Awake() { SpawnPosition = playerBody.position; Score = 0; TimeRemaining = roundDuration; CurrentState = RoundState.Ready; SetPlayerControl(false); Time.timeScale = 0; hud.SetActive(false); startPanel.SetActive(true); resultPanel.SetActive(false); RefreshHud(); } void Update() { if (CurrentState != RoundState.Playing) return; TimeRemaining = Mathf.Max(0, TimeRemaining - Time.deltaTime); RefreshHud(); if (TimeRemaining <= 0) Finish(RoundState.Lost); else if (playerBody.position.y < -6) Respawn(); } public void StartRound() { if (CurrentState != RoundState.Ready) return; CurrentState = RoundState.Playing; startPanel.SetActive(false); resultPanel.SetActive(false); hud.SetActive(true); Time.timeScale = 1; SetPlayerControl(true); RefreshHud(); } public bool TryCollect() { if (CurrentState != RoundState.Playing || Score >= targetScore) return false; Score++; hintUntil = 0; RefreshHud(); // All gems unlock the goal; collecting the last gem does not end Week 5's level. return true; } public void TryGoal() { if (CurrentState != RoundState.Playing) return; if (Score < targetScore) { ShowHint($"Collect all {targetScore} gems before reaching the goal."); return; } Finish(RoundState.Won); } public void Respawn() { if (CurrentState != RoundState.Playing) return; SetPlayerControl(false); playerBody.position = SpawnPosition; playerBody.transform.position = new Vector3(SpawnPosition.x, SpawnPosition.y, playerBody.transform.position.z); playerBody.angularVelocity = 0; Physics2D.SyncTransforms(); SetPlayerControl(true); ShowHint("Back at the start. Your collected gems are kept."); } public void RestartScene() { if (restarting || (CurrentState != RoundState.Won && CurrentState != RoundState.Lost)) return; restarting = true; Time.timeScale = 1; SceneManager.LoadScene(SceneManager.GetActiveScene().path); } void Finish(RoundState state) { if (CurrentState != RoundState.Playing) return; CurrentState = state; SetPlayerControl(false); Time.timeScale = 0; hud.SetActive(false); startPanel.SetActive(false); resultPanel.SetActive(true); resultText.text = state == RoundState.Won ? "Success" : "Time Up"; resultDetail.text = state == RoundState.Won ? $"All {targetScore} gems collected. Goal reached!" : "Find your route and try again."; } void SetPlayerControl(bool active) { movement.enabled = active; dash.enabled = active; animationDriver.enabled = active; if (!active) playerBody.linearVelocity = Vector2.zero; } void ShowHint(string message) { hintUntil = Time.unscaledTime + 2.5f; hintText.text = message; } void RefreshHud() { scoreText.text = $"{Score} / {targetScore}"; timerText.text = $"{TimeRemaining:0.0}"; if (Time.unscaledTime >= hintUntil) hintText.text = Score >= targetScore ? "All gems collected. Reach the goal!" : $"Collect {targetScore} gems, then reach the goal."; } void OnDestroy() { // A reloaded scene establishes its own Ready pause in Awake. // The old manager must not unpause that new scene during teardown. if (!restarting) Time.timeScale = 1; } } }