UNPKG

@hiddentao/clockwork-engine

Version:

A TypeScript/PIXI.js game engine for deterministic, replayable games with built-in rendering

184 lines (183 loc) 6.19 kB
import { Spritesheet } from "./Spritesheet"; export var AssetType; (function (AssetType) { AssetType["SPRITESHEET"] = "spritesheet"; AssetType["STATIC_IMAGE"] = "staticImage"; AssetType["SOUND"] = "sound"; })(AssetType || (AssetType = {})); /** * Asset loader for managing game assets with registration and preloading. * Based on patterns from game-base and tiki-kong. * * Games can use directly or extend for custom loading behavior. * * Example usage (direct): * ```typescript * const assetLoader = new AssetLoader(loader, rendering, audio) * assetLoader.register('sprites/player.png', 'spritesheet') // Full path * assetLoader.register('images/logo.png', 'staticImage') * assetLoader.register('sounds/jump.mp3', 'sound') * * await assetLoader.preloadAssets((loaded, total) => { * console.log(`Loading: ${loaded}/${total}`) * }) * * const playerSheet = assetLoader.getSpritesheet('sprites/player.png') * ``` * * Example usage (custom subclass for path conventions): * ```typescript * class MyAssetLoader extends AssetLoader { * async loadSpritesheet(id: string): Promise<Spritesheet> { * // Add custom path prefix * const sheet = await Spritesheet.load( * this.loader, * this.rendering, * `assets/sprites/${id}.png` * ) * this.spritesheets.set(id, sheet) * return sheet * } * } * // Then use: assetLoader.register('player', 'spritesheet') * ``` */ export class AssetLoader { constructor(loader, rendering, audio) { this.loader = loader; this.rendering = rendering; this.audio = audio; this.spritesheets = new Map(); this.staticImages = new Map(); this.sounds = new Set(); // Asset registration this.registeredSpritesheets = []; this.registeredStaticImages = []; this.registeredSounds = []; } /** * Register an asset for preloading. * Call this during game initialization for all required assets. * * @param id - Asset identifier * @param type - Asset type (spritesheet, staticImage, sound) */ register(id, type) { switch (type) { case AssetType.SPRITESHEET: if (!this.registeredSpritesheets.includes(id)) { this.registeredSpritesheets.push(id); } break; case AssetType.STATIC_IMAGE: if (!this.registeredStaticImages.includes(id)) { this.registeredStaticImages.push(id); } break; case AssetType.SOUND: if (!this.registeredSounds.includes(id)) { this.registeredSounds.push(id); } break; } } /** * Preload all registered assets. * Called by GameEngine.reset() before setup(). * * @param onProgress - Optional callback for tracking progress (loaded, total) */ async preloadAssets(onProgress) { const tasks = []; let loaded = 0; const total = this.registeredSpritesheets.length + this.registeredStaticImages.length + this.registeredSounds.length; const trackProgress = () => { loaded++; onProgress?.(loaded, total); }; // Load spritesheets for (const id of this.registeredSpritesheets) { tasks.push(this.loadSpritesheet(id).then(() => { trackProgress(); })); } // Load static images for (const id of this.registeredStaticImages) { tasks.push(this.loadStaticImage(id).then(() => { trackProgress(); })); } // Load sounds for (const id of this.registeredSounds) { tasks.push(this.loadSound(id).then(() => { trackProgress(); })); } await Promise.all(tasks); } /** * Load a spritesheet asset. * Virtual method - games can override in subclass for custom logic. * * Default implementation uses the ID as the file path directly. * Register with full path: `assetLoader.register('sprites/player.png', 'spritesheet')` * * @param id - Asset path (e.g., 'sprites/player.png') * @returns Promise resolving to Spritesheet instance */ async loadSpritesheet(id) { const spritesheet = await Spritesheet.load(this.loader, this.rendering, id); this.spritesheets.set(id, spritesheet); return spritesheet; } /** * Load a static image asset. * Virtual method - games can override in subclass for custom logic. * * Default implementation uses the ID as the file path directly. * Register with full path: `assetLoader.register('images/logo.png', 'staticImage')` * * @param id - Asset path (e.g., 'images/logo.png') * @returns Promise resolving to TextureId */ async loadStaticImage(id) { const imageUrl = await this.loader.fetchData(id); const textureId = await this.rendering.loadTexture(imageUrl); this.staticImages.set(id, textureId); return textureId; } /** * Load a sound asset. * Virtual method - games can override in subclass for custom logic. * * Default implementation uses the ID as the file path directly. * Register with full path: `assetLoader.register('sounds/jump.mp3', 'sound')` * * @param id - Asset path (e.g., 'sounds/jump.mp3') */ async loadSound(id) { const soundData = await this.loader.fetchData(id); await this.audio.loadSound(id, soundData); this.sounds.add(id); } /** * Get a loaded spritesheet by ID. * * @param id - Spritesheet identifier * @returns Spritesheet instance if loaded, undefined otherwise */ getSpritesheet(id) { return this.spritesheets.get(id); } /** * Get a loaded static image by ID. * * @param id - Image identifier * @returns TextureId if loaded, undefined otherwise */ getStaticImage(id) { return this.staticImages.get(id); } }