You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
210 lines
4.2 KiB
210 lines
4.2 KiB
/** |
|
* @fileoverview Canvas controller for scene management. |
|
* Handles sprites, scene objects, rendering, and game loop. |
|
*/ |
|
|
|
import { |
|
canvas, |
|
ctx, |
|
createSprite, |
|
drawSprite, |
|
drawInstance, |
|
drawGround, |
|
drawShadow, |
|
clearCanvas |
|
} from './canvas.js'; |
|
|
|
/** @type {Function|null} Callback to invoke on render */ |
|
let onRenderCallback = null; |
|
|
|
/** |
|
* Collection of sprite templates (shared image/frame data) |
|
* @type {Object.<string, import('./canvas.js').Sprite>} |
|
*/ |
|
export const sprites = { |
|
stand: createSprite({ source: 'stand.png' }), |
|
cup: createSprite({ |
|
source: 'cups.png', |
|
frameWidth: 251, |
|
frameHeight: 330, |
|
frameCount: 2 |
|
}), |
|
maker: createSprite({ |
|
source: 'maker.png', |
|
frameWidth: 562 / 2, |
|
frameHeight: 432, |
|
frameCount: 2 |
|
}), |
|
ice: createSprite({ |
|
source: 'ice.png' |
|
}), |
|
pitcher: createSprite({ |
|
source: 'pitcher_full.png' |
|
}), |
|
lemons: createSprite({ |
|
source: 'lemons.png' |
|
}), |
|
grass: createSprite({ |
|
source: 'grass.png' |
|
}), |
|
tree: createSprite({ |
|
source: 'tree.png' |
|
}), |
|
slide: createSprite({ |
|
source: 'slide.png' |
|
}), |
|
seesaw: createSprite({ |
|
source: 'seesaw.png' |
|
}), |
|
customer1: createSprite({ |
|
source: 'customer_1.png' |
|
}), |
|
customer2: createSprite({ |
|
source: 'customer_2.png' |
|
}), |
|
customer3: createSprite({ |
|
source: 'customer_3.png' |
|
}), |
|
customer4: createSprite({ |
|
source: 'customer_4.png' |
|
}), |
|
customer5: createSprite({ |
|
source: 'customer_5.png' |
|
}), |
|
customer6: createSprite({ |
|
source: 'customer_6.png' |
|
}), |
|
customer7: createSprite({ |
|
source: 'customer_7.png' |
|
}), |
|
customer8: createSprite({ |
|
source: 'customer_8.png' |
|
}), |
|
customer9: createSprite({ |
|
source: 'customer_9.png' |
|
}), |
|
customer10: createSprite({ |
|
source: 'customer_10.png' |
|
}), |
|
customer11: createSprite({ |
|
source: 'customer_11.png' |
|
}), |
|
customer12: createSprite({ |
|
source: 'customer_12.png' |
|
}), |
|
customer13: createSprite({ |
|
source: 'customer_13.png' |
|
}), |
|
customer14: createSprite({ |
|
source: 'customer_14.png' |
|
}), |
|
customer15: createSprite({ |
|
source: 'customer_15.png' |
|
}) |
|
}; |
|
|
|
/** |
|
* Checks if all sprites are loaded. |
|
* @returns {boolean} |
|
*/ |
|
export function allSpritesReady() { |
|
return Object.values(sprites).every(sprite => sprite.ready); |
|
} |
|
|
|
/** |
|
* Waits for all sprites to load, then calls the callback. |
|
* @param {Function} callback - Function to call when all sprites are ready |
|
*/ |
|
export function whenSpritesReady(callback) { |
|
function check() { |
|
if (allSpritesReady()) { |
|
callback(); |
|
} else { |
|
requestAnimationFrame(check); |
|
} |
|
} |
|
check(); |
|
} |
|
|
|
/** |
|
* Creates a cup instance. |
|
* |
|
* @param {number} x - X position |
|
* @param {number} y - Y position |
|
* @param {number} [scale=0.1] - Render scale |
|
* @returns {import('./canvas.js').Cup} |
|
*/ |
|
export function createCup(x, y, scale = 0.1) { |
|
return { |
|
sprite: sprites.cup, |
|
x, |
|
y, |
|
scale, |
|
frameIndex: 0, |
|
filled: false, |
|
|
|
/** Fills the cup */ |
|
fill() { |
|
this.filled = true; |
|
this.frameIndex = 1; |
|
}, |
|
|
|
/** Empties the cup */ |
|
empty() { |
|
this.filled = false; |
|
this.frameIndex = 0; |
|
}, |
|
|
|
/** @returns {boolean} Whether the cup is filled */ |
|
isFilled() { |
|
return this.filled; |
|
} |
|
}; |
|
} |
|
|
|
/** |
|
* Game objects - cups on the stand |
|
* @type {Array<import('./canvas.js').Cup>} |
|
*/ |
|
export const cups = [ |
|
createCup(455, 325, 0.15), |
|
createCup(495, 325, 0.15), |
|
createCup(535, 325, 0.15), |
|
createCup(575, 325, 0.15), |
|
]; |
|
|
|
/** |
|
* Renders the entire scene. |
|
*/ |
|
export function render() { |
|
clearCanvas(); |
|
|
|
// Background |
|
drawGround(sprites.grass); |
|
|
|
// Background trees |
|
drawSprite(sprites.tree, 600, 50, 0.25); |
|
drawSprite(sprites.tree, 200, 50, 0.2); |
|
|
|
// Playground equipment |
|
drawSprite(sprites.slide, 880, 80, 0.4); |
|
drawSprite(sprites.seesaw, 200, 280, 0.30); |
|
|
|
// Shadow under the stand |
|
drawShadow(500, 360, 180, 50); |
|
|
|
// Lemonade stand |
|
drawSprite(sprites.maker, 430, 160, 0.55); |
|
drawSprite(sprites.stand, 350, 50, 0.6); |
|
drawSprite(sprites.pitcher, 620, 290, 0.3); |
|
|
|
// Draw all cup instances |
|
cups.forEach(cup => drawInstance(cup)); |
|
|
|
// Customers |
|
drawSprite(sprites.customer2, 300, 180, 0.45); |
|
drawSprite(sprites.customer5, 700, 180, 0.45); |
|
|
|
// Invoke callback if set |
|
if (onRenderCallback) onRenderCallback(); |
|
}
|
|
|