Taller canvas + real background design for all 4 game templates
Phone screenshots showed the canvas was short and squat (960x600, 8:5) leaving most of the screen blank, with Clicker's hero list and Quiz's answer options too cramped to tap comfortably. Also the dark-mode canvas background was the same near-black as the page, so games had no visible frame and read as flat/undesigned. Bumps canvas to 960x720 (4:3) and uses the extra room to enlarge hero rows, quiz options/lifelines, and maze/snake grid cells. Adds a themed gradient frame around the canvas in play-session.tsx and replaces each scene's flat background fill with a subtle top-to-bottom gradient.
This commit is contained in:
1 parent
13bea64bb6
commit
65044fa828
6 files changed
+42
-26
No files matched your search
@@ -51,7 +51,7 @@ export const GameCanvas = forwardRef<
|
|||||||
mode: PhaserLib.Scale.FIT,
|
mode: PhaserLib.Scale.FIT,
|
||||||
autoCenter: PhaserLib.Scale.CENTER_BOTH,
|
autoCenter: PhaserLib.Scale.CENTER_BOTH,
|
||||||
width: 960,
|
width: 960,
|
||||||
height: 600,
|
height: 720,
|
||||||
},
|
},
|
||||||
scene: [SceneClass],
|
scene: [SceneClass],
|
||||||
});
|
});
|
||||||
@@ -75,5 +75,5 @@ export const GameCanvas = forwardRef<
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [templateType]);
|
}, [templateType]);
|
||||||
|
|
||||||
return <div ref={containerRef} className="aspect-[8/5] w-full overflow-hidden rounded-lg bg-black" />;
|
return <div ref={containerRef} className="aspect-[4/3] w-full overflow-hidden rounded-lg bg-black" />;
|
||||||
});
|
});
|
||||||
@@ -101,7 +101,7 @@ export function PlaySession({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto w-full max-w-4xl flex-1 px-1 py-4 sm:px-4 sm:py-6">
|
<div className="mx-auto w-full max-w-4xl flex-1 px-1 py-4 sm:px-4 sm:py-6">
|
||||||
<div className="relative">
|
<div className="relative rounded-xl border border-border bg-gradient-to-b from-accent-soft/40 to-surface p-2 shadow-lg sm:p-3">
|
||||||
<GameCanvas
|
<GameCanvas
|
||||||
ref={gameCanvasRef}
|
ref={gameCanvasRef}
|
||||||
key={attempt}
|
key={attempt}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const MONSTER_Y = 155;
|
|||||||
const HP_BAR_Y = 258;
|
const HP_BAR_Y = 258;
|
||||||
const HP_BAR_WIDTH = 500;
|
const HP_BAR_WIDTH = 500;
|
||||||
const HEROES_START_Y = 308;
|
const HEROES_START_Y = 308;
|
||||||
const HERO_ROW_GAP = 28;
|
const HERO_ROW_GAP = 38;
|
||||||
|
|
||||||
export class ClickerScene extends Phaser.Scene {
|
export class ClickerScene extends Phaser.Scene {
|
||||||
private definition!: ClickerDefinition;
|
private definition!: ClickerDefinition;
|
||||||
@@ -80,7 +80,11 @@ export class ClickerScene extends Phaser.Scene {
|
|||||||
this.heroLevels = this.definition.heroes.map((_, i) => initialState?.heroLevels[i] ?? 0);
|
this.heroLevels = this.definition.heroes.map((_, i) => initialState?.heroLevels[i] ?? 0);
|
||||||
this.finished = false;
|
this.finished = false;
|
||||||
|
|
||||||
this.cameras.main.setBackgroundColor(COLORS.bg);
|
this.add
|
||||||
|
.graphics()
|
||||||
|
.fillGradientStyle(0x23264a, 0x23264a, COLORS.bg, COLORS.bg, 1)
|
||||||
|
.fillRect(0, 0, 960, 720)
|
||||||
|
.setDepth(-10);
|
||||||
|
|
||||||
this.levelText = this.add.text(20, 15, "", { fontFamily: "sans-serif", fontSize: "20px", color: "#a3a8c6" });
|
this.levelText = this.add.text(20, 15, "", { fontFamily: "sans-serif", fontSize: "20px", color: "#a3a8c6" });
|
||||||
this.goldText = this.add
|
this.goldText = this.add
|
||||||
@@ -117,11 +121,11 @@ export class ClickerScene extends Phaser.Scene {
|
|||||||
|
|
||||||
this.definition.heroes.forEach((_, index) => {
|
this.definition.heroes.forEach((_, index) => {
|
||||||
const y = HEROES_START_Y + index * HERO_ROW_GAP;
|
const y = HEROES_START_Y + index * HERO_ROW_GAP;
|
||||||
const bg = this.add.rectangle(480, y, 780, 26, COLORS.heroRow).setStrokeStyle(1, 0x4f46e5).setInteractive({
|
const bg = this.add.rectangle(480, y, 780, 34, COLORS.heroRow).setStrokeStyle(1, 0x4f46e5).setInteractive({
|
||||||
useHandCursor: true,
|
useHandCursor: true,
|
||||||
});
|
});
|
||||||
const label = this.add
|
const label = this.add
|
||||||
.text(480, y, "", { fontFamily: "sans-serif", fontSize: "17px", color: "#eef0fb" })
|
.text(480, y, "", { fontFamily: "sans-serif", fontSize: "20px", color: "#eef0fb" })
|
||||||
.setOrigin(0.5);
|
.setOrigin(0.5);
|
||||||
bg.on("pointerdown", () => this.buyHero(index));
|
bg.on("pointerdown", () => this.buyHero(index));
|
||||||
this.heroRows.push({ bg, label });
|
this.heroRows.push({ bg, label });
|
||||||
|
|||||||
@@ -102,7 +102,11 @@ export class MazeScene extends Phaser.Scene {
|
|||||||
this.finished = false;
|
this.finished = false;
|
||||||
this.desiredDir = null;
|
this.desiredDir = null;
|
||||||
this.currentDir = null;
|
this.currentDir = null;
|
||||||
this.cameras.main.setBackgroundColor(COLORS.bg);
|
this.add
|
||||||
|
.graphics()
|
||||||
|
.fillGradientStyle(0x14162a, 0x14162a, COLORS.bg, COLORS.bg, 1)
|
||||||
|
.fillRect(0, 0, 960, 720)
|
||||||
|
.setDepth(-10);
|
||||||
|
|
||||||
const initialState = this.registry.get("initialState") as MazeProgress | undefined;
|
const initialState = this.registry.get("initialState") as MazeProgress | undefined;
|
||||||
this.collectedKeys = new Set(initialState?.collectedKeys ?? []);
|
this.collectedKeys = new Set(initialState?.collectedKeys ?? []);
|
||||||
@@ -112,7 +116,7 @@ export class MazeScene extends Phaser.Scene {
|
|||||||
this.grid = this.definition.grid;
|
this.grid = this.definition.grid;
|
||||||
this.rows = this.grid.length;
|
this.rows = this.grid.length;
|
||||||
this.cols = this.grid[0].length;
|
this.cols = this.grid[0].length;
|
||||||
this.cellSize = Math.max(10, Math.min(34, Math.floor(900 / this.cols), Math.floor(420 / this.rows)));
|
this.cellSize = Math.max(10, Math.min(40, Math.floor(900 / this.cols), Math.floor(550 / this.rows)));
|
||||||
this.offsetX = 480 - (this.cols * this.cellSize) / 2;
|
this.offsetX = 480 - (this.cols * this.cellSize) / 2;
|
||||||
this.offsetY = 130;
|
this.offsetY = 130;
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,11 @@ export class QuizScene extends Phaser.Scene {
|
|||||||
this.usedQuestionKeys = new Set();
|
this.usedQuestionKeys = new Set();
|
||||||
this.finished = false;
|
this.finished = false;
|
||||||
|
|
||||||
this.cameras.main.setBackgroundColor(COLORS.bg);
|
this.add
|
||||||
|
.graphics()
|
||||||
|
.fillGradientStyle(0x23264a, 0x23264a, COLORS.bg, COLORS.bg, 1)
|
||||||
|
.fillRect(0, 0, 960, 720)
|
||||||
|
.setDepth(-10);
|
||||||
|
|
||||||
this.levelText = this.add.text(30, 18, "", { fontFamily: "sans-serif", fontSize: "22px", color: "#a3a8c6" });
|
this.levelText = this.add.text(30, 18, "", { fontFamily: "sans-serif", fontSize: "22px", color: "#a3a8c6" });
|
||||||
this.prizeText = this.add
|
this.prizeText = this.add
|
||||||
@@ -113,16 +117,16 @@ export class QuizScene extends Phaser.Scene {
|
|||||||
this.walkAwayButton.on("pointerdown", () => this.walkAway());
|
this.walkAwayButton.on("pointerdown", () => this.walkAway());
|
||||||
|
|
||||||
const lifelineDefs: { kind: LifelineKind; x: number; label: string }[] = [
|
const lifelineDefs: { kind: LifelineKind; x: number; label: string }[] = [
|
||||||
{ kind: "fiftyFifty", x: 330, label: "50:50" },
|
{ kind: "fiftyFifty", x: 300, label: "50:50" },
|
||||||
{ kind: "audience", x: 480, label: "Зал" },
|
{ kind: "audience", x: 480, label: "Зал" },
|
||||||
{ kind: "phone", x: 630, label: "Звонок" },
|
{ kind: "phone", x: 660, label: "Звонок" },
|
||||||
];
|
];
|
||||||
lifelineDefs.forEach(({ kind, x, label }) => {
|
lifelineDefs.forEach(({ kind, x, label }) => {
|
||||||
const bg = this.add
|
const bg = this.add
|
||||||
.rectangle(x, 425, 130, 36, COLORS.lifeline)
|
.rectangle(x, 520, 150, 46, COLORS.lifeline)
|
||||||
.setStrokeStyle(1, 0x4f46e5)
|
.setStrokeStyle(1, 0x4f46e5)
|
||||||
.setInteractive({ useHandCursor: true });
|
.setInteractive({ useHandCursor: true });
|
||||||
this.add.text(x, 425, label, { fontFamily: "sans-serif", fontSize: "19px", color: "#eef0fb" }).setOrigin(0.5);
|
this.add.text(x, 520, label, { fontFamily: "sans-serif", fontSize: "21px", color: "#eef0fb" }).setOrigin(0.5);
|
||||||
bg.on("pointerdown", () => this.useLifeline(kind));
|
bg.on("pointerdown", () => this.useLifeline(kind));
|
||||||
this.lifelineButtons.set(kind, bg);
|
this.lifelineButtons.set(kind, bg);
|
||||||
});
|
});
|
||||||
@@ -170,20 +174,20 @@ export class QuizScene extends Phaser.Scene {
|
|||||||
|
|
||||||
private renderOptions() {
|
private renderOptions() {
|
||||||
this.clearOptions();
|
this.clearOptions();
|
||||||
const startY = 175;
|
const startY = 200;
|
||||||
const gap = 58;
|
const gap = 76;
|
||||||
|
|
||||||
this.shuffledOptions.forEach((option, i) => {
|
this.shuffledOptions.forEach((option, i) => {
|
||||||
const y = startY + i * gap;
|
const y = startY + i * gap;
|
||||||
const hidden = this.hiddenIndices.has(i);
|
const hidden = this.hiddenIndices.has(i);
|
||||||
const bg = this.add.rectangle(480, y, 780, 48, hidden ? COLORS.optionHidden : COLORS.option).setStrokeStyle(
|
const bg = this.add.rectangle(480, y, 780, 64, hidden ? COLORS.optionHidden : COLORS.option).setStrokeStyle(
|
||||||
1,
|
1,
|
||||||
hidden ? 0x292d4a : 0x4f46e5,
|
hidden ? 0x292d4a : 0x4f46e5,
|
||||||
);
|
);
|
||||||
const label = this.add
|
const label = this.add
|
||||||
.text(140, y, hidden ? "" : `${OPTION_LETTERS[i]}: ${option}`, {
|
.text(140, y, hidden ? "" : `${OPTION_LETTERS[i]}: ${option}`, {
|
||||||
fontFamily: "sans-serif",
|
fontFamily: "sans-serif",
|
||||||
fontSize: "22px",
|
fontSize: "24px",
|
||||||
color: "#eef0fb",
|
color: "#eef0fb",
|
||||||
})
|
})
|
||||||
.setOrigin(0, 0.5);
|
.setOrigin(0, 0.5);
|
||||||
@@ -283,10 +287,10 @@ export class QuizScene extends Phaser.Scene {
|
|||||||
|
|
||||||
private showAudienceOverlay(shares: Map<number, number>) {
|
private showAudienceOverlay(shares: Map<number, number>) {
|
||||||
this.clearOverlay();
|
this.clearOverlay();
|
||||||
const bg = this.add.rectangle(480, 505, 780, 70, COLORS.overlayBg).setStrokeStyle(1, 0x292d4a);
|
const bg = this.add.rectangle(480, 615, 780, 90, COLORS.overlayBg).setStrokeStyle(1, 0x292d4a);
|
||||||
this.overlayObjects.push(bg);
|
this.overlayObjects.push(bg);
|
||||||
const title = this.add
|
const title = this.add
|
||||||
.text(480, 478, "Помощь зала", { fontFamily: "sans-serif", fontSize: "17px", color: "#a3a8c6" })
|
.text(480, 585, "Помощь зала", { fontFamily: "sans-serif", fontSize: "17px", color: "#a3a8c6" })
|
||||||
.setOrigin(0.5);
|
.setOrigin(0.5);
|
||||||
this.overlayObjects.push(title);
|
this.overlayObjects.push(title);
|
||||||
|
|
||||||
@@ -296,7 +300,7 @@ export class QuizScene extends Phaser.Scene {
|
|||||||
entries.forEach(([optIndex, pct], i) => {
|
entries.forEach(([optIndex, pct], i) => {
|
||||||
const x = 480 - barAreaWidth / 2 + i * (barAreaWidth / entries.length) + barWidth / 2 + 8;
|
const x = 480 - barAreaWidth / 2 + i * (barAreaWidth / entries.length) + barWidth / 2 + 8;
|
||||||
const label = this.add
|
const label = this.add
|
||||||
.text(x, 495, `${OPTION_LETTERS[optIndex]}: ${Math.round(pct * 100)}%`, {
|
.text(x, 605, `${OPTION_LETTERS[optIndex]}: ${Math.round(pct * 100)}%`, {
|
||||||
fontFamily: "sans-serif",
|
fontFamily: "sans-serif",
|
||||||
fontSize: "18px",
|
fontSize: "18px",
|
||||||
color: "#eef0fb",
|
color: "#eef0fb",
|
||||||
@@ -304,7 +308,7 @@ export class QuizScene extends Phaser.Scene {
|
|||||||
.setOrigin(0.5);
|
.setOrigin(0.5);
|
||||||
this.overlayObjects.push(label);
|
this.overlayObjects.push(label);
|
||||||
const bar = this.add
|
const bar = this.add
|
||||||
.rectangle(x, 522, Math.max(6, barWidth * pct), 10, 0x818cf8)
|
.rectangle(x, 635, Math.max(6, barWidth * pct), 10, 0x818cf8)
|
||||||
.setOrigin(0.5);
|
.setOrigin(0.5);
|
||||||
this.overlayObjects.push(bar);
|
this.overlayObjects.push(bar);
|
||||||
});
|
});
|
||||||
@@ -312,10 +316,10 @@ export class QuizScene extends Phaser.Scene {
|
|||||||
|
|
||||||
private showPhoneOverlay(suggestedIndex: number) {
|
private showPhoneOverlay(suggestedIndex: number) {
|
||||||
this.clearOverlay();
|
this.clearOverlay();
|
||||||
const bg = this.add.rectangle(480, 500, 780, 60, COLORS.overlayBg).setStrokeStyle(1, 0x292d4a);
|
const bg = this.add.rectangle(480, 615, 780, 70, COLORS.overlayBg).setStrokeStyle(1, 0x292d4a);
|
||||||
this.overlayObjects.push(bg);
|
this.overlayObjects.push(bg);
|
||||||
const text = this.add
|
const text = this.add
|
||||||
.text(480, 500, `Друг на телефоне: «Я почти уверен, это ${OPTION_LETTERS[suggestedIndex]}»`, {
|
.text(480, 615, `Друг на телефоне: «Я почти уверен, это ${OPTION_LETTERS[suggestedIndex]}»`, {
|
||||||
fontFamily: "sans-serif",
|
fontFamily: "sans-serif",
|
||||||
fontSize: "19px",
|
fontSize: "19px",
|
||||||
color: "#eef0fb",
|
color: "#eef0fb",
|
||||||
|
|||||||
@@ -54,11 +54,15 @@ export class SnakeScene extends Phaser.Scene {
|
|||||||
this.onProgress = this.registry.get("onProgress");
|
this.onProgress = this.registry.get("onProgress");
|
||||||
this.finished = false;
|
this.finished = false;
|
||||||
|
|
||||||
this.cameras.main.setBackgroundColor(COLORS.bg);
|
this.add
|
||||||
|
.graphics()
|
||||||
|
.fillGradientStyle(0x14162a, 0x14162a, COLORS.bg, COLORS.bg, 1)
|
||||||
|
.fillRect(0, 0, 960, 720)
|
||||||
|
.setDepth(-10);
|
||||||
|
|
||||||
this.width = this.definition.width;
|
this.width = this.definition.width;
|
||||||
this.height = this.definition.height;
|
this.height = this.definition.height;
|
||||||
this.cellSize = Math.max(8, Math.min(30, Math.floor(920 / this.width), Math.floor(460 / this.height)));
|
this.cellSize = Math.max(8, Math.min(36, Math.floor(920 / this.width), Math.floor(580 / this.height)));
|
||||||
this.offsetX = 480 - (this.width * this.cellSize) / 2;
|
this.offsetX = 480 - (this.width * this.cellSize) / 2;
|
||||||
this.offsetY = 110;
|
this.offsetY = 110;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user