// 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 enemies = new List(); 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(); } }