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();
}
}