const ProgressBar=require('progress'); const assetSpecs=[ { name: 'fruit', filename: 'apple32.png', type: 'image' }, { name: 'superFruit', filename: 'peach-rainbow-anim.png', type: 'image' }, { name: 'decayFruit', filename: 'peach-decay-anim.png', type: 'image' }, { name: 'wall', filename: 'wall32.png', type: 'image' }, { name: 'flammable', filename: 'oil32.png', type: 'image' }, { name: 'hole', filename: 'hole-ts.png', type: 'image' }, { name: 'fire', filename: 'fire-anim.png', type: 'image' }, { name: 'portalA', filename: 'portal-a-anim.png', type: 'image' }, { name: 'portalB', filename: 'portal-b-anim.png', type: 'image' }, { name: 'portalC', filename: 'portal-c-anim.png', type: 'image' }, { name: 'portalD', filename: 'portal-d-anim.png', type: 'image' }, { name: 'key', filename: 'key32.png', type: 'image' }, { name: 'door', filename: 'door32.png', type: 'image' }, { name: 'snake', filename: 'snake.json', type: 'json' }, { name: 'levelList', filename: 'levelList.json', type: 'json' }, { name: 'config', filename: 'config.json', type: 'json' }, { name: 'metaConfig', filename: 'metaConfig.json', type: 'json' } ]; const tasks=[ { from: 'hole', type: 'tileset', steps: 1, tiles: ['base', 'ul', 'dr', 'dl', 'ur', 'l', 'r', 'd', 'u'] }, { from: 'fire', type: 'animation', steps: 3 }, { from: 'portalA', type: 'animation', steps: 3 }, { from: 'portalB', type: 'animation', steps: 3 }, { from: 'portalC', type: 'animation', steps: 3 }, { from: 'portalD', type: 'animation', steps: 3 }, { from: 'superFruit', type: 'animation', steps: 5 }, { from: 'decayFruit', type: 'animation', steps: 5 } ]; const cvs=document.createElement('canvas'); cvs.width=400; cvs.height=50; cvs.classList.add('progressBar'); cvs.classList.add('hiddenBottom'); const bar=new ProgressBar(assetSpecs.length*2+tasks.reduce((a, t) => a+t.steps, 0)); bar.addUpdateListener(() => bar.draw(cvs, '#fba49b', '#930a16')); bar.draw(cvs, '#fba49b', '#930a16'); document.body.appendChild(cvs); setTimeout(() => cvs.classList.remove('hiddenBottom'), 0); bar.addReadyListener(() => { cvs.classList.add('hiddenBottom'); setTimeout(() => document.body.removeChild(cvs), 1000); }); //XXX purposefully slow down asset loading const sleep=(ms=500) => new Promise(ok => setTimeout(ok, ms*Math.random())); const loadAsset=async (asset) => { const response=await fetch('assets/'+asset.filename); await sleep(); bar.update(); let result; switch(asset.type) { case 'json': result=await response.json(); break; case 'image': result=await createImageBitmap(await response.blob()); break; } await sleep(); bar.update(); return [asset.name, result]; }; let assets=Object.create(null); let ready=false; let readyListeners=[]; (async () => { let arr=await Promise .all( assetSpecs.map(a => loadAsset(a)) ); arr.forEach(([name, value]) => { assets[name]=value; }); for(let task of tasks) { const source=assets[task.from]; switch(task.type) { case 'tileset': { let asset=assets[task.from]=Object.create(null); for(let tId in task.tiles) { const tName=task.tiles[tId]; asset[tName]=await createImageBitmap(source, 0, source.width*tId, source.width, source.width); if(tId%(task.tiles.length/task.steps)==0) { await sleep(100); bar.update(); } } break; } case 'animation': { let anim=assets[task.from]=[]; let frameCount=source.height/source.width; for(let i=0; i fn.bind(fn)()); readyListeners=null; })(); const onReady=(fn) => { if(ready) fn.bind(fn)(); else readyListeners.push(fn); }; const get=(name) => { let asset=assets[name]; if(!asset) throw new Error("Unknown asset: "+name); return asset; }; return { onReady, get };