Source file unity-csharp/arcade-classics/space-invaders/SpaceInvadersGame.cs from the GDnD code examples. Download raw file

// GDnD wiki example
// Demonstrates: a small game state machine owning score and lives, with wave/win/lose transitions
// Related pages: [[overview-arcade-classics-as-learning-projects]], [[unity-gamemanager-pattern]], [[csharp-enums]], [[ui-design]]
 
using UnityEngine;
 
// Central controller. It owns score, lives, and the game State, and reacts to the
// three things that change that state: an invader killed (score), the wave cleared
// (next wave), and the player hit or invaders reaching the line (lose a life /
// game over). The State enum is the step-4 "state changes" learning target.
public class SpaceInvadersGame : MonoBehaviour
{
    public enum State { Playing, GameOver }
 
    [SerializeField] private PlayerShip player;
    [SerializeField] private InvaderFormation formation;
    [SerializeField] private int startingLives = 3;
    [Tooltip("Optional on-screen HUD. If empty, messages print to the Console.")]
    [SerializeField] private ArcadeHud hud;
 
    public State CurrentState { get; private set; }
    public int Score { get; private set; }
    public int Lives { get; private set; }
    public int Wave { get; private set; }
 
    private void Start()
    {
        player.Bind(this);
        formation.Bind(this, player.transform.position.y);
        StartGame();
    }
 
    private void StartGame()
    {
        Score = 0;
        Lives = startingLives;
        Wave = 1;
        CurrentState = State.Playing;
        formation.BuildWave();
        RefreshHud();
    }
 
    public void AddScore(int value)
    {
        Score += value;
        RefreshHud();
    }
 
    public void OnWaveCleared()
    {
        Wave++;
        RefreshHud();
        ShowMessage($"Wave cleared! Starting wave {Wave}. Score {Score}");
        formation.BuildWave();
    }
 
    public void OnPlayerHit()
    {
        LoseLife("Player hit");
    }
 
    public void OnInvadersReachedPlayer()
    {
        // Invaders landing is an instant loss of the run.
        ShowMessage("Invaders reached the line!");
        Lives = 0;
        EndOrContinue();
    }
 
    private void LoseLife(string reason)
    {
        if (CurrentState != State.Playing)
        {
            return;
        }
 
        Lives--;
        RefreshHud();
        ShowMessage($"{reason}. Lives remaining: {Lives}");
        EndOrContinue();
    }
 
    private void EndOrContinue()
    {
        if (Lives <= 0)
        {
            CurrentState = State.GameOver;
            ShowMessage($"Game over. Final score: {Score}");
            StartGame();
        }
    }
 
    // HUD helpers. The game owns the numbers; ArcadeHud only displays them.
    // With no HUD assigned, messages fall back to the Console instead of throwing.
    private void RefreshHud()
    {
        if (hud != null)
        {
            hud.SetStatus($"Score {Score}   Lives {Lives}   Wave {Wave}");
        }
    }
 
    private void ShowMessage(string message)
    {
        if (hud != null)
        {
            hud.ShowMessage(message);
        }
        else
        {
            Debug.Log(message);
        }
    }
}