Lecture 15: Service Locator Pattern - Managing Dependencies

๐Ÿ” Service Locator Pattern

Managing Dependencies with Service Locator

Game Programming - CSCI 3213

Spring 2026 - Lecture 15 (Final)

๐Ÿ“š Learning Objectives

Developer's Note: "Never Panic Early"

โš ๏ธ What to Expect During Implementation

As we implement this pattern, we'll be creating multiple files in a specific order. You will see errors in Unity and your IDE until all files are complete.

How to Handle Development Errors

  • Don't Ignore: Note the errors - they're telling you something
  • Don't Panic: These are expected until all files are created
  • Stay Calm: Follow the implementation order and errors will resolve

As Apollo 13's Fred Haise said: "Never Panic Early"

This is a critical skill for software development. Note problems, but stay focused on the implementation path.

๐ŸŒ Real-World Analogy

Restaurant Waiter System

Think about dining at a restaurant:

Without Service Locator (Direct Access):

  • You walk into the kitchen to get your food
  • You go to the bar to pour your drink
  • You find the chef to place your order
  • You need to know where everything is
  • Chaos! ๐Ÿ˜ต

With Service Locator (Waiter):

  • You tell the waiter what you want
  • Waiter knows where to get food, drinks, desserts
  • You don't need to know how the restaurant works
  • Waiter acts as intermediary between you and services
  • Organized! ๐Ÿ˜Š
The Pattern: Service Locator is like a waiter - it knows where all services are and brings them to you

๐Ÿ” Understanding Service Locator

Core Concept

Maintain a central registry of services and provide a mechanism to locate and access them globally.

Key Components:

  • Service Locator: Central registry (the waiter)
  • Service Contracts: Interfaces defining services (menu)
  • Service Providers: Concrete implementations (kitchen, bar)
  • Clients: Code that needs services (customers)

The Problem It Solves

You need global access to services (logging, analytics, ads) but don't want tight coupling or complex initialization throughout your codebase.

Pattern Type: Architectural - manages application-wide concerns

๐Ÿ—๏ธ Service Locator Structure

Architecture:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚          Client Code                โ”‚
โ”‚    (Requests services)              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
               โ”‚ GetService()
               โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚      Service Locator                 โ”‚ โ† Central Registry
โ”‚    (Dictionary of services)          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
               โ”‚ Returns instance
               โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚      Service Providers               โ”‚
โ”‚  - Logger (ILoggerService)           โ”‚
โ”‚  - Analytics (IAnalyticsService)     โ”‚
โ”‚  - Ads (IAdvertisement)              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                    

Flow:

  1. Services register themselves on startup
  2. Client requests service by interface type
  3. Service Locator looks up and returns instance
  4. Client uses service through interface

โœ… Benefits of Service Locator

โš ๏ธ Drawbacks & The Anti-Pattern Debate

Controversial Pattern: Some experts consider it an anti-pattern. Use wisely and only for truly global services!

๐ŸŽฏ When to Use Service Locator

Use Service Locator For:

DON'T Use Service Locator For:

Example Decision Tree:

Service Use Service Locator? Reason
Logger โœ… Yes Needed everywhere, non-gameplay
Analytics โœ… Yes Global tracking, removable
HUD โŒ No Scene-specific, not always loaded
PlayerController โŒ No Core gameplay, explicit dependency

๐Ÿ’ป ServiceLocator Class (1/2)

๐Ÿ“ File Structure Note

Create a new file: ServiceLocator.cs
Static class that manages service registration and retrieval.

using System; using System.Collections.Generic; public static class ServiceLocator { // Central registry: Type โ†’ Instance private static readonly IDictionary Services = new Dictionary(); // Register a service public static void RegisterService(T service) { if (!Services.ContainsKey(typeof(T))) { Services[typeof(T)] = service; } else { throw new ApplicationException( "Service already registered"); } }

๐Ÿ’ป ServiceLocator Class (2/2)

// Retrieve a service public static T GetService() { try { return (T)Services[typeof(T)]; } catch { throw new ApplicationException( "Requested service not found."); } } }

Key Features:

  • Static class: No instantiation needed
  • Generic methods: Type-safe registration/retrieval
  • Dictionary: Type as key, service instance as value
  • Exception handling: Clear error messages

๐Ÿ’ป Service Contracts (Interfaces)

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create separate interface files for best practices:
Assets/Scripts/Services/Interfaces/ILoggerService.cs
Assets/Scripts/Services/Interfaces/IAnalyticsService.cs
Assets/Scripts/Services/Interfaces/IAdvertisement.cs
โš ๏ธ These interface files go into your Unity project for Blade Racer.

// Logger service contract public interface ILoggerService { void Log(string message); } // Analytics service contract public interface IAnalyticsService { void SendEvent(string eventName); } // Advertisement service contract public interface IAdvertisement { void DisplayAd(); }
Why Interfaces? Clients depend on contracts, not implementations. Easy to swap or mock services!

๐Ÿ’ป Logger Service Provider

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/Services/Logger.cs
This is the concrete implementation of the ILoggerService interface.
โš ๏ธ This code goes into your Unity project for Blade Racer.

using UnityEngine; public class Logger : ILoggerService { public void Log(string message) { Debug.Log("Logged: " + message); } }

Production Logger Could Include:

// Enhanced logger example public void Log(string message, LogLevel level = LogLevel.Info) { string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); string logEntry = $"[{timestamp}] [{level}] {message}"; Debug.Log(logEntry); WriteToFile(logEntry); if (level == LogLevel.Error) SendToAnalytics(logEntry); }

๐Ÿ’ป Analytics Service Provider

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/Services/Analytics.cs
This is the concrete implementation of the IAnalyticsService interface.
โš ๏ธ This code goes into your Unity project for Blade Racer.

using UnityEngine; public class Analytics : IAnalyticsService { public void SendEvent(string eventName) { Debug.Log("Sent: " + eventName); } }

Real Analytics Integration:

public class UnityAnalytics : IAnalyticsService { public void SendEvent(string eventName) { // Unity Analytics Unity.Analytics.Analytics.CustomEvent(eventName); } } public class GoogleAnalytics : IAnalyticsService { public void SendEvent(string eventName) { // Google Analytics for Games FirebaseAnalytics.LogEvent(eventName); } }
Swap implementations: Easy to switch from Unity Analytics to Google Analytics!

๐Ÿ’ป Advertisement Service Provider

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/Services/Advertisement.cs
This is the concrete implementation of the IAdvertisement interface.
โš ๏ธ This code goes into your Unity project for Blade Racer.

using UnityEngine; public class Advertisement : IAdvertisement { public void DisplayAd() { Debug.Log("Displaying video advertisement"); } }

Real Ad Integration:

using UnityEngine.Advertisements; public class UnityAds : IAdvertisement { private string gameId = "1234567"; private string placementId = "rewardedVideo"; public void DisplayAd() { if (Advertisement.IsReady(placementId)) { Advertisement.Show(placementId); } else { Debug.LogWarning("Ad not ready"); } }

๐Ÿ’ป Client Code - Service Registration

๐Ÿ“ File Structure Note - TESTING CODE

Create a new file: Assets/Scripts/Testing/ClientServiceLocator.cs
This script tests your Service Locator implementation by registering and using services.
โš ๏ธ This is temporary testing code - you can remove it after testing your implementation.

using UnityEngine; public class ClientServiceLocator : MonoBehaviour { void Start() { RegisterServices(); } private void RegisterServices() { // Register Logger ILoggerService logger = new Logger(); ServiceLocator.RegisterService(logger); // Register Analytics IAnalyticsService analytics = new Analytics(); ServiceLocator.RegisterService(analytics); // Register Advertisement IAdvertisement advertisement = new Advertisement(); ServiceLocator.RegisterService(advertisement); }
Important: Register services as early as possible (e.g., in GameManager's Awake)

๐Ÿ’ป Client Code - Using Services

void OnGUI() { GUILayout.Label("Review output in the console:"); if (GUILayout.Button("Log Event")) { ILoggerService logger = ServiceLocator.GetService(); logger.Log("Hello World!"); } if (GUILayout.Button("Send Analytics")) { IAnalyticsService analytics = ServiceLocator.GetService(); analytics.SendEvent("button_clicked"); } if (GUILayout.Button("Display Advertisement")) { IAdvertisement advertisement = ServiceLocator.GetService(); advertisement.DisplayAd(); } } }
๐Ÿ’ก Alternative: When you have multiple test scripts with overlapping GUI buttons, consider using TestPanel.cs to combine all test controls into one draggable window. See the Event Bus lecture for the unified TestPanel implementation.

๐ŸŽฎ Updating TestPanel for Service Locator

๐Ÿ“ Evolving TestPanel.cs

Add Service Locator keyboard shortcuts and section to your TestPanel.cs.

// Add to TestPanel fields private bool _serviceLocatorExpanded = true; // Add to Update() - Service Locator shortcuts if (Input.GetKeyDown(KeyCode.L)) // L = Log { var logger = ServiceLocator.GetService<ILoggerService>(); logger?.Log("TestPanel log event"); } if (Input.GetKeyDown(KeyCode.Y)) // Y = analYtics { var analytics = ServiceLocator.GetService<IAnalyticsService>(); analytics?.SendEvent("test_event"); } if (Input.GetKeyDown(KeyCode.H)) // H = sHow ad { var ads = ServiceLocator.GetService<IAdvertisement>(); ads?.DisplayAd(); }
// Add DrawServiceLocatorSection() method void DrawServiceLocatorSection() { GUI.backgroundColor = new Color(0.2f, 0.6f, 0.2f); // Dark Green _serviceLocatorExpanded = GUILayout.Toggle( _serviceLocatorExpanded, "โ–ผ Service Locator", "button"); GUI.backgroundColor = Color.white; if (_serviceLocatorExpanded) { GUILayout.BeginVertical("box"); if (GUILayout.Button("Log Event (L)")) ServiceLocator.GetService<ILoggerService>()?.Log("Test"); if (GUILayout.Button("Send Analytics (Y)")) ServiceLocator.GetService<IAnalyticsService>()?.SendEvent("test"); if (GUILayout.Button("Display Ad (H)")) ServiceLocator.GetService<IAdvertisement>()?.DisplayAd(); GUILayout.EndVertical(); } }
Keymap Update: Add to DrawKeymapWindow():
L = Log Event, Y = Send Analytics, H = Display Ad (sHow)

๐Ÿงช Testing the Implementation

Steps:

  1. Create new Unity scene
  2. Create empty GameObject named "ServiceLocatorTest"
  3. Attach ClientServiceLocator script
  4. Press Play
  5. Click buttons to test each service
  6. Observe console output

Expected Output:

  • Log Event: "Logged: Hello World!"
  • Send Analytics: "Sent: button_clicked"
  • Display Advertisement: "Displaying video advertisement"
Services registered once: Can be accessed from any script anywhere in your game!

๐Ÿ”„ Runtime Service Swapping

Debug vs Release Builds:

private void RegisterServices() { #if UNITY_EDITOR || DEBUG // Debug builds: verbose logging ILoggerService logger = new VerboseLogger(); #else // Release builds: minimal logging ILoggerService logger = new MinimalLogger(); #endif ServiceLocator.RegisterService(logger); #if UNITY_EDITOR // Don't send analytics during development IAnalyticsService analytics = new MockAnalytics(); #else // Production: real analytics IAnalyticsService analytics = new UnityAnalytics(); #endif ServiceLocator.RegisterService(analytics); }
Benefit: No analytics noise during testing, but real tracking in production!

๐ŸŽฎ Platform-Specific Services

private void RegisterServices() { IAdvertisement ads; #if UNITY_IOS ads = new iOSAds(); #elif UNITY_ANDROID ads = new AndroidAds(); #elif UNITY_WEBGL ads = new WebGLAds(); #else ads = new MockAds(); // Editor/unknown platform #endif ServiceLocator.RegisterService(ads); // Platform-specific analytics #if UNITY_IOS || UNITY_ANDROID IAnalyticsService analytics = new MobileAnalytics(); #else IAnalyticsService analytics = new DesktopAnalytics(); #endif ServiceLocator.RegisterService(analytics); }
Flexibility: Client code stays the same across all platforms!

๐Ÿ”ง Enhanced Service Locator Features

Add Unregister Functionality:

public static void UnregisterService() { if (Services.ContainsKey(typeof(T))) { Services.Remove(typeof(T)); } } public static void UnregisterAllServices() { Services.Clear(); }

Add Safe GetService (No Exception):

public static T TryGetService() where T : class { if (Services.TryGetValue(typeof(T), out object service)) { return service as T; } return null; } // Usage var logger = ServiceLocator.TryGetService(); if (logger != null) { logger.Log("Service found!"); }

๐Ÿ” Service Availability Checking

public static bool HasService() { return Services.ContainsKey(typeof(T)); } // Usage in client code public class PlayerController : MonoBehaviour { void Start() { // Check before using if (ServiceLocator.HasService()) { var analytics = ServiceLocator.GetService(); analytics.SendEvent("player_spawned"); } } void OnDestroy() { // Safe even if analytics not registered var analytics = ServiceLocator.TryGetService(); analytics?.SendEvent("player_destroyed"); } }
Best Practice: Check service availability before use, especially for optional services

๐ŸŽฎ GameManager Integration

public class GameManager : MonoBehaviour { public static GameManager Instance { get; private set; } void Awake() { // Singleton pattern if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); // Register all services early RegisterServices(); } private void RegisterServices() { ServiceLocator.RegisterService( new Logger()); ServiceLocator.RegisterService( new UnityAnalytics()); ServiceLocator.RegisterService( new UnityAds()); Debug.Log("All services registered"); } }

๐Ÿงช Mock Services for Unit Testing

// Mock logger that doesn't actually log public class MockLogger : ILoggerService { public List<string> LoggedMessages = new List<string>(); public void Log(string message) { LoggedMessages.Add(message); // No Debug.Log - silent! } } // Unit test [Test] public void PlayerController_OnSpawn_LogsEvent() { // Arrange var mockLogger = new MockLogger(); ServiceLocator.RegisterService(mockLogger); var player = new GameObject().AddComponent(); // Act player.Spawn(); // Assert Assert.AreEqual(1, mockLogger.LoggedMessages.Count); Assert.AreEqual("Player spawned", mockLogger.LoggedMessages[0]); // Cleanup ServiceLocator.UnregisterService(); }

๐ŸŽฎ Real-World Game Usage

Player Actions Tracked with Services:

public class PlayerController : MonoBehaviour { private ILoggerService _logger; private IAnalyticsService _analytics; void Start() { _logger = ServiceLocator.GetService(); _analytics = ServiceLocator.GetService(); _logger.Log("Player controller initialized"); _analytics.SendEvent("player_session_start"); } public void CollectCoin() { _logger.Log("Coin collected"); _analytics.SendEvent("coin_collected"); // ... game logic } public void Die() { _logger.Log("Player died"); _analytics.SendEvent("player_death"); // Show rewarded ad? var ads = ServiceLocator.TryGetService(); ads?.DisplayAd(); } }

๐Ÿ”„ Alternative: Dependency Injection (DI)

What is Dependency Injection?

A technique where objects receive their dependencies from external sources rather than creating them.

Constructor Injection:

// Dependencies are explicit! public class PlayerController { private readonly ILoggerService _logger; private readonly IAnalyticsService _analytics; // Constructor injection - dependencies visible public PlayerController( ILoggerService logger, IAnalyticsService analytics) { _logger = logger; _analytics = analytics; } public void Die() { _logger.Log("Player died"); _analytics.SendEvent("player_death"); } }
Benefit: Dependencies are explicit in the constructor - no hidden surprises!

โš–๏ธ DI vs Service Locator

Aspect Service Locator Dependency Injection
Dependencies Hidden in method calls Explicit in constructor
Learning Curve Easy to learn Steeper (especially with frameworks)
Compile-Time Safety Runtime errors Compile-time errors
Testing Need to set up locator Inject mocks directly
Flexibility Very flexible More structured
Recommendation: Start with Service Locator. Migrate to DI as project grows.

๐Ÿ”ง DI Frameworks for Unity

Popular DI Frameworks:

When to Use DI Framework: When tight coupling and hidden dependencies become a problem

๐Ÿ’ป Extenject (Zenject) Example

// Installer - registers services public class GameInstaller : MonoInstaller { public override void InstallBindings() { Container.Bind() .To() .AsSingle(); Container.Bind() .To() .AsSingle(); } } // Usage - constructor injection public class PlayerController : MonoBehaviour { private ILoggerService _logger; private IAnalyticsService _analytics; // Zenject injects dependencies automatically [Inject] public void Construct( ILoggerService logger, IAnalyticsService analytics) { _logger = logger; _analytics = analytics; } }

๐ŸŽฎ Unity Built-In Services

Unity Services (No Manual Implementation Needed):

Still use Service Locator: Wrap Unity services for easier testing and flexibility

๐Ÿšซ Common Mistakes to Avoid

  1. Using Service Locator for Everything:
    • โŒ Enemy, Player, HUD in Service Locator
    • โœ… Only truly global services
  2. Not Handling Missing Services:
    // BAD - crashes if not registered var logger = ServiceLocator.GetService(); // GOOD - handles gracefully var logger = ServiceLocator.TryGetService(); logger?.Log("Message");
  3. Registering Too Late:
    • Services should be registered in Awake() of first scene
    • Use DontDestroyOnLoad() for service manager
  4. No Interface - Using Concrete Classes:
    // BAD - tight coupling ServiceLocator.RegisterService(new Logger()); var logger = ServiceLocator.GetService(); // GOOD - loose coupling ServiceLocator.RegisterService(new Logger()); var logger = ServiceLocator.GetService();

โšก Performance Tips

Optimization Strategies:

๐Ÿ› Debugging Tips

Add Debug Logging:

public static void RegisterService(T service) { string typeName = typeof(T).Name; if (!Services.ContainsKey(typeof(T))) { Services[typeof(T)] = service; Debug.Log($"[ServiceLocator] Registered: {typeName}"); } else { Debug.LogError($"[ServiceLocator] Already registered: {typeName}"); throw new ApplicationException("Service already registered"); } } public static T GetService() { string typeName = typeof(T).Name; if (Services.TryGetValue(typeof(T), out object service)) { Debug.Log($"[ServiceLocator] Retrieved: {typeName}"); return (T)service; } Debug.LogError($"[ServiceLocator] Service not found: {typeName}"); throw new ApplicationException("Requested service not found."); }

List All Registered Services:

public static void LogAllServices() { Debug.Log("=== Registered Services ==="); foreach (var kvp in Services) { Debug.Log($"{kvp.Key.Name}: {kvp.Value.GetType().Name}"); } }

๐ŸŽ“ Design Patterns We've Covered

Creational Patterns:

Structural Patterns:

Behavioral Patterns:

Architectural Patterns:

๐ŸŽฏ Pattern Selection Guide

Problem Pattern
Need exactly one instance Singleton
Creating/destroying is expensive Object Pool
Incompatible interfaces Adapter
Complex subsystem needs simplification Facade
Add features at runtime Decorator
Notify multiple objects of changes Observer
Swap algorithms at runtime Strategy
Operations on object structures Visitor
Global service access Service Locator
Spatial queries (collision, neighbors) Spatial Partition

๐Ÿ”— Combining Patterns

Patterns Work Together!

Example: Audio System

  • Singleton: AudioManager has one instance
  • Object Pool: AudioSources pooled and reused
  • Facade: Simple Play(), Stop(), SetVolume() interface
  • Service Locator: Globally accessible via ServiceLocator.GetService<IAudioService>()

Example: Enemy System

  • Object Pool: Enemy GameObjects reused
  • Strategy: Different AI behaviors
  • Observer: Notify when enemy dies
  • Spatial Partition: Efficient enemy-player collision
Key Insight: Patterns are building blocks - combine them to solve complex problems!

๐Ÿ“ Best Practices

Service Locator Dos:

Service Locator Don'ts:

General Pattern Advice:

๐Ÿš€ Continuing Your Journey

Books to Read:

Online Resources:

Practice Projects:

๐ŸŽฎ Final Project Ideas

Apply What You've Learned:

๐Ÿ“ Lecture Summary

What We Learned Today:

Course Completion:

Remember: Patterns are tools, not rules. Use them when they solve real problems!

Gaming History Moment ๐Ÿ•น๏ธ

Battle Royale & Live Service Games (2017-Present)

In 2017, PlayerUnknown's Battlegrounds (PUBG) popularized the Battle Royale genre, followed by Epic's Fortnite in 2018. These games pioneered the "live service" model - games that continuously evolve with seasons, events, and updates. Behind the scenes, they require an ecosystem of constantly-running services: matchmaking, anti-cheat, analytics, progression tracking, item shops, and social features.

The challenge? Any component in the game might need to access these services: UI needs the shop, gameplay needs analytics, inventory needs progression tracking. Hard-coding dependencies would create a tangled mess. Instead, modern live service games use service locators - centralized registries where any system can find the service it needs without tight coupling. When Epic adds a new seasonal feature, they register it as a service, and existing code can discover it dynamically.

Connection to Service Locator Pattern

Live service games are Service Locator implementations at massive scale! Just like our ServiceLocator provides global access to ILogService, IAdsService, and IAnalyticsService, Fortnite's architecture provides global access to MatchmakingService, ProgressionService, ShopService, etc. Any gameplay code can call ServiceLocator.Get<IMatchmaking>() without knowing how matchmaking works. This loose coupling enables rapid feature iteration - Epic ships updates every week because services are decoupled and discoverable!

Learn More: GDC: Fortnite Architecture Talk | The Architecture of PUBG

๐ŸŽ“ Course Complete!

Congratulations! ๐ŸŽ‰

You've completed Game Programming CSCI 3213!

What You've Accomplished:

  • Built a 2D game engine from scratch with JavaScript
  • Mastered Unity fundamentals and C# programming
  • Implemented 10+ design patterns in game contexts
  • Created production-ready, scalable game systems
  • Learned best practices for game architecture

Your Journey Continues:

  • Keep building games - practice makes perfect
  • Study open-source game projects
  • Participate in game jams
  • Read "Game Programming Patterns" by Robert Nystrom
  • Never stop learning! ๐Ÿ“š

Thank you for an amazing semester!

Questions? Office Hours: Monday/Wednesday 2:00-4:00 PM
SSM 204A - bobby.reed@okcu.edu

๐ŸŽฎ Good luck with your game development journey! ๐Ÿš€