feat(graphics): add procedural assets and visual polish to all scenes
- Add AssetLoader with procedurally generated sprites (rock, paper, scissors, avatars, VS badge, particles, ring glow) - Redesign BootScene with title glow, rotating avatars, ambient particles - Redesign LobbyScene with choice icons and improved layout - Redesign ArenaScene with player avatars, choice icons, visual feedback - Redesign ResultScene with large choice icons, VS badge, impact particles - Fix play page layout: remove debug magenta background Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
419ed6eef0
commit
fb4c8ffde6
6 changed files with 262 additions and 103 deletions
|
|
@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from 'react';
|
|||
export default function PlayPage() {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const gameRef = useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -13,11 +14,11 @@ export default function PlayPage() {
|
|||
try {
|
||||
const { initGame } = await import('@/phaser/Game');
|
||||
if (!mounted || !containerRef.current) return;
|
||||
// Delay slightly to ensure container has layout
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
await new Promise((r) => setTimeout(r, 200));
|
||||
if (!mounted || !containerRef.current) return;
|
||||
const game = initGame('phaser-container');
|
||||
gameRef.current = game;
|
||||
setLoaded(true);
|
||||
} catch (err: any) {
|
||||
console.error('Failed to init Phaser game:', err);
|
||||
setError(err?.message || 'Erreur de chargement du jeu');
|
||||
|
|
@ -35,22 +36,28 @@ export default function PlayPage() {
|
|||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="w-full h-screen flex flex-col items-center justify-center bg-slate-950 text-slate-200 gap-4 px-6">
|
||||
<p className="text-red-400 font-bold text-lg">
|
||||
Erreur de chargement
|
||||
</p>
|
||||
<div className="w-full flex flex-col items-center justify-center bg-slate-950 text-slate-200 gap-4 px-6"
|
||||
style={{ height: 'calc(100vh - 64px)' }}>
|
||||
<p className="text-red-400 font-bold text-lg">Erreur de chargement</p>
|
||||
<p className="text-slate-400 text-sm">{error}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full h-screen flex items-center justify-center bg-slate-950">
|
||||
<div
|
||||
id="phaser-container"
|
||||
ref={containerRef}
|
||||
style={{ width: '100%', height: '100%', minHeight: '600px' }}
|
||||
></div>
|
||||
<div
|
||||
ref={containerRef}
|
||||
id="phaser-container"
|
||||
className="relative w-full bg-slate-950"
|
||||
style={{ height: 'calc(100vh - 64px)', minHeight: '600px' }}
|
||||
>
|
||||
{!loaded && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-slate-950 z-10">
|
||||
<p className="text-cyan-400 text-lg font-bold animate-pulse">
|
||||
Chargement de l'arène...
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
106
apps/web/src/phaser/objects/AssetLoader.ts
Normal file
106
apps/web/src/phaser/objects/AssetLoader.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import * as Phaser from 'phaser';
|
||||
|
||||
export function generateAssets(scene: Phaser.Scene) {
|
||||
const g = scene.make.graphics({ x: 0, y: 0 });
|
||||
|
||||
// Rock — golem-like hexagon
|
||||
g.fillStyle(0x64748b, 1);
|
||||
g.fillCircle(64, 64, 56);
|
||||
g.fillStyle(0x475569, 1);
|
||||
g.fillCircle(64, 64, 44);
|
||||
g.fillStyle(0x94a3b8, 1);
|
||||
g.fillCircle(48, 48, 12);
|
||||
g.fillCircle(80, 48, 12);
|
||||
g.fillStyle(0x1e293b, 1);
|
||||
g.fillCircle(48, 48, 6);
|
||||
g.fillCircle(80, 48, 6);
|
||||
g.generateTexture('rock', 128, 128);
|
||||
g.clear();
|
||||
|
||||
// Paper — flowing scroll / leaf shape
|
||||
g.fillStyle(0x22c55e, 1);
|
||||
g.beginPath();
|
||||
g.moveTo(64, 8);
|
||||
g.lineTo(120, 40);
|
||||
g.lineTo(100, 120);
|
||||
g.lineTo(28, 120);
|
||||
g.lineTo(8, 40);
|
||||
g.closePath();
|
||||
g.fillPath();
|
||||
g.fillStyle(0x15803d, 1);
|
||||
g.beginPath();
|
||||
g.moveTo(64, 24);
|
||||
g.lineTo(100, 48);
|
||||
g.lineTo(88, 104);
|
||||
g.lineTo(40, 104);
|
||||
g.lineTo(28, 48);
|
||||
g.closePath();
|
||||
g.fillPath();
|
||||
g.generateTexture('paper', 128, 128);
|
||||
g.clear();
|
||||
|
||||
// Scissors — blade crossed shape
|
||||
g.lineStyle(8, 0xef4444, 1);
|
||||
g.beginPath();
|
||||
g.moveTo(20, 20);
|
||||
g.lineTo(108, 108);
|
||||
g.strokePath();
|
||||
g.beginPath();
|
||||
g.moveTo(108, 20);
|
||||
g.lineTo(20, 108);
|
||||
g.strokePath();
|
||||
g.fillStyle(0xfca5a5, 1);
|
||||
g.fillCircle(64, 64, 18);
|
||||
g.fillStyle(0xef4444, 1);
|
||||
g.fillCircle(64, 64, 10);
|
||||
g.generateTexture('scissors', 128, 128);
|
||||
g.clear();
|
||||
|
||||
// Avatar placeholder (glowing orb)
|
||||
g.fillStyle(0x06b6d4, 1);
|
||||
g.fillCircle(64, 64, 60);
|
||||
g.fillStyle(0x22d3ee, 1);
|
||||
g.fillCircle(64, 64, 48);
|
||||
g.fillStyle(0x0f172a, 1);
|
||||
g.fillCircle(64, 64, 36);
|
||||
g.generateTexture('avatar', 128, 128);
|
||||
g.clear();
|
||||
|
||||
// AI avatar (purple orb)
|
||||
g.fillStyle(0xa855f7, 1);
|
||||
g.fillCircle(64, 64, 60);
|
||||
g.fillStyle(0xc084fc, 1);
|
||||
g.fillCircle(64, 64, 48);
|
||||
g.fillStyle(0x0f172a, 1);
|
||||
g.fillCircle(64, 64, 36);
|
||||
g.generateTexture('avatarAI', 128, 128);
|
||||
g.clear();
|
||||
|
||||
// VS badge
|
||||
g.fillStyle(0xf59e0b, 1);
|
||||
g.fillCircle(32, 32, 28);
|
||||
g.fillStyle(0x78350f, 1);
|
||||
g.fillCircle(32, 32, 22);
|
||||
g.generateTexture('vs', 64, 64);
|
||||
g.clear();
|
||||
|
||||
// Spark particle
|
||||
g.fillStyle(0xffffff, 1);
|
||||
g.fillCircle(4, 4, 4);
|
||||
g.generateTexture('spark', 8, 8);
|
||||
g.clear();
|
||||
|
||||
// Flare particle
|
||||
g.fillStyle(0xffffff, 1);
|
||||
g.fillCircle(8, 8, 8);
|
||||
g.generateTexture('flare', 16, 16);
|
||||
g.clear();
|
||||
|
||||
// Ring glow
|
||||
g.lineStyle(4, 0x22d3ee, 1);
|
||||
g.strokeCircle(64, 64, 60);
|
||||
g.generateTexture('ring', 128, 128);
|
||||
g.clear();
|
||||
|
||||
g.destroy();
|
||||
}
|
||||
|
|
@ -28,6 +28,10 @@ export class ArenaScene extends Phaser.Scene {
|
|||
private actionButton!: UIButton;
|
||||
private particles!: any;
|
||||
private phase: 'commit' | 'reveal' = 'commit';
|
||||
private p1Avatar!: Phaser.GameObjects.Image;
|
||||
private p2Avatar!: Phaser.GameObjects.Image;
|
||||
private p1Label!: Phaser.GameObjects.Text;
|
||||
private p2Label!: Phaser.GameObjects.Text;
|
||||
|
||||
constructor() {
|
||||
super({ key: 'ArenaScene' });
|
||||
|
|
@ -57,11 +61,11 @@ export class ArenaScene extends Phaser.Scene {
|
|||
create() {
|
||||
const { width, height } = this.scale;
|
||||
|
||||
// Background gradient feel via dark rects
|
||||
this.add.rectangle(width / 2, height / 2, width, height, 0x0f172a);
|
||||
|
||||
// Title
|
||||
this.add
|
||||
.text(width / 2, 40, `TABLE #${this.matchId.slice(-6)}`, {
|
||||
.text(width / 2, 36, `TABLE #${this.matchId.slice(-6)}`, {
|
||||
fontSize: '28px',
|
||||
fontFamily: 'ui-sans-serif, system-ui, sans-serif',
|
||||
color: '#f8fafc',
|
||||
|
|
@ -70,43 +74,73 @@ export class ArenaScene extends Phaser.Scene {
|
|||
.setOrigin(0.5);
|
||||
|
||||
this.infoText = this.add
|
||||
.text(width / 2, 80, `Mise: 0.01 ETH | En attente du commit`, {
|
||||
.text(width / 2, 76, `Mise: ${this.betAmount} ETH | En attente du commit`, {
|
||||
fontSize: '16px',
|
||||
color: '#94a3b8',
|
||||
})
|
||||
.setOrigin(0.5);
|
||||
|
||||
this.statusText = this.add
|
||||
.text(width / 2, height / 2, 'CHOISIS TON SIGNE', {
|
||||
fontSize: '32px',
|
||||
.text(width / 2, height / 2 - 40, 'CHOISIS TON SIGNE', {
|
||||
fontSize: '36px',
|
||||
color: '#22d3ee',
|
||||
fontStyle: 'bold',
|
||||
})
|
||||
.setOrigin(0.5);
|
||||
|
||||
// Choice buttons
|
||||
// Player avatars
|
||||
const p1Name = this.isPlayer1 ? 'Toi' : (this.isAIMatch ? 'IA' : 'Adversaire');
|
||||
const p2Name = this.isPlayer1 ? (this.isAIMatch ? 'IA' : 'Adversaire') : 'Toi';
|
||||
|
||||
this.p1Avatar = this.add.image(140, height / 2 - 40, 'avatar').setScale(1.0);
|
||||
this.p2Avatar = this.add.image(width - 140, height / 2 - 40, this.isAIMatch ? 'avatarAI' : 'avatar').setScale(1.0);
|
||||
|
||||
// Glowing rings around avatars
|
||||
this.add.image(140, height / 2 - 40, 'ring').setScale(1.1).setAlpha(0.6);
|
||||
this.add.image(width - 140, height / 2 - 40, 'ring').setScale(1.1).setAlpha(0.6);
|
||||
|
||||
this.p1Label = this.add.text(140, height / 2 + 50, p1Name, { fontSize: '16px', color: '#cbd5e1' }).setOrigin(0.5);
|
||||
this.p2Label = this.add.text(width - 140, height / 2 + 50, p2Name, { fontSize: '16px', color: '#cbd5e1' }).setOrigin(0.5);
|
||||
|
||||
// Choice buttons with icons
|
||||
const choices = [
|
||||
{ label: 'PIERRE', value: Choice.Rock },
|
||||
{ label: 'FEUILLE', value: Choice.Paper },
|
||||
{ label: 'CISEAUX', value: Choice.Scissors },
|
||||
{ label: 'PIERRE', value: Choice.Rock, tex: 'rock' },
|
||||
{ label: 'FEUILLE', value: Choice.Paper, tex: 'paper' },
|
||||
{ label: 'CISEAUX', value: Choice.Scissors, tex: 'scissors' },
|
||||
];
|
||||
|
||||
choices.forEach((c, i) => {
|
||||
const btn = new UIButton(this, width / 2 - 280 + i * 280, height / 2 + 60, c.label, () => {
|
||||
const x = width / 2 - 280 + i * 280;
|
||||
const y = height / 2 + 120;
|
||||
const icon = this.add.image(x, y - 50, c.tex).setScale(0.6).setAlpha(0.8);
|
||||
const btn = new UIButton(this, x, y, c.label, () => {
|
||||
this.selectChoice(c.value);
|
||||
// Highlight selected icon
|
||||
choices.forEach((cc, ii) => {
|
||||
(this.children.getByName(`choiceIcon_${ii}`) as Phaser.GameObjects.Image)?.setAlpha(ii === i ? 1 : 0.3);
|
||||
});
|
||||
});
|
||||
icon.setName(`choiceIcon_${i}`);
|
||||
this.choiceButtons.push(btn);
|
||||
});
|
||||
|
||||
// Action button (commit / reveal)
|
||||
this.actionButton = new UIButton(this, width / 2, height / 2 + 160, 'Valider le Commit', () => {
|
||||
this.actionButton = new UIButton(this, width / 2, height / 2 + 240, 'Valider le Commit', () => {
|
||||
if (this.phase === 'commit') this.sendCommit();
|
||||
else if (this.phase === 'reveal') this.sendReveal();
|
||||
});
|
||||
}, 280);
|
||||
this.actionButton.setDisabled(true);
|
||||
|
||||
// Particle emitter for atmosphere (initially off)
|
||||
this.createParticles();
|
||||
// Atmosphere particles
|
||||
this.add.particles(0, 0, 'spark', {
|
||||
x: { min: 0, max: width },
|
||||
y: { min: 0, max: height },
|
||||
quantity: 1,
|
||||
frequency: 180,
|
||||
lifespan: 2000,
|
||||
alpha: { start: 0.2, end: 0 },
|
||||
scale: { start: 0.3, end: 0 },
|
||||
tint: [0x06b6d4, 0xa855f7],
|
||||
});
|
||||
|
||||
// Socket listeners
|
||||
socket.on('match:commitReceived', (data: { matchId: string }) => {
|
||||
|
|
@ -207,12 +241,6 @@ export class ArenaScene extends Phaser.Scene {
|
|||
this.cameras.main.zoomTo(1.1, 2000, 'Sine.easeInOut');
|
||||
this.cameras.main.shake(3000, 0.005);
|
||||
|
||||
// Intense particles
|
||||
if (this.particles) {
|
||||
this.particles.frequency = 30;
|
||||
this.particles.lifespan = 2000;
|
||||
}
|
||||
|
||||
// Flash effect
|
||||
const flash = this.add.rectangle(width / 2, height / 2, width, height, 0xffffff).setAlpha(0);
|
||||
this.tweens.add({
|
||||
|
|
@ -227,7 +255,7 @@ export class ArenaScene extends Phaser.Scene {
|
|||
// Countdown text
|
||||
let countdown = 4;
|
||||
const countdownText = this.add
|
||||
.text(width / 2, height / 2 - 100, `${countdown}`, {
|
||||
.text(width / 2, height / 2 - 120, `${countdown}`, {
|
||||
fontSize: '96px',
|
||||
color: '#f8fafc',
|
||||
fontStyle: '900',
|
||||
|
|
@ -282,25 +310,6 @@ export class ArenaScene extends Phaser.Scene {
|
|||
this.statusText.setText('Révélation envoyée...');
|
||||
}
|
||||
|
||||
createParticles() {
|
||||
const graphics = this.make.graphics({ x: 0, y: 0 });
|
||||
graphics.fillStyle(0xffffff, 1);
|
||||
graphics.fillCircle(4, 4, 4);
|
||||
graphics.generateTexture('spark', 8, 8);
|
||||
graphics.destroy();
|
||||
|
||||
this.particles = this.add.particles(0, 0, 'spark', {
|
||||
x: { min: 0, max: this.scale.width },
|
||||
y: { min: 0, max: this.scale.height },
|
||||
quantity: 1,
|
||||
frequency: 200,
|
||||
lifespan: 2000,
|
||||
alpha: { start: 0.2, end: 0 },
|
||||
scale: { start: 0.4, end: 0 },
|
||||
tint: [0x06b6d4, 0xa855f7],
|
||||
});
|
||||
}
|
||||
|
||||
shutdown() {
|
||||
socket.off('match:commitReceived');
|
||||
socket.off('match:suspenseStart');
|
||||
|
|
|
|||
|
|
@ -1,55 +1,68 @@
|
|||
import * as Phaser from 'phaser';
|
||||
import { UIButton } from '../objects/UIButton';
|
||||
import { generateAssets } from '../objects/AssetLoader';
|
||||
|
||||
export class BootScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'BootScene' });
|
||||
}
|
||||
|
||||
preload() {
|
||||
generateAssets(this);
|
||||
}
|
||||
|
||||
create() {
|
||||
const { width, height } = this.scale;
|
||||
|
||||
// Animated background gradient feel
|
||||
this.add.rectangle(width / 2, height / 2, width, height, 0x0f172a);
|
||||
|
||||
// Ambient particles
|
||||
this.add.particles(0, 0, 'flare', {
|
||||
x: { min: 0, max: width },
|
||||
y: { min: 0, max: height },
|
||||
quantity: 1,
|
||||
frequency: 80,
|
||||
lifespan: 4000,
|
||||
alpha: { start: 0.25, end: 0 },
|
||||
scale: { start: 0.6, end: 0 },
|
||||
tint: [0x06b6d4, 0xa855f7, 0xec4899],
|
||||
});
|
||||
|
||||
// Big glowing title
|
||||
this.add
|
||||
.text(width / 2, height / 2 - 80, 'THE ARENA', {
|
||||
fontSize: '64px',
|
||||
.text(width / 2, height / 2 - 120, 'THE ARENA', {
|
||||
fontSize: '80px',
|
||||
fontFamily: 'ui-sans-serif, system-ui, sans-serif',
|
||||
color: '#f8fafc',
|
||||
fontStyle: '900',
|
||||
})
|
||||
.setOrigin(0.5);
|
||||
.setOrigin(0.5)
|
||||
.setShadow(0, 0, '#06b6d4', 20, true, true);
|
||||
|
||||
this.add
|
||||
.text(width / 2, height / 2 - 20, 'RPS Royale', {
|
||||
fontSize: '24px',
|
||||
.text(width / 2, height / 2 - 40, 'RPS ROYALE', {
|
||||
fontSize: '28px',
|
||||
fontFamily: 'ui-sans-serif, system-ui, sans-serif',
|
||||
color: '#94a3b8',
|
||||
letterSpacing: 8,
|
||||
})
|
||||
.setOrigin(0.5);
|
||||
|
||||
new UIButton(this, width / 2, height / 2 + 80, 'Entrer dans l\'Arène', () => {
|
||||
// Decorative avatars rotating slowly
|
||||
const leftAvatar = this.add.image(width / 2 - 220, height / 2 + 40, 'avatar').setScale(0.9);
|
||||
const rightAvatar = this.add.image(width / 2 + 220, height / 2 + 40, 'avatarAI').setScale(0.9);
|
||||
this.tweens.add({ targets: leftAvatar, angle: 360, duration: 20000, repeat: -1, ease: 'Linear' });
|
||||
this.tweens.add({ targets: rightAvatar, angle: -360, duration: 20000, repeat: -1, ease: 'Linear' });
|
||||
|
||||
// VS badge
|
||||
this.add.image(width / 2, height / 2 + 40, 'vs').setScale(1.2);
|
||||
|
||||
new UIButton(this, width / 2, height / 2 + 180, 'Entrer dans\'Arène', () => {
|
||||
this.cameras.main.fadeOut(400, 0, 0, 0);
|
||||
this.time.delayedCall(400, () => {
|
||||
this.scene.start('LobbyScene');
|
||||
});
|
||||
});
|
||||
|
||||
// Simple particle effect for atmosphere
|
||||
const particles = this.add.particles(0, 0, 'flare', {
|
||||
x: { min: 0, max: width },
|
||||
y: { min: 0, max: height },
|
||||
quantity: 1,
|
||||
frequency: 100,
|
||||
lifespan: 3000,
|
||||
alpha: { start: 0.3, end: 0 },
|
||||
scale: { start: 0.5, end: 0 },
|
||||
tint: [0x06b6d4, 0xa855f7, 0xec4899],
|
||||
});
|
||||
|
||||
// Create a simple flare texture procedurally
|
||||
const graphics = this.make.graphics({ x: 0, y: 0, });
|
||||
graphics.fillStyle(0xffffff, 1);
|
||||
graphics.fillCircle(8, 8, 8);
|
||||
graphics.generateTexture('flare', 16, 16);
|
||||
graphics.destroy();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,14 +13,34 @@ export class LobbyScene extends Phaser.Scene {
|
|||
create() {
|
||||
const { width, height } = this.scale;
|
||||
|
||||
this.add.rectangle(width / 2, height / 2, width, height, 0x0f172a);
|
||||
|
||||
// Ambient particles
|
||||
this.add.particles(0, 0, 'spark', {
|
||||
x: { min: 0, max: width },
|
||||
y: { min: 0, max: height },
|
||||
quantity: 1,
|
||||
frequency: 120,
|
||||
lifespan: 3000,
|
||||
alpha: { start: 0.2, end: 0 },
|
||||
scale: { start: 0.3, end: 0 },
|
||||
tint: [0x06b6d4, 0xa855f7],
|
||||
});
|
||||
|
||||
this.add
|
||||
.text(width / 2, 60, 'LOBBY', {
|
||||
fontSize: '40px',
|
||||
.text(width / 2, 50, 'SALLES DE JEU', {
|
||||
fontSize: '44px',
|
||||
fontFamily: 'ui-sans-serif, system-ui, sans-serif',
|
||||
color: '#f8fafc',
|
||||
fontStyle: '900',
|
||||
})
|
||||
.setOrigin(0.5);
|
||||
.setOrigin(0.5)
|
||||
.setShadow(0, 0, '#22d3ee', 10, true, true);
|
||||
|
||||
// Decorative icons
|
||||
this.add.image(width / 2 - 260, 50, 'rock').setScale(0.35);
|
||||
this.add.image(width / 2, 50, 'paper').setScale(0.35);
|
||||
this.add.image(width / 2 + 260, 50, 'scissors').setScale(0.35);
|
||||
|
||||
this.statusText = this.add
|
||||
.text(width / 2, 120, '', {
|
||||
|
|
@ -29,17 +49,18 @@ export class LobbyScene extends Phaser.Scene {
|
|||
})
|
||||
.setOrigin(0.5);
|
||||
|
||||
new UIButton(this, width / 2 - 140, height / 2 + 80, 'Matchmaking (0.01 ETH)', () => {
|
||||
new UIButton(this, width / 2 - 200, height - 180, 'Matchmaking 0.01 ETH', () => {
|
||||
this.requestMatch('0.01');
|
||||
});
|
||||
}, 280);
|
||||
|
||||
new UIButton(this, width / 2 + 140, height / 2 + 80, 'Matchmaking (0.05 ETH)', () => {
|
||||
new UIButton(this, width / 2 + 200, height - 180, 'Matchmaking 0.05 ETH', () => {
|
||||
this.requestMatch('0.05');
|
||||
});
|
||||
}, 280);
|
||||
|
||||
new UIButton(this, width / 2, height / 2 + 160, 'Jouer contre l\'IA', () => {
|
||||
const aiBtn = new UIButton(this, width / 2, height - 90, 'Jouer contre l\'IA', () => {
|
||||
this.requestAIMatch('0.01');
|
||||
});
|
||||
}, 320, 64);
|
||||
aiBtn.setLabel('Jouer contre l\'IA');
|
||||
|
||||
socket.emit('lobby:join');
|
||||
|
||||
|
|
@ -58,7 +79,6 @@ export class LobbyScene extends Phaser.Scene {
|
|||
getAddress(): string {
|
||||
const eth = (window as any).ethereum;
|
||||
if (eth?.selectedAddress) return eth.selectedAddress;
|
||||
// Generate a guest address if no wallet
|
||||
let guest = (window as any).__guestAddress;
|
||||
if (!guest) {
|
||||
guest = '0x' + Array.from({ length: 40 }, () => Math.floor(Math.random() * 16).toString(16)).join('');
|
||||
|
|
@ -80,14 +100,13 @@ export class LobbyScene extends Phaser.Scene {
|
|||
}
|
||||
|
||||
renderTables(tables: any[]) {
|
||||
// Clear previous
|
||||
this.tablesText.forEach((t) => t.destroy());
|
||||
this.tablesText = [];
|
||||
|
||||
const { width } = this.scale;
|
||||
if (tables.length === 0) {
|
||||
const t = this.add
|
||||
.text(width / 2, 220, 'Aucune table active. Créez-en une !', { fontSize: '16px', color: '#64748b' })
|
||||
.text(width / 2, 200, 'Aucune table active. Créez-en une !', { fontSize: '16px', color: '#64748b' })
|
||||
.setOrigin(0.5);
|
||||
this.tablesText.push(t);
|
||||
return;
|
||||
|
|
@ -97,7 +116,7 @@ export class LobbyScene extends Phaser.Scene {
|
|||
const t = this.add
|
||||
.text(
|
||||
width / 2,
|
||||
200 + i * 40,
|
||||
180 + i * 44,
|
||||
`Table #${table.id.slice(0, 6)} — Mise: ${table.betAmount} ETH — Joueurs: ${table.players}/2`,
|
||||
{ fontSize: '16px', color: '#cbd5e1' }
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ interface ResultData {
|
|||
}
|
||||
|
||||
const choiceLabels = ['Pierre', 'Feuille', 'Ciseaux'];
|
||||
const choiceTextures = ['rock', 'paper', 'scissors'];
|
||||
|
||||
export class ResultScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
|
|
@ -39,19 +40,29 @@ export class ResultScene extends Phaser.Scene {
|
|||
color = '#ef4444';
|
||||
}
|
||||
|
||||
// Title
|
||||
this.add
|
||||
.text(width / 2, height / 2 - 140, resultText, {
|
||||
.text(width / 2, height / 2 - 180, resultText, {
|
||||
fontSize: '72px',
|
||||
fontFamily: 'ui-sans-serif, system-ui, sans-serif',
|
||||
color,
|
||||
fontStyle: '900',
|
||||
})
|
||||
.setOrigin(0.5);
|
||||
.setOrigin(0.5)
|
||||
.setShadow(0, 0, color, 20, true, true);
|
||||
|
||||
// Show both choices as big icons
|
||||
const p1Tex = choiceTextures[data.p1Choice];
|
||||
const p2Tex = choiceTextures[data.p2Choice];
|
||||
|
||||
this.add.image(width / 2 - 160, height / 2 - 40, p1Tex).setScale(1.4);
|
||||
this.add.image(width / 2 + 160, height / 2 - 40, p2Tex).setScale(1.4);
|
||||
this.add.image(width / 2, height / 2 - 40, 'vs').setScale(1.5);
|
||||
|
||||
this.add
|
||||
.text(
|
||||
width / 2,
|
||||
height / 2 - 60,
|
||||
height / 2 + 60,
|
||||
`Toi: ${choiceLabels[data.isPlayer1 ? data.p1Choice : data.p2Choice]} vs Adversaire: ${
|
||||
choiceLabels[data.isPlayer1 ? data.p2Choice : data.p1Choice]
|
||||
}`,
|
||||
|
|
@ -63,7 +74,7 @@ export class ResultScene extends Phaser.Scene {
|
|||
this.add
|
||||
.text(
|
||||
width / 2,
|
||||
height / 2,
|
||||
height / 2 + 110,
|
||||
isWinner ? `Gain: ${data.payout} ETH` : 'Mise perdue',
|
||||
{ fontSize: '28px', color: isWinner ? '#22c55e' : '#ef4444', fontStyle: 'bold' }
|
||||
)
|
||||
|
|
@ -71,13 +82,7 @@ export class ResultScene extends Phaser.Scene {
|
|||
}
|
||||
|
||||
// Impact particles
|
||||
const graphics = this.make.graphics({ x: 0, y: 0 });
|
||||
graphics.fillStyle(0xffffff, 1);
|
||||
graphics.fillCircle(4, 4, 4);
|
||||
graphics.generateTexture('resultSpark', 8, 8);
|
||||
graphics.destroy();
|
||||
|
||||
const emitter = this.add.particles(width / 2, height / 2 - 60, 'resultSpark', {
|
||||
const emitter = this.add.particles(width / 2, height / 2 - 60, 'spark', {
|
||||
speed: { min: 100, max: 300 },
|
||||
angle: { min: 0, max: 360 },
|
||||
quantity: 40,
|
||||
|
|
@ -88,7 +93,7 @@ export class ResultScene extends Phaser.Scene {
|
|||
});
|
||||
emitter?.explode(40);
|
||||
|
||||
new UIButton(this, width / 2, height / 2 + 140, 'Retour au Lobby', () => {
|
||||
new UIButton(this, width / 2, height / 2 + 220, 'Retour au Lobby', () => {
|
||||
this.cameras.main.fadeOut(400, 0, 0, 0);
|
||||
this.time.delayedCall(400, () => {
|
||||
this.scene.start('LobbyScene');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue