552 lines
24 KiB
HTML
552 lines
24 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>🎮 Редактор Уровней</title>
|
||
<link rel="stylesheet" href="css/style.css">
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
|
||
body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 10px;
|
||
background: linear-gradient(135deg, #050510 0%, #0a0a20 50%, #0f0a25 100%);
|
||
min-height: 100vh;
|
||
font-family: 'Rajdhani', sans-serif;
|
||
}
|
||
|
||
h1 {
|
||
color: #00ffff;
|
||
font-family: 'Orbitron', sans-serif;
|
||
text-shadow: 0 0 15px rgba(0, 255, 255, 0.5);
|
||
margin: 8px 0;
|
||
font-size: 22px;
|
||
}
|
||
|
||
.top-row {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-bottom: 10px;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
}
|
||
|
||
select, input {
|
||
padding: 8px 12px;
|
||
background: rgba(0, 40, 60, 0.9);
|
||
border: 1px solid rgba(0, 255, 255, 0.4);
|
||
border-radius: 6px;
|
||
color: #fff;
|
||
font-size: 14px;
|
||
}
|
||
|
||
select:focus, input:focus {
|
||
outline: none;
|
||
border-color: #00ffff;
|
||
box-shadow: 0 0 8px rgba(0, 255, 255, 0.3);
|
||
}
|
||
|
||
#levelName { width: 140px; }
|
||
|
||
.canvas-box {
|
||
border: 2px solid #00ffff;
|
||
border-radius: 8px;
|
||
box-shadow: 0 0 20px rgba(0, 255, 255, 0.15);
|
||
overflow: hidden;
|
||
}
|
||
|
||
#editorCanvas {
|
||
display: block;
|
||
cursor: crosshair;
|
||
background: #080812;
|
||
}
|
||
|
||
.toolbar-row {
|
||
display: flex;
|
||
gap: 6px;
|
||
margin-top: 10px;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
max-width: 920px;
|
||
}
|
||
|
||
.tool-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
padding: 8px 12px;
|
||
background: rgba(0, 255, 255, 0.08);
|
||
border: 1px solid rgba(0, 255, 255, 0.2);
|
||
border-radius: 6px;
|
||
color: #aaa;
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
transition: all 0.15s;
|
||
}
|
||
|
||
.tool-btn:hover {
|
||
background: rgba(0, 255, 255, 0.15);
|
||
color: #fff;
|
||
}
|
||
|
||
.tool-btn.active {
|
||
background: rgba(0, 255, 255, 0.3);
|
||
border-color: #00ffff;
|
||
color: #fff;
|
||
box-shadow: 0 0 10px rgba(0, 255, 255, 0.3);
|
||
}
|
||
|
||
.action-btn {
|
||
padding: 8px 14px;
|
||
border: 1px solid rgba(0, 255, 255, 0.3);
|
||
border-radius: 6px;
|
||
color: #fff;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.action-btn.save { background: rgba(50, 200, 80, 0.3); }
|
||
.action-btn.test { background: rgba(255, 80, 100, 0.3); }
|
||
.action-btn.clear { background: rgba(100, 100, 100, 0.3); }
|
||
|
||
.action-btn:hover { box-shadow: 0 0 12px rgba(0, 255, 255, 0.3); }
|
||
|
||
.info-bar {
|
||
margin-top: 8px;
|
||
padding: 8px 15px;
|
||
background: rgba(0, 0, 0, 0.4);
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
color: #777;
|
||
}
|
||
|
||
.info-bar span { color: #00ffff; font-weight: bold; }
|
||
|
||
.hint {
|
||
color: #666;
|
||
font-size: 11px;
|
||
margin-top: 5px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1>🎮 Редактор Уровней</h1>
|
||
|
||
<div class="top-row">
|
||
<select id="levelSelect" onchange="loadLevelFromList()">
|
||
<option value="">-- Выберите уровень --</option>
|
||
</select>
|
||
<input type="text" id="levelName" placeholder="Название">
|
||
<button class="action-btn save" onclick="saveToFile()">💾 Сохранить</button>
|
||
<button class="action-btn test" onclick="testLevel()">▶ Тест</button>
|
||
<button class="action-btn clear" onclick="clearLevel()">🗑️ Очистить</button>
|
||
</div>
|
||
|
||
<div class="canvas-box">
|
||
<canvas id="editorCanvas" width="900" height="480"></canvas>
|
||
</div>
|
||
|
||
<p class="hint">ЛКМ: разместить | ПКМ: удалить | Перетаскивание: двигать объекты</p>
|
||
|
||
<div class="toolbar-row">
|
||
<button class="tool-btn active" data-tool="platform"><span>🟫</span> Платформа</button>
|
||
<button class="tool-btn" data-tool="ground"><span>⬛</span> Земля</button>
|
||
<button class="tool-btn" data-tool="moving"><span>↔️</span> Движущаяся</button>
|
||
<button class="tool-btn" data-tool="coin"><span>💰</span> Монета</button>
|
||
<button class="tool-btn" data-tool="spike"><span>🔺</span> Шип</button>
|
||
<button class="tool-btn" data-tool="enemy"><span>👾</span> Враг</button>
|
||
<button class="tool-btn" data-tool="powerup"><span>⭐</span> Бонус</button>
|
||
<button class="tool-btn" data-tool="flag"><span>🚩</span> Флаг</button>
|
||
<button class="tool-btn" data-tool="checkpoint"><span>💠</span> Чекпоинт</button>
|
||
<button class="tool-btn" data-tool="player"><span>👤</span> Игрок</button>
|
||
<button class="tool-btn" data-tool="eraser"><span>❌</span> Ластик</button>
|
||
</div>
|
||
|
||
<div class="info-bar">
|
||
Объектов: <span id="objCount">0</span> | ЛКМ: разместить | ПКМ: удалить | Drag: переместить
|
||
</div>
|
||
|
||
<script src="js/levels.js"></script>
|
||
<script>
|
||
const canvas = document.getElementById('editorCanvas');
|
||
const ctx = canvas.getContext('2d');
|
||
|
||
let currentTool = 'platform';
|
||
let objects = [];
|
||
let playerStart = { x: 60, y: 400 };
|
||
let flagPos = { x: 800, y: 420 };
|
||
|
||
// Drag state
|
||
let dragging = null;
|
||
let dragOffset = { x: 0, y: 0 };
|
||
|
||
// Populate level selector
|
||
const select = document.getElementById('levelSelect');
|
||
LEVELS.forEach((level, i) => {
|
||
const opt = document.createElement('option');
|
||
opt.value = i;
|
||
opt.textContent = (i + 1) + '. ' + level.name;
|
||
select.appendChild(opt);
|
||
});
|
||
|
||
// Tool buttons
|
||
document.querySelectorAll('.tool-btn').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
document.querySelectorAll('.tool-btn').forEach(b => b.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
currentTool = btn.dataset.tool;
|
||
});
|
||
});
|
||
|
||
function loadLevelFromList() {
|
||
const idx = parseInt(document.getElementById('levelSelect').value);
|
||
if (isNaN(idx)) return;
|
||
|
||
const level = LEVELS[idx];
|
||
objects = [];
|
||
|
||
// Load platforms - check isGround flag or color
|
||
level.platforms?.forEach(p => {
|
||
const isGround = p.isGround || p.width >= 800 || p.x < 40;
|
||
const type = isGround ? 'ground' : 'platform';
|
||
objects.push({ type: type, x: p.x, y: p.y, width: p.width, height: p.height, color: p.color });
|
||
});
|
||
level.movingPlatforms?.forEach(p => objects.push({ type: 'moving', x: p.x, y: p.y, width: p.width, height: p.height, color: p.color, startX: p.startX, endX: p.endX, speed: p.speed }));
|
||
level.coins?.forEach(c => objects.push({ type: 'coin', x: c.x, y: c.y, width: 20, height: 20 }));
|
||
level.spikes?.forEach(s => objects.push({ type: 'spike', x: s.x, y: s.y, width: 30, height: 20 }));
|
||
level.patrolEnemies?.forEach(e => objects.push({ type: 'enemy', x: e.x, y: e.y, width: 30, height: 30, patrolLeft: e.patrolLeft, patrolRight: e.patrolRight }));
|
||
level.powerUps?.forEach(p => objects.push({ type: 'powerup', x: p.x, y: p.y, width: 24, height: 24, powerType: p.type }));
|
||
level.checkpoints?.forEach(c => objects.push({ type: 'checkpoint', x: c.x, y: c.y, width: 30, height: 40 }));
|
||
|
||
playerStart = level.playerStart || { x: 60, y: 400 };
|
||
flagPos = level.flag || { x: 800, y: 420 };
|
||
|
||
document.getElementById('levelName').value = level.name;
|
||
document.getElementById('objCount').textContent = objects.length;
|
||
render();
|
||
}
|
||
|
||
function getPos(e) {
|
||
const rect = canvas.getBoundingClientRect();
|
||
const scaleX = canvas.width / rect.width;
|
||
const scaleY = canvas.height / rect.height;
|
||
return {
|
||
x: (e.clientX - rect.left) * scaleX,
|
||
y: (e.clientY - rect.top) * scaleY
|
||
};
|
||
}
|
||
|
||
function findObjectAt(pos) {
|
||
// Check player
|
||
if (pos.x >= playerStart.x && pos.x <= playerStart.x + 28 && pos.y >= playerStart.y && pos.y <= playerStart.y + 38) {
|
||
return { type: 'player' };
|
||
}
|
||
// Check flag
|
||
if (pos.x >= flagPos.x && pos.x <= flagPos.x + 30 && pos.y >= flagPos.y && pos.y <= flagPos.y + 50) {
|
||
return { type: 'flag' };
|
||
}
|
||
// Check objects (reverse for top-most)
|
||
for (let i = objects.length - 1; i >= 0; i--) {
|
||
const o = objects[i];
|
||
const threshold = ['coin','spike','enemy','powerup','checkpoint'].includes(o.type) ? 25 : 40;
|
||
if (Math.abs(o.x + (o.width || 20)/2 - pos.x) < threshold && Math.abs(o.y + (o.height||20)/2 - pos.y) < threshold) {
|
||
return { type: 'object', index: i };
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
canvas.addEventListener('mousedown', e => {
|
||
const pos = getPos(e);
|
||
const found = findObjectAt(pos);
|
||
|
||
if (found) {
|
||
// Start dragging
|
||
if (found.type === 'player') {
|
||
dragging = { type: 'player', offsetX: pos.x - playerStart.x, offsetY: pos.y - playerStart.y };
|
||
} else if (found.type === 'flag') {
|
||
dragging = { type: 'flag', offsetX: pos.x - flagPos.x, offsetY: pos.y - flagPos.y };
|
||
} else if (found.type === 'object') {
|
||
const o = objects[found.index];
|
||
dragging = { type: 'object', index: found.index, offsetX: pos.x - o.x, offsetY: pos.y - o.y };
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Place new object
|
||
if (e.button === 0) { // Left click
|
||
placeObject(pos);
|
||
} else if (e.button === 2) { // Right click
|
||
removeObject(pos);
|
||
}
|
||
});
|
||
|
||
canvas.addEventListener('mousemove', e => {
|
||
const pos = getPos(e);
|
||
|
||
if (dragging) {
|
||
const snap = 10;
|
||
let newX, newY;
|
||
|
||
if (dragging.type === 'player') {
|
||
newX = Math.round((pos.x - dragging.offsetX) / snap) * snap;
|
||
newY = Math.round((pos.y - dragging.offsetY) / snap) * snap;
|
||
playerStart = { x: Math.max(0, Math.min(850, newX)), y: Math.max(0, Math.min(440, newY)) };
|
||
} else if (dragging.type === 'flag') {
|
||
newX = Math.round((pos.x - dragging.offsetX) / snap) * snap;
|
||
newY = Math.round((pos.y - dragging.offsetY) / snap) * snap;
|
||
flagPos = { x: Math.max(0, Math.min(850, newX)), y: Math.max(0, Math.min(430, newY)) };
|
||
} else if (dragging.type === 'object') {
|
||
newX = Math.round((pos.x - dragging.offsetX) / snap) * snap;
|
||
newY = Math.round((pos.y - dragging.offsetY) / snap) * snap;
|
||
const o = objects[dragging.index];
|
||
o.x = Math.max(0, Math.min(870 - o.width, newX));
|
||
o.y = Math.max(0, Math.min(460 - o.height, newY));
|
||
}
|
||
render();
|
||
} else {
|
||
// Cursor highlight
|
||
render();
|
||
const found = findObjectAt(pos);
|
||
if (found) {
|
||
ctx.strokeStyle = '#00ff00';
|
||
ctx.lineWidth = 2;
|
||
if (found.type === 'player') {
|
||
ctx.strokeRect(playerStart.x - 2, playerStart.y - 2, 32, 42);
|
||
} else if (found.type === 'flag') {
|
||
ctx.strokeRect(flagPos.x - 2, flagPos.y - 2, 34, 54);
|
||
} else {
|
||
const o = objects[found.index];
|
||
ctx.strokeRect(o.x - 2, o.y - 2, (o.width || 20) + 4, (o.height || 20) + 4);
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
canvas.addEventListener('mouseup', () => dragging = null);
|
||
canvas.addEventListener('mouseleave', () => dragging = null);
|
||
|
||
canvas.addEventListener('contextmenu', e => { e.preventDefault(); removeObject(getPos(e)); });
|
||
|
||
function placeObject(pos) {
|
||
const snap = 10;
|
||
const x = Math.round(pos.x / snap) * snap;
|
||
const y = Math.round(pos.y / snap) * snap;
|
||
|
||
if (currentTool === 'eraser') return;
|
||
|
||
// Check if already exists
|
||
const exists = findObjectAt(pos);
|
||
if (exists) return;
|
||
|
||
switch(currentTool) {
|
||
case 'player': playerStart = { x: x - 14, y: y - 19 }; break;
|
||
case 'flag': flagPos = { x: x - 2, y: y - 25 }; break;
|
||
case 'platform': objects.push({ type: 'platform', x: x - 50, y: y - 10, width: 100, height: 20, color: '#4a3a6a' }); break;
|
||
case 'ground': objects.push({ type: 'ground', x: x - 50, y: y - 20, width: 100, height: 40, color: '#2a2a4a' }); break;
|
||
case 'moving': objects.push({ type: 'moving', x: x - 40, y: y - 10, width: 80, height: 20, color: '#6a5a8a', startX: x - 80, endX: x + 80, speed: 2 }); break;
|
||
case 'coin': objects.push({ type: 'coin', x: x - 10, y: y - 10, width: 20, height: 20 }); break;
|
||
case 'spike': objects.push({ type: 'spike', x: x - 15, y: y - 10, width: 30, height: 20 }); break;
|
||
case 'enemy': objects.push({ type: 'enemy', x: x - 15, y: y - 15, width: 30, height: 30, patrolLeft: x - 80, patrolRight: x + 80 }); break;
|
||
case 'powerup': objects.push({ type: 'powerup', x: x - 12, y: y - 12, width: 24, height: 24, powerType: 'doubleJump' }); break;
|
||
case 'checkpoint': objects.push({ type: 'checkpoint', x: x - 15, y: y - 20, width: 30, height: 40 }); break;
|
||
}
|
||
document.getElementById('objCount').textContent = objects.length;
|
||
render();
|
||
}
|
||
|
||
function removeObject(pos) {
|
||
const found = findObjectAt(pos);
|
||
if (found && found.type === 'object') {
|
||
objects.splice(found.index, 1);
|
||
document.getElementById('objCount').textContent = objects.length;
|
||
render();
|
||
}
|
||
}
|
||
|
||
function render() {
|
||
const w = canvas.width, h = canvas.height;
|
||
|
||
// Background
|
||
ctx.fillStyle = '#080812';
|
||
ctx.fillRect(0, 0, w, h);
|
||
|
||
// Grid
|
||
ctx.strokeStyle = 'rgba(0, 255, 255, 0.05)';
|
||
for (let x = 0; x <= w; x += 20) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, h); ctx.stroke(); }
|
||
for (let y = 0; y <= h; y += 20) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(w, y); ctx.stroke(); }
|
||
|
||
// Objects
|
||
objects.forEach(obj => {
|
||
switch(obj.type) {
|
||
case 'platform': case 'ground': case 'moving':
|
||
ctx.fillStyle = obj.color;
|
||
ctx.fillRect(obj.x, obj.y, obj.width, obj.height);
|
||
ctx.strokeStyle = 'rgba(255,255,255,0.3)';
|
||
ctx.strokeRect(obj.x, obj.y, obj.width, obj.height);
|
||
break;
|
||
case 'coin':
|
||
ctx.fillStyle = '#ffd700';
|
||
ctx.beginPath(); ctx.arc(obj.x + 10, obj.y + 10, 10, 0, Math.PI * 2); ctx.fill();
|
||
break;
|
||
case 'spike':
|
||
ctx.fillStyle = '#ff4444';
|
||
ctx.beginPath(); ctx.moveTo(obj.x + 15, obj.y); ctx.lineTo(obj.x + 30, obj.y + 20); ctx.lineTo(obj.x, obj.y + 20); ctx.closePath(); ctx.fill();
|
||
break;
|
||
case 'enemy':
|
||
ctx.fillStyle = '#ff00ff';
|
||
ctx.beginPath(); ctx.arc(obj.x + 15, obj.y + 15, 14, 0, Math.PI * 2); ctx.fill();
|
||
break;
|
||
case 'powerup':
|
||
ctx.fillStyle = '#00ff88';
|
||
ctx.beginPath(); ctx.arc(obj.x + 12, obj.y + 12, 12, 0, Math.PI * 2); ctx.fill();
|
||
break;
|
||
case 'checkpoint':
|
||
ctx.fillStyle = '#00aa00';
|
||
ctx.fillRect(obj.x, obj.y, 4, 40);
|
||
ctx.fillRect(obj.x, obj.y, 25, 15);
|
||
break;
|
||
}
|
||
});
|
||
|
||
// Player
|
||
ctx.fillStyle = '#00d9ff';
|
||
ctx.fillRect(playerStart.x, playerStart.y, 28, 38);
|
||
ctx.strokeStyle = '#00ffff';
|
||
ctx.lineWidth = 2;
|
||
ctx.strokeRect(playerStart.x, playerStart.y, 28, 38);
|
||
|
||
// Flag
|
||
ctx.fillStyle = '#44ff44';
|
||
ctx.fillRect(flagPos.x, flagPos.y, 4, 50);
|
||
ctx.beginPath();
|
||
ctx.moveTo(flagPos.x + 4, flagPos.y);
|
||
ctx.lineTo(flagPos.x + 30, flagPos.y + 15);
|
||
ctx.lineTo(flagPos.x + 4, flagPos.y + 30);
|
||
ctx.closePath(); ctx.fill();
|
||
}
|
||
|
||
function clearLevel() {
|
||
if (confirm('Очистить уровень?')) {
|
||
objects = [];
|
||
playerStart = { x: 60, y: 400 };
|
||
flagPos = { x: 800, y: 420 };
|
||
document.getElementById('objCount').textContent = '0';
|
||
render();
|
||
}
|
||
}
|
||
|
||
function saveToFile() {
|
||
const name = document.getElementById('levelName').value || 'Новый уровень';
|
||
|
||
// Build the level object
|
||
const platforms = objects.filter(o => o.type === 'platform' || o.type === 'ground');
|
||
const movingPlatforms = objects.filter(o => o.type === 'moving');
|
||
const coins = objects.filter(o => o.type === 'coin');
|
||
const spikes = objects.filter(o => o.type === 'spike');
|
||
const enemies = objects.filter(o => o.type === 'enemy');
|
||
const powerups = objects.filter(o => o.type === 'powerup');
|
||
const checkpoints = objects.filter(o => o.type === 'checkpoint');
|
||
|
||
// Generate the code in the same format as levels.js
|
||
let code = ` // ${name}\n {\n`;
|
||
code += ` name: "${name}",\n`;
|
||
code += ` playerStart: { x: ${playerStart.x}, y: ${playerStart.y} },\n`;
|
||
|
||
code += ` platforms: [\n`;
|
||
platforms.forEach((p, i) => {
|
||
const isGround = p.type === 'ground' || p.width >= 800 || p.x < 35;
|
||
const comment = isGround ? '// ' + (p.width >= 800 ? 'Ground' : 'Wall') : '// Platform';
|
||
code += ` ${comment}\n`;
|
||
code += ` { x: ${p.x}, y: ${p.y}, width: ${p.width}, height: ${p.height}, color: '${p.color}', ${isGround ? 'isGround: true' : ''} },\n`;
|
||
});
|
||
code += ` ],\n`;
|
||
|
||
if (movingPlatforms.length > 0) {
|
||
code += ` movingPlatforms: [\n`;
|
||
movingPlatforms.forEach(p => {
|
||
code += ` { x: ${p.x}, y: ${p.y}, width: ${p.width}, height: ${p.height}, color: '${p.color}', startX: ${p.startX}, endX: ${p.endX}, speed: ${p.speed}, direction: 1, moveType: 'horizontal' },\n`;
|
||
});
|
||
code += ` ],\n`;
|
||
} else {
|
||
code += ` movingPlatforms: [],\n`;
|
||
}
|
||
|
||
code += ` coins: [\n`;
|
||
coins.forEach(c => {
|
||
code += ` { x: ${c.x}, y: ${c.y} },\n`;
|
||
});
|
||
code += ` ],\n`;
|
||
|
||
code += ` spikes: [\n`;
|
||
spikes.forEach(s => {
|
||
code += ` { x: ${s.x}, y: ${s.y}, width: ${s.width}, height: ${s.height} },\n`;
|
||
});
|
||
code += ` ],\n`;
|
||
|
||
code += ` patrolEnemies: [\n`;
|
||
enemies.forEach(e => {
|
||
code += ` { x: ${e.x}, y: ${e.y}, patrolLeft: ${e.patrolLeft}, patrolRight: ${e.patrolRight} },\n`;
|
||
});
|
||
code += ` ],\n`;
|
||
|
||
code += ` powerUps: [\n`;
|
||
powerups.forEach(p => {
|
||
code += ` { x: ${p.x}, y: ${p.y}, type: '${p.powerType}' },\n`;
|
||
});
|
||
code += ` ],\n`;
|
||
|
||
code += ` checkpoints: [\n`;
|
||
checkpoints.forEach(c => {
|
||
code += ` { x: ${c.x}, y: ${c.y} },\n`;
|
||
});
|
||
code += ` ],\n`;
|
||
|
||
code += ` flag: { x: ${flagPos.x}, y: ${flagPos.y} },\n`;
|
||
code += ` portal: null\n }`;
|
||
|
||
// Copy to clipboard
|
||
navigator.clipboard.writeText(code).then(() => {
|
||
alert('Код скопирован в буфер обмена!\nТеперь можно вставить в levels.js');
|
||
}).catch(() => {
|
||
// Fallback - download as file
|
||
const blob = new Blob([code], { type: 'text/plain' });
|
||
const a = document.createElement('a');
|
||
a.href = URL.createObjectURL(blob);
|
||
a.download = 'level.js.txt';
|
||
a.click();
|
||
});
|
||
}
|
||
|
||
function testLevel() {
|
||
if (objects.length === 0) { alert('Добавьте объекты!'); return; }
|
||
|
||
const name = document.getElementById('levelName').value || 'Тест';
|
||
const levelData = {
|
||
name: name,
|
||
playerStart: playerStart,
|
||
platforms: objects.filter(o => o.type === 'platform').map(p => ({ ...p, isGround: false })),
|
||
movingPlatforms: objects.filter(o => o.type === 'moving').map(p => ({ ...p, direction: 1, moveType: 'horizontal' })),
|
||
coins: objects.filter(o => o.type === 'coin').map(c => ({ x: c.x, y: c.y })),
|
||
spikes: objects.filter(o => o.type === 'spike').map(s => ({ x: s.x, y: s.y })),
|
||
patrolEnemies: objects.filter(o => o.type === 'enemy').map(e => ({ x: e.x, y: e.y, patrolLeft: e.patrolLeft, patrolRight: e.patrolRight })),
|
||
powerUps: objects.filter(o => o.type === 'powerup').map(p => ({ x: p.x, y: p.y, type: p.powerType })),
|
||
checkpoints: objects.filter(o => o.type === 'checkpoint').map(c => ({ x: c.x, y: c.y })),
|
||
flag: flagPos,
|
||
portal: null
|
||
};
|
||
|
||
localStorage.setItem('testLevel', JSON.stringify(levelData));
|
||
window.open('index.html?test=1', '_blank');
|
||
}
|
||
|
||
render();
|
||
</script>
|
||
</body>
|
||
</html>
|