波次、失败与重来
Assets/
Scenes/
Arena.unity
Scripts/
Prefabs/
dressed arena · waves spawned by GameDirector
你已经有的这一课新增这一课替换
一切都能用。Knight 跳得上方块,三个技能都打得中,敌人会追你也会伤你,竞技场有了光照、有了声音,跑得也快。按下 Play,你就能永远重复这些事 —— 因为什么都不会结束。
demo 只是让你有东西可玩,游戏会告诉你什么时候赢、什么时候输。这个差别就是一个 enum 加大约四十行代码。
三波敌人,然后是两种结局之一 —— 而每种结局都有回到起点的出口。
第 1 步 —— 一个 enum,一个地方
把状态散在各处脚本里,你就会花一个晚上去研究为什么打赢了却弹出失败界面。把它放在一个对象里,其他脚本只负责读。
using System.Collections;
using UnityEngine;
public enum GameState { Ready, Fighting, Cleared, Defeat }
public class GameDirector : MonoBehaviour
{
public static GameDirector I; // one director, reachable from anywhere
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--;
}
// wait until the player clears this wave, or dies trying
while (alive > 0 && State == GameState.Fighting) yield return null;
if (State != GameState.Fighting) yield break;
yield return new WaitForSeconds(2f); // a breath between waves
}
Finish(GameState.Cleared);
}
void Finish(GameState ending)
{
State = ending;
Time.timeScale = 0f; // freeze the arena behind the panel
}
public void Restart()
{
Time.timeScale = 1f;
UnityEngine.SceneManagement.SceneManager.LoadScene(
UnityEngine.SceneManagement.SceneManager.GetActiveScene().buildIndex);
}
}OnDied 就是你在 tab 02 里连着血条一起加到 CharacterStats 上的那个 event。Director 从不在场景里搜敌人 —— 每个敌人死的时候自己来报告。
Time.timeScale = 0f 一次停掉所有东西:敌人、动画、粒子。你不用挨个去禁用。
重载场景是最便宜的重开方式,而且绝不会留下残余状态 —— 整个竞技场都是从文件重新建出来的。
第 2 步 —— 两个面板
- 1
在 tab 01 那个 Canvas 里 —— 就是装着 HudPanel 和 PausePanel 的那个 —— 再加两个面板:VictoryPanel 和 DefeatPanel,每个都有一行标题和一个 Button。
- 2
在 Inspector 里把两个都设为 inactive。它们只在这局结束的时候才出现。
- 3
把每个 Button 的 OnClick 连到 GameDirector.Restart。
- 4
再加一个小脚本,盯着 GameDirector.I.State,显示对应的面板。
using UnityEngine;
public class EndPanels : MonoBehaviour
{
public GameObject victory;
public GameObject defeat;
void Update()
{
// Update still runs at timeScale 0 — that is why the panel can appear.
victory.SetActive(GameDirector.I.State == GameState.Cleared);
defeat.SetActive(GameDirector.I.State == GameState.Defeat);
}
}