Popcorning Guinea Pig
Search Results for

    Asset Registry

    The Popcorning Guinea Pig engine provides a centralized asset management system through the IAssetRegistry, enabling efficient loading, caching, and management of game assets.

    Overview

    The Asset Registry allows you to:

    • Load and manage textures, sounds, fonts, and other assets
    • Cache assets to improve performance
    • Organize assets with logical naming
    • Support multiple file formats
    • Handle asset dependencies
    • Implement asset hot-reloading during development
    • Manage memory usage with asset disposal

    IAssetRegistry Interface

    public interface IAssetRegistry
    {
        T LoadAsset<T>(string assetPath) where T : class;
        T GetAsset<T>(string assetPath) where T : class;
        bool IsAssetLoaded(string assetPath);
        void UnloadAsset(string assetPath);
        void UnloadAllAssets();
        void PreloadAssets(string[] assetPaths);
        string[] GetLoadedAssets();
        void SetAssetSearchPath(string path);
        void RegisterAssetLoader<T>(IAssetLoader<T> loader) where T : class;
    }
    

    Asset Types

    Supported Asset Types

    The engine supports various asset types out of the box:

    // Textures
    var texture = _assetRegistry.LoadAsset<Texture2D>("textures/player.png");
    var spriteSheet = _assetRegistry.LoadAsset<Texture2D>("sprites/animations.png");
    
    // Audio
    var music = _assetRegistry.LoadAsset<AudioClip>("audio/background_music.ogg");
    var soundEffect = _assetRegistry.LoadAsset<AudioClip>("audio/explosion.wav");
    
    // Fonts
    var font = _assetRegistry.LoadAsset<Font>("fonts/arial.ttf");
    var customFont = _assetRegistry.LoadAsset<Font>("fonts/game_font.otf");
    
    // Data files
    var gameData = _assetRegistry.LoadAsset<GameData>("data/levels.json");
    var config = _assetRegistry.LoadAsset<Configuration>("config/settings.xml");
    
    // Scenes
    var scene = _assetRegistry.LoadAsset<SceneData>("scenes/level1.pgpscn");
    

    Getting Started

    Initialize Asset Registry

    public class MyGame : Game
    {
        private readonly IAssetRegistry _assetRegistry;
        
        public MyGame()
        {
            _assetRegistry = GetService<IAssetRegistry>();
            
            // Set the base path for assets
            _assetRegistry.SetAssetSearchPath("assets/");
            
            // Preload critical assets
            PreloadCriticalAssets();
        }
        
        private void PreloadCriticalAssets()
        {
            var criticalAssets = new[]
            {
                "textures/ui/button.png",
                "textures/ui/background.png",
                "audio/ui/click.wav",
                "fonts/main_font.ttf"
            };
            
            _assetRegistry.PreloadAssets(criticalAssets);
        }
    }
    

    Loading Assets

    public class PlayerSprite
    {
        private readonly IAssetRegistry _assetRegistry;
        private Texture2D _playerTexture;
        
        public PlayerSprite()
        {
            _assetRegistry = GetService<IAssetRegistry>();
            LoadAssets();
        }
        
        private void LoadAssets()
        {
            // Load player texture
            _playerTexture = _assetRegistry.LoadAsset<Texture2D>("textures/player.png");
            
            // Load animation frames
            var walkAnimation = _assetRegistry.LoadAsset<Texture2D>("animations/player_walk.png");
            var jumpAnimation = _assetRegistry.LoadAsset<Texture2D>("animations/player_jump.png");
        }
    }
    

    Asset Organization

    Directory Structure

    Organize your assets with a clear directory structure:

    assets/
    ├── textures/
    │   ├── ui/              # User interface textures
    │   ├── characters/      # Character sprites and animations
    │   ├── environments/    # Background and environment art
    │   ├── effects/         # Visual effects and particles
    │   └── icons/           # Game icons and symbols
    ├── audio/
    │   ├── music/           # Background music tracks
    │   ├── sfx/             # Sound effects
    │   └── voice/           # Voice acting and dialogue
    ├── fonts/
    │   ├── ui/              # UI fonts
    │   └── display/         # Display and title fonts
    ├── data/
    │   ├── levels/          # Level data files
    │   ├── configs/         # Configuration files
    │   └── localization/    # Translation files
    └── scenes/
        ├── menus/           # Menu scenes
        └── gameplay/        # Gameplay scenes
    

    Naming Conventions

    Use consistent naming conventions for your assets:

    // Good naming examples
    _assetRegistry.LoadAsset<Texture2D>("textures/characters/player_idle.png");
    _assetRegistry.LoadAsset<Texture2D>("textures/ui/button_play_normal.png");
    _assetRegistry.LoadAsset<AudioClip>("audio/sfx/explosion_large.ogg");
    
    // Asset variants
    var buttonNormal = _assetRegistry.LoadAsset<Texture2D>("textures/ui/button_normal.png");
    var buttonHover = _assetRegistry.LoadAsset<Texture2D>("textures/ui/button_hover.png");
    var buttonPressed = _assetRegistry.LoadAsset<Texture2D>("textures/ui/button_pressed.png");
    

    Advanced Features

    Asset Caching

    The Asset Registry automatically caches loaded assets:

    public class AssetCacheExample
    {
        private readonly IAssetRegistry _assetRegistry;
        
        public void DemonstrateCache()
        {
            // First load - loads from disk and caches
            var texture1 = _assetRegistry.LoadAsset<Texture2D>("textures/player.png");
            
            // Second load - returns cached asset (fast)
            var texture2 = _assetRegistry.LoadAsset<Texture2D>("textures/player.png");
            
            // texture1 and texture2 reference the same object
            Debug.Assert(ReferenceEquals(texture1, texture2));
            
            // Check if asset is already loaded
            if (_assetRegistry.IsAssetLoaded("textures/player.png"))
            {
                var cachedTexture = _assetRegistry.GetAsset<Texture2D>("textures/player.png");
            }
        }
    }
    

    Custom Asset Loaders

    Create custom loaders for specialized asset types:

    // Define a custom asset type
    public class LevelData
    {
        public string Name { get; set; }
        public Vector2 PlayerStartPosition { get; set; }
        public List<EnemySpawn> Enemies { get; set; }
        public List<ItemSpawn> Items { get; set; }
    }
    
    // Implement a custom asset loader
    public class LevelDataLoader : IAssetLoader<LevelData>
    {
        public LevelData LoadAsset(string path)
        {
            var jsonContent = File.ReadAllText(path);
            return JsonSerializer.Deserialize<LevelData>(jsonContent);
        }
        
        public bool CanLoad(string path)
        {
            return Path.GetExtension(path).ToLower() == ".level";
        }
    }
    
    // Register the custom loader
    public class MyGame : Game
    {
        public MyGame()
        {
            var assetRegistry = GetService<IAssetRegistry>();
            assetRegistry.RegisterAssetLoader(new LevelDataLoader());
            
            // Now you can load custom asset types
            var level1 = assetRegistry.LoadAsset<LevelData>("levels/level1.level");
        }
    }
    

    Asset Dependencies

    Handle complex assets with dependencies:

    public class MaterialAsset
    {
        public Texture2D DiffuseTexture { get; set; }
        public Texture2D NormalTexture { get; set; }
        public Texture2D SpecularTexture { get; set; }
        public Color Tint { get; set; }
    }
    
    public class MaterialLoader : IAssetLoader<MaterialAsset>
    {
        private readonly IAssetRegistry _assetRegistry;
        
        public MaterialLoader(IAssetRegistry assetRegistry)
        {
            _assetRegistry = assetRegistry;
        }
        
        public MaterialAsset LoadAsset(string path)
        {
            var materialData = LoadMaterialData(path);
            
            return new MaterialAsset
            {
                DiffuseTexture = _assetRegistry.LoadAsset<Texture2D>(materialData.DiffusePath),
                NormalTexture = _assetRegistry.LoadAsset<Texture2D>(materialData.NormalPath),
                SpecularTexture = _assetRegistry.LoadAsset<Texture2D>(materialData.SpecularPath),
                Tint = materialData.Tint
            };
        }
    }
    

    Asset Hot-Reloading

    Enable hot-reloading for development:

    public class DevelopmentAssetRegistry : IAssetRegistry
    {
        private readonly FileSystemWatcher _watcher;
        private readonly Dictionary<string, object> _loadedAssets;
        
        public DevelopmentAssetRegistry()
        {
            _loadedAssets = new Dictionary<string, object>();
            
            if (IsDevelopmentMode())
            {
                SetupFileWatcher();
            }
        }
        
        private void SetupFileWatcher()
        {
            _watcher = new FileSystemWatcher("assets/")
            {
                IncludeSubdirectories = true,
                NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName
            };
            
            _watcher.Changed += OnAssetChanged;
            _watcher.Created += OnAssetChanged;
            _watcher.EnableRaisingEvents = true;
        }
        
        private void OnAssetChanged(object sender, FileSystemEventArgs e)
        {
            var assetPath = Path.GetRelativePath("assets/", e.FullPath);
            
            if (_loadedAssets.ContainsKey(assetPath))
            {
                // Reload the asset
                _loadedAssets.Remove(assetPath);
                LoadAsset<object>(assetPath); // Reload based on original type
                
                // Notify subscribers
                AssetReloaded?.Invoke(assetPath);
            }
        }
        
        public event Action<string> AssetReloaded;
    }
    

    Memory Management

    Asset Disposal

    Properly manage memory by unloading unused assets:

    public class LevelManager
    {
        private readonly IAssetRegistry _assetRegistry;
        private readonly List<string> _currentLevelAssets;
        
        public void LoadLevel(string levelName)
        {
            // Unload previous level assets
            UnloadCurrentLevelAssets();
            
            // Load new level assets
            _currentLevelAssets.Clear();
            LoadLevelAssets(levelName);
        }
        
        private void UnloadCurrentLevelAssets()
        {
            foreach (var assetPath in _currentLevelAssets)
            {
                _assetRegistry.UnloadAsset(assetPath);
            }
        }
        
        private void LoadLevelAssets(string levelName)
        {
            var levelAssets = GetRequiredAssetsForLevel(levelName);
            
            foreach (var assetPath in levelAssets)
            {
                _assetRegistry.LoadAsset<object>(assetPath);
                _currentLevelAssets.Add(assetPath);
            }
        }
    }
    

    Asset Streaming

    Implement asset streaming for large games:

    public class StreamingAssetManager
    {
        private readonly IAssetRegistry _assetRegistry;
        private readonly Queue<string> _streamingQueue;
        private readonly int _maxConcurrentLoads;
        
        public StreamingAssetManager(IAssetRegistry assetRegistry)
        {
            _assetRegistry = assetRegistry;
            _streamingQueue = new Queue<string>();
            _maxConcurrentLoads = 3;
        }
        
        public void RequestAsset(string assetPath, Action<object> onLoaded)
        {
            if (_assetRegistry.IsAssetLoaded(assetPath))
            {
                onLoaded?.Invoke(_assetRegistry.GetAsset<object>(assetPath));
                return;
            }
            
            _streamingQueue.Enqueue(assetPath);
            ProcessStreamingQueue();
        }
        
        private async void ProcessStreamingQueue()
        {
            var activeTasks = new List<Task>();
            
            while (_streamingQueue.Count > 0 && activeTasks.Count < _maxConcurrentLoads)
            {
                var assetPath = _streamingQueue.Dequeue();
                var task = Task.Run(() => LoadAssetAsync(assetPath));
                activeTasks.Add(task);
            }
            
            await Task.WhenAll(activeTasks);
        }
        
        private async Task LoadAssetAsync(string assetPath)
        {
            // Perform asset loading on background thread
            await Task.Run(() =>
            {
                _assetRegistry.LoadAsset<object>(assetPath);
            });
        }
    }
    

    Performance Optimization

    Asset Bundling

    Group related assets for efficient loading:

    public class AssetBundle
    {
        public string Name { get; set; }
        public List<string> Assets { get; set; }
    }
    
    public class AssetBundleManager
    {
        private readonly IAssetRegistry _assetRegistry;
        
        public void LoadBundle(AssetBundle bundle)
        {
            // Load all assets in the bundle
            _assetRegistry.PreloadAssets(bundle.Assets.ToArray());
        }
        
        public void UnloadBundle(AssetBundle bundle)
        {
            // Unload all assets in the bundle
            foreach (var assetPath in bundle.Assets)
            {
                _assetRegistry.UnloadAsset(assetPath);
            }
        }
    }
    
    // Usage
    var uiBundle = new AssetBundle
    {
        Name = "UI",
        Assets = new List<string>
        {
            "textures/ui/button.png",
            "textures/ui/panel.png",
            "textures/ui/slider.png",
            "audio/ui/click.wav"
        }
    };
    
    bundleManager.LoadBundle(uiBundle);
    

    Asset Variants

    Handle different quality levels and platforms:

    public class VariantAssetRegistry : IAssetRegistry
    {
        private readonly string _platform;
        private readonly QualityLevel _quality;
        
        public VariantAssetRegistry()
        {
            _platform = DetectPlatform();
            _quality = GetQualitySettings();
        }
        
        public T LoadAsset<T>(string assetPath) where T : class
        {
            // Try platform-specific version first
            var platformPath = GetPlatformVariant(assetPath);
            if (File.Exists(platformPath))
            {
                return LoadAssetInternal<T>(platformPath);
            }
            
            // Try quality-specific version
            var qualityPath = GetQualityVariant(assetPath);
            if (File.Exists(qualityPath))
            {
                return LoadAssetInternal<T>(qualityPath);
            }
            
            // Fall back to original asset
            return LoadAssetInternal<T>(assetPath);
        }
        
        private string GetPlatformVariant(string assetPath)
        {
            var directory = Path.GetDirectoryName(assetPath);
            var fileName = Path.GetFileNameWithoutExtension(assetPath);
            var extension = Path.GetExtension(assetPath);
            
            return Path.Combine(directory, $"{fileName}_{_platform}{extension}");
        }
        
        private string GetQualityVariant(string assetPath)
        {
            var directory = Path.GetDirectoryName(assetPath);
            var fileName = Path.GetFileNameWithoutExtension(assetPath);
            var extension = Path.GetExtension(assetPath);
            
            return Path.Combine(directory, $"{fileName}_{_quality.ToString().ToLower()}{extension}");
        }
    }
    

    Best Practices

    Asset Organization

    1. Consistent structure: Use a clear, consistent directory hierarchy
    2. Meaningful names: Use descriptive names that indicate content and purpose
    3. Size variants: Include multiple resolutions for different screen sizes
    4. Format optimization: Choose appropriate formats for each asset type

    Performance

    1. Preload critical assets: Load essential assets at startup
    2. Lazy loading: Load non-critical assets on-demand
    3. Memory management: Unload unused assets to free memory
    4. Asset streaming: Stream large assets for better performance

    Development

    1. Hot-reloading: Enable asset hot-reloading during development
    2. Asset validation: Validate asset integrity and dependencies
    3. Error handling: Handle missing or corrupted assets gracefully
    4. Debugging tools: Provide tools to inspect loaded assets

    Example: Complete Asset Management

    public class GameAssetManager
    {
        private readonly IAssetRegistry _assetRegistry;
        private readonly Dictionary<string, List<string>> _sceneBundles;
        
        public GameAssetManager()
        {
            _assetRegistry = GetService<IAssetRegistry>();
            _sceneBundles = new Dictionary<string, List<string>>();
            
            InitializeAssetBundles();
            PreloadCommonAssets();
        }
        
        private void InitializeAssetBundles()
        {
            _sceneBundles["MainMenu"] = new List<string>
            {
                "textures/ui/menu_background.png",
                "textures/ui/logo.png",
                "audio/music/menu_theme.ogg",
                "fonts/menu_font.ttf"
            };
            
            _sceneBundles["Gameplay"] = new List<string>
            {
                "textures/characters/player.png",
                "textures/environments/level1_bg.png",
                "audio/sfx/jump.wav",
                "audio/sfx/collect.wav"
            };
        }
        
        private void PreloadCommonAssets()
        {
            var commonAssets = new[]
            {
                "textures/ui/pixel.png",
                "fonts/default_font.ttf",
                "audio/ui/click.wav"
            };
            
            _assetRegistry.PreloadAssets(commonAssets);
        }
        
        public void LoadSceneAssets(string sceneName)
        {
            if (_sceneBundles.ContainsKey(sceneName))
            {
                var assets = _sceneBundles[sceneName].ToArray();
                _assetRegistry.PreloadAssets(assets);
            }
        }
        
        public void UnloadSceneAssets(string sceneName)
        {
            if (_sceneBundles.ContainsKey(sceneName))
            {
                foreach (var assetPath in _sceneBundles[sceneName])
                {
                    _assetRegistry.UnloadAsset(assetPath);
                }
            }
        }
        
        public T GetAsset<T>(string assetPath) where T : class
        {
            return _assetRegistry.GetAsset<T>(assetPath);
        }
    }
    

    The Asset Registry provides a powerful and flexible system for managing all your game assets efficiently and effectively.

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