/** * SettingsScene.js * Settings screen with sound, music, and vibration toggles. */ const { SCREEN_WIDTH, SCREEN_HEIGHT, COLORS, SCENE, } = require('../base/GameGlobal'); const { t } = require('../i18n/I18n'); const SettingsScene = { _settings: { soundEnabled: true, musicEnabled: true, vibrationEnabled: true, }, _buttons: {}, enter() { // Load settings from storage try { const saved = wx.getStorageSync('game_settings'); if (saved) { this._settings = { ...this._settings, ...JSON.parse(saved) }; } } catch (e) { console.warn('[Settings] Failed to load settings:', e); } this._buttons = {}; }, exit() { // Save settings try { wx.setStorageSync('game_settings', JSON.stringify(this._settings)); } catch (e) { console.warn('[Settings] Failed to save settings:', e); } }, update(dt) {}, render(ctx) { // Background ctx.fillStyle = COLORS.MENU_BG; ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); const cx = SCREEN_WIDTH / 2; // Reset button map each frame so layout changes don't keep stale rects. this._buttons = {}; // Title const titleY = Math.max(48, SCREEN_HEIGHT * 0.08); ctx.fillStyle = COLORS.MENU_TITLE; ctx.font = 'bold 28px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(t('settings.title'), cx, titleY); // Back button (reserved at bottom so we can layout rows above it). const backH = 42; const backMarginBottom = 28; const backCenterY = SCREEN_HEIGHT - backMarginBottom - backH / 2; // Rows: nickname + 3 toggles. Distribute evenly between title and back btn. const rows = [ { type: 'toggle', key: 'soundEnabled', label: t('settings.sound'), icon: '🔊' }, { type: 'toggle', key: 'musicEnabled', label: t('settings.music'), icon: '🎵' }, { type: 'toggle', key: 'vibrationEnabled', label: t('settings.vibration'), icon: '📳' }, ]; const rowH = 50; const topPad = titleY + 36; const bottomPad = backCenterY - backH / 2 - 20; const availH = Math.max(rowH * rows.length, bottomPad - topPad); const step = Math.max(rowH + 8, availH / rows.length); const firstCenterY = topPad + step / 2; for (let i = 0; i < rows.length; i++) { const row = rows[i]; const cy = firstCenterY + i * step; this._renderToggle(ctx, cx, cy, row); } // Profile entry button (below the last toggle row) const profileY = firstCenterY + rows.length * step; this._renderProfileButton(ctx, cx, profileY); // Back button this._renderBackButton(ctx, cx, backCenterY); }, _renderToggle(ctx, cx, y, toggle) { const w = SCREEN_WIDTH * 0.7; const h = 50; const x = cx - w / 2; const isOn = this._settings[toggle.key]; // Store button rect this._buttons[toggle.key] = { x, y: y - h / 2, w, h }; // Background ctx.fillStyle = '#1e1e3a'; ctx.fillRect(x, y - h / 2, w, h); ctx.strokeStyle = '#333366'; ctx.lineWidth = 1; ctx.strokeRect(x, y - h / 2, w, h); // Icon and label ctx.fillStyle = COLORS.HUD_TEXT; ctx.font = '16px Arial'; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.fillText(`${toggle.icon} ${toggle.label}`, x + 15, y); // Toggle switch const switchW = 50; const switchH = 26; const switchX = x + w - switchW - 15; const switchY = y - switchH / 2; // Switch track ctx.fillStyle = isOn ? '#4CAF50' : '#555555'; ctx.beginPath(); ctx.arc(switchX + switchH / 2, y, switchH / 2, Math.PI / 2, Math.PI * 3 / 2); ctx.arc(switchX + switchW - switchH / 2, y, switchH / 2, -Math.PI / 2, Math.PI / 2); ctx.closePath(); ctx.fill(); // Switch knob ctx.fillStyle = '#FFFFFF'; ctx.beginPath(); const knobX = isOn ? switchX + switchW - switchH / 2 : switchX + switchH / 2; ctx.arc(knobX, y, switchH / 2 - 3, 0, Math.PI * 2); ctx.fill(); }, _renderProfileButton(ctx, cx, y) { const w = SCREEN_WIDTH * 0.7; const h = 50; const x = cx - w / 2; this._buttons['profile'] = { x, y: y - h / 2, w, h }; // Background ctx.fillStyle = '#1e1e3a'; ctx.fillRect(x, y - h / 2, w, h); ctx.strokeStyle = '#333366'; ctx.lineWidth = 1; ctx.strokeRect(x, y - h / 2, w, h); // Icon and label ctx.fillStyle = COLORS.HUD_TEXT; ctx.font = '16px Arial'; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.fillText(`👤 ${t('settings.profile')}`, x + 15, y); // Arrow indicator ctx.fillStyle = '#888888'; ctx.font = '14px Arial'; ctx.textAlign = 'right'; ctx.fillText('›', x + w - 15, y); }, _renderBackButton(ctx, cx, y) { const w = SCREEN_WIDTH * 0.4; const h = 42; const x = cx - w / 2; this._buttons['back'] = { x, y: y - h / 2, w, h }; ctx.fillStyle = COLORS.MENU_BTN; ctx.strokeStyle = COLORS.MENU_BTN_BORDER; ctx.lineWidth = 2; ctx.fillRect(x, y - h / 2, w, h); ctx.strokeRect(x, y - h / 2, w, h); ctx.fillStyle = COLORS.MENU_BTN_TEXT; ctx.font = 'bold 16px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(t('common.back'), cx, y); }, handleTouch(eventType, e) { if (eventType !== 'touchstart') return; const touch = e.touches[0]; const tx = touch.clientX; const ty = touch.clientY; for (const [key, rect] of Object.entries(this._buttons)) { if (tx >= rect.x && tx <= rect.x + rect.w && ty >= rect.y && ty <= rect.y + rect.h) { if (key === 'back') { GameGlobal.sceneManager.switchTo(SCENE.MENU); } else if (key === 'profile') { const sm = GameGlobal.sceneManager; if (!sm._scenes.has(SCENE.PROFILE)) { const ProfileScene = require('./ProfileScene'); sm.register(SCENE.PROFILE, ProfileScene); } sm.switchTo(SCENE.PROFILE); } else if (this._settings.hasOwnProperty(key)) { this._settings[key] = !this._settings[key]; // Notify audio system GameGlobal.eventBus.emit('settings:changed', this._settings); } break; } } }, }; module.exports = SettingsScene;