Rendering things mostly

- "Miss warning" option is now on by default (ball's particles are red if catching it would be a "miss")
- "Show +X in gold"  option is now on by default (show a +X when combo increases)
- "High contrast" option added, off by default (applies lights layer again as "soft light" at the end of the render)
- "Colorful coins" option now applied at render time instead of coin spawn time, to make preview easier
- when settings are opened on pc, they show up on the side and the overlay is transparent to let you preview the changes
This commit is contained in:
Renan LE CARO 2025-04-04 12:07:24 +02:00
parent 7d518f14e5
commit f76c96019c
25 changed files with 255 additions and 132 deletions

View file

@ -28,7 +28,7 @@ let lastClickedItemIndex = -1;
export function requiredAsyncAlert<t>(p: {
title?: string;
content: (string | AsyncAlertAction<t>)[];
actionsAsGrid?: boolean;
className?:string;
}): Promise<t> {
return asyncAlert({ ...p, allowClose: false });
}
@ -37,16 +37,16 @@ export async function asyncAlert<t>({
title,
content = [],
allowClose = true,
actionsAsGrid = false,
className = '',
}: {
title?: string;
content: (string | AsyncAlertAction<t>)[];
allowClose?: boolean;
actionsAsGrid?: boolean;
className?:string;
}): Promise<t | void> {
updateAlertsOpen(+1);
return new Promise((resolve) => {
popupWrap.className = actionsAsGrid ? " actionsAsGrid" : "";
popupWrap.className = className ;
closeModaleButton.style.display = allowClose ? "" : "none";
const popup = document.createElement("div");

View file

@ -53,7 +53,7 @@ export async function openCreativeModePerksPicker(
while (
(choice = await requiredAsyncAlert<Upgrade | Level | "reset">({
title: t("lab.title", { lvl: currentLevel + 1 }),
actionsAsGrid: true,
className:'actionsAsGrid',
content: [
t("lab.instructions"),
{

View file

@ -1,10 +1,9 @@
* {
font-family:
Courier New,
Courier,
Lucida Sans Typewriter,
Lucida Typewriter,
monospace;
font-family: Courier New,
Courier,
Lucida Sans Typewriter,
Lucida Typewriter,
monospace;
box-sizing: border-box;
}
@ -30,6 +29,7 @@ body {
height: calc(var(--vh, 1vh) * 100);
width: 100vw;
}
canvas:not(#game) {
display: none;
}
@ -246,6 +246,22 @@ body:not(.has-alert-open) #popup {
color: white;
}
}
@media (min-width: 1400px) {
&.settings {
&:before {
opacity: 0;
}
& > div {
margin-right: 0;
max-width: 400px
}
}
}
}
/*Unlocks progress bar*/

View file

@ -798,6 +798,7 @@ async function openSettingsMenu() {
title: t("main_menu.settings_title"),
content: [t("main_menu.settings_help"), ...actions],
allowClose: true,
className:'settings',
});
if (cb) {
cb();
@ -874,8 +875,7 @@ async function openUnlocksList() {
t("unlocks.level"),
...levelActions,
],
allowClose: true,
actionsAsGrid: true,
allowClose: true,className:'actionsAsGrid',
});
if (tryOn) {
if (await confirmRestart(gameState)) {

View file

@ -430,9 +430,8 @@ export function explodeBrick(
cy,
ball.previousVX * (0.5 + Math.random()),
ball.previousVY * (0.5 + Math.random()),
gameState.perks.metamorphosis || isOptionOn("colorful_coins")
? color
: "gold",
color,
points,
);
}
@ -584,7 +583,8 @@ export function addToScore(gameState: GameState, coin: Coin) {
coin.previousY,
(gameState.canvasWidth - coin.x) / 100,
-coin.y / 100,
coin.color,
gameState.perks.metamorphosis || isOptionOn("colorful_coins") ? coin.color : 'gold',
true,
gameState.coinSize / 2,
100 + Math.random() * 50,
@ -1116,8 +1116,7 @@ export function gameStateTick(
coin.x,
coin.y,
0,
gameState.baseSpeed,
coin.color,
gameState.baseSpeed, gameState.perks.metamorphosis || isOptionOn("colorful_coins") ? coin.color : 'gold',
true,
5,
250,
@ -1173,7 +1172,6 @@ export function gameStateTick(
// Not using setbrick because we don't want to reset HP
gameState.bricks[hitBrick] = coin.color;
coin.metamorphosisPoints--;
schedulGameSound(gameState, "colorChange", coin.x, 0.3);
}
}

View file

@ -922,6 +922,36 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>contrast</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>contrast_help</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>credit_levels</name>
<description/>
@ -3192,6 +3222,56 @@
</concept_node>
</children>
</folder_node>
<folder_node>
<name>fountain_toss</name>
<children>
<concept_node>
<name>fullHelp</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>help</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>name</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>
<name>ghost_coins</name>
<children>
@ -3492,56 +3572,6 @@
</concept_node>
</children>
</folder_node>
<folder_node>
<name>fountain_toss</name>
<children>
<concept_node>
<name>fullHelp</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>true</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>true</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>help</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>true</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>true</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>name</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>true</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>true</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>
<name>metamorphosis</name>
<children>

View file

@ -56,6 +56,8 @@
"main_menu.colorful_coins_help": "Coins always spawn of the color of the brick",
"main_menu.comboIncreaseTexts": "Show +X in gold",
"main_menu.comboIncreaseTexts_help": "When the combo increase",
"main_menu.contrast": "High Contrast",
"main_menu.contrast_help": "More colorful and dark rendering",
"main_menu.credit_levels": "<h2>Levels source or reference link</h2>",
"main_menu.credits": "# Credits\n\nI pulled many background patterns from https://pattern.monster/\n\nSome of the sound generating code was written by ChatGPT, and heavily\nadapted to my usage over time.\n\nI wanted an APK to start in fullscreen and be able to list it on fdroid and the play store. I started with an empty view and went to work trimming it down, with the help of that tutorial : https://github.com/fractalwrench/ApkGolf/blob/master/blog/BLOG_POST.md\n\nColin (obigre) brought a lot of fantastic ideas to the game, here's his website (in French) : https://colin-crapahute.bearblog.dev/\n\n# Breakout games suggestions\n\nHere are a few interesting games in the breakout genre :\n\n- LBreakoutHD : https://sourceforge.net/p/lgames/code/HEAD/tree/trunk/lbreakouthd/\n- Wizorb : https://store.steampowered.com/app/207420/Wizorb/\n- Ricochet infinity : https://www.myabandonware.com/game/ricochet-infinity-dxm\n- First prototype of B71 : https://breakout-v1.lecaro.me/\n- Second prototype of B71: https://breakout-v2.lecaro.me/\n\n\n# PC game suggestions\n\nHere are a few games i've sank a lot of time in, and that inspired breakout in some way\n\n- Heat signature : https://www.humblebundle.com/store/heat-signature\n- FTL : https://www.gog.com/en/game/faster_than_light\n- Nova drift : https://www.gog.com/en/game/nova_drift\n- Noita : https://www.gog.com/en/game/noita\n- Enter the gungeon : https://www.gog.com/en/game/enter_the_gungeon\n- Zero Sivert : https://store.steampowered.com/app/1782120/ZERO_Sievert/\n- Factorio : https://www.factorio.com/\n- Nuclear throne : https://store.steampowered.com/app/242680/Nuclear_Throne/ (don't buy on GOG it's outdated) \n- Brigador : https://www.gog.com/en/game/brigador\n- Teleglitch https://www.gog.com/en/game/teleglitch_die_more_edition\n- Rollers of the realm : https://store.steampowered.com/app/262470/Rollers_of_the_Realm/\n",
"main_menu.donate": "You've played for {{hours}} hours",
@ -200,6 +202,9 @@
"upgrades.forgiving.fullHelp": "The first miss per level is free, then 10% of the combo, then 20% .. ",
"upgrades.forgiving.help": "Missing breaks reduces combo progressively instead of all at once.",
"upgrades.forgiving.name": "Forgiving",
"upgrades.fountain_toss.fullHelp": "",
"upgrades.fountain_toss.help": "When you miss a coin and your combo was under {{max}}, your combo has a probability of {{lvl}}/combo to grow by one.",
"upgrades.fountain_toss.name": "Fountain toss",
"upgrades.ghost_coins.fullHelp": "It's not a bug, it's a feature ! Coins fly through bricks slowly. Higher levels let them move faster. ",
"upgrades.ghost_coins.help": "Coins pass through bricks",
"upgrades.ghost_coins.name": "Ghost coins",
@ -218,9 +223,6 @@
"upgrades.left_is_lava.fullHelp": "Whenever you break a brick, your combo will increase by one, so you'll get one more coin from all the next bricks you break.\n\nHowever, your combo will reset as soon as your ball hits the left side . \n\nAs soon as your combo rises, the left side becomes red to remind you that you should avoid hitting them. \n",
"upgrades.left_is_lava.help": "+{{lvl}} combo per brick broken, resets on left side hit",
"upgrades.left_is_lava.name": "Avoid left side",
"upgrades.fountain_toss.fullHelp": "",
"upgrades.fountain_toss.help": "When you miss a coin and your combo was under {{max}}, your combo has a probability of {{lvl}}/combo to grow by one.",
"upgrades.fountain_toss.name": "Fountain toss",
"upgrades.metamorphosis.fullHelp": "With this perk, coins will be of the color of the brick they come from, and will color the first brick they touch in the same color. \n\nCoins spawn with the speed of the ball that broke them, which means you can aim a bit in the direction of the bricks you want to \"paint\".",
"upgrades.metamorphosis.help": "Each coins can stain {{lvl}} brick(s) with its color",
"upgrades.metamorphosis.name": "Metamorphosis",

View file

@ -56,6 +56,8 @@
"main_menu.colorful_coins_help": "Les pièces apparaissent toujours de la couleur de la brique",
"main_menu.comboIncreaseTexts": "Afficher un +X doré",
"main_menu.comboIncreaseTexts_help": "Quand le combo augmente",
"main_menu.contrast": "Contraste élevé",
"main_menu.contrast_help": "Affichage plus contrasté et coloré",
"main_menu.credit_levels": "<h2>Source ou référence des niveaux</h2>",
"main_menu.credits": "# Crédits\n\nJ'ai récupéré de nombreux motifs d'arrière-plan sur https://pattern.monster/\n\nUne partie du code de génération de sons a été écrite par ChatGPT et a été largement adaptée à mon utilisation au fil du temps.\n\nJe souhaitais un APK qui démarre en plein écran et puisse être listé sur Android et le Play Store. J'ai commencé avec une vue vide et je me suis attelé à la réduire, à l'aide de ce tutoriel : https://github.com/fractalwrench/ApkGolf/blob/master/blog/BLOG_POST.md\n\nColin (obigre) a apporté de nombreuses idées fantastiques au jeu. Voici son site web : https://colin-crapahute.bearblog.dev/\n\n# Autres jeux de casse-briques\n\nVoici quelques jeux intéressants dans le genre du casse-briques :\n\n- LBreakoutHD : un remake open source intéressant https://sourceforge.net/p/lgames/code/HEAD/tree/trunk/lbreakouthd/\n- Wizorb https://store.steampowered.com/app/207420/Wizorb/\n- Breakout multijoueur : JcJ avec multijoueur de type console aérienne https://casmo.itch.io/breakout-multiplayer\n- Ricochet Infinity : https://www.myabandonware.com/game/ricochet-infinity-dxm\n- Mes premières tentatives dans le genre : https://breakout-v1.lecaro.me/ (décontracté, plus proche du concept original de Breakout) et https://breakout-v2.lecaro.me/ (multijoueur)\n\n# Autres jeux PC à forte rejouabilité\n\n- Rollers of the realm : https://store.steampowered.com/app/262470/Rollers_of_the_Realm/\n- FTL : https://www.gog.com/en/game/faster_than_light\n- Nova Drift : https://www.gog.com/en/game/nova_drift\n- Noita : https://www.gog.com/en/game/noita\n- Enter the Gungeon : https://www.gog.com/en/game/enter_the_gungeon\n- Zero Sivert : https://store.steampowered.com/app/1782120/ZERO_Sievert/\n- Factorio : https://www.factorio.com/\n- Nuclear throne : https://store.steampowered.com/app/242680/Nuclear_Throne/ (ne l'achetez pas sur GOG, c'est obsolète)\n- Brigador : https://www.gog.com/en/game/brigador\n- Teleglitch https://www.gog.com/en/game/teleglitch_die_more_edition\n- Broforce : https://www.gog.com/en/game/broforce\n- Spelunky : https://www.gog.com/en/game/spelunky",
"main_menu.donate": "Vous avez joué {{hours}} heures",
@ -200,6 +202,9 @@
"upgrades.forgiving.fullHelp": " La première brique ratée par niveau ne coûte rien, la suivante 10%, 20%, etc.",
"upgrades.forgiving.help": "Rater les briques fait perdre un portion progressivement plu importante du combo",
"upgrades.forgiving.name": "L'erreur est humaine",
"upgrades.fountain_toss.fullHelp": "",
"upgrades.fountain_toss.help": "Quand une pièce est perdue alors que votre combo était en dessous de {{max}}, votre combo à une probabilité de {{lvl}}/combo d'être incrémenté",
"upgrades.fountain_toss.name": "Pièce dans la fontaine",
"upgrades.ghost_coins.fullHelp": "Ce n'est pas une bug, c'est une fonctionnalité Les pièces passent à travers les briques doucement. ",
"upgrades.ghost_coins.help": "Les pièces traversent les briques",
"upgrades.ghost_coins.name": "Pièces fantôme",
@ -218,9 +223,6 @@
"upgrades.left_is_lava.fullHelp": "Chaque fois que vous cassez une brique, votre combo augmente d'une unité, ce qui vous permet d'obtenir une pièce de plus à chaque fois que vous cassez une brique.\n\nCependant, votre combinaison se réinitialise dès que votre balle touche le côté gauche.\n\nDès que votre combo augmente, le côté gauche devient rouge pour vous rappeler que vous devez éviter de le frapper.",
"upgrades.left_is_lava.help": "+{{lvl}} combo par brique, RAZ en touchant le bord gauche",
"upgrades.left_is_lava.name": "Éviter le côté gauche",
"upgrades.fountain_toss.fullHelp": "",
"upgrades.fountain_toss.help": "Quand une pièce est perdue alors que votre combo était en dessous de {{max}}, votre combo à une probabilité de {{lvl}}/combo d'être incrémenté",
"upgrades.fountain_toss.name": "Pièce dans la fontaine",
"upgrades.metamorphosis.fullHelp": "Avec cette amélioration, les pièces seront de la couleur de la brique d'où elles proviennent et coloreront la première brique qu'elles toucheront. \n\nLes pièces apparaissent à la vitesse de la balle qui les a cassées, ce qui signifie que vous pouvez viser un peu dans la direction des briques que vous voulez \"peindre\".",
"upgrades.metamorphosis.help": "Chaque pièces peut tacher {{lvl}} brique(s) avec sa couleur",
"upgrades.metamorphosis.name": "Métamorphose",

View file

@ -35,6 +35,11 @@ export const options = {
name: t("main_menu.extra_bright"),
help: t("main_menu.extra_bright_help"),
},
contrast: {
default: false,
name: t("main_menu.contrast"),
help: t("main_menu.contrast_help"),
},
show_fps: {
default: false,
name: t("main_menu.show_fps"),
@ -72,12 +77,12 @@ export const options = {
help: t("main_menu.donation_reminder_help"),
},
red_miss: {
default: false,
default: true,
name: t("main_menu.red_miss"),
help: t("main_menu.red_miss_help"),
},
comboIncreaseTexts: {
default: false,
default: true,
name: t("main_menu.comboIncreaseTexts"),
help: t("main_menu.comboIncreaseTexts_help"),
},

View file

@ -39,7 +39,7 @@ const haloCanvasCtx = haloCanvas.getContext("2d", {
alpha: false,
}) as CanvasRenderingContext2D;
export const haloScale = 4;
export const haloScale = 8;
export function render(gameState: GameState) {
if (!gameState.readyToRender) return;
@ -99,6 +99,10 @@ export function render(gameState: GameState) {
scoreDisplay.className =
gameState.lastScoreIncrease > gameState.levelTime - 500 ? "active" : "";
// Clear
if (!isOptionOn("basic") && level.svg && level.color === "#000000") {
haloCanvasCtx.globalCompositeOperation = "source-over";
@ -107,23 +111,24 @@ export function render(gameState: GameState) {
haloCanvasCtx.fillRect(0, 0, width / haloScale, height / haloScale);
haloCanvasCtx.globalCompositeOperation = "screen";
haloCanvasCtx.globalAlpha = 0.6;
forEachLiveOne(gameState.coins, (coin) => {
haloCanvasCtx.globalAlpha = 0.9;
const color= gameState.perks.metamorphosis || isOptionOn("colorful_coins") ?
coin.color : 'gold';
haloCanvasCtx.globalAlpha = 0.5;
drawFuzzyBall(
haloCanvasCtx,
coin.color,
color,
(gameState.coinSize * 2) / haloScale,
coin.x / haloScale,
coin.y / haloScale,
);
if (isOptionOn("extra_bright")) {
haloCanvasCtx.globalAlpha = 0.5;
haloCanvasCtx.globalAlpha = 0.2;
drawFuzzyBall(
haloCanvasCtx,
coin.color,
color,
(gameState.coinSize * 10) / haloScale,
coin.x / haloScale,
coin.y / haloScale,
@ -131,6 +136,7 @@ export function render(gameState: GameState) {
}
});
gameState.balls.forEach((ball) => {
haloCanvasCtx.globalAlpha = 0.5;
drawFuzzyBall(
haloCanvasCtx,
gameState.ballsColor,
@ -138,14 +144,17 @@ export function render(gameState: GameState) {
ball.x / haloScale,
ball.y / haloScale,
);
if (isOptionOn("extra_bright"))
if (isOptionOn("extra_bright")) {
haloCanvasCtx.globalAlpha = 0.2;
drawFuzzyBall(
haloCanvasCtx,
gameState.ballsColor,
(gameState.ballSize * 6) / haloScale,
ball.x / haloScale,
ball.y / haloScale,
haloCanvasCtx,
gameState.ballsColor,
(gameState.ballSize * 6) / haloScale,
ball.x / haloScale,
ball.y / haloScale,
);
}
});
haloCanvasCtx.globalAlpha = isOptionOn("extra_bright") ? 0.2 : 0.05;
gameState.bricks.forEach((color, index) => {
@ -172,14 +181,16 @@ export function render(gameState: GameState) {
x / haloScale,
y / haloScale,
);
if (isOptionOn("extra_bright"))
if (isOptionOn("extra_bright")) {
haloCanvasCtx.globalAlpha *= 0.5
drawFuzzyBall(
haloCanvasCtx,
color,
(size * 6) / haloScale,
x / haloScale,
y / haloScale,
haloCanvasCtx,
color,
(size * 6) / haloScale,
x / haloScale,
y / haloScale,
);
}
});
ctx.globalAlpha = 1;
@ -265,26 +276,26 @@ export function render(gameState: GameState) {
// Coins
ctx.globalAlpha = 1;
forEachLiveOne(gameState.coins, (coin) => {
const color= gameState.perks.metamorphosis || isOptionOn("colorful_coins") ?
coin.color : 'gold'
// ctx.globalCompositeOperation = "source-over";
ctx.globalCompositeOperation =
coin.color === "gold" ||
color === "gold" ||
level.color !== "#000000" ||
isOptionOn("opaque_coins")
? "source-over"
: "screen";
// ctx.globalCompositeOperation =
// coin.color === "gold" || level.color ? "source-over" : "screen";
drawCoin(
ctx,
coin.color,
color,
coin.size,
coin.x,
coin.y,
(hasCombo && gameState.perks.asceticism && "red") ||
(coin.color === "gold" && "gold") ||
(color === "gold" && "gold") ||
isOptionOn("opaque_coins")
? gameState.puckColor
: coin.color,
: color,
coin.a,
);
});
@ -539,6 +550,22 @@ export function render(gameState: GameState) {
1,
);
if (!isOptionOn("basic") && isOptionOn("contrast") && level.svg && level.color === "#000000") {
// haloCanvasCtx.globalCompositeOperation = 'multiply';
// haloCanvasCtx.fillRect(0,0,haloCanvas.width,haloCanvas.height)
haloCanvasCtx.fillStyle = 'white'
haloCanvasCtx.globalAlpha = 0.25;
haloCanvasCtx.globalCompositeOperation = 'screen';
haloCanvasCtx.fillRect(0,0,haloCanvas.width,haloCanvas.height)
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "soft-light";
ctx.drawImage(haloCanvas, 0, 0, width, height);
}
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = 1;
if (isOptionOn("mobile-mode") && !gameState.running) {
drawText(
ctx,
@ -551,6 +578,7 @@ export function render(gameState: GameState) {
);
}
if (shaked) {
ctx.resetTransform();
}

View file

@ -38,8 +38,7 @@ export async function openStartingPerksEditor() {
});
const perk: Upgrade | null | void = await asyncAlert({
title: t("main_menu.starting_perks"),
actionsAsGrid: true,
title: t("main_menu.starting_perks"),className:'actionsAsGrid',
content: [
t("main_menu.starting_perks_checked"),
...buttons.filter((b) => b.checked),