Meme Merge Evolution+ | Full Menu & Progression
by CyberPanther37762 lines34.5 KB🛠️ Phaser (2D game framework)
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>Meme Merge Evolution+ | Full Menu & Progression</title>
<script src="https://cdn.jsdelivr.net/npm/phaser@3.70.0/dist/phaser.min.js"></script>
<script src="https://yandex.ru/games/sdk/v2"></script>
<style>
* {
-webkit-tap-highlight-color: transparent;
user-select: none;
}
body {
margin: 0;
background: radial-gradient(circle at center, #08081a 0%, #000000 100%);
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: 'Arial', sans-serif;
}
canvas {
border-radius: 24px;
box-shadow: 0 0 60px rgba(0, 242, 254, 0.15), 0 0 30px rgba(0,0,0,0.8);
}
</style>
</head>
<body>
<script>
// ================================
// YANDEX SDK
// ================================
let ysdk = null;
if (typeof YaGames !== 'undefined') {
YaGames.init().then(sdk => { ysdk = sdk; }).catch(err => console.error(err));
}
// ================================
// WEB AUDIO SYNTHESIZER
// ================================
const SoundFX = {
ctx: null,
enabled: true,
musicEnabled: false,
init() {
if (!this.ctx && this.enabled) {
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
if (this.ctx.state === 'suspended') {
this.ctx.resume();
}
}
},
playSpawn() {
if (!this.enabled) return;
this.init();
if (!this.ctx) return;
const ctx = this.ctx;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(150, ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(80, ctx.currentTime + 0.1);
gain.gain.setValueAtTime(0.15, ctx.currentTime);
gain.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.1);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start();
osc.stop(ctx.currentTime + 0.1);
},
playMerge(combo = 1) {
if (!this.enabled) return;
this.init();
if (!this.ctx) return;
const ctx = this.ctx;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
const startFreq = 200 + (combo * 40);
const endFreq = 500 + (combo * 80);
osc.type = 'triangle';
osc.frequency.setValueAtTime(startFreq, ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(endFreq, ctx.currentTime + 0.2);
gain.gain.setValueAtTime(0.2, ctx.currentTime);
gain.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.2);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start();
osc.stop(ctx.currentTime + 0.2);
},
playGameOver() {
if (!this.enabled) return;
this.init();
if (!this.ctx) return;
const ctx = this.ctx;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(180, ctx.currentTime);
osc.frequency.linearRampToValueAtTime(50, ctx.currentTime + 0.6);
gain.gain.setValueAtTime(0.25, ctx.currentTime);
gain.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.6);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start();
osc.stop(ctx.currentTime + 0.6);
},
playUIClick() {
if (!this.enabled) return;
this.init();
if (!this.ctx) return;
const ctx = this.ctx;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(880, ctx.currentTime);
gain.gain.setValueAtTime(0.1, ctx.currentTime);
gain.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.1);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start();
osc.stop(ctx.currentTime + 0.1);
}
};
// ================================
// GLOBALS & STORAGE HELPERS
// ================================
function loadSettings() {
return {
soundEnabled: localStorage.getItem('soundEnabled') !== 'false',
musicEnabled: localStorage.getItem('musicEnabled') === 'true',
quality: localStorage.getItem('quality') === 'low' ? 'low' : 'high'
};
}
function saveSettings(settings) {
localStorage.setItem('soundEnabled', settings.soundEnabled);
localStorage.setItem('musicEnabled', settings.musicEnabled);
localStorage.setItem('quality', settings.quality);
SoundFX.enabled = settings.soundEnabled;
}
function loadStats() {
return {
bestScore: parseInt(localStorage.getItem('bestScore')) || 0,
bestCombo: parseInt(localStorage.getItem('bestCombo')) || 0,
totalGames: parseInt(localStorage.getItem('totalGames')) || 0,
lastDate: localStorage.getItem('lastDate') || '',
bestToday: parseInt(localStorage.getItem('bestToday')) || 0
};
}
function updateStats(score, combo) {
let stats = loadStats();
stats.totalGames++;
if (score > stats.bestScore) stats.bestScore = score;
if (combo > stats.bestCombo) stats.bestCombo = combo;
const today = new Date().toDateString();
if (stats.lastDate !== today) {
stats.bestToday = score;
stats.lastDate = today;
} else {
if (score > stats.bestToday) stats.bestToday = score;
}
localStorage.setItem('bestScore', stats.bestScore);
localStorage.setItem('bestCombo', stats.bestCombo);
localStorage.setItem('totalGames', stats.totalGames);
localStorage.setItem('bestToday', stats.bestToday);
localStorage.setItem('lastDate', stats.lastDate);
return stats;
}
// ================================
// DYNAMIC RESOLUTION
// ================================
function getGameDimensions() {
let maxWidth = Math.min(window.innerWidth, 500);
let maxHeight = Math.min(window.innerHeight, 850);
let ratio = maxHeight / maxWidth;
if (ratio > 1.8) {
maxWidth = Math.floor(maxHeight / 1.78);
} else if (ratio < 1.5) {
maxHeight = Math.floor(maxWidth * 1.78);
}
return { width: maxWidth, height: maxHeight };
}
let GAME_WIDTH = 420;
let GAME_HEIGHT = 760;
const TOP_LIMIT = 140;
const MAX_LEVEL = 8;
let BASE_GRAVITY = 1250;
// ================================
// LEVEL COLORS
// ================================
const LEVEL_COLORS = [
0x00f2fe, 0x00ff87, 0xff007f, 0xff9f43, 0x7000ff, 0xfeff33, 0x00ffff, 0xffffff
];
const LEVEL_GLOW = [
0x80f9ff, 0x80ffc3, 0xff80bf, 0xffcf9e, 0xb880ff, 0xfffd96, 0x96ffff, 0xffffff
];
// ================================
// BOOT SCENE (SPLASH)
// ================================
class BootScene extends Phaser.Scene {
constructor() { super({ key: 'BootScene' }); }
preload() { }
create() {
let dims = getGameDimensions();
window.GAME_WIDTH = dims.width;
window.GAME_HEIGHT = dims.height;
this.cameras.main.setBackgroundColor('#000000');
let logo = this.add.text(dims.width/2, dims.height/2, 'MERGE EVOLUTION', {
fontSize: '32px',
fontFamily: 'Arial Black',
color: '#ffffff',
stroke: '#ff00ff',
strokeThickness: 4
}).setOrigin(0.5);
this.tweens.add({
targets: logo,
scale: 1.2,
alpha: 0.5,
duration: 800,
yoyo: true,
repeat: 1,
onComplete: () => this.scene.start('MenuScene')
});
SoundFX.enabled = loadSettings().soundEnabled;
}
}
// ================================
// MENU SCENE
// ================================
class MenuScene extends Phaser.Scene {
constructor() {
super({ key: 'MenuScene' });
}
create() {
const w = GAME_WIDTH;
const h = GAME_HEIGHT;
this.add.rectangle(0, 0, w, h, 0x050515).setOrigin(0);
this.createBackgroundParticles(w, h);
const title = this.add.text(w/2, h*0.2, '⚡ MERGE ARENA ⚡', {
fontSize: '38px', fontFamily: 'Arial Black', color: '#ffffff', stroke: '#ff00ff', strokeThickness: 4,
shadow: { offsetX: 0, offsetY: 0, color: '#ff00ff', blur: 15, fill: true }
}).setOrigin(0.5);
const btnStyle = { fontSize: '28px', fontFamily: 'Arial Black', color: '#ffffff', stroke: '#000', strokeThickness: 3 };
const createBtn = (text, y, onClick) => {
const btn = this.add.text(w/2, y, text, btnStyle).setOrigin(0.5).setInteractive({ useHandCursor: true });
btn.setBackgroundColor('#222244');
btn.setPadding(16, 12, 16, 12);
btn.on('pointerover', () => btn.setColor('#ffff00'));
btn.on('pointerout', () => btn.setColor('#ffffff'));
btn.on('pointerdown', () => {
SoundFX.playUIClick();
onClick();
});
return btn;
};
createBtn('🎮 ИГРАТЬ', h*0.4, () => {
this.scene.start('GameScene');
});
createBtn('⚔️ ЧЕЛЛЕНДЖ ДНЯ', h*0.52, () => {
this.showComingSoon();
});
createBtn('🔧 НАСТРОЙКИ', h*0.64, () => {
this.scene.start('SettingsScene');
});
createBtn('🏆 РЕКОРДЫ', h*0.76, () => {
this.scene.start('RecordsScene');
});
this.tweens.add({ targets: title, scale: 1.05, duration: 1500, yoyo: true, repeat: -1, ease: 'Sine.easeInOut' });
}
createBackgroundParticles(w, h) {
for (let i = 0; i < 40; i++) {
const x = Phaser.Math.Between(0, w);
const y = Phaser.Math.Between(0, h);
const particle = this.add.circle(x, y, Phaser.Math.Between(2, 5), 0x00f2fe, 0.3);
this.tweens.add({
targets: particle,
y: particle.y - Phaser.Math.Between(40, 120),
x: particle.x + Phaser.Math.Between(-20, 20),
alpha: 0,
duration: Phaser.Math.Between(2000, 4000),
onComplete: () => { if(particle.active) particle.destroy(); },
repeat: -1
});
}
}
showComingSoon() {
const w = GAME_WIDTH;
const h = GAME_HEIGHT;
const msg = this.add.text(w/2, h/2, 'СКОРО!', { fontSize: '44px', fontFamily: 'Arial Black', color: '#ffaa00', stroke: '#000', strokeThickness: 4 }).setOrigin(0.5);
this.time.delayedCall(1000, () => { msg.destroy(); });
}
}
// ================================
// SETTINGS SCENE
// ================================
class SettingsScene extends Phaser.Scene {
constructor() {
super({ key: 'SettingsScene' });
}
create() {
const w = GAME_WIDTH;
const h = GAME_HEIGHT;
this.add.rectangle(0, 0, w, h, 0x050515).setOrigin(0);
this.createFloatingParticles(w, h);
const title = this.add.text(w/2, 60, 'НАСТРОЙКИ', { fontSize: '36px', fontFamily: 'Arial Black', color: '#00f2fe', stroke: '#000', strokeThickness: 3 }).setOrigin(0.5);
let settings = loadSettings();
this.createToggleButton(w/2, 160, 'Звук: ', settings.soundEnabled, (val) => { settings.soundEnabled = val; saveSettings(settings); SoundFX.enabled = val; });
this.createToggleButton(w/2, 240, 'Музыка: ', settings.musicEnabled, (val) => { settings.musicEnabled = val; saveSettings(settings); });
const qualityHigh = this.add.text(w/2 - 80, 320, 'ВЫСОКОЕ', { fontSize: '24px', fontFamily: 'Arial', color: settings.quality === 'high' ? '#00ff87' : '#888' }).setInteractive({ useHandCursor: true });
const qualityLow = this.add.text(w/2 + 80, 320, 'НИЗКОЕ', { fontSize: '24px', fontFamily: 'Arial', color: settings.quality === 'low' ? '#00ff87' : '#888' }).setInteractive({ useHandCursor: true });
qualityHigh.on('pointerdown', () => {
settings.quality = 'high';
saveSettings(settings);
qualityHigh.setColor('#00ff87');
qualityLow.setColor('#888');
SoundFX.playUIClick();
});
qualityLow.on('pointerdown', () => {
settings.quality = 'low';
saveSettings(settings);
qualityLow.setColor('#00ff87');
qualityHigh.setColor('#888');
SoundFX.playUIClick();
});
const backBtn = this.add.text(w/2, h - 80, '◀ НАЗАД', { fontSize: '32px', fontFamily: 'Arial Black', color: '#ffffff', backgroundColor: '#222244', padding: {x:20,y:10} }).setOrigin(0.5).setInteractive({ useHandCursor: true });
backBtn.on('pointerdown', () => {
SoundFX.playUIClick();
this.scene.start('MenuScene');
});
}
createToggleButton(x, y, label, initialValue, onChange) {
const txt = this.add.text(x - 100, y, label + (initialValue ? 'ВКЛ' : 'ВЫКЛ'), { fontSize: '28px', fontFamily: 'Arial', color: '#ffffff' }).setOrigin(0, 0.5);
const btn = this.add.rectangle(x + 60, y, 70, 40, initialValue ? 0x44ff44 : 0x444444).setInteractive({ useHandCursor: true });
const circle = this.add.circle(btn.x + (initialValue ? 20 : -20), btn.y, 16, 0xffffff);
btn.on('pointerdown', () => {
const newVal = !initialValue;
initialValue = newVal;
txt.setText(label + (newVal ? 'ВКЛ' : 'ВЫКЛ'));
btn.setFillStyle(newVal ? 0x44ff44 : 0x444444);
circle.x = btn.x + (newVal ? 20 : -20);
onChange(newVal);
SoundFX.playUIClick();
});
return { txt, btn, circle };
}
createFloatingParticles(w, h) {
for (let i = 0; i < 30; i++) {
const x = Phaser.Math.Between(0, w);
const y = Phaser.Math.Between(0, h);
const particle = this.add.circle(x, y, Phaser.Math.Between(2, 4), 0x00ffff, 0.2);
this.tweens.add({ targets: particle, y: y - 40, x: x + 20, alpha: 0, duration: 3000, repeat: -1, yoyo: true });
}
}
}
// ================================
// RECORDS SCENE
// ================================
class RecordsScene extends Phaser.Scene {
constructor() {
super({ key: 'RecordsScene' });
}
create() {
const w = GAME_WIDTH;
const h = GAME_HEIGHT;
this.add.rectangle(0, 0, w, h, 0x050515).setOrigin(0);
this.add.text(w/2, 50, '🏆 РЕКОРДЫ 🏆', { fontSize: '36px', fontFamily: 'Arial Black', color: '#ffd700', stroke: '#000', strokeThickness: 3 }).setOrigin(0.5);
const stats = loadStats();
const records = [
{ label: 'ЛУЧШИЙ СЧЁТ', value: stats.bestScore },
{ label: 'ЛУЧШЕЕ КОМБО', value: stats.bestCombo + 'x' },
{ label: 'РЕКОРД СЕГОДНЯ', value: stats.bestToday },
{ label: 'ВСЕГО ИГР', value: stats.totalGames }
];
records.forEach((rec, i) => {
this.add.text(w/2 - 120, 150 + i * 70, rec.label + ':', { fontSize: '24px', fontFamily: 'Arial', color: '#ccc' }).setOrigin(0, 0.5);
this.add.text(w/2 + 80, 150 + i * 70, rec.value.toString(), { fontSize: '32px', fontFamily: 'Arial Black', color: '#00ff87' }).setOrigin(1, 0.5);
});
const backBtn = this.add.text(w/2, h - 80, '◀ ГЛАВНОЕ МЕНЮ', { fontSize: '28px', fontFamily: 'Arial Black', color: '#ffffff', backgroundColor: '#222244', padding: {x:20,y:10} }).setOrigin(0.5).setInteractive({ useHandCursor: true });
backBtn.on('pointerdown', () => {
SoundFX.playUIClick();
this.scene.start('MenuScene');
});
this.createParticlesBg(w, h);
}
createParticlesBg(w, h) {
for (let i = 0; i < 40; i++) {
const star = this.add.circle(Phaser.Math.Between(0, w), Phaser.Math.Between(0, h), 2, 0xffaa44, 0.3);
this.tweens.add({ targets: star, alpha: 0, duration: 1500, yoyo: true, repeat: -1 });
}
}
}
// ================================
// GAME SCENE WITH PROGRESSION
// ================================
class GameScene extends Phaser.Scene {
constructor() {
super({ key: 'GameScene' });
}
preload() {
for (let i = 0; i < MAX_LEVEL; i++) {
let size = 50 + i * 12;
let radius = size / 2;
let g = this.make.graphics({ x: 0, y: 0, add: false });
for (let glow = 12; glow > 0; glow -= 2) g.fillStyle(LEVEL_GLOW[i], 0.12 - glow * 0.008).fillCircle(radius, radius, radius + glow);
g.fillStyle(0x000000, 0.4).fillCircle(radius + 4, radius + 4, radius);
g.fillStyle(LEVEL_COLORS[i], 1).fillCircle(radius, radius, radius);
for (let j = 0; j < radius; j += 4) g.fillStyle(0xffffff, 0.15 * (1 - j / radius)).fillCircle(radius, radius - j, radius - j);
g.fillStyle(0xffffff, 0.6).fillCircle(radius - 10, radius - 10, radius / 3.2);
g.lineStyle(2.5, 0xffffff, 0.6).strokeCircle(radius, radius, radius - 2);
g.lineStyle(2, LEVEL_GLOW[i], 0.7).strokeCircle(radius, radius, radius - 3);
g.generateTexture('ball' + (i + 1), size, size);
}
}
create() {
const w = GAME_WIDTH;
const h = GAME_HEIGHT;
this.input.once('pointerdown', () => { SoundFX.init(); });
// Difficulty progression vars
this.difficulty = 1;
this.maxBallsCreatedCount = 0; // счётчик созданных максимальных шаров (уровень 8)
this.currentMaxBalls = 60;
this.baseGravity = BASE_GRAVITY;
this.updateDifficultyParams();
this.score = 0;
this.combo = 0;
this.gameOver = false;
this.canSpawn = true;
this.nextLevel = 1;
this.isGameOverTriggered = false;
this.comboTimer = null;
let savedBest = localStorage.getItem('bestScore');
this.bestScore = savedBest ? parseInt(savedBest) : 0;
this.physics.world.setBounds(0, 0, w, h);
this.balls = this.physics.add.group();
this.bgGlow = this.add.circle(w / 2, h / 2, 200, 0xffffff, 0).setBlendMode(Phaser.BlendModes.ADD);
this.createStarBackground(w, h);
// Danger line
this.add.rectangle(w / 2, TOP_LIMIT, w, 3, 0xff0055, 0.9);
this.add.rectangle(w / 2, TOP_LIMIT, w, 16, 0xff0055, 0.2);
// UI
this.scoreText = this.add.text(12, 12, '0', { fontSize: '46px', fontFamily: 'Arial Black', color: '#ffffff', shadow: { offsetX: 0, offsetY: 0, color: '#00f2fe', blur: 12, fill: true } });
this.bestText = this.add.text(w - 12, 20, '🏆 ' + this.formatScore(this.bestScore), { fontSize: '18px', color: '#ffd700', fontFamily: 'Arial', fontStyle: 'bold', shadow: { offsetX: 2, offsetY: 2, color: '#000', blur: 4 } }).setOrigin(1, 0);
this.comboText = this.add.text(w / 2, 105, '', { fontSize: '38px', color: '#ff00ff', fontStyle: 'bold', fontFamily: 'Arial Black', stroke: '#000', strokeThickness: 5 }).setOrigin(0.5).setAlpha(0);
// Difficulty Bar
this.diffBarBg = this.add.rectangle(w/2, 115, w - 40, 12, 0x333333).setOrigin(0.5);
this.diffBarFill = this.add.rectangle(w/2 - (w-40)/2, 115, 0, 12, 0xffaa44).setOrigin(0, 0.5);
this.diffText = this.add.text(w/2, 130, 'СЛОЖНОСТЬ 1', { fontSize: '14px', color: '#ffaa44', fontFamily: 'Arial', fontStyle: 'bold' }).setOrigin(0.5);
// Счётчик максимальных шаров (для отладки на экране)
this.counterText = this.add.text(w/2, 148, 'Белых шаров: 0', { fontSize: '12px', color: '#ffffff', fontFamily: 'Arial' }).setOrigin(0.5);
// КНОПКА ДОМОЙ (выход в меню)
this.homeBtn = this.add.text(w - 50, 12, '🏠', { fontSize: '36px', fontFamily: 'Arial', color: '#ffffff', backgroundColor: '#000000aa', padding: {x:8,y:4} })
.setOrigin(0.5)
.setInteractive({ useHandCursor: true });
this.homeBtn.on('pointerover', () => this.homeBtn.setColor('#ffaa44'));
this.homeBtn.on('pointerout', () => this.homeBtn.setColor('#ffffff'));
this.homeBtn.on('pointerdown', () => {
SoundFX.playUIClick();
updateStats(this.score, this.combo);
this.scene.start('MenuScene');
});
this.previewContainer = this.add.container(w - 45, 68);
this.previewBg = this.add.circle(0, 0, 34, 0x000000, 0.6).setStrokeStyle(2.5, 0x00f2fe, 0.8);
this.preview = this.add.image(0, 0, 'ball1').setScale(0.85);
this.previewContainer.add([this.previewBg, this.preview]);
this.tweens.add({ targets: this.previewContainer, angle: 360, duration: 8000, repeat: -1 });
this.aimLine = this.add.graphics();
this.input.on('pointermove', (pointer) => {
if (this.gameOver) return;
this.aimLine.clear();
this.aimLine.lineStyle(2.5, 0x00f2fe, 0.5);
this.aimLine.lineBetween(pointer.x, 110, pointer.x, h);
});
this.input.on('pointerdown', (pointer) => {
if (this.gameOver) {
this.addFlash(0xffffff, 0.6);
this.scene.restart();
return;
}
if (!this.canSpawn) return;
if (this.balls.getLength() >= this.currentMaxBalls) {
this.endGame();
return;
}
SoundFX.playSpawn();
this.createRipple(pointer.x, pointer.y);
this.addFlash(LEVEL_COLORS[this.nextLevel - 1], 0.15);
this.spawnBall(pointer.x, 90, this.nextLevel);
this.canSpawn = false;
this.tweens.add({ targets: this.previewContainer, scale: 0.7, duration: 100, yoyo: true });
this.cameras.main.shake(30, 0.004);
this.time.delayedCall(250, () => { if (!this.gameOver) this.canSpawn = true; });
// Dynamic spawn probabilities based on difficulty
let r = Math.random();
let chance2 = Math.min(0.25 + this.difficulty * 0.01, 0.45);
let chance3 = Math.min(0.1 + this.difficulty * 0.008, 0.25);
if (r < chance3) this.nextLevel = 3;
else if (r < chance2 + chance3) this.nextLevel = 2;
else this.nextLevel = 1;
if (this.nextLevel > MAX_LEVEL - 2) this.nextLevel = MAX_LEVEL - 2;
this.preview.setTexture('ball' + this.nextLevel);
});
this.physics.add.collider(this.balls, this.balls, this.handleCollision, null, this);
this.time.addEvent({ delay: 5000, callback: this.cleanupBalls, callbackScope: this, loop: true });
this.showStartEffect(w, h);
console.log('🎮 Игра запущена! Сложность повышается каждые 3 БЕЛЫХ шара (8 уровень)');
}
updateDifficultyParams() {
const w = GAME_WIDTH;
let gravityVal = this.baseGravity * (1 + (this.difficulty-1) * 0.05);
this.physics.world.gravity.y = Math.min(gravityVal, 2800);
this.currentMaxBalls = Math.max(40, 60 - Math.floor(this.difficulty/2));
let fillPercent = this.difficulty / 20;
if (this.diffBarFill) {
this.diffBarFill.width = (w - 40) * Math.min(fillPercent, 1);
}
if (this.diffText) {
this.diffText.setText(`СЛОЖНОСТЬ ${this.difficulty}`);
}
if (this.counterText) {
let needed = 3 - (this.maxBallsCreatedCount % 3);
if (needed === 0) needed = 3;
this.counterText.setText(`⚪ Белых шаров: ${this.maxBallsCreatedCount} (до след. ур.: ${needed})`);
}
if (this.diffBarFill && this.diffBarFill.width > 0) this.diffBarFill.setVisible(true);
console.log(`📊 Параметры сложности ${this.difficulty}: гравитация=${Math.round(gravityVal)}, макс.шаров=${this.currentMaxBalls}`);
}
increaseDifficulty() {
if (this.difficulty >= 20) return;
this.difficulty = Math.min(20, this.difficulty + 1);
this.updateDifficultyParams();
const w = GAME_WIDTH;
const h = GAME_HEIGHT;
const notif = this.add.text(w/2, h/2 - 100, `⚠️ СЛОЖНОСТЬ ${this.difficulty} ⚠️`, { fontSize: '28px', fontFamily: 'Arial Black', color: '#ff5500', stroke: '#000', strokeThickness: 4 }).setOrigin(0.5);
this.tweens.add({ targets: notif, scale: 1.2, alpha: 0, duration: 800, onComplete: () => notif.destroy() });
this.cameras.main.shake(200, 0.008);
console.log(`🔥🔥🔥 СЛОЖНОСТЬ ПОВЫШЕНА ДО ${this.difficulty}! Создано белых шаров: ${this.maxBallsCreatedCount}`);
}
createStarBackground(w, h) {
for (let i = 0; i < 60; i++) {
const x = Phaser.Math.Between(0, w);
const y = Phaser.Math.Between(0, h);
const star = this.add.circle(x, y, Phaser.Math.Between(1, 2.5), 0xffffff, Phaser.Math.Between(0.2, 0.5));
this.tweens.add({ targets: star, alpha: 0.05, duration: Phaser.Math.Between(1000, 3000), yoyo: true, repeat: -1 });
}
}
showStartEffect(w, h) {
this.addFlash(0x00f2fe, 0.5);
const startText = this.add.text(w / 2, h / 2, '⚡ NEON MERGE ⚡', { fontSize: '42px', fontFamily: 'Arial Black', color: '#ffffff', stroke: '#ff00ff', strokeThickness: 4 }).setOrigin(0.5).setAlpha(0);
this.tweens.add({ targets: startText, scale: 1.2, alpha: 1, duration: 400, yoyo: true, onComplete: () => startText.destroy() });
}
createRipple(x, y) { const ripple = this.add.circle(x, y, 5, 0x00ffff, 0.8); this.tweens.add({ targets: ripple, scale: 3, alpha: 0, duration: 200, onComplete: () => ripple.destroy() }); }
addFlash(color, intensity) { const w = GAME_WIDTH; const h = GAME_HEIGHT; const flash = this.add.rectangle(w/2, h/2, w, h, color, intensity); this.tweens.add({ targets: flash, alpha: 0, duration: 150, onComplete: () => flash.destroy() }); }
formatScore(score) { if (score >= 1000000) return (score/1000000).toFixed(1)+'M'; if (score>=1000) return (score/1000).toFixed(0)+'K'; return score.toString(); }
spawnBall(x, y, level) {
if (this.gameOver) return;
const ball = this.balls.create(x, y, 'ball' + level);
if (!ball) return;
ball.setData('level', level);
ball.setData('isMerging', false);
ball.setData('dangerTime', null);
ball.setBounce(0.2).setDrag(2).setMass(1 + level * 0.3).setCollideWorldBounds(true).setCircle(ball.width/2);
ball.setScale(0);
this.tweens.add({ targets: ball, scale: 1, duration: 200, ease: 'Back.Out' });
// ОТСЛЕЖИВАНИЕ БЕЛЫХ ШАРОВ (уровень 8)
if (level === MAX_LEVEL) {
this.maxBallsCreatedCount++;
console.log(`✨✨✨ СОЗДАН БЕЛЫЙ ШАР #${this.maxBallsCreatedCount} ✨✨✨`);
// Обновляем счётчик на экране
if (this.counterText) {
let needed = 3 - (this.maxBallsCreatedCount % 3);
if (needed === 0) needed = 3;
this.counterText.setText(`⚪ Белых шаров: ${this.maxBallsCreatedCount} (до след. ур.: ${needed})`);
}
// Каждые 3 белых шара повышаем сложность
if (this.maxBallsCreatedCount % 3 === 0 && this.difficulty < 20) {
this.increaseDifficulty();
}
}
for (let i=0;i<6;i++) { const angle=Math.random()*Math.PI*2; const spark=this.add.circle(x,y,2.5,LEVEL_COLORS[level-1],0.8); this.tweens.add({targets:spark, x:x+Math.cos(angle)*30, y:y+Math.sin(angle)*30, alpha:0, duration:250, onComplete:()=>spark.destroy()}); }
}
juiceSquash(ball,sx,sy){ if(ball&&ball.active&&!ball.getData('isMerging')) this.tweens.add({targets:ball,scaleX:sx,scaleY:sy,duration:70,yoyo:true});}
handleCollision(ball1,ball2){
if(!ball1||!ball2||!ball1.active||!ball2.active||this.gameOver) return;
let l1=ball1.getData('level'), l2=ball2.getData('level');
if(l1!==l2) { let v1=ball1.body.velocity, v2=ball2.body.velocity; if(Math.hypot(v1.x-v2.x,v1.y-v2.y)>100) { this.juiceSquash(ball1,1.18,0.82); this.juiceSquash(ball2,0.82,1.18); } return; }
if(l1>=MAX_LEVEL||ball1.getData('isMerging')||ball2.getData('isMerging')) return;
if(Phaser.Math.Distance.Between(ball1.x,ball1.y,ball2.x,ball2.y)>ball1.width/2+ball2.width/2+5) return;
ball1.setData('isMerging',true); ball2.setData('isMerging',true);
this.combo++; SoundFX.playMerge(this.combo);
let x=(ball1.x+ball2.x)/2, y=(ball1.y+ball2.y)/2, nextLevel=l1+1;
let scoreGain=Math.min(l1*25*Math.min(this.combo,10),5000);
this.score=Math.min(this.score+scoreGain,999999);
this.showScorePopup(x,y,scoreGain);
this.bgGlow.setPosition(x,y).setFillStyle(LEVEL_COLORS[l1-1],0.35).setScale(0.1);
this.tweens.add({targets:this.bgGlow,scale:3+l1*0.5,alpha:0,duration:450});
if(this.score>this.bestScore){ this.bestScore=this.score; localStorage.setItem('bestScore',this.bestScore); this.bestText.setText('🏆 '+this.formatScore(this.bestScore)); this.addFlash(0xffd700,0.2);}
this.scoreText.setText(this.formatScore(this.score)); this.tweens.add({targets:this.scoreText,scale:1.3,duration:90,yoyo:true});
if(this.combo>=2){ this.comboText.setText(this.combo+'x COMBO!').setAlpha(1).setScale(0.2).setColor(this.combo>=5?'#ff00ff':'#00ffff'); this.tweens.add({targets:this.comboText,scale:1.4,alpha:0,duration:500}); if(this.combo>=5&&navigator.vibrate) navigator.vibrate(60); }
this.cameras.main.shake(80,0.006+l1*0.002);
this.createExplosion(x,y,LEVEL_COLORS[l1-1],l1);
ball1.destroy(); ball2.destroy();
// ВАЖНО: создаём новый шар следующего уровня
this.time.delayedCall(40, () => {
if (!this.gameOver) {
this.spawnBall(x, y, nextLevel);
}
});
if(this.comboTimer) this.comboTimer.remove();
this.comboTimer=this.time.delayedCall(1800,()=>{ if(this.combo>0&&!this.gameOver){ this.combo=0; const lost=this.add.text(GAME_WIDTH/2,130,'COMBO LOST',{fontSize:'16px',color:'#ff3b3b'}).setOrigin(0.5); this.tweens.add({targets:lost,alpha:0,y:105,duration:500,onComplete:()=>lost.destroy()}); } });
}
showScorePopup(x,y,score){ const pop=this.add.text(x,y,'+'+score,{fontSize:'30px',fontFamily:'Arial Black',color:'#ffff00',stroke:'#000',strokeThickness:3}).setOrigin(0.5); this.tweens.add({targets:pop,y:y-65,alpha:0,scale:1.5,duration:400,onComplete:()=>pop.destroy()}); }
createExplosion(x,y,color,level){ let quality = loadSettings().quality; let count = (quality === 'low') ? Math.min(12, 8 + level) : Math.min(32, 16 + level*2); for(let i=0;i<count;i++){ let angle=Math.random()*Math.PI*2, speed=Phaser.Math.Between(90,220), part=this.add.circle(x,y,Phaser.Math.Between(3,8),color,0.95); this.tweens.add({targets:part,x:x+Math.cos(angle)*speed,y:y+Math.sin(angle)*speed,alpha:0,scale:0,duration:350+Math.random()*200,onComplete:()=>part.destroy()}); } }
cleanupBalls(){ if(this.gameOver) return; [...this.balls.getChildren()].forEach(b=>{if(b&&b.y>GAME_HEIGHT+150)b.destroy();}); }
update(){
if(this.gameOver) return;
let anyDanger=false;
[...this.balls.getChildren()].forEach(ball=>{
if(!ball.body) return;
if(ball.body.velocity.y>300&&Math.random()<0.35){ const trail=this.add.circle(ball.x,ball.y,ball.width/2.2,LEVEL_COLORS[ball.getData('level')-1],0.4); this.tweens.add({targets:trail,alpha:0,scale:0.5,duration:120,onComplete:()=>trail.destroy()}); }
let stable=Math.abs(ball.body.velocity.y)<15&&Math.abs(ball.body.velocity.x)<8;
if(ball.y+ball.height/2<TOP_LIMIT){
if(stable){
if(!ball.getData('dangerTime')) ball.setData('dangerTime',this.time.now);
else if(this.time.now-ball.getData('dangerTime')>1500) anyDanger=true;
} else ball.setData('dangerTime',null);
} else ball.setData('dangerTime',null);
});
if(anyDanger&&!this.isGameOverTriggered) this.endGame();
}
endGame(){
if(this.gameOver||this.isGameOverTriggered) return;
this.gameOver=true; this.isGameOverTriggered=true;
SoundFX.playGameOver();
updateStats(this.score, this.combo);
if(this.physics) this.physics.pause();
this.aimLine.clear();
this.addFlash(0xff0055,0.4);
const w = GAME_WIDTH;
const h = GAME_HEIGHT;
const overlay=this.add.rectangle(w/2,h/2,w,h,0x02020a,0.9); this.tweens.add({targets:overlay,alpha:1,duration:400});
this.add.text(w/2,200,'GAME OVER',{fontSize:'52px',color:'#ff0055',fontStyle:'bold',fontFamily:'Arial Black',stroke:'#000',strokeThickness:5}).setOrigin(0.5).setScale(1);
this.add.text(w/2,310,this.formatScore(this.score),{fontSize:'64px',color:'#ffff00',fontStyle:'bold',fontFamily:'Arial Black',stroke:'#000',strokeThickness:4}).setOrigin(0.5);
const tapText=this.add.text(w/2,510,'👆 TAP TO RESTART 👆',{fontSize:'22px',color:'#ffffff'}).setOrigin(0.5);
this.tweens.add({targets:tapText,scale:1.05,duration:400,yoyo:true,repeat:-1});
if(ysdk) ysdk.adv.showFullscreenAdv().catch(e=>console.error(e));
}
}
// ================================
// CONFIG AND START
// ================================
let dims = getGameDimensions();
GAME_WIDTH = dims.width;
GAME_HEIGHT = dims.height;
BASE_GRAVITY = 1250;
const config = {
type: Phaser.AUTO,
width: GAME_WIDTH,
height: GAME_HEIGHT,
backgroundColor: '#050515',
scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH },
physics: { default: 'arcade', arcade: { gravity: { y: BASE_GRAVITY }, debug: false } },
scene: [BootScene, MenuScene, SettingsScene, RecordsScene, GameScene],
disableContextMenu: true
};
window.addEventListener('load', () => { new Phaser.Game(config); });
</script>
</body>
</html>Game Source: Meme Merge Evolution+ | Full Menu & Progression
Creator: CyberPanther37
Libraries: phaser
Complexity: complex (762 lines, 34.5 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: meme-merge-evolution-full-menu-progressi-cyberpanther37" to link back to the original. Then publish at arcadelab.ai/publish.