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]]
 
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;
 
    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();
        Debug.Log($"Wave {Wave} — Lives {Lives}, Score {Score}");
    }
 
    public void AddScore(int value)
    {
        Score += value;
    }
 
    public void OnWaveCleared()
    {
        Wave++;
        Debug.Log($"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.
        Debug.Log("Invaders reached the line!");
        Lives = 0;
        EndOrContinue();
    }
 
    private void LoseLife(string reason)
    {
        if (CurrentState != State.Playing)
        {
            return;
        }
 
        Lives--;
        Debug.Log($"{reason}. Lives remaining: {Lives}");
        EndOrContinue();
    }
 
    private void EndOrContinue()
    {
        if (Lives <= 0)
        {
            CurrentState = State.GameOver;
            Debug.Log($"Game over. Final score: {Score}");
            StartGame();
        }
    }
}