U
tab 04Thế giới & Vòng lặp game7 bài — chạm để mở
06 / 07 · 12 phút
Thế giới & Vòng lặp game

Đợt tấn công, thua và chơi lại

dự án của bạn tới lúc này

Assets/

Scenes/

Arena.unity

VictoryPanelDefeatPanelSpawnPoint ×4

Scripts/

CharacterStats.OnDied eventHealthBar.csGameDirector.csEndPanels.cs

Prefabs/

Enemy.prefab

dressed arena · waves spawned by GameDirector

đã có sẵnbài này thêmbài này thay thế

Mọi thứ đều chạy. Knight nhảy được lên khối, ba skill đánh trúng, kẻ địch biết đuổi và làm bạn đau, sân đấu đã có sáng, có tiếng, chạy mượt. Bấm Play là bạn làm được hết — mãi mãi, vì chẳng có gì kết thúc cả.

Bản demo cho bạn làm được nhiều thứ. Một game nói cho bạn biết khi nào thắng và khi nào thua. Khác biệt đó nằm ở một enum và chừng bốn chục dòng.

sơ đồ động

Ba đợt, rồi một trong hai kết cục — và từ kết cục nào cũng có đường về vạch xuất phát.

Bước 1 — một enum, một chỗ

Rải trạng thái khắp các script rồi bạn sẽ mất một buổi tối để hỏi vì sao màn thua hiện lên giữa lúc thắng. Giữ nó trong một đối tượng, mọi script khác chỉ việc đọc.

GameDirector.cs
using System.Collections;
using UnityEngine;

public enum GameState { Ready, Fighting, Cleared, Defeat }

public class GameDirector : MonoBehaviour
{
    public static GameDirector I;              // một director, ở đâu cũng gọi tới được

    public GameState State { get; private set; } = GameState.Ready;

    public Transform[] spawnPoints;
    public GameObject enemyPrefab;
    public CharacterStats player;
    public int[] waveSizes = { 2, 3, 4 };

    int alive;

    void Awake() => I = this;

    void Start()
    {
        player.OnDied += () => Finish(GameState.Defeat);
        StartCoroutine(RunWaves());
    }

    IEnumerator RunWaves()
    {
        State = GameState.Fighting;

        foreach (int size in waveSizes)
        {
            alive = size;
            for (int i = 0; i < size; i++)
            {
                Transform where = spawnPoints[Random.Range(0, spawnPoints.Length)];
                GameObject e = Instantiate(enemyPrefab, where.position, where.rotation);
                e.GetComponent<CharacterStats>().OnDied += () => alive--;
            }

            // chờ tới khi người chơi dọn xong đợt này, hoặc chết giữa chừng
            while (alive > 0 && State == GameState.Fighting) yield return null;
            if (State != GameState.Fighting) yield break;

            yield return new WaitForSeconds(2f);   // nghỉ một nhịp giữa hai đợt
        }

        Finish(GameState.Cleared);
    }

    void Finish(GameState ending)
    {
        State = ending;
        Time.timeScale = 0f;                        // đóng băng sân đấu phía sau panel
    }

    public void Restart()
    {
        Time.timeScale = 1f;
        UnityEngine.SceneManagement.SceneManager.LoadScene(
            UnityEngine.SceneManagement.SceneManager.GetActiveScene().buildIndex);
    }
}
  • OnDied chính là event bạn đã thêm vào CharacterStats ở tab 02, cùng lúc với thanh máu. Director không đi lùng kẻ địch trong cảnh — mỗi con tự báo khi nó đi.

  • Time.timeScale = 0f dừng tất cả cùng lúc: kẻ địch, hoạt ảnh, particle. Bạn không phải tắt từng thứ.

  • Nạp lại scene là cách chơi lại rẻ nhất và không bao giờ để sót trạng thái cũ — cả sân đấu được dựng lại từ file.

Bước 2 — hai panel

  1. 1

    Trong Canvas từ tab 01 — cái đang chứa HudPanel và PausePanel — thêm hai panel nữa: VictoryPanel và DefeatPanel, mỗi cái có một dòng tiêu đề và một Button.

  2. 2

    Đặt cả hai thành inactive trong Inspector. Chúng chỉ tồn tại khi ván đấu kết thúc.

  3. 3

    Nối OnClick của mỗi Button tới GameDirector.Restart.

  4. 4

    Thêm một script nhỏ theo dõi GameDirector.I.State rồi bật đúng panel tương ứng.

EndPanels.cs
using UnityEngine;

public class EndPanels : MonoBehaviour
{
    public GameObject victory;
    public GameObject defeat;

    void Update()
    {
        // Update vẫn chạy khi timeScale bằng 0 — nhờ vậy panel mới hiện ra được.
        victory.SetActive(GameDirector.I.State == GameState.Cleared);
        defeat.SetActive(GameDirector.I.State == GameState.Defeat);
    }
}