Files
tankwar_proj/js/scenes/MenuScene.js
T
2026-05-18 07:39:03 +08:00

708 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* MenuScene.js
* Main menu scene — military-tech themed UI with game title and mode selection.
* Rendered entirely with Canvas API (no DOM).
*/
const {
SCREEN_WIDTH,
SCREEN_HEIGHT,
COLORS,
SCENE,
GAME_MODE,
} = require('../base/GameGlobal');
const { t } = require('../i18n/I18n');
// ============================================================
// Style
// ============================================================
const MC = {
BG_TOP: '#0b0e17',
BG_BOT: '#141b2d',
ACCENT: '#e94560',
GOLD: '#FFD700',
GOLD_DIM: '#B8860B',
BTN_BG: '#16213e',
BTN_BORDER: '#1e3054',
BTN_HOVER: '#0f3460',
BTN_TEXT: '#E8E8E8',
TITLE: '#FFD700',
SUBTITLE: '#8899AA',
FOOTER: '#445566',
TANK_BODY: '#FFD700',
TANK_TRACK: '#B8860B',
};
// ============================================================
// 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;
const HALF_BTN_WIDTH = (BTN_WIDTH - BTN_GAP) / 2;
const THIRD_BTN_WIDTH = (BTN_WIDTH - BTN_GAP * 2) / 3;
// 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: daily gold, skin, ranking, settings (grid)
// NOTE: Shop button is temporarily disabled
const UTIL_BUTTONS = [
{ labelKey: 'dailyGold.btn', mode: null, scene: 'DAILY_GOLD' },
{ labelKey: 'menu.skin', mode: null, scene: SCENE.SKIN },
{ 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 (2 rows x 2 cols)
const utilStartY = BTN_START_Y + MAIN_BUTTONS.length * (BTN_HEIGHT + BTN_GAP) + BTN_GAP;
const utilBtnRects = UTIL_BUTTONS.map((btn, i) => {
const row = Math.floor(i / 2);
const col = 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,
};
});
const buttonRects = [...mainBtnRects, ...utilBtnRects];
// ============================================================
// Menu Scene
// ============================================================
const MenuScene = {
_pressedIndex: -1,
_tankAnim: 0,
enter() {
this._pressedIndex = -1;
this._tankAnim = 0;
this._avatarImg = null;
// Load avatar image if profile has one
const profile = GameGlobal.playerProfile;
if (profile && profile.avatarUrl) {
this._loadAvatarImage(profile.avatarUrl);
}
// Listen for profile updates (avatar may arrive after initial render)
this._profileHandler = (data) => {
if (data && data.avatarUrl && !this._avatarImg) {
this._loadAvatarImage(data.avatarUrl);
}
};
const bus = GameGlobal.eventBus;
if (bus && typeof bus.on === 'function') {
bus.on('profile:updated', this._profileHandler);
}
// Kick off nickname acquisition as early as possible so that later
// network messages (CREATE_TEAM, SOLO_MATCH, ...) can carry it.
this._initPlayerProfile();
if (GameGlobal._pendingTeamId) {
const teamId = GameGlobal._pendingTeamId;
GameGlobal._pendingTeamId = null;
console.log(`[MenuScene] Found pendingTeamId: ${teamId}, will auto-navigate to TeamRoomScene`);
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);
}
if (GameGlobal._pendingRoomId) {
const roomId = GameGlobal._pendingRoomId;
GameGlobal._pendingRoomId = null;
console.log(`[MenuScene] Found pendingRoomId: ${roomId}, will auto-navigate to RoomScene`);
setTimeout(() => {
console.log(`[MenuScene] Auto-navigating to RoomScene with roomId: ${roomId}`);
const sm = GameGlobal.sceneManager;
if (!sm._scenes.has(SCENE.PVP_ROOM)) {
const RoomScene = require('./RoomScene');
sm.register(SCENE.PVP_ROOM, RoomScene);
}
sm.switchTo(SCENE.PVP_ROOM, { roomId });
}, 100);
}
},
exit() {
this._pressedIndex = -1;
// Destroy UserInfoButton overlay when leaving the menu
const profile = GameGlobal.playerProfile;
if (profile && typeof profile.destroyUserInfoButton === 'function') {
profile.destroyUserInfoButton();
}
// Remove profile update listener
const bus = GameGlobal.eventBus;
if (bus && typeof bus.off === 'function' && this._profileHandler) {
bus.off('profile:updated', this._profileHandler);
}
this._profileHandler = null;
this._avatarImg = null;
},
update(dt) {
this._tankAnim += dt;
},
render(ctx) {
// ---- Background ----
const bgGrad = ctx.createLinearGradient(0, 0, 0, SCREEN_HEIGHT);
bgGrad.addColorStop(0, MC.BG_TOP);
bgGrad.addColorStop(1, MC.BG_BOT);
ctx.fillStyle = bgGrad;
ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
// Scan-lines
ctx.globalAlpha = 0.025;
ctx.fillStyle = '#FFFFFF';
for (let sy = 0; sy < SCREEN_HEIGHT; sy += 4) {
ctx.fillRect(0, sy, SCREEN_WIDTH, 1);
}
ctx.globalAlpha = 1;
// Top accent bar
const accentGrad = ctx.createLinearGradient(0, 0, SCREEN_WIDTH, 0);
accentGrad.addColorStop(0, 'transparent');
accentGrad.addColorStop(0.3, MC.ACCENT);
accentGrad.addColorStop(0.7, MC.ACCENT);
accentGrad.addColorStop(1, 'transparent');
ctx.fillStyle = accentGrad;
ctx.fillRect(0, 0, SCREEN_WIDTH, 3);
// ---- Player Avatar & Nickname (top-left) ----
const profile = GameGlobal.playerProfile;
const avatarSize = 28;
const avatarX = 10;
const avatarY = 10;
const avatarR = avatarSize / 2;
// Avatar circle background
ctx.save();
ctx.beginPath();
ctx.arc(avatarX + avatarR, avatarY + avatarR, avatarR, 0, Math.PI * 2);
ctx.closePath();
ctx.fillStyle = 'rgba(30,48,84,0.7)';
ctx.fill();
ctx.strokeStyle = 'rgba(255,215,0,0.4)';
ctx.lineWidth = 1;
ctx.stroke();
// Avatar image or default icon
if (profile && profile.avatarUrl && this._avatarImg && this._avatarImg.complete) {
ctx.clip();
ctx.drawImage(this._avatarImg, avatarX, avatarY, avatarSize, avatarSize);
} else {
// Default user icon (simple silhouette)
ctx.fillStyle = 'rgba(255,215,0,0.5)';
ctx.beginPath();
ctx.arc(avatarX + avatarR, avatarY + avatarR - 2, avatarR * 0.35, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.ellipse(avatarX + avatarR, avatarY + avatarR + avatarR * 0.55, avatarR * 0.55, avatarR * 0.3, 0, Math.PI, 0);
ctx.fill();
}
ctx.restore();
// Nickname
const displayName = profile ? profile.getDisplayName() : 'Tanker';
ctx.font = 'bold 11px Arial';
ctx.fillStyle = MC.GOLD;
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.fillText(displayName, avatarX + avatarSize + 6, avatarY + avatarR - 5);
// Hint text (only if not yet granted)
if (profile && !profile.granted) {
ctx.font = '9px Arial';
ctx.fillStyle = MC.SUBTITLE;
ctx.fillText(t('menu.tapToAuth') || 'Tap to authorize', avatarX + avatarSize + 6, avatarY + avatarR + 8);
}
// ---- Gold Balance (top-right pill) ----
const gold = GameGlobal.currencyManager ? GameGlobal.currencyManager.getGold() : 0;
const goldText = `🪙 ${gold}`;
ctx.font = 'bold 12px Arial';
const gtw = ctx.measureText(goldText).width;
const pillW = gtw + 16;
const pillH = 22;
const pillX = SCREEN_WIDTH - pillW - 12;
const pillY = 10;
ctx.fillStyle = 'rgba(255, 215, 0, 0.08)';
ctx.strokeStyle = 'rgba(255, 215, 0, 0.3)';
ctx.lineWidth = 1;
this._roundRect(ctx, pillX, pillY, pillW, pillH, pillH / 2);
ctx.fill();
ctx.stroke();
ctx.fillStyle = MC.GOLD;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(goldText, pillX + pillW / 2, pillY + pillH / 2);
// ---- Title with glow ----
ctx.save();
ctx.shadowColor = MC.GOLD;
ctx.shadowBlur = 16;
ctx.fillStyle = MC.TITLE;
ctx.font = 'bold 34px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(t('menu.title'), SCREEN_WIDTH / 2, SCREEN_HEIGHT * 0.15);
ctx.restore();
// ---- Subtitle ----
ctx.fillStyle = MC.SUBTITLE;
ctx.font = '13px Arial';
ctx.textAlign = 'center';
ctx.fillText(t('menu.subtitle'), SCREEN_WIDTH / 2, SCREEN_HEIGHT * 0.22);
// ---- Animated Tank Icon ----
this._drawTankIcon(ctx, SCREEN_WIDTH / 2, SCREEN_HEIGHT * 0.30);
// ---- Main Buttons ----
for (let i = 0; i < mainBtnRects.length; i++) {
const btn = mainBtnRects[i];
const isPressed = this._pressedIndex === i;
this._drawMenuButton(ctx, btn, t(btn.labelKey), isPressed, 'bold 16px Arial', 8);
}
// ---- Utility Buttons ----
for (let i = 0; i < utilBtnRects.length; i++) {
const btn = utilBtnRects[i];
const globalIdx = mainBtnRects.length + i;
const isPressed = this._pressedIndex === globalIdx;
const isDailyGold = btn.scene === 'DAILY_GOLD';
let label = t(btn.labelKey);
let customBg = null;
if (isDailyGold) {
const remaining = GameGlobal.adManager ? GameGlobal.adManager.getDailyGoldRemaining() : 0;
if (remaining > 0) {
label = `${t('dailyGold.btn')} ${remaining}/3`;
customBg = '#1a3a2a';
} else {
label = t('dailyGold.exhausted') || 'Come back tomorrow';
customBg = '#2a2a2a';
}
}
this._drawMenuButton(ctx, btn, label, isPressed, 'bold 13px Arial', 6, customBg);
}
// ---- Footer ----
ctx.fillStyle = MC.FOOTER;
ctx.font = '10px Arial';
ctx.textAlign = 'center';
ctx.fillText('v1.0.0', SCREEN_WIDTH / 2, SCREEN_HEIGHT - 18);
},
// ---- Menu Button ----
_drawMenuButton(ctx, btn, label, isPressed, font, radius, customBg) {
const r = radius || 8;
// Shadow
ctx.fillStyle = 'rgba(0,0,0,0.2)';
this._roundRect(ctx, btn.x + 1, btn.y + 2, btn.w, btn.h, r);
ctx.fill();
// Body
ctx.fillStyle = isPressed ? MC.BTN_HOVER : (customBg || MC.BTN_BG);
ctx.strokeStyle = MC.BTN_BORDER;
ctx.lineWidth = 1.5;
this._roundRect(ctx, btn.x, btn.y, btn.w, btn.h, r);
ctx.fill();
ctx.stroke();
// Label
ctx.fillStyle = isPressed ? MC.TITLE : MC.BTN_TEXT;
ctx.font = font || 'bold 16px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(label, btn.x + btn.w / 2, btn.y + btn.h / 2);
},
// ---- Tank Icon ----
_drawTankIcon(ctx, cx, cy) {
const bounce = Math.sin(this._tankAnim * 3) * 2;
const pulse = 0.5 + 0.5 * Math.sin(this._tankAnim * 2);
const s = 15; // body half-size (square tank: 2s × 2s)
ctx.save();
ctx.translate(cx, cy + bounce);
// ── 1. OUTER GLOW HALO (breathing golden aura) ──
ctx.save();
ctx.globalAlpha = 0.15 + pulse * 0.12;
ctx.fillStyle = MC.GOLD;
ctx.beginPath();
ctx.arc(0, 0, s * 1.55, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
// ── 2. GROUND SHADOW (soft ellipse underneath) ──
ctx.save();
ctx.globalAlpha = 0.35;
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.ellipse(0, s + 6, s * 1.1, 3, 0, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
// ── 3. TRACKS (left & right, with segment pattern) ──
const trackW = 5;
const trackX = s;
// Left track
ctx.fillStyle = '#4A3508';
this._roundRect(ctx, -trackX - trackW, -s, trackW, s * 2, 1.5);
ctx.fill();
// Right track
this._roundRect(ctx, trackX, -s, trackW, s * 2, 1.5);
ctx.fill();
// Track top highlight
ctx.fillStyle = 'rgba(255, 220, 120, 0.35)';
ctx.fillRect(-trackX - trackW + 0.8, -s + 0.8, trackW - 1.6, 1);
ctx.fillRect(trackX + 0.8, -s + 0.8, trackW - 1.6, 1);
// Track segment lines (metallic plate pattern)
ctx.strokeStyle = 'rgba(0, 0, 0, 0.55)';
ctx.lineWidth = 0.8;
for (let ty = -s + 4; ty < s - 1; ty += 4) {
ctx.beginPath();
ctx.moveTo(-trackX - trackW, ty);
ctx.lineTo(-trackX, ty);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(trackX, ty);
ctx.lineTo(trackX + trackW, ty);
ctx.stroke();
}
// ── 4. BODY (square, with vertical metallic gradient) ──
ctx.save();
ctx.shadowColor = MC.GOLD;
ctx.shadowBlur = 12;
const bodyGrad = ctx.createLinearGradient(0, -s, 0, s);
bodyGrad.addColorStop(0, '#FFF3A8'); // top highlight
bodyGrad.addColorStop(0.3, MC.GOLD); // main gold
bodyGrad.addColorStop(0.75, '#C89A1C');
bodyGrad.addColorStop(1, '#7A5A0A'); // bottom shadow
ctx.fillStyle = bodyGrad;
this._roundRect(ctx, -s, -s, s * 2, s * 2, 3);
ctx.fill();
ctx.restore();
// ── 5. BODY EDGE OUTLINE ──
ctx.strokeStyle = 'rgba(255, 235, 150, 0.7)';
ctx.lineWidth = 1;
this._roundRect(ctx, -s, -s, s * 2, s * 2, 3);
ctx.stroke();
// ── 6. TOP HIGHLIGHT STRIP (bright metallic sheen) ──
ctx.fillStyle = 'rgba(255, 255, 255, 0.35)';
this._roundRect(ctx, -s + 2, -s + 2, s * 2 - 4, 3, 1.5);
ctx.fill();
// ── 7. ARMOR PLATE DETAIL (X-cross rivets in 4 corners + center crosshair) ──
const rivetOffset = s * 0.6;
ctx.fillStyle = '#6B4A08';
for (const [rx, ry] of [[-rivetOffset, -rivetOffset], [rivetOffset, -rivetOffset],
[-rivetOffset, rivetOffset], [rivetOffset, rivetOffset]]) {
ctx.beginPath();
ctx.arc(rx, ry, 1.4, 0, Math.PI * 2);
ctx.fill();
// Tiny rivet highlight
ctx.fillStyle = 'rgba(255, 240, 180, 0.8)';
ctx.beginPath();
ctx.arc(rx - 0.3, ry - 0.3, 0.5, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#6B4A08';
}
// ── 8. TURRET (diamond-shaped center cap with gradient) ──
const turretR = s * 0.42;
ctx.save();
ctx.shadowColor = MC.GOLD;
ctx.shadowBlur = 6;
const turretGrad = ctx.createRadialGradient(-turretR * 0.3, -turretR * 0.3, 0, 0, 0, turretR);
turretGrad.addColorStop(0, '#FFF3A8');
turretGrad.addColorStop(0.5, '#E0B020');
turretGrad.addColorStop(1, '#8B6914');
ctx.fillStyle = turretGrad;
// Diamond (rotated square) for "military hatch" feel
ctx.beginPath();
ctx.moveTo(0, -turretR);
ctx.lineTo(turretR, 0);
ctx.lineTo(0, turretR);
ctx.lineTo(-turretR, 0);
ctx.closePath();
ctx.fill();
ctx.restore();
// Turret edge
ctx.strokeStyle = 'rgba(255, 235, 150, 0.8)';
ctx.lineWidth = 0.8;
ctx.beginPath();
ctx.moveTo(0, -turretR);
ctx.lineTo(turretR, 0);
ctx.lineTo(0, turretR);
ctx.lineTo(-turretR, 0);
ctx.closePath();
ctx.stroke();
// Turret internal cross
ctx.strokeStyle = 'rgba(0, 0, 0, 0.35)';
ctx.lineWidth = 0.6;
ctx.beginPath();
ctx.moveTo(0, -turretR); ctx.lineTo(0, turretR);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(-turretR, 0); ctx.lineTo(turretR, 0);
ctx.stroke();
// Turret center hatch
ctx.fillStyle = '#FFF3A8';
ctx.beginPath();
ctx.arc(0, 0, 2.5, 0, Math.PI * 2);
ctx.fill();
// ── 9. BARREL (thick, with metallic gradient) ──
const barrelW = 6;
const barrelH = 16;
const barrelY = -s - barrelH;
ctx.save();
const barrelGrad = ctx.createLinearGradient(-barrelW / 2, 0, barrelW / 2, 0);
barrelGrad.addColorStop(0, '#6B4A08');
barrelGrad.addColorStop(0.3, '#B8860B');
barrelGrad.addColorStop(0.5, '#FFF3A8');
barrelGrad.addColorStop(0.7, '#B8860B');
barrelGrad.addColorStop(1, '#6B4A08');
ctx.fillStyle = barrelGrad;
this._roundRect(ctx, -barrelW / 2, barrelY, barrelW, barrelH, 1.5);
ctx.fill();
ctx.restore();
// Barrel outline
ctx.strokeStyle = 'rgba(255, 235, 150, 0.55)';
ctx.lineWidth = 0.6;
this._roundRect(ctx, -barrelW / 2, barrelY, barrelW, barrelH, 1.5);
ctx.stroke();
// ── 10. MUZZLE TIP (flared end with glow) ──
ctx.save();
ctx.shadowColor = '#FFF3A8';
ctx.shadowBlur = 5 + pulse * 3;
ctx.fillStyle = '#2A1D05';
this._roundRect(ctx, -barrelW / 2 - 1, barrelY - 2, barrelW + 2, 3, 1);
ctx.fill();
ctx.restore();
// Muzzle inner bright dot
ctx.fillStyle = `rgba(255, 240, 150, ${0.6 + pulse * 0.4})`;
ctx.beginPath();
ctx.arc(0, barrelY - 0.5, 1, 0, Math.PI * 2);
ctx.fill();
// ── 11. HEADLIGHTS (front of tank — 2 small glowing dots) ──
ctx.fillStyle = `rgba(255, 255, 200, ${0.7 + pulse * 0.3})`;
ctx.shadowColor = '#FFF3A8';
ctx.shadowBlur = 4;
ctx.beginPath();
ctx.arc(-s * 0.55, -s + 2.5, 1.3, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(s * 0.55, -s + 2.5, 1.3, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
ctx.restore();
},
// ---- Utility ----
_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();
},
// ============================================================
// Player Profile (nickname acquisition)
// ============================================================
/**
* Load avatar image from URL for Canvas rendering.
* @param {string} url
* @private
*/
_loadAvatarImage(url) {
if (!url || this._avatarImg) return;
try {
const img = wx.createImage();
img.onload = () => {
this._avatarImg = img;
};
img.onerror = () => {
console.warn('[MenuScene] Failed to load avatar image');
};
img.src = url;
} catch (e) {
console.warn('[MenuScene] _loadAvatarImage error:', e && e.message);
}
},
/**
* Kick off profile acquisition on menu enter.
*
* Since WeChat 2022-10, `wx.getUserInfo` returns "scope unauthorized" —
* the ONLY way to get the real nickname + avatar is `wx.createUserInfoButton`.
* We create a transparent overlay button that covers the avatar area in the
* top-left corner. When the user taps their avatar, WeChat returns the real
* profile data.
* @private
*/
_initPlayerProfile() {
const profile = GameGlobal.playerProfile;
if (!profile) return;
// If already granted (from a previous session's cache), no button needed
if (profile.granted) return;
// Layout must match the avatar area drawn in render():
// avatarX=10, avatarY=10, avatarSize=28 + nickname text area
const avatarLayout = {
x: 8,
y: 8,
width: 120,
height: 32,
};
// Create the UserInfoButton — visible style covering avatar + nickname area
if (typeof profile.fetchSilent === 'function') {
profile.fetchSilent(avatarLayout).then((ok) => {
console.log('[MenuScene] fetchSilent completed, granted=', ok);
if (ok) {
// Button was tapped and profile was granted — destroy it
profile.destroyUserInfoButton();
}
}).catch((e) => {
console.warn('[MenuScene] fetchSilent error:', e);
});
}
},
// ============================================================
// Touch
// ============================================================
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;
const sm = GameGlobal.sceneManager;
if (btn.scene === SCENE.GAME) {
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') {
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.SKIN) {
if (!sm._scenes.has(SCENE.SKIN)) {
const SkinScene = require('./SkinScene');
sm.register(SCENE.SKIN, SkinScene);
}
sm.switchTo(SCENE.SKIN);
} 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;