A Unity 2D space shooter game inspired by the classic arcade game Star Castle.
Starcastle is a 2D arcade-style shooter where the player must navigate around a protected enemy fortress (the Starcastle), destroy its defensive shields, and eliminate the core while avoiding enemy projectiles and seeking missiles.
This was developed as part of a Unity game dev tutorial
Unity Version: Originally built using Unity 2021 but now updated to 2022.3 LTS Render Pipeline: Built-in 2D
The project follows a modular, component-based architecture with clear separation of concerns. The codebase leverages several established design patterns to create maintainable and scalable game systems.
graph TB
subgraph "Core Systems"
GM[GameManager<br/>State Machine]
LM[LevelManager]
SM[ScoreManager]
SNDM[SoundManager]
MSM[MusicManager]
end
subgraph "Player Systems"
PS[PlayerShip<br/>IDamageable]
PT[PlayerTurret]
PP[PlayerProjectile]
end
subgraph "Enemy Systems"
SC[Starcastle<br/>IDamageable]
SH[Shield]
RS[RingSegment<br/>IDamageable]
ET[EnemyTurret]
ML[MissileLauncher]
SM_MISSILE[SeekingMissile<br/>IDamageable]
end
subgraph "Shared Components"
DMG[Damageable<br/>IDamageable]
SW[ScreenWrap]
end
GM -->|State Events| LM
GM -->|State Events| PS
GM -->|State Events| ML
LM -->|Start Level| GM
SM -->|Score Events| UI[UIManager]
PS -.implements.-> IDMG[IDamageable Interface]
SC -.implements.-> IDMG
RS -.implements.-> IDMG
SM_MISSILE -.implements.-> IDMG
DMG -.implements.-> IDMG
PT -->|Instantiates| PP
ET -->|Instantiates| EP[EnemyProjectile]
ML -->|Instantiates| SM_MISSILE
SH -->|Generates| RS
SC -->|Contains| SH
SC -->|Contains| ET
SC -->|Contains| ML
PS -->|Uses| SW
SM_MISSILE -->|Uses| SW
Multiple manager classes use the Singleton pattern to ensure single instances that persist across scenes and are globally accessible.
Implementation Examples:
GameManagerScoreManagerSoundManagerMusicManager
public static GameManager Instance;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
}
else
{
Instance = this;
}
}Benefits:
- Centralized state management
- Easy access from any script
- Prevents duplicate manager instances
The IDamageable interface provides a contract for all objects that can take damage, enabling polymorphic damage handling.
Implementers:
PlayerShipStarcastleRingSegmentSeekingMissileDamageable(generic component)
public interface IDamageable
{
void TakeDamage(int damage);
}Benefits:
- Consistent damage handling across different entity types
- Loose coupling between projectiles and targets
- Easy to add new damageable objects
The GameManager implements a state machine to control game flow through distinct states.
States:
GetReady- Countdown before gameplayPlaying- Active gameplayPlayerShipDestroyed- Player death handlingStarcastleDestroyed- Level completion
public enum GameStates
{
GetReady,
Playing,
PlayerShipDestroyed,
StarcastleDestroyed
}State Transitions:
stateDiagram-v2
[*] --> GetReady: RestartLevel()
GetReady --> Playing: StartPlaying()
Playing --> PlayerShipDestroyed: Player Dies
Playing --> StarcastleDestroyed: Starcastle Destroyed
PlayerShipDestroyed --> GetReady: Lives > 0
PlayerShipDestroyed --> GameOver: Lives = 0
StarcastleDestroyed --> GetReady: Next Level
GameOver --> [*]
Benefits:
- Clear game flow control
- Predictable state transitions
- Easy debugging and testing
The project uses Unity's UnityEvent system to implement the Observer pattern, allowing loose coupling between systems.
Key Events:
GameStateChanged- Notifies listeners when game state changesScoreUpdatedEvent- Notifies UI when score changes
Example Usage:
public GameStateChangedEvent GameStateChanged = new GameStateChangedEvent();
// In LevelManager
GameManager.Instance.GameStateChanged.AddListener(OnGameStateChanged);Benefits:
- Decoupled communication between systems
- Multiple listeners can react to the same event
- Easy to add new observers without modifying subjects
While not explicitly implemented as a formal pool, the project uses object instantiation and destruction patterns suitable for pooling optimization.
Poolable Objects:
- Projectiles (Player and Enemy)
- Seeking Missiles
- Explosion effects
Unity's component-based architecture is leveraged throughout the project, with clear single-responsibility components.
Examples:
ScreenWrap- Handles screen wrapping behaviorPlayerTurret- Handles weapon firingDamageable- Handles health and destruction
Different turret types (PlayerTurret, EnemyTurret, MissileLauncher) implement different firing strategies while sharing similar structure.
/Assets
├── _project/ # Main project folder
│ ├── Scenes/ # Game scenes
│ │ ├── TitleScene.unity
│ │ ├── MainScene.unity
│ │ └── GameOver.unity
│ ├── Scripts/ # All C# scripts
│ │ ├── Managers/ # Singleton managers
│ │ │ ├── GameManager.cs
│ │ │ ├── LevelManager.cs
│ │ │ ├── ScoreManager.cs
│ │ │ ├── SoundManager.cs
│ │ │ ├── MusicManager.cs
│ │ │ └── LoadPersistentObjects.cs
│ │ ├── Player/ # Player-related scripts
│ │ │ ├── PlayerShip.cs
│ │ │ ├── PlayerTurret.cs
│ │ │ └── PlayerProjectile.cs
│ │ ├── Enemy/ # Enemy-related scripts
│ │ │ ├── Starcastle.cs
│ │ │ ├── Shield.cs
│ │ │ ├── RingSegment.cs
│ │ │ ├── RingDefinition.cs
│ │ │ ├── SegmentDefinition.cs
│ │ │ ├── EnemyTurret.cs
│ │ │ ├── EnemyProjectile.cs
│ │ │ ├── MissileLauncher.cs
│ │ │ └── SeekingMissile.cs
│ │ ├── UI/ # UI-related scripts
│ │ │ ├── UIManager.cs
│ │ │ ├── ScoreboardUI.cs
│ │ │ ├── GameOverUI.cs
│ │ │ └── PlayerLife.cs
│ │ ├── TitleScreen/
│ │ │ └── TitleScreenUI.cs
│ │ ├── IDamageable.cs # Interface for damageable objects
│ │ ├── Damageable.cs # Generic damageable component
│ │ └── ScreenWrap.cs # Screen wrapping utility
│ ├── Prefabs/ # Game object prefabs
│ ├── Materials/ # Materials
│ ├── PhysicsMaterials/ # 2D physics materials
│ ├── Sounds/ # Audio assets
│ └── Resources/ # Runtime-loaded resources
│ └── Managers.prefab # Persistent managers
├── Third Party/ # External assets
│ ├── 2D Space Kit/
│ ├── Laser Weapons Sound Pack/
│ └── Music Tracks/
└── TextMesh Pro/ # TMP package assets
sequenceDiagram
participant Runtime
participant LoadPersistent
participant Managers
participant GameManager
participant LevelManager
Runtime->>LoadPersistent: BeforeSceneLoad
LoadPersistent->>Managers: Instantiate Managers Prefab
LoadPersistent->>Managers: DontDestroyOnLoad
Note over Managers: Managers persist across scenes
LevelManager->>GameManager: Subscribe to GameStateChanged
LevelManager->>GameManager: RestartLevel()
GameManager->>GameManager: Lives = 3
GameManager->>GameManager: ResetScore()
GameManager->>GameManager: GetReady()
GameManager->>GameManager: SpawnStarcastle()
GameManager->>LevelManager: GameStateChanged(GetReady)
LevelManager->>LevelManager: StartCountdown (3s)
LevelManager->>GameManager: StartPlaying()
GameManager->>GameManager: SpawnPlayerShip()
GameManager->>LevelManager: GameStateChanged(Playing)
sequenceDiagram
participant PlayerProjectile
participant RingSegment
participant ScoreManager
participant Starcastle
participant GameManager
PlayerProjectile->>RingSegment: OnCollisionEnter2D
PlayerProjectile->>RingSegment: TakeDamage(1)
RingSegment->>ScoreManager: AddScore(points)
RingSegment->>RingSegment: SetActive(false)
Note over RingSegment: When all segments destroyed...
PlayerProjectile->>Starcastle: OnCollisionEnter2D
PlayerProjectile->>Starcastle: TakeDamage(1)
Starcastle->>ScoreManager: AddScore(points + levelBonus)
Starcastle->>GameManager: StarcastleDestroyed()
GameManager->>GameManager: Set State = StarcastleDestroyed
GameManager->>GameManager: Invoke GetReady (3s delay)
The Shield class procedurally generates rotating ring segments around the Starcastle:
- Multiple concentric rings with configurable radius and segment count
- Counter-rotating rings for visual effect
- Each segment can be individually destroyed
- Ring colors customizable per definition
Level-based difficulty increases through:
SeekingMissilespeed and turn rate scale with level- Score bonuses multiply with level progression
- Missile duration increases with level
The ScreenWrap component enables classic arcade-style screen wrapping:
- Objects exiting one edge appear on the opposite edge
- Smooth viewport-based calculations
- Applied to player ship and missiles
The LoadPersistentObjects static class uses Unity's RuntimeInitializeOnLoadMethod attribute to:
- Load managers before scene initialization
- Ensure managers persist across scene transitions
- Provide consistent state management
The project uses Unity's layer system for collision filtering:
| Layer | Player | Enemy | PlayerProjectile | EnemyProjectile | RingSegment | EnemyMissile |
|---|---|---|---|---|---|---|
| Player | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ |
| Enemy | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
| PlayerProjectile | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ |
| EnemyProjectile | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| RingSegment | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
| EnemyMissile | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| Key | Action |
|---|---|
| Z | Rotate Left |
| X | Rotate Right |
| N | Thrust Forward |
| M | Fire Weapon |
The project implements a two-tier audio system:
- Background music tracks for different game states
- Singleton-based management
- Enum-driven track selection
- One-shot sound effects
- Volume control per effect
- Shared AudioSource pooling
- Interface-Based Design:
IDamageableenables polymorphic damage handling - Event-Driven Architecture: UnityEvents reduce coupling between systems
- Singleton Managers: Centralized state and service management
- Component Composition: Small, focused components over monolithic scripts
- State Machine: Clear game flow control
- Layer-Based Collision: Efficient physics filtering
- Object Lifecycle Management: Proper initialization and cleanup
- Separation of Concerns: Clear boundaries between game logic, rendering, and audio
- Object Pooling: Implement pooling for projectiles and effects to reduce garbage collection
- Input System: Migrate from legacy Input to new Input System package
- ScriptableObject Configuration: Move hard-coded values to ScriptableObject assets
- Command Pattern: Replace direct input handling with command pattern for rebindable controls
- Service Locator: Replace static Singleton references with a service locator pattern
- Dependency Injection: Reduce tight coupling between managers and game objects
- Audio Mixer: Implement Unity Audio Mixer for better sound management
- Save System: Expand beyond high score to include settings and progression
Third-Party Assets:
- 2D Space Kit
- Laser Weapons Sound Pack
- Music Tracks
- TextMesh Pro