Source file unity-csharp/arcade-classics/robotron-lite/RobotronGame.cs from the GDnD code examples. Download raw file

// GDnD wiki example
// Demonstrates: spawn pressure (an accelerating enemy spawner), survival-arena contact, and score/lives
// Related pages: [[overview-arcade-classics-as-learning-projects]], [[csharp-collections]], [[unity-gamemanager-pattern]]
 
using System.Collections.Generic;
using UnityEngine;
 
// Runs the survival arena: spawns enemies at the edges on a timer that gets
// faster over time (spawn pressure), tracks them, and ends the run when an enemy
// reaches the player too many times. Owns score and lives.
public class RobotronGame : MonoBehaviour
{
    [SerializeField] private TwinStickPlayer player;
    [SerializeField] private Enemy enemyPrefab;
    [SerializeField] private int startingLives = 3;
 
    [Header("Spawn pressure")]
    [SerializeField] private float startSpawnInterval = 1.6f;
    [SerializeField] private float minSpawnInterval = 0.35f;
    [Tooltip("Seconds shaved off the spawn interval per spawn.")]
    [SerializeField] private float intervalRampPerSpawn = 0.03f;
 
    [Header("Survival")]
    [SerializeField] private float contactDistance = 0.5f;
    [SerializeField] private float respawnInvulnerability = 2f;
 
    public int Score { get; private set; }
    public int Lives { get; private set; }
 
    private readonly List<Enemy> enemies = new List<Enemy>();
    private Camera cachedCamera;
    private float spawnTimer;
    private float currentInterval;
 
    private void Start()
    {
        cachedCamera = Camera.main;
        player.Bind(this);
        NewGame();
    }
 
    private void NewGame()
    {
        ClearEnemies();
        Score = 0;
        Lives = startingLives;
        currentInterval = startSpawnInterval;
        spawnTimer = 0f;
        player.Respawn(respawnInvulnerability);
        Debug.Log($"Survive! Lives {Lives}.");
    }
 
    private void Update()
    {
        TickSpawning();
        CheckContact();
    }
 
    private void TickSpawning()
    {
        spawnTimer += Time.deltaTime;
        if (spawnTimer < currentInterval)
        {
            return;
        }
 
        spawnTimer -= currentInterval;
        SpawnEnemy();
 
        // The arena gets busier the longer you live.
        currentInterval = Mathf.Max(minSpawnInterval, currentInterval - intervalRampPerSpawn);
    }
 
    private void SpawnEnemy()
    {
        Enemy enemy = Instantiate(enemyPrefab, RandomEdgePosition(), Quaternion.identity);
        enemy.Init(this, player.transform);
        enemies.Add(enemy);
    }
 
    private void CheckContact()
    {
        if (player.IsInvulnerable)
        {
            return;
        }
 
        foreach (Enemy enemy in enemies)
        {
            if (enemy != null &&
                Vector3.Distance(enemy.transform.position, player.transform.position) < contactDistance)
            {
                OnPlayerHit();
                return;
            }
        }
    }
 
    // Called by an enemy when a bullet kills it.
    public void OnEnemyKilled(Enemy enemy)
    {
        enemies.Remove(enemy);
        Score += 100;
        Destroy(enemy.gameObject);
    }
 
    private void OnPlayerHit()
    {
        Lives--;
        Debug.Log($"Hit! Lives remaining: {Lives}");
 
        if (Lives <= 0)
        {
            Debug.Log($"Game over. Final score: {Score}");
            NewGame();
            return;
        }
 
        player.Respawn(respawnInvulnerability);
    }
 
    private Vector3 RandomEdgePosition()
    {
        float halfHeight = cachedCamera.orthographicSize - 0.3f;
        float halfWidth = cachedCamera.orthographicSize * cachedCamera.aspect - 0.3f;
 
        // Pick a random point just inside one of the four edges.
        return Random.Range(0, 2) == 0
            ? new Vector3(Random.Range(-halfWidth, halfWidth), Random.Range(0, 2) == 0 ? halfHeight : -halfHeight, 0f)
            : new Vector3(Random.Range(0, 2) == 0 ? halfWidth : -halfWidth, Random.Range(-halfHeight, halfHeight), 0f);
    }
 
    private void ClearEnemies()
    {
        foreach (Enemy enemy in enemies)
        {
            if (enemy != null)
            {
                Destroy(enemy.gameObject);
            }
        }
 
        enemies.Clear();
    }
}