Smuggler’s Run: Interstellar Escape

Smuggler’s Run: Interstellar Escape is an Alternate Control endless-runner game that bridges the gap between physical hardware and digital gameplay.
Developed as a final project for University of Tulsa’s CSG3813: Alternate Controls Challenge, the game immerses players in an intergalactic escape where the primary challenge lies in mastering a custom-built interface.
While the demo has been adapted for keyboard and mouse, the intended experience utilizes a custom Arduino-powered controller featuring floor pedals and a crank wheel.
Project Roles
This project was a collaborative effort between game developers and toy designers:
Game Development team: Haiying Zeng, Ryan Pederson, William Gulick
Toy Design (Hardware team): Jacob Peerson, Vincent Ume-Ezeoke, Carter Reed
I served as Lead Sound Designer and Systems Programmer.
Systems Programmer: Environment & Mechanics
- Endless Procedural Generation:
- Developed a dynamic environment system that instantiates terrain and obstacles infinitely while implementing a cleanup protocol to manage memory after projectiles and segments pass the player.
- Warp System:
- Built a multi-planet traversal system featuring three distinct environment versions, allowing players to warp between different planetary biomes.
- Core Game Logic:
- Engineered the “Lives” system and the projectile instantiation logic to ensure balanced difficulty scaling.
Lead Sound Designer & Composer
- Dynamic Music Engine:
- Composed the original soundtrack with an immersive speed-up effect, where the tempo increases the longer the player survives to heighten tension.
- Audio-Visual Sync:
- Designed and implemented specific sound effects for the “Warp” mechanic to provide tactile feedback during planet transitions.
- Feedback Loops:
- Curated the sci-fi soundscape to provide immediate auditory cues for environmental hazards and movement.
Smuggler’s Run: Interstellar Escape Music
Code Sample: Procedural Environment Management
This script manages the procedural instantiation and cleanup of environment segments, and the “Warp” logic that transitions players between different planetary environments.
/// <summary>
/// Manages the endless generation of tunnels, obstacles, and planetary transitions.
/// </summary>
public class TunnelManager : MonoBehaviour
{
// Singleton pattern for global access to spawning logic
public static TunnelManager TMinstance { get; private set; }
private static GameManager gm;
[Header("Generation Settings")]
[SerializeField] private float chanceObstacle = 0.2f;
[SerializeField] private GameObject LastTunnel; // Tracks the end of the world for the next spawn point
[Header("Planet Data")]
[SerializeField] private int _currentPlanetIndex;
[SerializeField] private GameObject _planetChangeTrigger;
// Arrays categorized by planet to allow distinct visual/hazard profiles per world
[SerializeField] private GameObject[] _Planet0TunnelPrefabs;
[SerializeField] private GameObject[] _Planet0ObstaclePrefabs;
// ... Additional arrays for Planets 1-3
// Internal 2D arrays to dynamically switch biome data at runtime
private GameObject[][] tunnelPrefabs = new GameObject[4][];
private GameObject[][] ObstaclePrefabs = new GameObject[4][];
private void Awake()
{
// Singleton enforcement
if (TMinstance != null && TMinstance != this) { Destroy(this); }
else { TMinstance = this; }
Initialize2DArrays();
}
/// <summary>
/// Connects Inspector-assigned arrays into a 2D matrix for efficient planet swapping.
/// </summary>
private void Initialize2DArrays()
{
tunnelPrefabs[0] = _Planet0TunnelPrefabs; // Space/Transition Biome
ObstaclePrefabs[0] = _Planet0ObstaclePrefabs;
// ... (Repeated for all 4 planet indices)
}
/// <summary>
/// Handles the "Endless" loop. Instantiates a new segment while cleaning up the old one.
/// </summary>
/// <param name="_TunnelToDelete">The segment the player just exited.</param>
public void SpawnTunnel(GameObject _TunnelToDelete)
{
// Calculate the exact position of the next tunnel based on current game velocity
Vector3 spawnPos = LastTunnel.transform.position + (5 - Time.deltaTime * gm.getCurrSpeed()) * Vector3.forward;
GameObject nextTunnel = Instantiate(
tunnelPrefabs[_currentPlanetIndex][Random.Range(0, tunnelPrefabs[_currentPlanetIndex].Length)],
spawnPos,
LastTunnel.transform.rotation);
// Procedural Obstacle Spawning logic
if (Random.value < chanceObstacle)
{
Instantiate(
ObstaclePrefabs[_currentPlanetIndex][Random.Range(0, ObstaclePrefabs[_currentPlanetIndex].Length)],
spawnPos + Random.Range(-1, 2) * 5 * Vector3.right + Random.Range(-1, 2) * 5 * Vector3.up,
LastTunnel.transform.rotation);
}
LastTunnel = nextTunnel;
Destroy(_TunnelToDelete); // Memory Management: Keeps the hierarchy clean
}
/// <summary>
/// Coroutine to handle the 'Warp' sequence.
/// Temporarily shifts to the "Space" biome before transitioning to a new planet.
/// </summary>
public IEnumerator ChangePlanets()
{
var tempPlanetIndex = _currentPlanetIndex;
_currentPlanetIndex = 0; // Index 0 is the "Space" transition biome
SpawnTrigger();
gm.SetIsInSpace(true); // Triggers visual/audio effects in GameManager
yield return new WaitForSeconds(3);
// Progress to the next planet in the sequence
_currentPlanetIndex = (tempPlanetIndex < 3) ? tempPlanetIndex + 1 : 1;
SpawnTrigger();
// Dynamic Difficulty Scaling: Increase obstacle frequency after every warp
if (chanceObstacle < 1) { chanceObstacle += 0.05f; }
yield return new WaitForSeconds(3);
}
}