mirror of
https://gitlab.com/lecarore/breakout71.git
synced 2025-04-22 21:16:14 -04:00
Added particle and sound effect when coin drops below the "waterline" of the puck
This commit is contained in:
parent
354a6490e9
commit
06843047d2
9 changed files with 162 additions and 82 deletions
37
src/game.ts
37
src/game.ts
|
@ -452,7 +452,6 @@ startWork('gameStateTick')
|
|||
gameStateTick(gameState, frames);
|
||||
}
|
||||
|
||||
startWork('render')
|
||||
if (gameState.running || gameState.needsRender) {
|
||||
gameState.needsRender = false;
|
||||
render(gameState);
|
||||
|
@ -478,10 +477,12 @@ setInterval(() => {
|
|||
FPSCounter = 0;
|
||||
}, 1000);
|
||||
|
||||
const showStats= window.location.search.includes("stress")
|
||||
let total={}
|
||||
let lastTick=performance.now();
|
||||
let doing= ''
|
||||
function startWork(what){
|
||||
export function startWork(what){
|
||||
if(!showStats) return
|
||||
const newNow=performance.now();
|
||||
if(doing) {
|
||||
total[doing] = (total[doing]||0) + ( newNow-lastTick )
|
||||
|
@ -489,12 +490,12 @@ function startWork(what){
|
|||
lastTick=newNow
|
||||
doing=what
|
||||
}
|
||||
if(showStats)
|
||||
setInterval(()=>{
|
||||
const totalTime = sumOfValues(total)
|
||||
console.log(
|
||||
console.debug(
|
||||
liveCount(gameState.coins) +' coins\n'+
|
||||
Object.entries(total).sort((a,b)=>b[1]-a[1]).filter(a=>a[1]>1).map(t=>t[0]+':'+(t[1]/totalTime*100).toFixed(2)+'% ('+t[1]+'ms)').join('\n'))
|
||||
|
||||
total={}
|
||||
},2000)
|
||||
|
||||
|
@ -1040,27 +1041,29 @@ export function restart(params: RunParams) {
|
|||
play();
|
||||
}
|
||||
}
|
||||
if (window.location.search.includes("autoplay")) {
|
||||
if (window.location.search.match(/autoplay|stress/) ) {
|
||||
startComputerControlledGame();
|
||||
} else if (window.location.search.includes("stress")) {
|
||||
if(!isOptionOn('show_fps'))
|
||||
if(!isOptionOn('show_fps'))
|
||||
toggleOption('show_fps')
|
||||
restart({
|
||||
level:allLevels.find(l=>l.name=='Worms'),
|
||||
perks:{base_combo:5000, pierce:20, rainbow:3, sapper:2, etherealcoins:1}
|
||||
});
|
||||
}else {
|
||||
} else {
|
||||
restart({});
|
||||
}
|
||||
|
||||
export function startComputerControlledGame() {
|
||||
const perks: Partial<PerksMap> = { base_combo: 20, pierce: 3 };
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const u = sample(upgrades);
|
||||
|
||||
perks[u.id] ||= Math.floor(Math.random() * u.max) + 1;
|
||||
const perks: Partial<PerksMap> = { base_combo: 20, pierce: 3 };
|
||||
if(window.location.search.includes("stress")){
|
||||
|
||||
Object.assign(perks,{base_combo:5000, pierce:20, rainbow:3, sapper:2, etherealcoins:1, bricks_attract_ball:1, respawn:3})
|
||||
|
||||
}else{
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const u = sample(upgrades);
|
||||
|
||||
perks[u.id] ||= Math.floor(Math.random() * u.max) + 1;
|
||||
}
|
||||
perks.superhot = 0;
|
||||
}
|
||||
perks.superhot = 0;
|
||||
restart({
|
||||
level: sample(allLevels.filter((l) => l.color === "#000000")),
|
||||
computer_controlled: true,
|
||||
|
|
|
@ -17,10 +17,7 @@ import { run } from "jest";
|
|||
import { editRawLevelList } from "./levelEditor";
|
||||
|
||||
export function addToTotalPlayTime(ms: number) {
|
||||
try {
|
||||
setSettingValue('breakout_71_total_play_time', getSettingValue('breakout_71_total_play_time',0)+ms)
|
||||
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
export function gameOver(title: string, intro: string) {
|
||||
|
|
|
@ -461,11 +461,11 @@ export function explodeBrick(
|
|||
gameState.runStatistics.coins_spawned += coinsToSpawn;
|
||||
gameState.runStatistics.bricks_broken++;
|
||||
|
||||
const maxCoins = getCurrentMaxCoins() * (isOptionOn("basic") ? 0.5 : 1);
|
||||
const maxCoins = getCurrentMaxCoins()
|
||||
const spawnableCoins =
|
||||
liveCount(gameState.coins) > getCurrentMaxCoins()
|
||||
? 1
|
||||
: Math.floor(maxCoins - liveCount(gameState.coins)) / 3;
|
||||
: Math.floor((maxCoins - liveCount(gameState.coins)) /2) ;
|
||||
|
||||
const pointsPerCoin = Math.max(1, Math.ceil(coinsToSpawn / spawnableCoins));
|
||||
|
||||
|
@ -1220,6 +1220,9 @@ export function gameStateTick(
|
|||
const hitBorder = bordersHitCheck(gameState, coin, coin.size / 2, frames);
|
||||
if(coin.previousY<gameState.gameZoneHeight && coin.y>gameState.gameZoneHeight && coin.vy>0 && speed > 20) {
|
||||
schedulGameSound(gameState, "plouf", coin.x, clamp(speed, 20,100)/100*0.2);
|
||||
if(!isOptionOn('basic')){
|
||||
makeParticle(gameState, coin.x,gameState.gameZoneHeight, -coin.vx/5, -coin.vy/5, coin.color, false )
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
} from "./game_utils";
|
||||
import { Coin, colorString, GameState } from "./types";
|
||||
import { t } from "./i18n/i18n";
|
||||
import { gameState, lastMeasuredFPS } from "./game";
|
||||
import {gameState, lastMeasuredFPS, startWork} from "./game";
|
||||
import { isOptionOn } from "./options";
|
||||
import {
|
||||
catchRateBest,
|
||||
|
@ -25,6 +25,7 @@ import {
|
|||
wallBouncedBest,
|
||||
wallBouncedGood,
|
||||
} from "./pure_functions";
|
||||
import {getCurrentMaxCoins} from "./settings";
|
||||
|
||||
export const gameCanvas = document.getElementById("game") as HTMLCanvasElement;
|
||||
export const ctx = gameCanvas.getContext("2d", {
|
||||
|
@ -51,6 +52,7 @@ const haloCanvasCtx = haloCanvas.getContext("2d", {
|
|||
export const haloScale = 16;
|
||||
|
||||
export function render(gameState: GameState) {
|
||||
startWork('render:init')
|
||||
const level = currentLevelInfo(gameState);
|
||||
|
||||
const hasCombo = gameState.combo > baseCombo(gameState);
|
||||
|
@ -70,11 +72,14 @@ export function render(gameState: GameState) {
|
|||
? (gameState.levelSpawnedCoins - gameState.levelLostCoins) /
|
||||
gameState.levelSpawnedCoins
|
||||
: 1;
|
||||
|
||||
startWork('render:scoreDisplay')
|
||||
scoreDisplay.innerHTML =
|
||||
(isOptionOn("show_fps") || gameState.computer_controlled
|
||||
? `
|
||||
<span class="${(Math.abs(lastMeasuredFPS - 60) < 2 && " ") || (Math.abs(lastMeasuredFPS - 60) < 10 && "good") || "bad"}">
|
||||
<span>
|
||||
${Math.floor(liveCount(gameState.coins) / getCurrentMaxCoins() * 100)} %
|
||||
</span><span> / </span>
|
||||
<span class="${(Math.abs(lastMeasuredFPS - 60) < 2 && " ") || (Math.abs(lastMeasuredFPS - 60) < 10 && "good") || "bad"}">
|
||||
${lastMeasuredFPS} FPS
|
||||
</span><span> / </span>
|
||||
|
||||
|
@ -102,19 +107,20 @@ export function render(gameState: GameState) {
|
|||
(gameState.computer_controlled && "computer_controlled") ||
|
||||
(gameState.lastScoreIncrease > gameState.levelTime - 500 && "active") ||
|
||||
"";
|
||||
|
||||
// Clear
|
||||
if (!isOptionOn("basic") && level.svg && level.color === "#000000") {
|
||||
|
||||
startWork('render:halo:clear')
|
||||
haloCanvasCtx.globalCompositeOperation = "source-over";
|
||||
haloCanvasCtx.globalAlpha = 0.99;
|
||||
haloCanvasCtx.fillStyle = level.color;
|
||||
haloCanvasCtx.fillRect(0, 0, width / haloScale, height / haloScale);
|
||||
|
||||
const brightness = isOptionOn("extra_bright") ? 3 : 1;
|
||||
|
||||
haloCanvasCtx.globalCompositeOperation = "lighten";
|
||||
haloCanvasCtx.globalAlpha =
|
||||
0.1 + (0.5 * 10) / (liveCount(gameState.coins) + 10);
|
||||
startWork('render:halo:coins')
|
||||
forEachLiveOne(gameState.coins, (coin) => {
|
||||
const color = getCoinRenderColor(gameState, coin);
|
||||
drawFuzzyBall(
|
||||
|
@ -126,6 +132,7 @@ export function render(gameState: GameState) {
|
|||
);
|
||||
});
|
||||
|
||||
startWork('render:halo:balls')
|
||||
gameState.balls.forEach((ball) => {
|
||||
haloCanvasCtx.globalAlpha = 0.3 * (1 - ballTransparency(ball, gameState));
|
||||
drawFuzzyBall(
|
||||
|
@ -136,6 +143,8 @@ export function render(gameState: GameState) {
|
|||
ball.y / haloScale,
|
||||
);
|
||||
});
|
||||
|
||||
startWork('render:halo:bricks')
|
||||
haloCanvasCtx.globalAlpha = 0.05;
|
||||
gameState.bricks.forEach((color, index) => {
|
||||
if (!color) return;
|
||||
|
@ -151,6 +160,7 @@ export function render(gameState: GameState) {
|
|||
);
|
||||
});
|
||||
|
||||
startWork('render:halo:particles')
|
||||
haloCanvasCtx.globalCompositeOperation = "screen";
|
||||
forEachLiveOne(gameState.particles, (flash) => {
|
||||
const { x, y, time, color, size, duration } = flash;
|
||||
|
@ -174,6 +184,7 @@ export function render(gameState: GameState) {
|
|||
ctx.drawImage(haloCanvas, 0, 0, width, height);
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
|
||||
startWork('render:halo:pattern')
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.globalCompositeOperation = "multiply";
|
||||
if (level.svg && background.width && background.complete) {
|
||||
|
@ -225,6 +236,8 @@ export function render(gameState: GameState) {
|
|||
ctx.fillRect(0, 0, width, height);
|
||||
}
|
||||
} else {
|
||||
|
||||
startWork('render:halo-basic')
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
ctx.fillStyle = level.color || "#000";
|
||||
|
@ -237,6 +250,7 @@ export function render(gameState: GameState) {
|
|||
});
|
||||
}
|
||||
|
||||
startWork('render:explosionshake')
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
const lastExplosionDelay = Date.now() - gameState.lastExplosion + 5;
|
||||
|
@ -249,7 +263,7 @@ export function render(gameState: GameState) {
|
|||
Math.sin(Date.now() + 36) * amplitude,
|
||||
);
|
||||
}
|
||||
|
||||
startWork('render:coins')
|
||||
// Coins
|
||||
ctx.globalAlpha = 1;
|
||||
forEachLiveOne(gameState.coins, (coin) => {
|
||||
|
@ -272,6 +286,7 @@ export function render(gameState: GameState) {
|
|||
coin.a,
|
||||
);
|
||||
});
|
||||
startWork('render:ball shade')
|
||||
// Black shadow around balls
|
||||
if (!isOptionOn("basic")) {
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
|
@ -289,10 +304,11 @@ export function render(gameState: GameState) {
|
|||
);
|
||||
});
|
||||
}
|
||||
|
||||
startWork('render:bricks')
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
renderAllBricks();
|
||||
|
||||
startWork('render:lights')
|
||||
ctx.globalCompositeOperation = "screen";
|
||||
forEachLiveOne(gameState.lights, (flash) => {
|
||||
const { x, y, time, color, size, duration } = flash;
|
||||
|
@ -309,6 +325,7 @@ export function render(gameState: GameState) {
|
|||
);
|
||||
});
|
||||
|
||||
startWork('render:texts')
|
||||
ctx.globalCompositeOperation = "screen";
|
||||
forEachLiveOne(gameState.texts, (flash) => {
|
||||
const { x, y, time, color, size, duration } = flash;
|
||||
|
@ -318,6 +335,7 @@ export function render(gameState: GameState) {
|
|||
drawText(ctx, flash.text, color, size, x, y - elapsed / 10);
|
||||
});
|
||||
|
||||
startWork('render:particles')
|
||||
forEachLiveOne(gameState.particles, (particle) => {
|
||||
const { x, y, time, color, size, duration } = particle;
|
||||
const elapsed = gameState.levelTime - time;
|
||||
|
@ -325,6 +343,8 @@ export function render(gameState: GameState) {
|
|||
ctx.globalCompositeOperation = "screen";
|
||||
drawBall(ctx, color, size, x, y);
|
||||
});
|
||||
|
||||
startWork('render:extra_life')
|
||||
if (gameState.perks.extra_life) {
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
|
@ -339,9 +359,9 @@ export function render(gameState: GameState) {
|
|||
}
|
||||
}
|
||||
|
||||
startWork('render:balls')
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
|
||||
gameState.balls.forEach((ball) => {
|
||||
const drawingColor = gameState.ballsColor;
|
||||
const ballAlpha = 1 - ballTransparency(ball, gameState);
|
||||
|
@ -390,10 +410,10 @@ export function render(gameState: GameState) {
|
|||
ctx.stroke();
|
||||
}
|
||||
});
|
||||
// The puck
|
||||
|
||||
startWork('render:puck')
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
|
||||
drawPuck(
|
||||
ctx,
|
||||
gameState.puckColor,
|
||||
|
@ -404,6 +424,7 @@ export function render(gameState: GameState) {
|
|||
gameState.perks.streak_shots && hasCombo ? getDashOffset(gameState) : -1,
|
||||
);
|
||||
|
||||
startWork('render:combotext')
|
||||
if (gameState.combo > 1) {
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
const comboText = "x " + gameState.combo;
|
||||
|
@ -443,8 +464,8 @@ export function render(gameState: GameState) {
|
|||
);
|
||||
}
|
||||
}
|
||||
startWork('render:Borders')
|
||||
// Borders
|
||||
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
ctx.globalAlpha = 1;
|
||||
|
||||
|
@ -527,6 +548,7 @@ export function render(gameState: GameState) {
|
|||
1,
|
||||
);
|
||||
|
||||
startWork('render:contrast')
|
||||
if (
|
||||
!isOptionOn("basic") &&
|
||||
isOptionOn("contrast") &&
|
||||
|
@ -547,6 +569,7 @@ export function render(gameState: GameState) {
|
|||
ctx.imageSmoothingEnabled = false;
|
||||
}
|
||||
|
||||
startWork('render:breakout.lecaro.me?autoplay')
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
ctx.globalAlpha = 1;
|
||||
|
||||
|
@ -561,6 +584,7 @@ export function render(gameState: GameState) {
|
|||
(gameState.canvasHeight - gameState.gameZoneHeight) / 2,
|
||||
);
|
||||
}
|
||||
startWork('render:mobile_press_to_play')
|
||||
if (isOptionOn("mobile-mode") && !gameState.running) {
|
||||
drawText(
|
||||
ctx,
|
||||
|
@ -580,8 +604,10 @@ export function render(gameState: GameState) {
|
|||
// ctx.fillRect(0,gameState.gameZoneHeight, gameState.canvasWidth, gameState.canvasHeight-gameState.gameZoneHeight)
|
||||
// }
|
||||
// ctx.globalAlpha=1
|
||||
startWork('render:askForWakeLock')
|
||||
askForWakeLock(gameState);
|
||||
|
||||
startWork('render:resetTransform')
|
||||
if (shaked) {
|
||||
ctx.resetTransform();
|
||||
}
|
||||
|
|
|
@ -19,14 +19,25 @@ export function getSettingValue<T>(key: string, defaultValue: T) {
|
|||
return (cachedSettings[key] as T) ?? defaultValue;
|
||||
}
|
||||
|
||||
// We avoid using localstorage synchronously for perf reasons
|
||||
let needsSaving= new Set()
|
||||
export function setSettingValue<T>(key: string, value: T) {
|
||||
cachedSettings[key] = value;
|
||||
if(cachedSettings[key] !==value){
|
||||
needsSaving.add(key)
|
||||
cachedSettings[key] = value;
|
||||
}
|
||||
}
|
||||
setInterval(()=>{
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
for(let key of needsSaving){
|
||||
localStorage.setItem(key, JSON.stringify(cachedSettings[key]));
|
||||
}
|
||||
needsSaving.clear()
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
}
|
||||
}, 500)
|
||||
|
||||
|
||||
export function getTotalScore() {
|
||||
return getSettingValue("breakout_71_total_score", 0);
|
||||
|
@ -39,5 +50,5 @@ export function getCurrentMaxParticles() {
|
|||
return getCurrentMaxCoins()
|
||||
}
|
||||
export function cycleMaxCoins() {
|
||||
setSettingValue("max_coins", (getSettingValue("max_coins", 2) + 1) % 10);
|
||||
setSettingValue("max_coins", (getSettingValue("max_coins", 2) + 1) % 7);
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ export const sounds = {
|
|||
|
||||
plouf: (volume: number, pan: number) => {
|
||||
if (!isOptionOn("sound")) return;
|
||||
createSingleBounceSound(240, pan, volume*0.5);
|
||||
createSingleBounceSound(500, pan, volume*0.5);
|
||||
// createWaterDropSound(800, pan, volume*0.2, 0.2,'triangle')
|
||||
},
|
||||
|
||||
|
|
|
@ -818,7 +818,7 @@ export const rawUpgrades = [
|
|||
threshold: 215000,
|
||||
gift: false,
|
||||
id: "bricks_attract_ball",
|
||||
max: 3,
|
||||
max: 1,
|
||||
name: t("upgrades.bricks_attract_ball.name"),
|
||||
help: (lvl: number) =>
|
||||
t("upgrades.bricks_attract_ball.tooltip", { count: lvl * 3 }),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue