diff --git a/src/scenes/MatchScene.ts b/src/scenes/MatchScene.ts index 68de549..654a7b8 100644 --- a/src/scenes/MatchScene.ts +++ b/src/scenes/MatchScene.ts @@ -925,64 +925,111 @@ export class MatchScene extends Phaser.Scene { } private buildTouchZones(): void { - // 3 touch zones внизу экрана для mobile + desktop click. - // Visual: полупрозрачные overlay'и; реагируют на pointerdown. - // Booster — placeholder в Phase 2, реальный effect в Phase 3. - const zoneH = 220; - const zoneY = GAME_HEIGHT - zoneH / 2; - const boosterW = 140; - const sideW = (GAME_WIDTH - boosterW) / 2; + // Corner buttons (v3.12) — L/R + BOOST. Disk-форма, ≥100px диаметр + // (mobile-friendly), в нижних углах вне playfield/guard rails. + // Те же handlers что и keyboard A/D/S. В aiming-фазе L/R = rotate aim. - // Left zone — в aiming-фазе rotate aim вместо flipper (mirror keyboard logic) - const leftZone = this.add.rectangle(sideW / 2, zoneY, sideW - 8, zoneH, PALETTE.player, 0.06); - leftZone.setStrokeStyle(1, PALETTE.player, 0.25); - leftZone.setInteractive(); - leftZone.on('pointerdown', () => { - if (this.booster.getState() === 'aiming') { - this.booster.rotateAim(-1); - } else { - this.playerFlipperLeft.activate(); - } - this.tweens.add({ targets: leftZone, fillAlpha: 0.22, duration: 60, yoyo: true }); - }); - this.add - .text(sideW / 2, zoneY, '◀ L', { - fontFamily: 'monospace', fontSize: '20px', color: '#ff006e', fontStyle: 'bold', - }) - .setOrigin(0.5).setAlpha(0.7); + // Bottom Y — около нижнего края, ниже guard rail и playfield + const buttonY = GAME_HEIGHT - 70; + const sideButtonR = 52; // диаметр 104 — выше mobile threshold 72-88 + const centerButtonR = 44; // BOOST чуть меньше, центр - // Right zone - const rightX = GAME_WIDTH - sideW / 2; - const rightZone = this.add.rectangle(rightX, zoneY, sideW - 8, zoneH, PALETTE.player, 0.06); - rightZone.setStrokeStyle(1, PALETTE.player, 0.25); - rightZone.setInteractive(); - rightZone.on('pointerdown', () => { - if (this.booster.getState() === 'aiming') { - this.booster.rotateAim(1); - } else { - this.playerFlipperRight.activate(); - } - this.tweens.add({ targets: rightZone, fillAlpha: 0.22, duration: 60, yoyo: true }); + // Player L — левый нижний угол + this.createCornerButton({ + x: 70, + y: buttonY, + radius: sideButtonR, + color: PALETTE.player, + label: 'L', + arrow: '◀', + onTap: () => { + if (this.booster.getState() === 'aiming') { + this.booster.rotateAim(-1); + } else { + this.playerFlipperLeft.activate(); + } + }, }); - this.add - .text(rightX, zoneY, 'R ▶', { - fontFamily: 'monospace', fontSize: '20px', color: '#ff006e', fontStyle: 'bold', - }) - .setOrigin(0.5).setAlpha(0.7); - // Booster zone (центр, узкая) — Phase 2 placeholder - const boosterZone = this.add.rectangle(GAME_WIDTH / 2, zoneY, boosterW, zoneH * 0.5, PALETTE.accent, 0.05); - boosterZone.setStrokeStyle(1, PALETTE.accent, 0.3); - boosterZone.setInteractive(); - boosterZone.on('pointerdown', () => { - this.tweens.add({ targets: boosterZone, fillAlpha: 0.25, duration: 80, yoyo: true }); - this.handleBoosterTap(); + // Player R — правый нижний угол + this.createCornerButton({ + x: GAME_WIDTH - 70, + y: buttonY, + radius: sideButtonR, + color: PALETTE.player, + label: 'R', + arrow: '▶', + onTap: () => { + if (this.booster.getState() === 'aiming') { + this.booster.rotateAim(1); + } else { + this.playerFlipperRight.activate(); + } + }, }); + + // BOOST — центр, отдельной кнопкой + this.createCornerButton({ + x: GAME_WIDTH / 2, + y: buttonY, + radius: centerButtonR, + color: PALETTE.accent, + label: 'B', + onTap: () => this.handleBoosterTap(), + }); + } + + private createCornerButton(args: { + x: number; + y: number; + radius: number; + color: number; + label: string; + arrow?: string; // optional rotation hint ◀/▶ + onTap: () => void; + }): void { + const { x, y, radius, color, label, arrow, onTap } = args; + const colorHex = `#${color.toString(16).padStart(6, '0')}`; + + // Outer glow (двухслойный neon outline) + const glow = this.add.circle(x, y, radius + 4, color, 0.12); + glow.setStrokeStyle(2, color, 0.4); + + // Основной disk + const disk = this.add.circle(x, y, radius, color, 0.18); + disk.setStrokeStyle(3, color, 0.85); + disk.setInteractive({ useHandCursor: true }); + disk.on('pointerdown', () => { + onTap(); + this.tweens.add({ + targets: [glow, disk], + scale: { from: 1.0, to: 0.88 }, + duration: 60, + yoyo: true, + }); + }); + + // Label (L / R / B) this.add - .text(GAME_WIDTH / 2, zoneY, 'BOOST', { - fontFamily: 'monospace', fontSize: '18px', color: '#ffbe0b', fontStyle: 'bold', + .text(x, y - (arrow ? 6 : 0), label, { + fontFamily: 'monospace', + fontSize: '32px', + color: colorHex, + fontStyle: 'bold', }) - .setOrigin(0.5).setAlpha(0.7); + .setOrigin(0.5); + + // Arrow hint под label (только для L/R) + if (arrow) { + this.add + .text(x, y + 18, arrow, { + fontFamily: 'monospace', + fontSize: '14px', + color: colorHex, + }) + .setOrigin(0.5) + .setAlpha(0.6); + } } private buildHUD(): void {