/
raa036
/
GenAI
Обзор
Документация
Войти
/
raa036
/
GenAI
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
game1.html
2 128 строк
79 KB
raa036
upload files
11 янв 2026, 22:11
11 янв 2026, 22:11
6ed81e6
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <title>CYBER DASH 8.0 - ULTIMATE EDITION</title> <style> body, html { margin: 0; padding: 0; background: #000; overflow: hidden; height: 100%; width: 100%; font-family: 'Segoe UI', sans-serif; color: #fff; } canvas { display: block; position: absolute; top: 0; left: 0; } /* Меню и Интерфейс */ #ui-layer { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; pointer-events: none; z-index: 10; } .panel { background: rgba(0,0,0,0.9); border: 2px solid #0ff; padding: 40px; text-align: center; pointer-events: auto; box-shadow: 0 0 50px rgba(0,255,255,0.2); z-index: 20; } .btn { display: block; width: 250px; padding: 15px; margin: 10px auto; background: transparent; border: 1px solid #0ff; color: #0ff; font-size: 18px; cursor: pointer; transition: 0.3s; text-transform: uppercase; } .btn:hover { background: #0ff; color: #000; box-shadow: 0 0 20px #0ff; } #hud { position: absolute; top: 20px; left: 20px; font-size: 18px; font-weight: 700; color: #0ff; display: none; align-items: center; gap: 12px; z-index: 5; } #color-box { width: 28px; height: 28px; border: 2px solid #fff; box-shadow: 0 0 10px rgba(255,255,255,0.2); } #hud .stat { font-size: 16px; color: #0ff; } #vol-ctrl { position: absolute; bottom: 20px; right: 20px; pointer-events: auto; color: #0ff; z-index: 5; } #editor-hint { position: absolute; top: 20px; right: 20px; background: rgba(255,0,255,0.12); padding: 10px; display: none; color: #f0f; z-index: 5; } #creator-ui { position: absolute; top: 70px; left: 20px; background: rgba(0,0,0,0.8); padding: 15px; border: 2px solid #0ff; display: none; flex-direction: column; gap: 10px; z-index: 5; } .creator-btn { padding: 8px 15px; background: transparent; border: 1px solid #0ff; color: #0ff; cursor: pointer; transition: 0.3s; min-width: 120px; text-align: center; } .creator-btn:hover, .creator-btn.active { background: #0ff; color: #000; } #color-palette { display: flex; gap: 5px; margin-top: 10px; } .color-swatch { width: 25px; height: 25px; border: 2px solid white; cursor: pointer; } .color-swatch.active { border: 2px solid #fff; box-shadow: 0 0 10px #fff; } #creator-mode-selector { position: absolute; bottom: 80px; left: 20px; background: rgba(0,0,0,0.8); padding: 15px; border: 2px solid #0ff; display: none; flex-direction: column; gap: 10px; z-index: 5; } #play-level-btn { position: absolute; bottom: 20px; left: 20px; padding: 12px 24px; background: #0f0; border: none; color: #000; font-weight: bold; cursor: pointer; display: none; z-index: 5; font-size: 16px; } #back-to-edit-btn { position: absolute; bottom: 20px; left: 20px; padding: 12px 24px; background: #f0f; border: none; color: #000; font-weight: bold; cursor: pointer; display: none; z-index: 5; font-size: 16px; } #creator-info { position: absolute; top: 20px; left: 20px; color: #0ff; z-index: 5; display: none; } #creator-hud { position: absolute; top: 20px; left: 20px; color: #0ff; z-index: 5; display: none; } #mode-indicator { position: absolute; top: 20px; right: 20px; padding: 10px 20px; background: rgba(0,0,0,0.8); border: 2px solid #0ff; color: #0ff; font-weight: bold; display: none; z-index: 5; } </style> </head> <body> <div id="ui-layer"> <div id="main-menu" class="panel"> <h1 style="font-size: 48px; margin-bottom: 30px; letter-spacing: 10px;">CYBER DASH</h1> <button class="btn" onclick="initLevel(1)">Level 1: CUBE</button> <button class="btn" onclick="initLevel(2)">Level 2: ROCKET</button> <button class="btn" onclick="initLevel(3)">Level 3: HYBRID</button> <button class="btn" onclick="initCreatorMode()" style="border-color: #f0f; color: #f0f;">CREATOR MODE</button> </div> <div id="victory-screen" class="panel" style="display: none;"> <h1 style="color: #0f0; font-size: 60px;">MISSION COMPLETE!</h1> <p id="final-stat"></p> <button class="btn" onclick="backToMenu()">В МЕНЮ</button> </div> </div> <div id="hud"> <div class="stat">SCORE: <span id="score-val">0</span></div> <div id="color-box" title="Текущий цвет игрока"></div> <div class="stat">COLOR: <span id="color-name">BLUE</span></div> </div> <div id="creator-hud"> <div class="stat">CREATOR MODE</div> </div> <div id="mode-indicator"> MODE: <span id="current-mode">CUBE</span> </div> <div id="editor-hint">ИГРА: Space - прыжок, Shift - рывок, W/S - управление ракетой, R - смена цвета</div> <div id="creator-info"> <div>CREATOR MODE: Карта статична</div> <div>Прокрутка: WASD или стрелки</div> <div>Удаление: клик с SHIFT</div> </div> <div id="creator-ui"> <div style="color: #0ff; font-weight: bold; margin-bottom: 5px;">TOOLS:</div> <button class="creator-btn active" onclick="setCreatorTool('floor_spike')">Floor Spike</button> <button class="creator-btn" onclick="setCreatorTool('floor_wall')">Floor Wall</button> <button class="creator-btn" onclick="setCreatorTool('floor_double')">Floor Double</button> <button class="creator-btn" onclick="setCreatorTool('ladder')">Ladder</button> <button class="creator-btn" onclick="setCreatorTool('moving_platform')">Moving Platform</button> <button class="creator-btn" onclick="setCreatorTool('ceiling_spike')">Ceiling Spike</button> <button class="creator-btn" onclick="setCreatorTool('ceiling_wall')">Ceiling Wall</button> <button class="creator-btn" onclick="setCreatorTool('ceiling_double')">Ceiling Double</button> <div style="color: #0ff; font-weight: bold; margin-top: 15px; margin-bottom: 5px;">COLOR:</div> <div id="color-palette"> <div class="color-swatch active" style="background: #0ff;" onclick="setCreatorColor('#0ff')"></div> <div class="color-swatch" style="background: #f0f;" onclick="setCreatorColor('#f0f')"></div> </div> <button class="creator-btn" onclick="clearLevel()" style="margin-top: 15px; background: rgba(255,0,0,0.3);">CLEAR LEVEL</button> </div> <div id="creator-mode-selector"> <div style="color: #0ff; font-weight: bold; margin-bottom: 10px;">SELECT PLAYER MODE:</div> <button class="creator-btn active" onclick="selectPlayMode('cube')">PLAY AS CUBE</button> <button class="creator-btn" onclick="selectPlayMode('rocket')">PLAY AS ROCKET</button> </div> <button id="play-level-btn" onclick="showModeSelector()">SELECT PLAYER & PLAY</button> <button id="back-to-edit-btn" onclick="backToEditing()">BACK TO EDITING</button> <div id="vol-ctrl"> VOL: <input type="range" id="vol" min="0" max="100" value="40"> </div> <canvas id="g"></canvas> <script> // Инициализация переменных const canvas = document.getElementById('g'); const ctx = canvas.getContext('2d'); const scoreDisplay = document.getElementById('score-val'); const colorBox = document.getElementById('color-box'); const colorName = document.getElementById('color-name'); const hud = document.getElementById('hud'); const creatorHud = document.getElementById('creator-hud'); const modeIndicator = document.getElementById('mode-indicator'); const currentModeSpan = document.getElementById('current-mode'); const editorHint = document.getElementById('editor-hint'); const mainMenu = document.getElementById('main-menu'); const victoryScreen = document.getElementById('victory-screen'); const creatorUI = document.getElementById('creator-ui'); const creatorInfo = document.getElementById('creator-info'); const creatorModeSelector = document.getElementById('creator-mode-selector'); const playLevelBtn = document.getElementById('play-level-btn'); const backToEditBtn = document.getElementById('back-to-edit-btn'); let audioCtx, masterGain, currentOscillator, currentGain, musicInterval; let gameLoopId = null; // Цвета для игры (только голубой и розовый) const COLORS = [ {name: 'BLUE', hex: '#0ff'}, {name: 'PINK', hex: '#f0f'} ]; // Состояние игры const state = { screen: 'menu', // 'menu', 'playing', 'victory', 'creating', 'testing' levelId: 1, mode: 'cube', // 'cube', 'rocket' time: 0, lastFrame: 0, player: { x: 200, y: 0, vy: 0, w: 35, h: 35, dashing: 0, colorIndex: 0, color: '#0ff', enginePower: 0, transformationProgress: 0, // Для анимации перехода transformDirection: 0, // 0 = нет, 1 = в ракету, -1 = в куб prevMode: 'cube' }, levelData: [], particles: [], specialEffects: [], isHoldingW: false, isHoldingS: false, floorY: 0, ceilingY: 0, // Переменные для режима создателя creatorData: [], cameraX: 0, cameraY: 0, cameraSpeed: 10, creatorTool: 'floor_spike', creatorColor: '#0ff', creatorWallHeight: 120, // Для Level 3 (HYBRID) nextTransformationTime: 0, transformationInterval: 20000, // 20 секунд между переходами isTransforming: false }; // Оригинальные уровни с разной музыкой const LEVELS = { 1: { mode: 'cube', musicFreq: 220, // Высокая частота для куба floorY: 0.7, data: [ {t: 1500, type: 'spike', color: '#0ff'}, {t: 2500, type: 'spike', color: '#f0f'}, {t: 3500, type: 'double', color: '#0ff'}, {t: 4500, type: 'wall', h: 80, color: '#f0f'}, {t: 5500, type: 'spike', color: '#0ff'}, {t: 6500, type: 'double', color: '#f0f'}, {t: 7500, type: 'wall', h: 100, color: '#0ff'}, // Средняя сложность (10000-30000 мс) {t: 10000, type: 'spike', color: '#f0f'}, {t: 12000, type: 'double', color: '#0ff'}, {t: 14000, type: 'wall', h: 120, color: '#f0f'}, {t: 16000, type: 'spike', color: '#0ff'}, {t: 18000, type: 'double', color: '#f0f'}, {t: 20000, type: 'wall', h: 140, color: '#0ff'}, {t: 22000, type: 'spike', color: '#f0f'}, {t: 24000, type: 'double', color: '#0ff'}, {t: 26000, type: 'wall', h: 160, color: '#f0f'}, {t: 28000, type: 'spike', color: '#0ff'}, // Высокая сложность (30000-45000 мс) {t: 30000, type: 'double', color: '#f0f'}, {t: 32000, type: 'wall', h: 180, color: '#0ff'}, {t: 34000, type: 'spike', color: '#f0f'}, {t: 36000, type: 'double', color: '#0ff'}, {t: 38000, type: 'wall', h: 200, color: '#f0f'}, {t: 40000, type: 'spike', color: '#0ff'}, {t: 42000, type: 'double', color: '#f0f'}, {t: 44000, type: 'wall', h: 220, color: '#0ff'}, // Финальный этап (45000-60000 мс) {t: 46000, type: 'spike', color: '#f0f'}, {t: 48000, type: 'double', color: '#0ff'}, {t: 50000, type: 'wall', h: 240, color: '#f0f'}, {t: 52000, type: 'spike', color: '#0ff'}, {t: 54000, type: 'double', color: '#f0f'}, {t: 56000, type: 'wall', h: 260, color: '#0ff'}, {t: 58000, type: 'spike', color: '#f0f'}, {t: 59000, type: 'double', color: '#0ff'}, {t: 59500, type: 'wall', h: 280, color: '#f0f'}, {t: 59800, type: 'spike', color: '#0ff'} ] }, 2: { mode: 'rocket', musicFreq: 140, // Низкая частота для ракеты floorY: 0.85, ceilingY: 0.15, data: [ {t: 1000, type: 'floor_spike', color: '#0ff'}, {t: 2000, type: 'ceiling_spike', color: '#f0f'}, {t: 3000, type: 'floor_wall', h: 150, color: '#0ff'}, {t: 4000, type: 'ceiling_wall', h: 150, color: '#f0f'}, {t: 6000, type: 'floor_double', color: '#f0f'}, {t: 7000, type: 'ceiling_double', color: '#0ff'}, {t: 8000, type: 'floor_wall', h: 180, color: '#f0f'}, {t: 9000, type: 'ceiling_wall', h: 180, color: '#0ff'}, {t: 11000, type: 'floor_wall', h: 120, color: '#0ff'}, {t: 11500, type: 'ceiling_wall', h: 120, color: '#f0f'}, {t: 12000, type: 'floor_spike', color: '#0ff'}, {t: 12500, type: 'ceiling_spike', color: '#f0f'}, {t: 14000, type: 'floor_wall', h: 200, color: '#f0f'}, {t: 14500, type: 'ceiling_wall', h: 200, color: '#0ff'}, {t: 15000, type: 'floor_double', color: '#f0f'}, {t: 15500, type: 'ceiling_double', color: '#0ff'}, {t: 17000, type: 'floor_spike', color: '#0ff'}, {t: 17500, type: 'ceiling_spike', color: '#f0f'}, {t: 18000, type: 'floor_wall', h: 220, color: '#0ff'}, {t: 18500, type: 'ceiling_wall', h: 100, color: '#f0f'}, {t: 19000, type: 'floor_double', color: '#0ff'}, {t: 19500, type: 'ceiling_double', color: '#f0f'}, {t: 21000, type: 'floor_wall', h: 240, color: '#f0f'}, {t: 21500, type: 'ceiling_wall', h: 120, color: '#0ff'}, {t: 22000, type: 'floor_spike', color: '#f0f'}, {t: 22500, type: 'ceiling_spike', color: '#0ff'}, {t: 23000, type: 'floor_double', color: '#f0f'}, {t: 23500, type: 'ceiling_double', color: '#0ff'}, {t: 25000, type: 'floor_wall', h: 260, color: '#0ff'}, {t: 25500, type: 'ceiling_wall', h: 140, color: '#f0f'}, {t: 26000, type: 'floor_spike', color: '#0ff'}, {t: 26500, type: 'ceiling_spike', color: '#f0f'}, {t: 28000, type: 'floor_double', color: '#f0f'}, {t: 28500, type: 'ceiling_double', color: '#0ff'}, {t: 29000, type: 'floor_wall', h: 200, color: '#f0f'}, {t: 29500, type: 'ceiling_wall', h: 160, color: '#0ff'}, {t: 30000, type: 'floor_spike', color: '#f0f'}, {t: 30500, type: 'ceiling_spike', color: '#0ff'}, {t: 32000, type: 'floor_wall', h: 280, color: '#0ff'}, {t: 32500, type: 'ceiling_wall', h: 80, color: '#f0f'}, {t: 33000, type: 'floor_double', color: '#0ff'}, {t: 33500, type: 'ceiling_double', color: '#f0f'}, {t: 34000, type: 'floor_wall', h: 120, color: '#0ff'}, {t: 34500, type: 'ceiling_wall', h: 240, color: '#f0f'}, {t: 36000, type: 'floor_spike', color: '#f0f'}, {t: 36500, type: 'ceiling_spike', color: '#0ff'}, {t: 37000, type: 'floor_wall', h: 300, color: '#f0f'}, {t: 37500, type: 'ceiling_wall', h: 100, color: '#0ff'}, {t: 38000, type: 'floor_double', color: '#f0f'}, {t: 38500, type: 'ceiling_double', color: '#0ff'}, {t: 40000, type: 'floor_spike', color: '#0ff'}, {t: 40200, type: 'ceiling_spike', color: '#f0f'}, {t: 40400, type: 'floor_spike', color: '#0ff'}, {t: 40600, type: 'ceiling_spike', color: '#f0f'}, {t: 40800, type: 'floor_double', color: '#0ff'}, {t: 41000, type: 'ceiling_double', color: '#f0f'}, {t: 42000, type: 'floor_wall', h: 320, color: '#f0f'}, {t: 42500, type: 'ceiling_wall', h: 60, color: '#0ff'}, {t: 43000, type: 'floor_wall', h: 140, color: '#f0f'}, {t: 43500, type: 'ceiling_wall', h: 240, color: '#0ff'}, {t: 44000, type: 'floor_spike', color: '#f0f'}, {t: 44500, type: 'ceiling_spike', color: '#0ff'}, {t: 46000, type: 'floor_double', color: '#0ff'}, {t: 46500, type: 'ceiling_double', color: '#f0f'}, {t: 47000, type: 'floor_wall', h: 280, color: '#0ff'}, {t: 47500, type: 'ceiling_wall', h: 80, color: '#f0f'}, {t: 48000, type: 'floor_spike', color: '#0ff'}, {t: 48500, type: 'ceiling_spike', color: '#f0f'}, {t: 50000, type: 'floor_wall', h: 200, color: '#f0f'}, {t: 50200, type: 'ceiling_wall', h: 160, color: '#0ff'}, {t: 50400, type: 'floor_spike', color: '#f0f'}, {t: 50600, type: 'ceiling_spike', color: '#0ff'}, {t: 50800, type: 'floor_double', color: '#f0f'}, {t: 51000, type: 'ceiling_double', color: '#0ff'}, {t: 52000, type: 'floor_wall', h: 180, color: '#0ff'}, {t: 52500, type: 'ceiling_wall', h: 140, color: '#f0f'}, {t: 53000, type: 'floor_spike', color: '#0ff'}, {t: 53500, type: 'ceiling_spike', color: '#f0f'}, {t: 54000, type: 'floor_double', color: '#0ff'}, {t: 54500, type: 'ceiling_double', color: '#f0f'}, {t: 55000, type: 'floor_wall', h: 160, color: '#0ff'}, {t: 55500, type: 'ceiling_wall', h: 160, color: '#f0f'}, {t: 56000, type: 'floor_spike', color: '#0ff'}, {t: 56500, type: 'ceiling_spike', color: '#f0f'}, {t: 57000, type: 'floor_double', color: '#0ff'}, {t: 57500, type: 'ceiling_double', color: '#f0f'}, {t: 58000, type: 'floor_wall', h: 240, color: '#0ff'}, {t: 58500, type: 'ceiling_wall', h: 100, color: '#f0f'}, {t: 59000, type: 'floor_spike', color: '#0ff'}, {t: 59500, type: 'ceiling_spike', color: '#f0f'}, {t: 59800, type: 'floor_double', color: '#0ff'}, {t: 59900, type: 'ceiling_double', color: '#f0f'} ] }, 3: { // Уровень с переходами между кубом и ракетой musicFreq: 180, // Средняя частота для гибрида floorY: 0.7, ceilingY: 0.15, // В этом уровне режим будет меняться динамически data: [ // Начало - режим куба (0-20000 мс) {t: 1500, type: 'spike', color: '#0ff'}, {t: 2500, type: 'ladder', x: 0, steps: 5, stepX: 60, stepY: -40, color: '#f0f'}, {t: 4000, type: 'spike', color: '#f0f'}, {t: 5500, type: 'double', color: '#0ff'}, {t: 7000, type: 'moving_platform', x: 0, width: 200, speed: 2, vertical: false, color: '#f0f'}, {t: 9000, type: 'wall', h: 120, color: '#0ff'}, {t: 11000, type: 'spike', color: '#f0f'}, {t: 13000, type: 'ladder', x: 0, steps: 4, stepX: 70, stepY: -35, color: '#0ff'}, {t: 15000, type: 'double', color: '#f0f'}, {t: 17000, type: 'moving_platform', x: 0, width: 150, speed: 3, vertical: true, color: '#0ff'}, // Переход в ракету в 20000 мс // Режим ракеты (20000-40000 мс) - препятствия на потолке и полу {t: 21000, type: 'floor_spike', color: '#0ff'}, {t: 22000, type: 'ceiling_spike', color: '#f0f'}, {t: 23000, type: 'floor_wall', h: 180, color: '#0ff'}, {t: 24000, type: 'ceiling_wall', h: 120, color: '#f0f'}, {t: 26000, type: 'floor_double', color: '#0ff'}, {t: 27000, type: 'ceiling_double', color: '#f0f'}, {t: 29000, type: 'floor_spike', color: '#f0f'}, {t: 30000, type: 'ceiling_spike', color: '#0ff'}, {t: 32000, type: 'floor_wall', h: 220, color: '#f0f'}, {t: 33000, type: 'ceiling_wall', h: 100, color: '#0ff'}, {t: 35000, type: 'floor_double', color: '#0ff'}, {t: 36000, type: 'ceiling_double', color: '#f0f'}, {t: 38000, type: 'floor_spike', color: '#f0f'}, {t: 39000, type: 'ceiling_spike', color: '#0ff'}, // Переход в куб в 40000 мс // Снова куб (40000-60000 мс) - более сложные препятствия {t: 41000, type: 'ladder', x: 0, steps: 6, stepX: 50, stepY: -30, color: '#0ff'}, {t: 43000, type: 'spike', color: '#f0f'}, {t: 45000, type: 'moving_platform', x: 0, width: 180, speed: 2.5, vertical: false, color: '#0ff'}, {t: 47000, type: 'double', color: '#f0f'}, {t: 49000, type: 'ladder', x: 0, steps: 5, stepX: 65, stepY: -45, color: '#0ff'}, {t: 51000, type: 'wall', h: 160, color: '#f0f'}, {t: 53000, type: 'spike', color: '#0ff'}, {t: 55000, type: 'moving_platform', x: 0, width: 120, speed: 4, vertical: true, color: '#f0f'}, {t: 57000, type: 'double', color: '#0ff'}, {t: 59000, type: 'ladder', x: 0, steps: 7, stepX: 55, stepY: -25, color: '#f0f'}, {t: 59500, type: 'spike', color: '#0ff'}, {t: 59800, type: 'wall', h: 200, color: '#f0f'} ] } }; // Настройка размера canvas function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; // Обновляем позиции пола и потолка if (state.screen === 'creating') { state.floorY = canvas.height * 0.7; state.ceilingY = canvas.height * 0.15; } else if (state.screen === 'playing') { if (state.levelId === 3) { // Для гибридного уровня используем настройки из уровня state.floorY = canvas.height * LEVELS[3].floorY; state.ceilingY = canvas.height * LEVELS[3].ceilingY; } else if (state.mode === 'rocket') { state.floorY = canvas.height * LEVELS[state.levelId].floorY; state.ceilingY = canvas.height * LEVELS[state.levelId].ceilingY; } else { state.floorY = canvas.height * LEVELS[state.levelId].floorY; } } } // Инициализация аудио с разной музыкой для каждого уровня function initAudio() { if (audioCtx) { // Останавливаем предыдущую музыку if (musicInterval) { clearInterval(musicInterval); } if (currentOscillator) { currentOscillator.stop(); } } else { try { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } catch (e) { console.warn('Audio not available', e); return; } } // Создаем мастер гейн с регулируемой громкостью masterGain = audioCtx.createGain(); masterGain.connect(audioCtx.destination); // Устанавливаем начальную громкость const volume = document.getElementById('vol').value / 100; masterGain.gain.value = volume * 0.3; // Уменьшаем громкость для музыки // Запускаем музыку в зависимости от уровня startLevelMusic(); } function startLevelMusic() { if (!audioCtx) return; // Определяем частоту музыки для текущего уровня let freq = 180; // По умолчанию if (state.levelId === 3 && state.mode === 'cube') { freq = 220; // Для куба в гибридном уровне } else if (state.levelId === 3 && state.mode === 'rocket') { freq = 140; // Для ракеты в гибридном уровне } else if (LEVELS[state.levelId]) { freq = LEVELS[state.levelId].musicFreq; } // Запускаем интервал для создания битов musicInterval = setInterval(() => { if (state.screen !== 'playing' || !audioCtx) return; const t = audioCtx.currentTime; const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); // Настраиваем частоту let currentFreq = freq; if (state.mode === 'rocket' && state.levelId !== 3) { // Для ракеты делаем звук более "реактивным" currentFreq = freq + Math.sin(state.time / 1000) * 50; } else if (state.levelId === 3) { // Для гибридного уровня меняем частоту в зависимости от режима currentFreq = state.mode === 'cube' ? 220 : 140; } osc.frequency.setValueAtTime(currentFreq, t); // Настраиваем огибающую gain.gain.setValueAtTime(0.15, t); gain.gain.exponentialRampToValueAtTime(0.001, t + 0.15); osc.connect(gain); gain.connect(masterGain); osc.start(t); osc.stop(t + 0.15); // Сохраняем текущий осциллятор для возможной остановки currentOscillator = osc; currentGain = gain; }, 200); } // Создание частиц function createParticle(x, y, color, type = 'trail') { state.particles.push({ x, y, vx: type === 'win' ? (Math.random() - 0.5) * 20 : -6 + (Math.random()-0.5)*2, vy: type === 'win' ? (Math.random() - 0.5) * 20 : (Math.random() - 0.5) * 3, life: 1.0, size: type === 'win' ? Math.random() * 8 + 2 : type === 'rocket_trail' ? Math.random() * 6 + 3 : 6, color, type }); } // Создание специального эффекта (для трансформаций) function createSpecialEffect(x, y, color, effectType = 'transform') { state.specialEffects.push({ x, y, size: 10, maxSize: 100, life: 1.0, color, type: effectType }); } // Обновление частиц function updateParticles(dt) { for (let i = state.particles.length - 1; i >= 0; i--) { let p = state.particles[i]; p.x += p.vx; p.y += p.vy; p.life -= (p.type === 'win' ? 0.6 : p.type === 'rocket_trail' ? 1.5 : 2.5) * dt; if (p.life <= 0) { state.particles.splice(i, 1); } } // Обновление специальных эффектов for (let i = state.specialEffects.length - 1; i >= 0; i--) { let e = state.specialEffects[i]; e.life -= 0.8 * dt; e.size += 50 * dt; if (e.life <= 0) { state.specialEffects.splice(i, 1); } } } // Установка цвета игрока function setPlayerColorByIndex(idx) { const c = COLORS[idx % 2]; state.player.colorIndex = idx % 2; state.player.color = c.hex; if (colorBox) colorBox.style.background = c.hex; if (colorName) colorName.innerText = c.name; } // ============ ОРИГИНАЛЬНЫЕ УРОВНИ (Level 1, Level 2, Level 3) ============ function initLevel(id) { // Остановка предыдущего цикла if (gameLoopId) { cancelAnimationFrame(gameLoopId); } // Сброс состояния state.levelId = id; state.screen = 'playing'; state.time = 0; state.lastFrame = performance.now(); state.particles = []; state.specialEffects = []; state.isHoldingW = false; state.isHoldingS = false; // Для Level 3 настраиваем переходы if (id === 3) { state.mode = 'cube'; // Начинаем с куба state.nextTransformationTime = 20000; // Первый переход через 20 секунд state.player.transformationProgress = 0; state.player.transformDirection = 0; state.player.prevMode = 'cube'; modeIndicator.style.display = 'block'; currentModeSpan.textContent = 'CUBE'; } else { state.mode = LEVELS[id].mode; modeIndicator.style.display = 'none'; } state.levelData = JSON.parse(JSON.stringify(LEVELS[id].data)); // Обновление интерфейса mainMenu.style.display = 'none'; victoryScreen.style.display = 'none'; hud.style.display = 'flex'; editorHint.style.display = 'block'; creatorUI.style.display = 'none'; creatorInfo.style.display = 'none'; creatorModeSelector.style.display = 'none'; playLevelBtn.style.display = 'none'; backToEditBtn.style.display = 'none'; creatorHud.style.display = 'none'; // Настройка игрока resize(); if (id === 3) { // Для гибридного уровня начинаем с куба state.player.w = 35; state.player.h = 35; state.player.y = canvas.height * LEVELS[3].floorY - state.player.h - 10; state.player.vy = 0; state.player.x = 200; state.floorY = canvas.height * LEVELS[3].floorY; state.ceilingY = canvas.height * LEVELS[3].ceilingY; } else if (state.mode === 'rocket') { state.player.w = 40; state.player.h = 60; state.player.y = canvas.height * 0.5 - state.player.h/2; state.player.vy = 0; state.player.x = 200; state.floorY = canvas.height * LEVELS[id].floorY; state.ceilingY = canvas.height * LEVELS[id].ceilingY; } else { state.player.w = 35; state.player.h = 35; state.player.y = canvas.height * LEVELS[id].floorY - state.player.h - 10; state.player.vy = 0; state.player.x = 200; state.floorY = canvas.height * LEVELS[id].floorY; } state.player.dashing = 0; state.player.enginePower = 0; // Сброс цвета игрока на голубой setPlayerColorByIndex(0); // Инициализация аудио с правильной музыкой для уровня initAudio(); // Запуск игрового цикла gameLoop(); } // ============ РЕЖИМ СОЗДАТЕЛЯ ============ // Инициализация режима создателя function initCreatorMode() { if (gameLoopId) { cancelAnimationFrame(gameLoopId); } // Сброс состояния state.screen = 'creating'; state.time = 0; state.lastFrame = performance.now(); state.particles = []; state.specialEffects = []; state.isHoldingW = false; state.isHoldingS = false; state.cameraX = 0; state.cameraY = 0; state.creatorData = []; state.creatorTool = 'floor_spike'; state.creatorColor = '#0ff'; // Обновление интерфейса mainMenu.style.display = 'none'; hud.style.display = 'none'; editorHint.style.display = 'none'; creatorUI.style.display = 'flex'; creatorInfo.style.display = 'block'; creatorModeSelector.style.display = 'none'; playLevelBtn.style.display = 'block'; backToEditBtn.style.display = 'none'; creatorHud.style.display = 'block'; modeIndicator.style.display = 'none'; // Настройка canvas resize(); // Запуск игрового цикла gameLoop(); } // Инструменты создателя function setCreatorTool(tool) { state.creatorTool = tool; document.querySelectorAll('.creator-btn').forEach(btn => btn.classList.remove('active')); event.target.classList.add('active'); } function setCreatorColor(color) { state.creatorColor = color; document.querySelectorAll('.color-swatch').forEach(swatch => swatch.classList.remove('active')); event.target.classList.add('active'); } function clearLevel() { if (confirm('Очистить весь уровень?')) { state.creatorData = []; } } // Показать выбор режима игры function showModeSelector() { if (state.creatorData.length === 0) { alert('Сначала создайте препятствия!'); return; } creatorModeSelector.style.display = 'flex'; playLevelBtn.style.display = 'none'; } // Выбор режима игры для созданного уровня function selectPlayMode(mode) { state.mode = mode; state.screen = 'playing'; // Теперь играем созданным уровнем state.levelData = convertCreatorToGameData(); // Настройка игрока if (mode === 'rocket') { state.player.w = 40; state.player.h = 60; state.player.y = canvas.height * 0.5 - state.player.h/2; state.floorY = canvas.height * 0.85; state.ceilingY = canvas.height * 0.15; currentModeSpan.textContent = 'ROCKET'; } else { state.player.w = 35; state.player.h = 35; state.player.y = canvas.height * 0.7 - state.player.h - 10; state.floorY = canvas.height * 0.7; currentModeSpan.textContent = 'CUBE'; } state.player.x = 200; state.player.vy = 0; state.player.dashing = 0; state.player.enginePower = 0; state.player.color = '#0ff'; state.player.colorIndex = 0; // Обновление интерфейса creatorModeSelector.style.display = 'none'; creatorUI.style.display = 'none'; creatorInfo.style.display = 'none'; editorHint.style.display = 'block'; backToEditBtn.style.display = 'block'; hud.style.display = 'flex'; creatorHud.style.display = 'none'; modeIndicator.style.display = 'block'; // Сброс времени state.time = 0; state.lastFrame = performance.now(); // Инициализация аудио initAudio(); } // Конвертация данных создания в игровые данные function convertCreatorToGameData() { const gameData = []; for (let obj of state.creatorData) { const t = (obj.x - 200) / 0.6; // Определяем тип для игры let gameType = obj.type; if (obj.type === 'floor_spike') gameType = 'spike'; if (obj.type === 'floor_double') gameType = 'double'; if (obj.type === 'floor_wall') gameType = 'wall'; const gameObj = { t: Math.round(t), type: gameType, color: obj.color, h: obj.h || 80, steps: obj.steps, stepX: obj.stepX, stepY: obj.stepY, width: obj.width, speed: obj.speed, vertical: obj.vertical }; gameData.push(gameObj); } // Сортируем по времени появления gameData.sort((a, b) => a.t - b.t); return gameData; } // Возврат к редактированию function backToEditing() { state.screen = 'creating'; backToEditBtn.style.display = 'none'; editorHint.style.display = 'none'; hud.style.display = 'none'; creatorUI.style.display = 'flex'; creatorInfo.style.display = 'block'; playLevelBtn.style.display = 'block'; creatorHud.style.display = 'block'; modeIndicator.style.display = 'none'; // Сброс камеры на стартовую позицию state.cameraX = 0; state.cameraY = 0; } // ============ ОБЩИЙ ИГРОВОЙ ЦИКЛ ============ function gameLoop() { const now = performance.now(); let dt = (now - state.lastFrame) / 1000; if (dt > 0.1) dt = 0.1; state.lastFrame = now; if (state.screen === 'playing') { state.time += dt * 1000; updateGame(dt); } else if (state.screen === 'creating') { updateCreator(dt); } updateParticles(dt); draw(); gameLoopId = requestAnimationFrame(gameLoop); } // Обновление в режиме создания function updateCreator(dt) { // Обработка движения камеры if (state.isHoldingW) state.cameraY -= state.cameraSpeed; if (state.isHoldingS) state.cameraY += state.cameraSpeed; if (state.isHoldingA) state.cameraX -= state.cameraSpeed; if (state.isHoldingD) state.cameraX += state.cameraSpeed; // Убираем ограничения камеры - можно двигаться бесконечно // state.cameraX = Math.max(state.cameraX, -50000); // state.cameraY = Math.max(state.cameraY, -canvas.height * 10); // state.cameraY = Math.min(state.cameraY, canvas.height * 10); } // Функция трансформации для Level 3 function transformPlayer(toMode) { if (state.isTransforming) return; state.isTransforming = true; state.player.prevMode = state.mode; state.player.transformDirection = toMode === 'rocket' ? 1 : -1; state.player.transformationProgress = 0; // Создаем эффект трансформации createSpecialEffect(state.player.x + state.player.w/2, state.player.y + state.player.h/2, state.player.color, 'transform'); // Меняем музыку if (musicInterval) { clearInterval(musicInterval); } startLevelMusic(); } // Обновление трансформации function updateTransformation(dt) { if (state.player.transformDirection === 0) return; state.player.transformationProgress += dt * 3; // Скорость трансформации if (state.player.transformationProgress >= 1) { // Завершение трансформации state.player.transformationProgress = 1; // Меняем режим if (state.player.transformDirection === 1) { // В ракету state.mode = 'rocket'; state.player.w = 40; state.player.h = 60; currentModeSpan.textContent = 'ROCKET'; } else { // В куб state.mode = 'cube'; state.player.w = 35; state.player.h = 35; currentModeSpan.textContent = 'CUBE'; } // Сбрасываем трансформацию state.player.transformDirection = 0; state.player.transformationProgress = 0; state.isTransforming = false; // Создаем завершающий эффект createSpecialEffect(state.player.x + state.player.w/2, state.player.y + state.player.h/2, state.player.color, 'transform_complete'); } } // Обновление в режиме игры function updateGame(dt) { let p = state.player; // Проверка трансформации для Level 3 if (state.levelId === 3) { // Проверяем, не пора ли трансформироваться if (state.time >= state.nextTransformationTime && !state.isTransforming) { if (state.mode === 'cube') { transformPlayer('rocket'); } else { transformPlayer('cube'); } state.nextTransformationTime += state.transformationInterval; } // Обновляем анимацию трансформации updateTransformation(dt); } if (state.mode === 'rocket') { // Ракета падает с гравитацией p.vy += 400 * dt; // Управление двигателем if (state.isHoldingW) { p.enginePower = Math.min(p.enginePower + 6 * dt, 1.0); p.vy -= 2000 * dt * p.enginePower; // Создание частиц выхлопа if (Math.random() < 0.7) { const exhaustX = p.x - 15; const exhaustY = p.y + p.h * 0.7 + (Math.random() - 0.5) * 10; createParticle(exhaustX, exhaustY, p.color, 'rocket_trail'); } } else { p.enginePower = Math.max(p.enginePower - 3 * dt, 0); } if (state.isHoldingS) { p.vy += 1000 * dt; } // Ограничение скорости p.vy = Math.max(-500, Math.min(500, p.vy)); // Коллизия с потолком и полом if (p.y < 0) { p.y = 0; p.vy = 0; die(); } if (p.y + p.h > state.floorY) { p.y = state.floorY - p.h; p.vy = 0; die(); } } else { // Кубик if (p.dashing > 0) { p.dashing -= dt; p.x += 1200 * dt; } else { p.x += (200 - p.x) * 5 * dt; p.vy += 2600 * dt; } // Коллизия с полом if (p.y > state.floorY - p.h) { p.y = state.floorY - p.h; p.vy = 0; } if (p.y < 0) { die(); } } // Если идет трансформация, плавно меняем размер if (state.player.transformDirection !== 0) { const targetW = state.player.transformDirection === 1 ? 40 : 35; const targetH = state.player.transformDirection === 1 ? 60 : 35; p.w = 35 + (targetW - 35) * p.transformationProgress; p.h = 35 + (targetH - 35) * p.transformationProgress; // Корректируем позицию, чтобы не проваливаться if (state.player.transformDirection === 1) { // В ракету - поднимаем немного p.y -= 25 * dt; } else { // В куб - опускаем p.y += 25 * dt; } } // Обновление позиции p.y += p.vy * dt; // Создание частиц шлейфа if (state.mode === 'rocket') { if (Math.random() < 0.3 && p.enginePower > 0.1) { createParticle(p.x - 10, p.y + p.h/2, p.color, 'rocket_trail'); } } else if ((state.time % 120) < 40) { createParticle(p.x - 10, p.y + p.h/2, p.color); } // Обновление счета let score = Math.floor(state.time / 100); if (scoreDisplay) scoreDisplay.innerText = score; // Проверка победы (60000 мс = 600 очков) if (state.time >= 60000) { win(); return; } // Проверка столкновений с препятствиями for (let obj of state.levelData) { let ox = (obj.t - state.time) * 0.6 + p.x; if (ox > p.x + p.w + 100 || ox < -200) continue; const sameColor = obj.color === p.color; // Проверка столкновений if (obj.type === 'spike' || obj.type === 'floor_spike') { // Проверяем, должно ли это препятствие быть активным в текущем режиме if (state.levelId === 3) { // В гибридном уровне некоторые препятствия только для определенных режимов if (obj.type === 'floor_spike' && state.mode !== 'rocket') continue; if (obj.type === 'spike' && state.mode !== 'cube') continue; } let hitH = obj.h || 70; let floorPos = state.floorY; let inX = (ox > p.x - 10 && ox < p.x + p.w + 10); let spikeTop = floorPos - hitH; if (inX && (p.y + p.h > spikeTop + 10)) { if (!sameColor) { die(); return; } } } else if (obj.type === 'ceiling_spike') { if (state.levelId === 3 && state.mode !== 'rocket') continue; let hitH = obj.h || 70; let inX = (ox > p.x - 10 && ox < p.x + p.w + 10); let spikeBottom = state.ceilingY + hitH; if (inX && (p.y < spikeBottom - 10)) { if (!sameColor) { die(); return; } } } else if (obj.type === 'double' || obj.type === 'floor_double') { if (state.levelId === 3) { if (obj.type === 'floor_double' && state.mode !== 'rocket') continue; if (obj.type === 'double' && state.mode !== 'cube') continue; } let hitH = obj.h || 70; let floorPos = state.floorY; let leftX = ox, rightX = ox + 35; let spikeTop = floorPos - hitH; let inLeft = (leftX > p.x - 10 && leftX < p.x + p.w + 10) && (p.y + p.h > spikeTop + 10); let inRight = (rightX > p.x - 10 && rightX < p.x + p.w + 10) && (p.y + p.h > spikeTop + 10); if ((inLeft || inRight) && !sameColor) { die(); return; } } else if (obj.type === 'ceiling_double') { if (state.levelId === 3 && state.mode !== 'rocket') continue; let hitH = obj.h || 70; let leftX = ox, rightX = ox + 35; let spikeBottom = state.ceilingY + hitH; let inLeft = (leftX > p.x - 10 && leftX < p.x + p.w + 10) && (p.y < spikeBottom - 10); let inRight = (rightX > p.x - 10 && rightX < p.x + p.w + 10) && (p.y < spikeBottom - 10); if ((inLeft || inRight) && !sameColor) { die(); return; } } else if (obj.type === 'wall' || obj.type === 'floor_wall') { if (state.levelId === 3) { if (obj.type === 'floor_wall' && state.mode !== 'rocket') continue; if (obj.type === 'wall' && state.mode !== 'cube') continue; } let wallHeight = obj.h || 80; let wallTop = state.floorY - wallHeight; let wallWidth = 40; let inX = (ox < p.x + p.w && ox + wallWidth > p.x); let inY = (p.y + p.h > wallTop); if (inX && inY && !sameColor) { die(); return; } } else if (obj.type === 'ceiling_wall') { if (state.levelId === 3 && state.mode !== 'rocket') continue; let wallHeight = obj.h || 80; let wallBottom = state.ceilingY + wallHeight; let wallWidth = 40; let inX = (ox < p.x + p.w && ox + wallWidth > p.x); let inY = (p.y < wallBottom); if (inX && inY && !sameColor) { die(); return; } } else if (obj.type === 'ladder' && state.mode === 'cube') { // Лестница - серия платформ const steps = obj.steps || 5; const stepX = obj.stepX || 60; const stepY = obj.stepY || -40; const platformWidth = 60; const platformHeight = 20; for (let i = 0; i < steps; i++) { let platformX = ox + i * stepX; let platformY = state.floorY - 10 + i * stepY; // Проверяем столкновение с каждой ступенькой let inX = (platformX < p.x + p.w && platformX + platformWidth > p.x); let inY = (p.y + p.h > platformY && p.y < platformY + platformHeight); if (inX && inY) { // Если цвет не совпадает - смерть if (!sameColor) { die(); return; } // Если игрок падает на платформу сверху if (p.vy > 0 && p.y + p.h - p.vy * dt <= platformY) { p.y = platformY - p.h; p.vy = 0; } } } } else if (obj.type === 'moving_platform') { // Движущаяся платформа const width = obj.width || 200; const speed = obj.speed || 2; const vertical = obj.vertical || false; const platformHeight = 20; // Вычисляем смещение платформы let offset = Math.sin(state.time / 1000 * speed) * 100; let platformX, platformY; if (vertical) { platformX = ox; platformY = state.floorY - 100 + offset; } else { platformX = ox + offset; platformY = state.floorY - 100; } // Проверяем столкновение let inX = (platformX < p.x + p.w && platformX + width > p.x); let inY = (p.y + p.h > platformY && p.y < platformY + platformHeight); if (inX && inY) { if (!sameColor) { die(); return; } // Если игрок падает на платформу сверху if (p.vy > 0 && p.y + p.h - p.vy * dt <= platformY) { p.y = platformY - p.h; p.vy = 0; // Если платформа движется горизонтально, игрок движется с ней if (!vertical) { p.x += Math.cos(state.time / 1000 * speed) * speed * 0.6; } } } } } } // Отрисовка function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); // Фон ctx.fillStyle = '#050510'; ctx.fillRect(0, 0, canvas.width, canvas.height); if (state.screen === 'creating') { drawCreatorMode(); } else { drawGameMode(); } // Отрисовка частиц for (let p of state.particles) { ctx.globalAlpha = Math.max(0, Math.min(1, p.life)); ctx.fillStyle = p.color; if (p.type === 'rocket_trail') { ctx.fillRect(p.x, p.y, p.size * 1.5, p.size); } else { ctx.fillRect(p.x, p.y, p.size, p.size); } } // Отрисовка специальных эффектов for (let e of state.specialEffects) { ctx.globalAlpha = e.life * 0.7; ctx.strokeStyle = e.color; ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(e.x, e.y, e.size, 0, Math.PI * 2); ctx.stroke(); if (e.type === 'transform_complete') { ctx.fillStyle = e.color; ctx.beginPath(); ctx.arc(e.x, e.y, e.size * 0.5, 0, Math.PI * 2); ctx.fill(); } } ctx.globalAlpha = 1; } // Отрисовка в режиме создания function drawCreatorMode() { // Сдвигаем канвас для камеры ctx.save(); ctx.translate(state.cameraX, state.cameraY); // Отрисовка сетки (для удобства) drawGrid(); // Отрисовка пола и потолка ctx.strokeStyle = '#0ff'; ctx.lineWidth = 4; // Пол (для куба и ракеты) ctx.beginPath(); ctx.moveTo(-50000, state.floorY); ctx.lineTo(50000, state.floorY); ctx.stroke(); // Потолок (для ракеты) ctx.beginPath(); ctx.moveTo(-50000, state.ceilingY); ctx.lineTo(50000, state.ceilingY); ctx.stroke(); // Отрисовка созданных препятствий for (let obj of state.creatorData) { ctx.fillStyle = obj.color; // Рассчитываем позицию с учетом камеры const drawX = obj.x; const drawY = obj.y; if (obj.type === 'floor_spike') { drawSpike(ctx, drawX, state.floorY, 35, 70, false); } else if (obj.type === 'ceiling_spike') { drawSpike(ctx, drawX, state.ceilingY, 35, 70, true); } else if (obj.type === 'floor_double') { drawSpike(ctx, drawX, state.floorY, 35, 70, false); drawSpike(ctx, drawX + 35, state.floorY, 35, 70, false); } else if (obj.type === 'ceiling_double') { drawSpike(ctx, drawX, state.ceilingY, 35, 70, true); drawSpike(ctx, drawX + 35, state.ceilingY, 35, 70, true); } else if (obj.type === 'floor_wall') { const wallHeight = obj.h || 120; ctx.fillRect(drawX, state.floorY - wallHeight, 40, wallHeight); // Рамка для выделения ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.strokeRect(drawX, state.floorY - wallHeight, 40, wallHeight); ctx.strokeStyle = '#0ff'; ctx.lineWidth = 4; } else if (obj.type === 'ceiling_wall') { const wallHeight = obj.h || 120; ctx.fillRect(drawX, state.ceilingY, 40, wallHeight); // Рамка для выделения ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.strokeRect(drawX, state.ceilingY, 40, wallHeight); ctx.strokeStyle = '#0ff'; ctx.lineWidth = 4; } else if (obj.type === 'ladder') { const steps = obj.steps || 5; const stepX = obj.stepX || 60; const stepY = obj.stepY || -40; const platformWidth = 60; const platformHeight = 20; for (let i = 0; i < steps; i++) { let platformX = drawX + i * stepX; let platformY = state.floorY - 10 + i * stepY; ctx.fillRect(platformX, platformY, platformWidth, platformHeight); // Рамка для выделения ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.strokeRect(platformX, platformY, platformWidth, platformHeight); ctx.strokeStyle = '#0ff'; ctx.lineWidth = 4; } } else if (obj.type === 'moving_platform') { const width = obj.width || 200; const platformHeight = 20; let platformY = state.floorY - 100; ctx.fillRect(drawX, platformY, width, platformHeight); // Рамка для выделения ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.strokeRect(drawX, platformY, width, platformHeight); ctx.strokeStyle = '#0ff'; ctx.lineWidth = 4; // Стрелка для указания движения ctx.strokeStyle = obj.color; ctx.lineWidth = 3; ctx.beginPath(); if (obj.vertical) { ctx.moveTo(drawX + width/2, platformY - 20); ctx.lineTo(drawX + width/2, platformY - 50); ctx.moveTo(drawX + width/2 - 5, platformY - 45); ctx.lineTo(drawX + width/2, platformY - 55); ctx.lineTo(drawX + width/2 + 5, platformY - 45); } else { ctx.moveTo(drawX, platformY - 20); ctx.lineTo(drawX + 50, platformY - 20); ctx.moveTo(drawX + 45, platformY - 25); ctx.lineTo(drawX + 55, platformY - 20); ctx.lineTo(drawX + 45, platformY - 15); } ctx.stroke(); } } // Отрисовка курсора создания drawCreatorCursor(); ctx.restore(); } // Отрисовка сетки function drawGrid() { ctx.strokeStyle = 'rgba(0, 255, 255, 0.1)'; ctx.lineWidth = 1; const gridSize = 100; const startX = Math.floor(-state.cameraX / gridSize) * gridSize - gridSize * 10; const startY = Math.floor(-state.cameraY / gridSize) * gridSize - gridSize * 10; const endX = canvas.width - state.cameraX + gridSize * 10; const endY = canvas.height - state.cameraY + gridSize * 10; // Вертикальные линии for (let x = startX; x <= endX; x += gridSize) { ctx.beginPath(); ctx.moveTo(x, startY); ctx.lineTo(x, endY); ctx.stroke(); } // Горизонтальные линии for (let y = startY; y <= endY; y += gridSize) { ctx.beginPath(); ctx.moveTo(startX, y); ctx.lineTo(endX, y); ctx.stroke(); } } // Отрисовка курсора создания function drawCreatorCursor() { // Получаем позицию мыши относительно канваса с учетом камеры const mouseX = lastMouseX + state.cameraX; const mouseY = lastMouseY + state.cameraY; // Отрисовка превью объекта ctx.fillStyle = state.creatorColor; ctx.globalAlpha = 0.7; if (state.creatorTool === 'floor_spike') { drawSpike(ctx, mouseX, state.floorY, 35, 70, false); } else if (state.creatorTool === 'ceiling_spike') { drawSpike(ctx, mouseX, state.ceilingY, 35, 70, true); } else if (state.creatorTool === 'floor_double') { drawSpike(ctx, mouseX, state.floorY, 35, 70, false); drawSpike(ctx, mouseX + 35, state.floorY, 35, 70, false); } else if (state.creatorTool === 'ceiling_double') { drawSpike(ctx, mouseX, state.ceilingY, 35, 70, true); drawSpike(ctx, mouseX + 35, state.ceilingY, 35, 70, true); } else if (state.creatorTool === 'floor_wall') { const wallHeight = state.creatorWallHeight; ctx.fillRect(mouseX, state.floorY - wallHeight, 40, wallHeight); } else if (state.creatorTool === 'ceiling_wall') { const wallHeight = state.creatorWallHeight; ctx.fillRect(mouseX, state.ceilingY, 40, wallHeight); } else if (state.creatorTool === 'ladder') { const steps = 5; const stepX = 60; const stepY = -40; const platformWidth = 60; const platformHeight = 20; for (let i = 0; i < steps; i++) { let platformX = mouseX + i * stepX; let platformY = state.floorY - 10 + i * stepY; ctx.fillRect(platformX, platformY, platformWidth, platformHeight); } } else if (state.creatorTool === 'moving_platform') { const width = 200; const platformHeight = 20; let platformY = state.floorY - 100; ctx.fillRect(mouseX, platformY, width, platformHeight); // Стрелка для указания движения ctx.strokeStyle = state.creatorColor; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(mouseX + width/2, platformY - 20); ctx.lineTo(mouseX + width/2, platformY - 50); ctx.moveTo(mouseX + width/2 - 5, platformY - 45); ctx.lineTo(mouseX + width/2, platformY - 55); ctx.lineTo(mouseX + width/2 + 5, platformY - 45); ctx.stroke(); } ctx.globalAlpha = 1; } // Отрисовка в режиме игры function drawGameMode() { // Пол и потолок ctx.strokeStyle = '#0ff'; ctx.lineWidth = 4; if (state.levelId === 3 || state.mode === 'rocket') { // Потолок для ракеты и гибридного уровня ctx.beginPath(); ctx.moveTo(0, state.ceilingY); ctx.lineTo(canvas.width, state.ceilingY); ctx.stroke(); } // Пол ctx.beginPath(); ctx.moveTo(0, state.floorY); ctx.lineTo(canvas.width, state.floorY); ctx.stroke(); // Отрисовка препятствий for (let obj of state.levelData) { let ox = (obj.t - state.time) * 0.6 + state.player.x; if (ox < -200 || ox > canvas.width + 200) continue; // Проверяем, должно ли это препятствие отображаться в текущем режиме if (state.levelId === 3) { if (obj.type.includes('floor_') && state.mode !== 'rocket') continue; if (obj.type.includes('ceiling_') && state.mode !== 'rocket') continue; if ((obj.type === 'spike' || obj.type === 'double' || obj.type === 'wall' || obj.type === 'ladder' || obj.type === 'moving_platform') && state.mode !== 'cube') continue; } ctx.fillStyle = obj.color || '#0ff'; if (obj.type === 'spike' || obj.type === 'floor_spike') { drawSpike(ctx, ox, state.floorY, 35, obj.h || 70, false); } else if (obj.type === 'ceiling_spike') { drawSpike(ctx, ox, state.ceilingY, 35, obj.h || 70, true); } else if (obj.type === 'double' || obj.type === 'floor_double') { drawSpike(ctx, ox, state.floorY, 35, obj.h || 70, false); drawSpike(ctx, ox + 35, state.floorY, 35, obj.h || 70, false); } else if (obj.type === 'ceiling_double') { drawSpike(ctx, ox, state.ceilingY, 35, obj.h || 70, true); drawSpike(ctx, ox + 35, state.ceilingY, 35, obj.h || 70, true); } else if (obj.type === 'wall' || obj.type === 'floor_wall') { const wallHeight = obj.h || 80; ctx.fillRect(ox, state.floorY - wallHeight, 40, wallHeight); } else if (obj.type === 'ceiling_wall') { const wallHeight = obj.h || 80; ctx.fillRect(ox, state.ceilingY, 40, wallHeight); } else if (obj.type === 'ladder') { const steps = obj.steps || 5; const stepX = obj.stepX || 60; const stepY = obj.stepY || -40; const platformWidth = 60; const platformHeight = 20; for (let i = 0; i < steps; i++) { let platformX = ox + i * stepX; let platformY = state.floorY - 10 + i * stepY; ctx.fillRect(platformX, platformY, platformWidth, platformHeight); } } else if (obj.type === 'moving_platform') { const width = obj.width || 200; const speed = obj.speed || 2; const vertical = obj.vertical || false; const platformHeight = 20; // Вычисляем смещение платформы let offset = Math.sin(state.time / 1000 * speed) * 100; let platformX, platformY; if (vertical) { platformX = ox; platformY = state.floorY - 100 + offset; } else { platformX = ox + offset; platformY = state.floorY - 100; } ctx.fillRect(platformX, platformY, width, platformHeight); // Отрисовка стрелки направления ctx.strokeStyle = obj.color; ctx.lineWidth = 2; ctx.beginPath(); if (vertical) { ctx.moveTo(platformX + width/2, platformY - 10); ctx.lineTo(platformX + width/2, platformY - 30); ctx.moveTo(platformX + width/2 - 5, platformY - 25); ctx.lineTo(platformX + width/2, platformY - 35); ctx.lineTo(platformX + width/2 + 5, platformY - 25); } else { ctx.moveTo(platformX, platformY - 15); ctx.lineTo(platformX + 30, platformY - 15); ctx.moveTo(platformX + 25, platformY - 20); ctx.lineTo(platformX + 35, platformY - 15); ctx.lineTo(platformX + 25, platformY - 10); } ctx.stroke(); } } // Отрисовка игрока ctx.fillStyle = state.player.color; ctx.shadowBlur = 20; ctx.shadowColor = state.player.color; // Если идет трансформация, делаем пульсацию if (state.player.transformDirection !== 0) { ctx.globalAlpha = 0.7 + 0.3 * Math.sin(state.time / 50); } if (state.mode === 'rocket') { drawRocket(ctx, state.player.x, state.player.y, state.player.w, state.player.h, state.player.enginePower); } else { // Для куба во время трансформации рисуем промежуточную форму if (state.player.transformDirection === 1) { // Трансформация в ракету drawTransformingCube(ctx, state.player.x, state.player.y, state.player.w, state.player.h, state.player.transformationProgress); } else if (state.player.transformDirection === -1) { // Трансформация в куб drawTransformingRocket(ctx, state.player.x, state.player.y, state.player.w, state.player.h, state.player.transformationProgress); } else { // Обычный куб ctx.fillRect(state.player.x, state.player.y, state.player.w, state.player.h); } } ctx.globalAlpha = 1; ctx.shadowBlur = 0; } // Отрисовка ракеты function drawRocket(ctx, x, y, w, h, enginePower) { // Корпус ракеты ctx.beginPath(); ctx.moveTo(x + w, y + h/2); ctx.lineTo(x + w * 0.7, y); ctx.lineTo(x + w * 0.7, y + h); ctx.closePath(); ctx.fill(); // Основной корпус ctx.fillRect(x, y, w * 0.7, h); // Двигатель const engineColor = enginePower > 0.5 ? '#ff6600' : '#ff3300'; ctx.fillStyle = engineColor; ctx.fillRect(x - 8, y + h/4, 8, h/2); // Огонь из двигателя if (enginePower > 0) { const flameLength = enginePower * 25; ctx.fillStyle = '#ff9900'; ctx.beginPath(); ctx.moveTo(x - 8, y + h/3); ctx.lineTo(x - 8 - flameLength, y + h/2); ctx.lineTo(x - 8, y + 2*h/3); ctx.closePath(); ctx.fill(); ctx.fillStyle = '#ffff00'; ctx.beginPath(); ctx.moveTo(x - 8, y + h/2.5); ctx.lineTo(x - 8 - flameLength/2, y + h/2); ctx.lineTo(x - 8, y + h/1.75); ctx.closePath(); ctx.fill(); } // Окно кабины ctx.fillStyle = '#ffffff'; ctx.beginPath(); ctx.arc(x + w * 0.5, y + h/2, w/6, 0, Math.PI * 2); ctx.fill(); // Восстанавливаем цвет игрока ctx.fillStyle = state.player.color; } // Отрисовка куба во время трансформации в ракету function drawTransformingCube(ctx, x, y, w, h, progress) { // Промежуточная форма между кубом и ракетой ctx.save(); ctx.translate(x + w/2, y + h/2); // Наклон в зависимости от прогресса ctx.rotate(progress * 0.5); // Форма меняется от квадрата к треугольнику if (progress < 0.5) { // Больше похоже на куб const size = w * (1 - progress); ctx.fillRect(-size/2, -size/2, size, size); } else { // Больше похоже на ракету const rocketW = w * (0.7 + 0.3 * (progress - 0.5) * 2); const rocketH = h * (1 + (progress - 0.5) * 2); ctx.beginPath(); ctx.moveTo(rocketW/2, 0); ctx.lineTo(-rocketW/2 * 0.7, -rocketH/2); ctx.lineTo(-rocketW/2 * 0.7, rocketH/2); ctx.closePath(); ctx.fill(); ctx.fillRect(-rocketW/2 * 0.7, -rocketH/2, rocketW * 0.7, rocketH); } ctx.restore(); } // Отрисовка ракеты во время трансформации в куб function drawTransformingRocket(ctx, x, y, w, h, progress) { // Промежуточная форма между ракетой и кубом ctx.save(); ctx.translate(x + w/2, y + h/2); // Наклон в зависимости от прогресса ctx.rotate((1 - progress) * -0.5); if (progress < 0.5) { // Больше похоже на ракету const rocketW = w * (1 - progress); const rocketH = h * (1 - progress); ctx.beginPath(); ctx.moveTo(rocketW/2, 0); ctx.lineTo(-rocketW/2 * 0.7, -rocketH/2); ctx.lineTo(-rocketW/2 * 0.7, rocketH/2); ctx.closePath(); ctx.fill(); ctx.fillRect(-rocketW/2 * 0.7, -rocketH/2, rocketW * 0.7, rocketH); } else { // Больше похоже на куб const size = w * (0.5 + (progress - 0.5) * 2); ctx.fillRect(-size/2, -size/2, size, size); } ctx.restore(); } // Отрисовка шипа function drawSpike(ctx, x, y, w, h, upsideDown = false) { ctx.beginPath(); if (upsideDown) { ctx.moveTo(x, y); ctx.lineTo(x + w/2, y + h); ctx.lineTo(x + w, y); } else { ctx.moveTo(x, y); ctx.lineTo(x + w/2, y - h); ctx.lineTo(x + w, y); } ctx.closePath(); ctx.fill(); } // Смерть function die() { const finalScore = Math.floor(state.time/100); state.screen = 'menu'; hud.style.display = 'none'; editorHint.style.display = 'none'; creatorHud.style.display = 'none'; modeIndicator.style.display = 'none'; mainMenu.style.display = 'block'; // Останавливаем музыку if (musicInterval) { clearInterval(musicInterval); } alert("SYSTEM FAILURE! SCORE: " + finalScore); } // Победа function win() { state.screen = 'victory'; hud.style.display = 'none'; editorHint.style.display = 'none'; creatorHud.style.display = 'none'; modeIndicator.style.display = 'none'; victoryScreen.style.display = 'block'; document.getElementById('final-stat').innerText = 'SCORE: ' + Math.floor(state.time/100); // Останавливаем музыку if (musicInterval) { clearInterval(musicInterval); } // Салют for(let i = 0; i < 70; i++) { createParticle(canvas.width/2, canvas.height/2, '#0ff', 'win'); } for(let i = 0; i < 70; i++) { createParticle(canvas.width/2, canvas.height/2, '#f0f', 'win'); } } // Возврат в меню function backToMenu() { state.screen = 'menu'; hud.style.display = 'none'; editorHint.style.display = 'none'; creatorHud.style.display = 'none'; modeIndicator.style.display = 'none'; victoryScreen.style.display = 'none'; mainMenu.style.display = 'block'; // Останавливаем музыку if (musicInterval) { clearInterval(musicInterval); } } // Переменные для мыши let lastMouseX = 0; let lastMouseY = 0; // Обработчики мыши для режима создания canvas.addEventListener('mousemove', (e) => { const rect = canvas.getBoundingClientRect(); lastMouseX = e.clientX - rect.left; lastMouseY = e.clientY - rect.top; }); canvas.addEventListener('mousedown', (e) => { if (state.screen !== 'creating') return; e.preventDefault(); // Рассчитываем абсолютные координаты с учетом камеры const x = lastMouseX + state.cameraX; const y = lastMouseY + state.cameraY; if (e.shiftKey) { // Удаление: находим ближайшее препятствие let minDist = Infinity; let minIndex = -1; for (let i = 0; i < state.creatorData.length; i++) { const obj = state.creatorData[i]; const dist = Math.sqrt(Math.pow(obj.x - x, 2) + Math.pow(obj.y - y, 2)); if (dist < 50 && dist < minDist) { minDist = dist; minIndex = i; } } if (minIndex !== -1) { state.creatorData.splice(minIndex, 1); } } else { // Добавление препятствия const newObj = { x: x, y: y, type: state.creatorTool, color: state.creatorColor, h: state.creatorTool.includes('wall') ? state.creatorWallHeight : 70 }; // Дополнительные параметры для специальных препятствий if (state.creatorTool === 'ladder') { newObj.steps = 5; newObj.stepX = 60; newObj.stepY = -40; } else if (state.creatorTool === 'moving_platform') { newObj.width = 200; newObj.speed = 2; newObj.vertical = false; } state.creatorData.push(newObj); } }); // Управление document.addEventListener('keydown', (e) => { if (state.screen === 'creating') { // Управление камерой if (e.code === 'KeyW' || e.code === 'ArrowUp') state.isHoldingW = true; if (e.code === 'KeyS' || e.code === 'ArrowDown') state.isHoldingS = true; if (e.code === 'KeyA' || e.code === 'ArrowLeft') state.isHoldingA = true; if (e.code === 'KeyD' || e.code === 'ArrowRight') state.isHoldingD = true; } else if (state.screen === 'playing') { // Управление в игре if (e.code === 'Space' && state.mode === 'cube') { if (state.player.y >= state.floorY - state.player.h - 10) { state.player.vy = -850; } } if (e.code === 'KeyW') state.isHoldingW = true; if (e.code === 'KeyS') state.isHoldingS = true; if (e.code === 'ShiftLeft' || e.code === 'ShiftRight') { state.player.dashing = 0.25; } // Смена цвета if (e.code === 'KeyR') { setPlayerColorByIndex(state.player.colorIndex + 1); } } }); document.addEventListener('keyup', (e) => { if (e.code === 'KeyW' || e.code === 'ArrowUp') state.isHoldingW = false; if (e.code === 'KeyS' || e.code === 'ArrowDown') state.isHoldingS = false; if (e.code === 'KeyA' || e.code === 'ArrowLeft') state.isHoldingA = false; if (e.code === 'KeyD' || e.code === 'ArrowRight') state.isHoldingD = false; }); // Настройка громкости document.getElementById('vol').addEventListener('input', (e) => { const volume = e.target.value / 100; if (masterGain) { masterGain.gain.value = volume * 0.3; // Умножаем на 0.3 для комфортной громкости } }); // Инициализация при загрузке window.addEventListener('load', () => { resize(); setPlayerColorByIndex(0); }); // Обработка изменения размера окна window.addEventListener('resize', resize); </script> </body> </html>