iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0

完善整個遊戲

昨天完成了分數系統,不過實際遊玩時發現,遊戲還是少了一個比較完整的開始與結束流程。目前只要進入遊戲,小鳥就會直接開始掉落,死亡後也只有停止遊戲,沒有一個正式的結算畫面。所以今天就開始製作遊戲的狀態管理,讓遊戲變成「等待開始→遊戲進行→遊戲結束→重新開始」的流程。

製作遊戲狀態

這次先建立GameManager,用三種狀態來判斷遊戲目前進行到哪裡:

  • WaitingToStart:等待玩家開始遊戲
  • Playing:遊戲進行中
  • GameOver:遊戲結束,顯示結算畫面
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using TMPro;

public class GameManager : MonoBehaviour
{
    public static GameManager Instance;

    public enum GameState
    {
        WaitingToStart, // 等待玩家點擊畫面開始
        Playing,        // 遊戲進行中
        GameOver        // 遊戲結束(顯示結算 UI)
    }

    [Header("當前遊戲狀態")]
    public GameState currentState = GameState.WaitingToStart;

    [Header("UI 面板設定")]
    [Tooltip("結算畫面的 Panel/Image 容器")]
    public GameObject gameOverPanel;

    [Tooltip("結算畫面中央顯示本次分數的 Text")]
    public TMP_Text finalScoreText;

    [Tooltip("結算畫面右上角顯示最高分數的 Text")]
    public TMP_Text gameOverHighScoreText;

    [Tooltip("開局提示點擊文字(例如:Tap to Start),可選")]
    public GameObject startHintUI;

    private static bool isRestarting = false; // 紀錄是否剛按下「重新開始」

    void Awake()
    {
        if (Instance == null) Instance = this;
        else Destroy(gameObject);
    }

    void Start()
    {
        // 剛進入遊戲或重新載入時,凍結時間或暫停生成
        Time.timeScale = 0f; // 暫停遊戲時間,使小鳥與障礙物靜止
        currentState = GameState.WaitingToStart;

        if (gameOverPanel != null) gameOverPanel.SetActive(false);
        if (startHintUI != null) startHintUI.SetActive(true);
    }

    void Update()
    {
        // 若處於等待開始狀態,點擊畫面/鍵盤即可開始
        if (currentState == GameState.WaitingToStart)
        {
            bool isComputerInput = Input.GetKeyDown(KeyCode.Space) || Input.GetMouseButtonDown(0);
            bool isMobileInput = Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began;

            if (isComputerInput || isMobileInput)
            {
                StartGame();
            }
        }
    }

    // 正式開始遊戲
    public void StartGame()
    {
        currentState = GameState.Playing;
        Time.timeScale = 1f; // 恢復正常遊戲速度

        if (startHintUI != null) startHintUI.SetActive(false);

        // 通知分數系統開始計分
        if (ScoreManager.Instance != null)
        {
            ScoreManager.Instance.StartScoring();
        }

        // 通知小鳥執行開局的第一跳
        BirdController bird = FindObjectOfType<BirdController>();
        if (bird != null)
        {
            bird.OnGameStartJump();
        }
    }

    // 小鳥死亡時呼叫:觸發結算 UI
    public void OnGameOver()
    {
        currentState = GameState.GameOver;

        // 停止計分並獲取最終分數
        if (ScoreManager.Instance != null)
        {
            ScoreManager.Instance.StopScoring();
            
            int finalScore = ScoreManager.Instance.GetFinalScore();
            int highScore = PlayerPrefs.GetInt("HighScore", 0);

            if (finalScoreText != null) finalScoreText.text = finalScore.ToString("D6");
            if (gameOverHighScoreText != null) gameOverHighScoreText.text = "BEST: " + highScore.ToString("D6");
        }

        // 顯示結算畫面
        if (gameOverPanel != null)
        {
            gameOverPanel.SetActive(true);
        }
    }

    // 按下結算畫面的「重新開始」按鈕時呼叫
    public void OnRestartButtonClick()
    {
        Time.timeScale = 1f; // 恢復時間刻度以利重置
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex); // 重新載入當前場景
    }
}

這裡主要是利用Time.timeScale = 0讓遊戲一開始先暫停,等玩家點擊後才恢復遊戲。
開始遊戲時也會通知ScoreManager開始計分,並讓小鳥執行第一次跳躍。

修改小鳥的操作

有了遊戲狀態後,小鳥也需要判斷現在是不是正在遊戲中。原本只要點擊畫面就會跳躍,所以這裡修改成只有Playing狀態才能正常跳躍。

void Update()
{
    // 【修改】若小鳥死亡,或遊戲尚未開始(WaitingToStart/GameOver),不響應普通跳躍輸入
    if (isDead || GameManager.Instance == null || GameManager.Instance.currentState != GameManager.GameState.Playing) 
        return;

    // 防範 UI 穿透點擊(點擊技能鍵時不觸發背景普通跳躍)
    if (EventSystem.current != null && EventSystem.current.IsPointerOverGameObject())
    {
        return;
    }

    bool isComputerInput = Input.GetKeyDown(KeyCode.Space) || Input.GetMouseButtonDown(0);
    bool isMobileInput = Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began;

    if (isComputerInput || isMobileInput)
    {
        Jump();
    }
}

// 【新增】專門給 GameManager 在點擊開局時呼叫的第一跳
public void OnGameStartJump()
{
    Jump();
}

另外新增OnGameStartJump(),讓GameManager在遊戲開始時控制小鳥進行第一次跳躍。

死亡時則改成通知GameManager:

void Die()
{
    if (isDead) return;
    isDead = true;
    Debug.Log("小鳥撞到了!遊戲結束。");

    // 【修改】呼叫 GameManager 觸發結算流程與 UI
    if (GameManager.Instance != null)
    {
        GameManager.Instance.OnGameOver();
    }
}

這樣撞到岩石後,就會正式進入GameOver狀態。

讓每一局重新計算分數

最後也修改了ScoreManager的Start(),讓每次重新載入遊戲時,分數重新從零開始。

void Start()
{
    rockSpawner = FindObjectOfType<RockSpawner>();
    currentScoreRaw = 0f;
    isScoring = false; // 初始不計分,等待 GameManager 發出 StartScoring 訊號
}

這樣重新開始後,本局分數會重新計算,但之前使用 PlayerPrefs 保存的最高分仍然會保留。

整個流程就變成:

等待開始→玩家點擊→開始遊戲→開始計分→小鳥死亡→停止計分→顯示本次分數與最高分→重新開始→分數重新計算

第十七天的成果

今天主要完成了遊戲的開始與結算流程。現在進入遊戲後不會直接開始,而是等待玩家點擊;死亡後也會顯示本次分數與最高分,按下重新開始後則會重新載入遊戲。
由於剩下天數不多,下一款遊戲應該會製作簡單一點的。


上一篇
Day 16|製作分數計算與顯示
系列文
從基礎到實作:使用 Google AI 學習程式設計 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言