Replace text mute buttons with Phaser Graphics API speaker icon

Subagents consistently use this.add.text() for mute buttons because the
skill doc said "speaker icon" without providing code. Now SKILL.md has
complete drawMuteIcon() + _createMuteButton() code using fillRect,
fillTriangle, arc, and lineBetween that subagents can copy verbatim.

- SKILL.md: full Mute Button section with Graphics API drawing code
- game-creator.md, make-game.md: ban text buttons, reference skill code
- flappy-bird example: working reference implementation (UIScene, AudioBridge, GameState, EventBus)
- phaser-2d template: add AUDIO_TOGGLE_MUTE event

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
glitchtrend
2026-02-23 06:01:37 -08:00
parent 7459a72443
commit c75ab0520b
10 changed files with 146 additions and 7 deletions
@@ -1,4 +1,5 @@
import { eventBus, Events } from '../core/EventBus.js';
import { gameState } from '../core/GameState.js';
import { audioManager } from './AudioManager.js';
import { menuTheme, gameplayBGM, gameOverTheme } from './music.js';
import { flapSfx, scoreSfx, deathSfx, clickSfx } from './sfx.js';
@@ -16,4 +17,11 @@ export function initAudioBridge() {
eventBus.on(Events.BIRD_FLAP, () => flapSfx());
eventBus.on(Events.SCORE_CHANGED, () => scoreSfx());
eventBus.on(Events.BIRD_DIED, () => deathSfx());
// Mute toggle
eventBus.on(Events.AUDIO_TOGGLE_MUTE, () => {
gameState.isMuted = !gameState.isMuted;
try { localStorage.setItem('muted', gameState.isMuted); } catch (_) {}
if (gameState.isMuted) audioManager.stopMusic();
});
}
@@ -23,6 +23,7 @@ export const Events = {
MUSIC_GAMEPLAY: 'music:gameplay',
MUSIC_GAMEOVER: 'music:gameover',
MUSIC_STOP: 'music:stop',
AUDIO_TOGGLE_MUTE: 'audio:toggleMute',
};
class EventBus {
@@ -1,6 +1,7 @@
class GameState {
constructor() {
this.bestScore = 0;
this.isMuted = localStorage.getItem('muted') === 'true';
this.reset();
}
+49 -1
View File
@@ -1,6 +1,33 @@
import Phaser from 'phaser';
import { GAME, COLORS, TRANSITION, UI } from '../core/Constants.js';
import { eventBus, Events } from '../core/EventBus.js';
import { gameState } from '../core/GameState.js';
const ICON_SIZE = 16;
const MUTE_MARGIN = 12;
function drawMuteIcon(gfx, muted, size) {
gfx.clear();
const s = size;
gfx.fillStyle(0xffffff);
gfx.fillRect(-s * 0.15, -s * 0.15, s * 0.15, s * 0.3);
gfx.fillTriangle(-s * 0.15, -s * 0.3, -s * 0.15, s * 0.3, -s * 0.45, 0);
if (!muted) {
gfx.lineStyle(2, 0xffffff);
gfx.beginPath();
gfx.arc(0, 0, s * 0.2, -Math.PI / 4, Math.PI / 4);
gfx.strokePath();
gfx.beginPath();
gfx.arc(0, 0, s * 0.35, -Math.PI / 4, Math.PI / 4);
gfx.strokePath();
} else {
gfx.lineStyle(3, 0xff4444);
gfx.lineBetween(s * 0.05, -s * 0.25, s * 0.35, s * 0.25);
gfx.lineBetween(s * 0.05, s * 0.25, s * 0.35, -s * 0.25);
}
}
export class UIScene extends Phaser.Scene {
constructor() {
@@ -10,7 +37,6 @@ export class UIScene extends Phaser.Scene {
create() {
const cx = GAME.WIDTH / 2;
// Big centered score (Flappy Bird style)
this.scoreText = this.add.text(cx, GAME.HEIGHT * UI.SCORE_Y_RATIO, '0', {
fontSize: `${Math.round(GAME.HEIGHT * UI.SCORE_FONT_RATIO)}px`,
fontFamily: UI.FONT,
@@ -33,9 +59,31 @@ export class UIScene extends Phaser.Scene {
};
eventBus.on(Events.SCORE_CHANGED, this.onScoreChanged);
this._createMuteButton();
this.events.on('shutdown', () => {
eventBus.off(Events.SCORE_CHANGED, this.onScoreChanged);
});
}
_createMuteButton() {
const x = this.cameras.main.width - MUTE_MARGIN - ICON_SIZE;
const y = this.cameras.main.height - MUTE_MARGIN - ICON_SIZE;
this.muteBg = this.add.circle(x, y, ICON_SIZE + 4, 0x000000, 0.3)
.setInteractive({ useHandCursor: true })
.setDepth(100);
this.muteIcon = this.add.graphics().setDepth(100);
this.muteIcon.setPosition(x, y);
drawMuteIcon(this.muteIcon, gameState.isMuted, ICON_SIZE);
this.muteBg.on('pointerdown', () => this._toggleMute());
this.input.keyboard.on('keydown-M', () => this._toggleMute());
}
_toggleMute() {
eventBus.emit(Events.AUDIO_TOGGLE_MUTE);
drawMuteIcon(this.muteIcon, gameState.isMuted, ICON_SIZE);
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB