Popcorning Guinea Pig
Search Results for

    Game Architecture

    The Popcorning Guinea Pig engine follows a clean, modular architecture designed to provide flexibility while maintaining simplicity. Understanding the core architecture will help you build more effective games and extend the engine when needed.

    Overview

    The engine is built around several key architectural principles:

    • Scene-Based Organization: Games are composed of scenes that manage their own state and lifecycle
    • Service-Oriented Design: Core functionality is provided through injectable services
    • Component Composition: UI elements and game objects are built through composition
    • Event-Driven Communication: Loose coupling through event systems and callbacks

    Core Architecture Diagram

    ┌─────────────────────────────────────────────────────────────┐
    │                        Game Class                           │
    │  ┌─────────────────┐    ┌──────────────────────────────────┐│
    │  │   Initialization │    │            Services              ││
    │  │   - Window       │    │  ┌─────────────────────────────┐ ││
    │  │   - Raylib       │    │  │ ISoundPlayer                │ ││
    │  │   - Services     │    │  │ ISettingsService            │ ││
    │  └─────────────────┘    │  │ ILocalizationService        │ ││
    │                         │  │ IDiscordService             │ ││
    │  ┌─────────────────┐    │  │ IAssetRegistry              │ ││
    │  │   Game Loop      │    │  │ IInputService               │ ││
    │  │   - Update       │    │  └─────────────────────────────┘ ││
    │  │   - Render       │    └──────────────────────────────────┘│
    │  │   - Input        │                                        │
    │  └─────────────────┘    ┌──────────────────────────────────┐│
    │                         │         Scene Manager            ││
    │                         │  ┌─────────────────────────────┐ ││
    │                         │  │ Current Scene               │ ││
    │                         │  │ Scene Queue                 │ ││
    │                         │  │ Transition System           │ ││
    │                         │  └─────────────────────────────┘ ││
    │                         └──────────────────────────────────┘│
    └─────────────────────────────────────────────────────────────┘
    

    Game Class

    The Game class is the entry point and orchestrator of your application. It manages the overall lifecycle and coordinates between systems.

    Core Responsibilities

    public abstract class Game
    {
        // Initialization and setup
        public void Init(int width, int height, int fps = 60, string title = "Game Title")
        
        // Resource loading (override in your game)
        public virtual void Load()
        
        // Game loop methods (override in your game)
        public virtual void Update(double deltaTime)
        public abstract void Render()
        
        // Main game loop execution
        public void RunGame()
        
        // Cleanup and shutdown
        protected virtual void CleanupResources()
    }
    

    Implementation Pattern

    public class MyGame : Game
    {
        private SceneManager sceneManager;
        private ISoundPlayer soundPlayer;
        private ISettingsService settingsService;
        
        public override void Load()
        {
            // Initialize services
            soundPlayer = new SoundPlayer();
            settingsService = new SettingsService();
            
            // Initialize scene management
            sceneManager = new SceneManager();
            
            // Load initial scene
            var mainMenu = new MainMenuScene(sceneManager, soundPlayer);
            sceneManager.ChangeScene(mainMenu);
        }
        
        public override void Update(double deltaTime)
        {
            // Update scene system
            sceneManager.Update(deltaTime);
        }
        
        public override void Render()
        {
            // Delegate rendering to scene manager
            sceneManager.Render();
        }
        
        protected override void CleanupResources()
        {
            soundPlayer?.Dispose();
            sceneManager?.UnloadAllScenes();
        }
    }
    

    Scene System Architecture

    Scenes are self-contained units of gameplay or UI that manage their own state and resources.

    Scene Lifecycle

    public abstract class Scene
    {
        // Called when scene becomes active
        public virtual void Load() { }
        
        // Called every frame while active
        public virtual void Update(double deltaTime) { }
        
        // Called every frame for rendering
        public virtual void Render(int width, int height) { }
        
        // Called when scene is deactivated
        public virtual void Unload() { }
    }
    

    Scene Types

    Base Scene (2D)

    • Handles 2D UI elements and rendering
    • Manages user input and interaction
    • Suitable for menus, 2D games, UI screens
    public class MainMenuScene : Scene
    {
        private List<Button> buttons = new();
        private Panel backgroundPanel;
        
        public override void Load()
        {
            // Create UI elements
            var playButton = new Button(/*parameters*/);
            buttons.Add(playButton);
        }
        
        public override void Update(double deltaTime)
        {
            foreach (var button in buttons)
                button.Update();
        }
        
        public override void Render(int width, int height)
        {
            Raylib.ClearBackground(Color.DARKBLUE);
            
            backgroundPanel?.Draw();
            foreach (var button in buttons)
                button.Draw();
        }
    }
    

    3D Scene

    • Extends base Scene with 3D capabilities
    • Manages 3D cameras and models
    • Handles 3D UI elements and interactions
    public class GameplayScene : Scene3D
    {
        public override void Load()
        {
            // Set up 3D camera
            var camera = new Camera3D
            {
                position = new Vector3(0, 10, 10),
                target = new Vector3(0, 0, 0),
                up = new Vector3(0, 1, 0),
                fovy = 45.0f,
                projection = CameraProjection.CAMERA_PERSPECTIVE
            };
            SetMainCamera(camera);
            
            // Load 3D models
            var worldModel = Raylib.LoadModel("assets/world.obj");
            SetWorldModel(worldModel);
        }
        
        public override void Render(int width, int height)
        {
            // 3D rendering is handled automatically
            // You can add custom 3D rendering here
            base.Render(width, height);
        }
    }
    

    Service Architecture

    Services provide reusable functionality across the application and implement dependency injection patterns.

    Service Interface Pattern

    public interface ISoundPlayer
    {
        Task PlaySound(string soundPath, float volume = 1.0f);
        Task PlayMenuClick();
        Task StartGameBackgroundMusic();
        Task StopGameBackgroundMusic();
        void Dispose();
    }
    
    public class SoundPlayer : ISoundPlayer
    {
        // Implementation details...
        
        public async Task PlaySound(string soundPath, float volume = 1.0f)
        {
            // Play sound using Raylib
        }
        
        // Other interface implementations...
    }
    

    Service Injection

    public class GameScene : Scene
    {
        private readonly ISoundPlayer soundPlayer;
        private readonly ISettingsService settingsService;
        
        public GameScene(ISoundPlayer soundPlayer, ISettingsService settingsService)
        {
            this.soundPlayer = soundPlayer;
            this.settingsService = settingsService;
        }
        
        private async void OnButtonClick()
        {
            await soundPlayer.PlayMenuClick();
            // Handle button logic
        }
    }
    

    Component Composition

    UI elements and game objects use composition for flexibility and reusability.

    UI Component Hierarchy

    // Base UI element interface
    public interface IUIElement
    {
        Vector2 GetWorldPosition();
        Vector2 GetLocalPosition();
        void Update();
        void Draw();
        
        // Hierarchical relationships
        void AddChild(IUIElement child);
        void RemoveChild(IUIElement child);
        void RemoveFromParent();
    }
    
    // Base implementation with hierarchy support
    public abstract class UIElementBase : IUIElement
    {
        protected IUIElement parent;
        protected List<IUIElement> children = new();
        protected Vector2 localPosition;
        
        public virtual Vector2 GetWorldPosition()
        {
            if (parent != null)
                return parent.GetWorldPosition() + localPosition;
            return localPosition;
        }
        
        public virtual void Update()
        {
            UpdateSelf();
            foreach (var child in children)
                child.Update();
        }
        
        public virtual void Draw()
        {
            DrawSelf();
            foreach (var child in children)
                child.Draw();
        }
        
        protected virtual void UpdateSelf() { }
        protected abstract void DrawSelf();
    }
    

    Composition Example

    public class GameUI : UIElementBase
    {
        public GameUI()
        {
            // Create health bar
            var healthBar = new Panel(/*parameters*/);
            var healthFill = new Panel(/*parameters*/);
            healthBar.AddChild(healthFill);
            AddChild(healthBar);
            
            // Create inventory panel
            var inventory = new GridContainer(/*parameters*/);
            for (int i = 0; i < 10; i++)
            {
                var slot = new Button(/*parameters*/);
                inventory.AddChild(slot);
            }
            AddChild(inventory);
        }
        
        protected override void DrawSelf()
        {
            // Draw background or container-specific elements
        }
    }
    

    Event System

    The engine uses callbacks and events for loose coupling between components.

    Event Patterns

    Button Events

    var button = new Button(position, size, "Click Me");
    button.SetOnClick(() => {
        // Handle click
        Console.WriteLine("Button clicked!");
    });
    
    button.SetOnHover(() => {
        // Handle hover
        button.SetColors(Color.LIGHTBLUE, Color.BLUE, Color.DARKBLUE);
    });
    

    Custom Events

    public class GameEvents
    {
        public static event Action<int> OnScoreChanged;
        public static event Action<Player> OnPlayerDeath;
        
        public static void TriggerScoreChange(int newScore)
        {
            OnScoreChanged?.Invoke(newScore);
        }
    }
    
    // Usage
    GameEvents.OnScoreChanged += (score) => {
        scoreLabel.SetText($"Score: {score}");
    };
    

    Resource Management

    The engine provides structured resource management to prevent memory leaks and optimize performance.

    Scene Resource Management

    public class ResourceManagedScene : Scene
    {
        private List<Texture2D> textures = new();
        private List<Sound> sounds = new();
        private List<IDisposable> disposables = new();
        
        public override void Load()
        {
            // Load resources and track them
            var backgroundTexture = Raylib.LoadTexture("background.png");
            textures.Add(backgroundTexture);
            
            var clickSound = Raylib.LoadSound("click.wav");
            sounds.Add(clickSound);
            
            var soundPlayer = new SoundPlayer();
            disposables.Add(soundPlayer);
        }
        
        public override void Unload()
        {
            // Clean up all resources
            foreach (var texture in textures)
                Raylib.UnloadTexture(texture);
            textures.Clear();
            
            foreach (var sound in sounds)
                Raylib.UnloadSound(sound);
            sounds.Clear();
            
            foreach (var disposable in disposables)
                disposable.Dispose();
            disposables.Clear();
        }
    }
    

    Asset Registry Pattern

    public class AssetRegistry : IAssetRegistry
    {
        private Dictionary<string, Texture2D> textures = new();
        private Dictionary<string, Font> fonts = new();
        
        public void RegisterAsset(string id, string path, AssetType type)
        {
            switch (type)
            {
                case AssetType.Texture:
                    if (!textures.ContainsKey(id))
                        textures[id] = Raylib.LoadTexture(path);
                    break;
                case AssetType.Font:
                    if (!fonts.ContainsKey(id))
                        fonts[id] = Raylib.LoadFont(path);
                    break;
            }
        }
        
        public T GetAsset<T>(string id) where T : struct
        {
            if (typeof(T) == typeof(Texture2D) && textures.ContainsKey(id))
                return (T)(object)textures[id];
            // Handle other types...
            
            return default(T);
        }
    }
    

    Threading Considerations

    The engine is designed to run on the main thread, but provides patterns for background processing.

    Async Service Operations

    public class NetworkService
    {
        public async Task<PlayerData> LoadPlayerDataAsync(string playerId)
        {
            // Perform network operation on background thread
            var data = await httpClient.GetStringAsync($"/players/{playerId}");
            
            // Return to main thread for UI updates
            return JsonSerializer.Deserialize<PlayerData>(data);
        }
    }
    
    // Usage in scene
    public class LoadingScene : Scene
    {
        private async void LoadPlayerData()
        {
            try
            {
                var playerData = await networkService.LoadPlayerDataAsync(playerId);
                
                // Update UI on main thread
                playerNameLabel.SetText(playerData.Name);
                playerLevelLabel.SetText($"Level {playerData.Level}");
            }
            catch (Exception ex)
            {
                errorLabel.SetText($"Failed to load: {ex.Message}");
            }
        }
    }
    

    Performance Considerations

    Scene Update Optimization

    public class OptimizedScene : Scene
    {
        private List<IUpdatable> activeElements = new();
        private List<IUpdatable> staticElements = new();
        private bool needsStaticUpdate = false;
        
        public override void Update(double deltaTime)
        {
            // Always update active elements
            foreach (var element in activeElements)
                element.Update(deltaTime);
            
            // Update static elements only when needed
            if (needsStaticUpdate)
            {
                foreach (var element in staticElements)
                    element.Update(deltaTime);
                needsStaticUpdate = false;
            }
        }
        
        public void MarkStaticNeedsUpdate()
        {
            needsStaticUpdate = true;
        }
    }
    

    Memory Management

    public class MemoryEfficientScene : Scene
    {
        private ObjectPool<Particle> particlePool = new();
        
        public void CreateExplosion(Vector2 position)
        {
            // Reuse particles instead of creating new ones
            for (int i = 0; i < 10; i++)
            {
                var particle = particlePool.Get();
                particle.Initialize(position);
                // Use particle...
            }
        }
        
        public void ReturnParticle(Particle particle)
        {
            particlePool.Return(particle);
        }
    }
    

    Best Practices

    1. Scene Organization

    • Keep scenes focused on specific functionality
    • Use composition over inheritance for complex UI
    • Implement proper resource cleanup in Unload()

    2. Service Design

    • Use interfaces for all services
    • Implement dependency injection patterns
    • Keep services stateless when possible

    3. Performance

    • Cache frequently accessed objects
    • Use object pooling for temporary objects
    • Minimize allocations in Update loops

    4. Architecture

    • Follow single responsibility principle
    • Use events for loose coupling
    • Implement proper error handling

    Architecture Extensions

    Custom Scene Types

    public abstract class NetworkScene : Scene
    {
        protected NetworkService networkService;
        
        protected NetworkScene(NetworkService networkService)
        {
            this.networkService = networkService;
        }
        
        protected virtual async Task OnNetworkEvent(NetworkMessage message)
        {
            // Handle network events
        }
    }
    

    Custom Services

    public interface IAnalyticsService
    {
        void TrackEvent(string eventName, Dictionary<string, object> parameters);
        Task FlushEventsAsync();
    }
    
    public class AnalyticsService : IAnalyticsService
    {
        // Implementation...
    }
    

    Common Patterns

    Singleton Pattern (Use Sparingly)

    public class GameState
    {
        private static GameState instance;
        public static GameState Instance => instance ??= new GameState();
        
        public int Score { get; set; }
        public Player CurrentPlayer { get; set; }
    }
    

    Observer Pattern

    public class HealthSystem
    {
        public event Action<int> OnHealthChanged;
        private int health;
        
        public int Health
        {
            get => health;
            set
            {
                if (health != value)
                {
                    health = value;
                    OnHealthChanged?.Invoke(health);
                }
            }
        }
    }
    

    Further Reading

    • Scene Management - Detailed scene system documentation
    • Services - Complete service implementation guide
    • UI System - UI architecture and components
    • Steam Integration - Platform service integration

    This architecture provides a solid foundation for building games while maintaining flexibility for different project needs. The modular design allows you to use only the parts you need while providing room for extension and customization.

    • Edit this page
    In this article
    Back to top © 2024 Popcorning Guinea Pig Game Engine. A custom C# game engine for indie developers.