Skip to content

gbradburn/Starcastle

Repository files navigation

Starcastle

A Unity 2D space shooter game inspired by the classic arcade game Star Castle.

Project Overview

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


Architecture Overview

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
Loading

Design Patterns

1. Singleton Pattern

Multiple manager classes use the Singleton pattern to ensure single instances that persist across scenes and are globally accessible.

Implementation Examples:

  • GameManager
  • ScoreManager
  • SoundManager
  • MusicManager
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

2. Interface Pattern (IDamageable)

The IDamageable interface provides a contract for all objects that can take damage, enabling polymorphic damage handling.

Implementers:

  • PlayerShip
  • Starcastle
  • RingSegment
  • SeekingMissile
  • Damageable (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

3. State Machine Pattern

The GameManager implements a state machine to control game flow through distinct states.

States:

  • GetReady - Countdown before gameplay
  • Playing - Active gameplay
  • PlayerShipDestroyed - Player death handling
  • StarcastleDestroyed - 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 --> [*]
Loading

Benefits:

  • Clear game flow control
  • Predictable state transitions
  • Easy debugging and testing

4. Observer Pattern (Unity Events)

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 changes
  • ScoreUpdatedEvent - 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

5. Object Pool Pattern (Implicit)

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

6. Component Pattern

Unity's component-based architecture is leveraged throughout the project, with clear single-responsibility components.

Examples:

  • ScreenWrap - Handles screen wrapping behavior
  • PlayerTurret - Handles weapon firing
  • Damageable - Handles health and destruction

7. Strategy Pattern (Partial Application)

Different turret types (PlayerTurret, EnemyTurret, MissileLauncher) implement different firing strategies while sharing similar structure.


Project 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

System Flow

Initialization Flow

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)
Loading

Combat Flow

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)
Loading

Key Features

Dynamic Shield System

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

Difficulty Scaling

Level-based difficulty increases through:

  • SeekingMissile speed and turn rate scale with level
  • Score bonuses multiply with level progression
  • Missile duration increases with level

Screen Wrapping

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

Persistent Managers

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

Collision Matrix

The project uses Unity's layer system for collision filtering:

Layer Player Enemy PlayerProjectile EnemyProjectile RingSegment EnemyMissile
Player
Enemy
PlayerProjectile
EnemyProjectile
RingSegment
EnemyMissile

Input Controls

Key Action
Z Rotate Left
X Rotate Right
N Thrust Forward
M Fire Weapon

Audio System

The project implements a two-tier audio system:

Music Manager

  • Background music tracks for different game states
  • Singleton-based management
  • Enum-driven track selection

Sound Manager

  • One-shot sound effects
  • Volume control per effect
  • Shared AudioSource pooling

Best Practices Demonstrated

  1. Interface-Based Design: IDamageable enables polymorphic damage handling
  2. Event-Driven Architecture: UnityEvents reduce coupling between systems
  3. Singleton Managers: Centralized state and service management
  4. Component Composition: Small, focused components over monolithic scripts
  5. State Machine: Clear game flow control
  6. Layer-Based Collision: Efficient physics filtering
  7. Object Lifecycle Management: Proper initialization and cleanup
  8. Separation of Concerns: Clear boundaries between game logic, rendering, and audio

Potential Improvements

  1. Object Pooling: Implement pooling for projectiles and effects to reduce garbage collection
  2. Input System: Migrate from legacy Input to new Input System package
  3. ScriptableObject Configuration: Move hard-coded values to ScriptableObject assets
  4. Command Pattern: Replace direct input handling with command pattern for rebindable controls
  5. Service Locator: Replace static Singleton references with a service locator pattern
  6. Dependency Injection: Reduce tight coupling between managers and game objects
  7. Audio Mixer: Implement Unity Audio Mixer for better sound management
  8. Save System: Expand beyond high score to include settings and progression

Credits

Third-Party Assets:

  • 2D Space Kit
  • Laser Weapons Sound Pack
  • Music Tracks
  • TextMesh Pro

About

Starcastle game made in Unity

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors