Browse Source

add customer animations

master
Stephanie Gredell 3 weeks ago
parent
commit
a49761cb4c
  1. 16
      canvas.js
  2. 57
      canvasController.js
  3. 2
      index.html
  4. 52
      index.js
  5. 2
      style.css

16
canvas.js

@ -118,6 +118,22 @@ export function drawSprite(spriteObject, x, y, scale) {
); );
} }
/**
* Draws a sprite with alpha transparency.
*
* @param {Sprite} sprite - The sprite to draw
* @param {number} x - X position
* @param {number} y - Y position
* @param {number} scale - Scale factor
* @param {number} alpha - Alpha transparency (0-1)
*/
export function drawSpriteWithAlpha(sprite, x, y, scale, alpha) {
ctx.save();
ctx.globalAlpha = alpha;
drawSprite(sprite, x, y, scale);
ctx.restore();
}
/** /**
* Draws a cup (or any object with sprite, x, y, scale, frameIndex). * Draws a cup (or any object with sprite, x, y, scale, frameIndex).
* *

57
canvasController.js

@ -8,6 +8,7 @@ import {
ctx, ctx,
createSprite, createSprite,
drawSprite, drawSprite,
drawSpriteWithAlpha,
drawInstance, drawInstance,
drawGround, drawGround,
drawShadow, drawShadow,
@ -103,6 +104,44 @@ export const sprites = {
}) })
}; };
// Customer positions (alternating left/right)
const customerPositions = [
{ x: 300, y: 180 }, // left
{ x: 700, y: 180 } // right
];
// Active customer state
export let activeCustomer = {
spriteKey: null,
positionIndex: 0,
alpha: 0
};
// All customer sprite keys
const customerKeys = [
'customer1', 'customer2', 'customer3', 'customer4', 'customer5',
'customer6', 'customer7', 'customer8', 'customer9', 'customer10',
'customer11', 'customer12', 'customer13', 'customer14', 'customer15'
];
/**
* Sets the active customer for rendering.
* @param {string|null} spriteKey - Customer sprite key or null
* @param {number} positionIndex - 0 for left, 1 for right
* @param {number} alpha - Transparency (0-1)
*/
export function setActiveCustomer(spriteKey, positionIndex, alpha) {
activeCustomer = { spriteKey, positionIndex, alpha };
}
/**
* Gets a random customer sprite key.
* @returns {string} Random customer key
*/
export function getRandomCustomerKey() {
return customerKeys[Math.floor(Math.random() * customerKeys.length)];
}
/** /**
* Checks if all sprites are loaded. * Checks if all sprites are loaded.
* @returns {boolean} * @returns {boolean}
@ -167,10 +206,10 @@ export function createCup(x, y, scale = 0.1) {
* @type {Array<import('./canvas.js').Cup>} * @type {Array<import('./canvas.js').Cup>}
*/ */
export const cups = [ export const cups = [
createCup(455, 325, 0.15), createCup(455, 285, 0.15),
createCup(495, 325, 0.15), createCup(495, 285, 0.15),
createCup(535, 325, 0.15), createCup(535, 285, 0.15),
createCup(575, 325, 0.15), createCup(575, 285, 0.15),
]; ];
/** /**
@ -196,14 +235,16 @@ export function render() {
// Lemonade stand // Lemonade stand
drawSprite(sprites.maker, 430, 160, 0.55); drawSprite(sprites.maker, 430, 160, 0.55);
drawSprite(sprites.stand, 350, 50, 0.6); drawSprite(sprites.stand, 350, 50, 0.6);
drawSprite(sprites.pitcher, 620, 290, 0.3); drawSprite(sprites.pitcher, 620, 260, 0.3);
// Draw all cup instances // Draw all cup instances
cups.forEach(cup => drawInstance(cup)); cups.forEach(cup => drawInstance(cup));
// Customers // Active customer (with fade)
drawSprite(sprites.customer2, 300, 180, 0.45); if (activeCustomer.spriteKey && activeCustomer.alpha > 0) {
drawSprite(sprites.customer5, 700, 180, 0.45); const pos = customerPositions[activeCustomer.positionIndex];
drawSpriteWithAlpha(sprites[activeCustomer.spriteKey], pos.x, pos.y, 0.45, activeCustomer.alpha);
}
// Invoke callback if set // Invoke callback if set
if (onRenderCallback) onRenderCallback(); if (onRenderCallback) onRenderCallback();

2
index.html

@ -58,7 +58,7 @@
</section> </section>
<section class="game_section day_section"> <section class="game_section day_section">
<h2 class="section_title">Day <span data-bind="current_day">1</span></h2> <h2 class="section_title">Game</h2>
<p class="section_hint">Cups sold: <span data-bind="cups_sold">0</span></p> <p class="section_hint">Cups sold: <span data-bind="cups_sold">0</span></p>
<p class="section_hint">Earnings: <span data-bind="total_earnings" data-format="currency">$0.00</span></p> <p class="section_hint">Earnings: <span data-bind="total_earnings" data-format="currency">$0.00</span></p>
<button class="start_day_btn">Start Day</button> <button class="start_day_btn">Start Day</button>

52
index.js

@ -4,7 +4,7 @@
*/ */
import { init_game, set_price_per_cup, calculate_supply_cost, calculate_cost_per_cup, make_lemonade, set_weather, calculate_maximum_cups_available } from './game.js'; import { init_game, set_price_per_cup, calculate_supply_cost, calculate_cost_per_cup, make_lemonade, set_weather, calculate_maximum_cups_available } from './game.js';
import { sprites, cups, render, whenSpritesReady } from './canvasController.js'; import { sprites, cups, render, whenSpritesReady, setActiveCustomer, getRandomCustomerKey } from './canvasController.js';
import { createReactiveState, updateBindings } from './binding.js'; import { createReactiveState, updateBindings } from './binding.js';
// Initialize game state // Initialize game state
@ -36,20 +36,54 @@ function generateSalesAttempts(maxCups, cupsSold) {
return attempts; return attempts;
} }
/**
* Fades a customer in or out using requestAnimationFrame.
* @param {string} spriteKey - Customer sprite key
* @param {number} posIndex - Position index (0=left, 1=right)
* @param {number} fromAlpha - Starting alpha
* @param {number} toAlpha - Ending alpha
* @param {number} duration - Duration in ms
* @param {Function} onComplete - Callback when complete
*/
function fadeCustomer(spriteKey, posIndex, fromAlpha, toAlpha, duration, onComplete) {
const startTime = performance.now();
function step() {
const elapsed = performance.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const alpha = fromAlpha + (toAlpha - fromAlpha) * progress;
setActiveCustomer(spriteKey, posIndex, alpha);
render();
if (progress < 1) {
requestAnimationFrame(step);
} else {
onComplete();
}
}
step();
}
function animateSales(attempts, onComplete) { function animateSales(attempts, onComplete) {
let i = 0; let i = 0;
let cupIndex = 0; let cupIndex = 0;
let posIndex = 0;
function nextAttempt() { function nextAttempt() {
if (i >= attempts.length) { if (i >= attempts.length) {
sprites.maker.frameIndex = 0; // Maker idle setActiveCustomer(null, 0, 0);
sprites.maker.frameIndex = 0;
render(); render();
onComplete(); onComplete();
return; return;
} }
// Pick random customer, alternate position
const customerKey = getRandomCustomerKey();
posIndex = (posIndex + 1) % 2;
// Fade in customer
fadeCustomer(customerKey, posIndex, 0, 1, 500, () => {
if (attempts[i]) { if (attempts[i]) {
// Buy: maker active, empty cup, wait, refill, maker idle // Buy: maker active, empty cup, wait, refill, maker idle, fade out
sprites.maker.frameIndex = 1; sprites.maker.frameIndex = 1;
cups[cupIndex].empty(); cups[cupIndex].empty();
render(); render();
@ -58,14 +92,22 @@ function animateSales(attempts, onComplete) {
cups[cupIndex].fill(); cups[cupIndex].fill();
render(); render();
cupIndex = (cupIndex + 1) % cups.length; cupIndex = (cupIndex + 1) % cups.length;
// Fade out customer
fadeCustomer(customerKey, posIndex, 1, 0, 500, () => {
i++; i++;
nextAttempt(); nextAttempt();
}, 400); });
}, 600);
} else { } else {
// Pass: skip instantly // Pass: brief pause then fade out
setTimeout(() => {
fadeCustomer(customerKey, posIndex, 1, 0, 500, () => {
i++; i++;
nextAttempt(); nextAttempt();
});
}, 500);
} }
});
} }
nextAttempt(); nextAttempt();

2
style.css

@ -113,7 +113,7 @@ body {
} }
.game_section .section_hint { .game_section .section_hint {
font-size: 11px; font-size: 13px;
margin: 2px 0 8px; margin: 2px 0 8px;
} }

Loading…
Cancel
Save