feat: Add Pause system, User Profile Dashboard, and shift grid with neighbour fence

- Implement in-game pause with Space/KeyP hotkeys and pause banner overlay
- Split Auth modal into clean Profile Dashboard (stats, coins, cloud save status) and Guest Login
- Shift lawn startY to 108 to make top-row zombie HP bars 100% visible
- Render decorative wooden neighbour fence above top row
This commit is contained in:
Antigravity committed 2026-09-17 10:12:56 +00:00
1 parent 82654d8f64
commit 2cd343e8ef
4 files changed
+328 -42

No files matched your search

+102 -18
View File
@@ -63,6 +63,7 @@ window.GameEngine = class GameEngine {
this.isGameOver = false;
this.isVictory = false;
this.gameSpeed = 1.0;
this.isPaused = false;
this.lastTime = 0;
this.hoverCell = null;
this.loopStarted = false;
@@ -187,35 +188,62 @@ window.GameEngine = class GameEngine {
if (window.PvZAudio) window.PvZAudio.playCoin();
}
togglePause() {
if (!this.isRunning) return;
this.isPaused = !this.isPaused;
const btnPause = document.getElementById('btn-pause');
if (btnPause) {
btnPause.textContent = this.isPaused ? '▶️ Играть' : '⏸️ Пауза';
if (this.isPaused) btnPause.classList.add('active');
else btnPause.classList.remove('active');
}
this.triggerHaptic(15);
if (window.PvZAudio) window.PvZAudio.playButtonClick();
}
updateProfileUI() {
const badgeName = document.getElementById('badge-user-name');
const btnAuth = document.getElementById('btn-auth-open');
const btnLogoutNav = document.getElementById('btn-auth-logout-nav');
const authStatus = document.getElementById('auth-current-status');
const btnLogoutModal = document.getElementById('btn-auth-logout');
const btnAuthGuest = document.getElementById('btn-auth-guest');
const loggedInView = document.getElementById('auth-logged-in-view');
const guestView = document.getElementById('auth-guest-view');
const userTitle = document.getElementById('profile-user-title');
const statCoins = document.getElementById('profile-stat-coins');
const statLevel = document.getElementById('profile-stat-level');
const statScore = document.getElementById('profile-stat-score');
const statMaterials = document.getElementById('profile-stat-materials');
if (window.PvZAuth) {
const user = window.PvZAuth.getCurrentUser();
const isGuest = window.PvZAuth.isGuest();
const uData = window.PvZAuth.getUserData();
if (badgeName) badgeName.textContent = isGuest ? '👤 Гость' : `👤 ${user}`;
if (btnAuth) btnAuth.textContent = isGuest ? 'Войти' : 'Сменить';
if (btnAuth) btnAuth.textContent = isGuest ? 'Войти' : 'Профиль';
if (btnLogoutNav) {
if (isGuest) btnLogoutNav.classList.add('hidden');
else btnLogoutNav.classList.remove('hidden');
}
if (authStatus) {
authStatus.innerHTML = isGuest
? 'Текущий режим: <strong>Гость</strong>'
: `Вы вошли как: <strong>${user}</strong>`;
}
if (btnLogoutModal) {
if (isGuest) btnLogoutModal.classList.add('hidden');
else btnLogoutModal.classList.remove('hidden');
}
if (btnAuthGuest) {
if (isGuest) btnAuthGuest.classList.remove('hidden');
else btnAuthGuest.classList.add('hidden');
if (loggedInView && guestView) {
if (isGuest) {
loggedInView.classList.add('hidden');
guestView.classList.remove('hidden');
} else {
loggedInView.classList.remove('hidden');
guestView.classList.add('hidden');
if (userTitle) userTitle.textContent = user;
if (statCoins) statCoins.textContent = (uData.coins || (this.economy ? this.economy.totalCoins : 0)).toLocaleString('ru-RU');
if (statLevel) statLevel.textContent = `Уровень ${uData.level || this.currentLevel || 1}`;
if (statScore) statScore.textContent = (uData.highScore || this.highScore || 0).toLocaleString('ru-RU');
if (statMaterials) {
const mats = uData.materials || (this.workshop ? this.workshop.materials : {});
const totalMats = (mats.bioHumus || 0) + (mats.scrapMetal || 0) + (mats.sunEssence || 0) + (mats.crystals || 0);
statMaterials.textContent = `${totalMats} шт.`;
}
}
}
}
}
@@ -775,6 +803,26 @@ window.GameEngine = class GameEngine {
});
}
// Pause Button
const btnPause = document.getElementById('btn-pause');
if (btnPause) {
btnPause.addEventListener('click', () => {
this.togglePause();
});
}
// Global Pause Hotkeys (Space, KeyP)
window.addEventListener('keydown', (e) => {
// Do not intercept hotkeys if user is typing in an input or textarea
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement?.tagName)) return;
if (e.code === 'Space' || e.code === 'KeyP') {
e.preventDefault();
if (this.isRunning) {
this.togglePause();
}
}
});
// Music
const btnMusic = document.getElementById('btn-music');
if (btnMusic) {
@@ -813,6 +861,7 @@ window.GameEngine = class GameEngine {
const authModal = document.getElementById('auth-modal');
const btnAuthOpen = document.getElementById('btn-auth-open');
const btnAuthClose = document.getElementById('btn-auth-close');
const btnAuthCloseProfile = document.getElementById('btn-auth-close-profile');
const btnAuthSubmit = document.getElementById('btn-auth-submit');
const btnAuthGuest = document.getElementById('btn-auth-guest');
const btnAuthLogout = document.getElementById('btn-auth-logout');
@@ -848,7 +897,7 @@ window.GameEngine = class GameEngine {
if (authError) authError.classList.add('hidden');
this.updateProfileUI();
authModal.classList.remove('hidden');
if (authUser) authUser.focus();
if (authUser && window.PvZAuth && window.PvZAuth.isGuest()) authUser.focus();
});
}
if (btnAuthClose && authModal) {
@@ -856,6 +905,11 @@ window.GameEngine = class GameEngine {
authModal.classList.add('hidden');
});
}
if (btnAuthCloseProfile && authModal) {
btnAuthCloseProfile.addEventListener('click', () => {
authModal.classList.add('hidden');
});
}
if (btnAuthGuest && authModal) {
btnAuthGuest.addEventListener('click', () => {
handleLogout();
@@ -2300,7 +2354,7 @@ window.GameEngine = class GameEngine {
this.lastTime = timestamp;
const dt = rawDt * this.gameSpeed;
if (this.isRunning) {
if (this.isRunning && !this.isPaused) {
this.update(dt);
}
this.render();
@@ -2466,6 +2520,36 @@ window.GameEngine = class GameEngine {
: (this.currentMode === 'roguelike' ? `Рогалик: Этаж ${this.roguelike ? this.roguelike.currentFloor : 1}` : `Уровень ${this.currentLevel}`)));
this.ctx.fillText(`${modeLabel} | Счёт: ${this.score}`, 880, 78);
this.ctx.restore();
// 11. Pause Banner Overlay
if (this.isPaused) {
this.ctx.save();
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.65)';
this.ctx.fillRect(0, 0, this.logicalWidth, this.logicalHeight);
// Center Modal Card
this.ctx.fillStyle = '#2a160c';
this.ctx.strokeStyle = '#e09f3e';
this.ctx.lineWidth = 4;
this.ctx.beginPath();
this.ctx.roundRect(260, 210, 380, 180, 16);
this.ctx.fill();
this.ctx.stroke();
this.ctx.fillStyle = '#ffeb3b';
this.ctx.font = 'bold 28px sans-serif';
this.ctx.textAlign = 'center';
this.ctx.fillText('⏸️ ИГРА НА ПАУЗЕ', this.logicalWidth / 2, 270);
this.ctx.fillStyle = '#e0e0e0';
this.ctx.font = '14px sans-serif';
this.ctx.fillText('Все растения и зомби остановлены.', this.logicalWidth / 2, 308);
this.ctx.fillStyle = '#81c784';
this.ctx.font = 'bold 15px sans-serif';
this.ctx.fillText('Нажмите Пробел или кнопку «▶️ Играть»', this.logicalWidth / 2, 345);
this.ctx.restore();
}
}
drawRake(ctx, x, y) {