/** * MenuScene.js * Main menu scene - displays game title and mode selection buttons. * Rendered entirely with Canvas API (no DOM). */ const { SCREEN_WIDTH, SCREEN_HEIGHT, COLORS, SCENE, GAME_MODE, } = require('../base/GameGlobal'); const { t } = require('../i18n/I18n'); // ============================================================ // Button Layout // ============================================================ const BTN_WIDTH = Math.min(SCREEN_WIDTH * 0.55, 280); const BTN_HEIGHT = Math.min(36, SCREEN_HEIGHT * 0.07); const BTN_GAP = Math.min(8, SCREEN_HEIGHT * 0.015); const BTN_START_Y = SCREEN_HEIGHT * 0.38; const BTN_X = (SCREEN_WIDTH - BTN_WIDTH) / 2; // Half-width buttons for the utility row (shop, battle pass, ranking, settings) const HALF_BTN_WIDTH = (BTN_WIDTH - BTN_GAP) / 2; // Main game mode buttons (full width, vertical) const MAIN_BUTTONS = [ { labelKey: 'menu.classic', mode: GAME_MODE.CLASSIC, scene: SCENE.GAME }, { labelKey: 'menu.endless', mode: GAME_MODE.ENDLESS, scene: SCENE.GAME }, { labelKey: 'menu.pvp', mode: GAME_MODE.PVP, scene: SCENE.PVP_ROOM }, { labelKey: 'menu.team3v3', mode: GAME_MODE.TEAM_3V3, scene: SCENE.TEAM_ROOM }, ]; // Utility buttons: shop, daily gold, ranking, settings (2x2 grid) const UTIL_BUTTONS = [ { labelKey: 'menu.shop', mode: null, scene: SCENE.SHOP }, { labelKey: 'dailyGold.btn', mode: null, scene: 'DAILY_GOLD' }, { labelKey: 'menu.ranking', mode: null, scene: SCENE.RANKING }, { labelKey: 'menu.settings', mode: null, scene: SCENE.SETTINGS }, ]; // Pre-calculate button rects for main buttons const mainBtnRects = MAIN_BUTTONS.map((btn, i) => ({ x: BTN_X, y: BTN_START_Y + i * (BTN_HEIGHT + BTN_GAP), w: BTN_WIDTH, h: BTN_HEIGHT, ...btn, })); // Pre-calculate button rects for utility buttons (2x2 grid) const utilStartY = BTN_START_Y + MAIN_BUTTONS.length * (BTN_HEIGHT + BTN_GAP) + BTN_GAP; const utilBtnRects = UTIL_BUTTONS.map((btn, i) => { const col = i % 2; const row = Math.floor(i / 2); return { x: BTN_X + col * (HALF_BTN_WIDTH + BTN_GAP), y: utilStartY + row * (BTN_HEIGHT + BTN_GAP), w: HALF_BTN_WIDTH, h: BTN_HEIGHT, ...btn, }; }); // Combined list for unified iteration const buttonRects = [...mainBtnRects, ...utilBtnRects]; // ============================================================ // Menu Scene // ============================================================ const MenuScene = { _pressedIndex: -1, _tankAnim: 0, // simple animation timer enter() { this._pressedIndex = -1; this._tankAnim = 0; // Auto-navigate to team room if there's a pending invite teamId if (GameGlobal._pendingTeamId) { const teamId = GameGlobal._pendingTeamId; GameGlobal._pendingTeamId = null; console.log(`[MenuScene] Found pendingTeamId: ${teamId}, will auto-navigate to TeamRoomScene`); // Use setTimeout to allow the scene to fully initialize first setTimeout(() => { console.log(`[MenuScene] Auto-navigating to TeamRoomScene with teamId: ${teamId}`); const sm = GameGlobal.sceneManager; if (!sm._scenes.has(SCENE.TEAM_ROOM)) { const TeamRoomScene = require('./TeamRoomScene'); sm.register(SCENE.TEAM_ROOM, TeamRoomScene); } sm.switchTo(SCENE.TEAM_ROOM, { teamId }); }, 100); } }, exit() {}, update(dt) { this._tankAnim += dt; }, render(ctx) { // Background ctx.fillStyle = COLORS.MENU_BG; ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); // Decorative top bar const gradient = ctx.createLinearGradient(0, 0, SCREEN_WIDTH, 0); gradient.addColorStop(0, '#0f3460'); gradient.addColorStop(0.5, '#e94560'); gradient.addColorStop(1, '#0f3460'); ctx.fillStyle = gradient; ctx.fillRect(0, 0, SCREEN_WIDTH, 4); // Gold balance display at top const gold = GameGlobal.currencyManager ? GameGlobal.currencyManager.getGold() : 0; ctx.fillStyle = '#FFD700'; ctx.font = 'bold 14px Arial'; ctx.textAlign = 'right'; ctx.textBaseline = 'top'; ctx.fillText(`🪙 ${gold}`, SCREEN_WIDTH - 15, 12); // Title ctx.fillStyle = COLORS.MENU_TITLE; ctx.font = 'bold 36px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(t('menu.title'), SCREEN_WIDTH / 2, SCREEN_HEIGHT * 0.15); // Subtitle ctx.fillStyle = '#AAAAAA'; ctx.font = '14px Arial'; ctx.fillText(t('menu.subtitle'), SCREEN_WIDTH / 2, SCREEN_HEIGHT * 0.22); // Animated tank icon (simple oscillating triangle) this._drawTankIcon(ctx, SCREEN_WIDTH / 2, SCREEN_HEIGHT * 0.30); // Main game mode buttons (full width) for (let i = 0; i < mainBtnRects.length; i++) { const btn = mainBtnRects[i]; const isPressed = this._pressedIndex === i; ctx.fillStyle = isPressed ? '#0f3460' : COLORS.MENU_BTN; ctx.strokeStyle = COLORS.MENU_BTN_BORDER; ctx.lineWidth = 2; this._roundRect(ctx, btn.x, btn.y, btn.w, btn.h, 8); ctx.fill(); ctx.stroke(); ctx.fillStyle = isPressed ? COLORS.MENU_TITLE : COLORS.MENU_BTN_TEXT; ctx.font = 'bold 18px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(t(btn.labelKey), btn.x + btn.w / 2, btn.y + btn.h / 2); } // Utility buttons (2x2 grid, smaller font) for (let i = 0; i < utilBtnRects.length; i++) { const btn = utilBtnRects[i]; const globalIdx = mainBtnRects.length + i; const isPressed = this._pressedIndex === globalIdx; // Special rendering for daily gold button const isDailyGold = btn.scene === 'DAILY_GOLD'; let label = t(btn.labelKey); let btnColor = COLORS.MENU_BTN; if (isDailyGold) { const remaining = GameGlobal.adManager ? GameGlobal.adManager.getDailyGoldRemaining() : 0; if (remaining > 0) { label = `${t('dailyGold.btn')} ${remaining}/3`; btnColor = '#2E7D32'; // green tint } else { label = t('dailyGold.exhausted') || 'Come back tomorrow'; btnColor = '#555555'; } } ctx.fillStyle = isPressed ? '#0f3460' : btnColor; ctx.strokeStyle = COLORS.MENU_BTN_BORDER; ctx.lineWidth = 1.5; this._roundRect(ctx, btn.x, btn.y, btn.w, btn.h, 6); ctx.fill(); ctx.stroke(); ctx.fillStyle = isPressed ? COLORS.MENU_TITLE : COLORS.MENU_BTN_TEXT; ctx.font = 'bold 14px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(label, btn.x + btn.w / 2, btn.y + btn.h / 2); } // Footer ctx.fillStyle = '#555555'; ctx.font = '11px Arial'; ctx.textAlign = 'center'; ctx.fillText('v1.0.0', SCREEN_WIDTH / 2, SCREEN_HEIGHT - 20); }, /** * Draw a simple animated tank icon. */ _drawTankIcon(ctx, cx, cy) { const bounce = Math.sin(this._tankAnim * 3) * 3; const size = 20; ctx.save(); ctx.translate(cx, cy + bounce); // Tank body ctx.fillStyle = COLORS.MENU_TITLE; ctx.fillRect(-size, -size / 2, size * 2, size); // Tank turret ctx.fillRect(-3, -size / 2 - 14, 6, 14); // Tank tracks ctx.fillStyle = '#B8860B'; ctx.fillRect(-size - 4, -size / 2, 4, size); ctx.fillRect(size, -size / 2, 4, size); ctx.restore(); }, /** * Draw a rounded rectangle path. */ _roundRect(ctx, x, y, w, h, r) { ctx.beginPath(); ctx.moveTo(x + r, y); ctx.lineTo(x + w - r, y); ctx.arcTo(x + w, y, x + w, y + r, r); ctx.lineTo(x + w, y + h - r); ctx.arcTo(x + w, y + h, x + w - r, y + h, r); ctx.lineTo(x + r, y + h); ctx.arcTo(x, y + h, x, y + h - r, r); ctx.lineTo(x, y + r); ctx.arcTo(x, y, x + r, y, r); ctx.closePath(); }, handleTouch(eventType, e) { if (eventType === 'touchstart') { const touch = e.touches[0]; const tx = touch.clientX; const ty = touch.clientY; for (let i = 0; i < buttonRects.length; i++) { const btn = buttonRects[i]; if (tx >= btn.x && tx <= btn.x + btn.w && ty >= btn.y && ty <= btn.y + btn.h) { this._pressedIndex = i; break; } } } else if (eventType === 'touchend') { if (this._pressedIndex >= 0) { const btn = buttonRects[this._pressedIndex]; this._pressedIndex = -1; // Navigate to the target scene const sm = GameGlobal.sceneManager; if (btn.scene === SCENE.GAME) { // Route through BuffSelectScene for PvE modes if (!sm._scenes.has(SCENE.BUFF_SELECT)) { const BuffSelectScene = require('./BuffSelectScene'); sm.register(SCENE.BUFF_SELECT, BuffSelectScene); } sm.switchTo(SCENE.BUFF_SELECT, { mode: btn.mode }); } else if (btn.scene === 'DAILY_GOLD') { // Handle daily gold ad const adm = GameGlobal.adManager; if (adm && adm.getDailyGoldRemaining() > 0) { adm.showDailyGoldAd((completed) => { if (completed) { try { wx.showToast({ title: t('dailyGold.reward') || '+100 Gold!', icon: 'none', duration: 1500 }); } catch (e) {} } }); } } else if (btn.scene === SCENE.SHOP) { if (!sm._scenes.has(SCENE.SHOP)) { const ShopScene = require('./ShopScene'); sm.register(SCENE.SHOP, ShopScene); } sm.switchTo(SCENE.SHOP); } else if (btn.scene === SCENE.SETTINGS) { if (!sm._scenes.has(SCENE.SETTINGS)) { const SettingsScene = require('./SettingsScene'); sm.register(SCENE.SETTINGS, SettingsScene); } sm.switchTo(SCENE.SETTINGS); } else if (btn.scene === SCENE.RANKING) { if (!sm._scenes.has(SCENE.RANKING)) { const RankingScene = require('./RankingScene'); sm.register(SCENE.RANKING, RankingScene); } sm.switchTo(SCENE.RANKING); } else if (btn.scene === SCENE.PVP_ROOM) { if (!sm._scenes.has(SCENE.PVP_ROOM)) { const RoomScene = require('./RoomScene'); sm.register(SCENE.PVP_ROOM, RoomScene); } sm.switchTo(SCENE.PVP_ROOM); } else if (btn.scene === SCENE.TEAM_ROOM) { if (!sm._scenes.has(SCENE.TEAM_ROOM)) { const TeamRoomScene = require('./TeamRoomScene'); sm.register(SCENE.TEAM_ROOM, TeamRoomScene); } sm.switchTo(SCENE.TEAM_ROOM); } } } }, }; module.exports = MenuScene;