📜
tilemap.js
Back
📝 Javascript ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
// Debug alert for mobile debugging if (typeof debugAlert === 'function') { debugAlert('tilemap.js starting to load'); } // Multiple tilemap support let tilemaps = [ { id: 0, name: "Map 1", width: 20, height: 15, tileSize: 32, data: [] } ]; let currentTilemapIndex = 0; let nextTilemapId = 1; // Current tilemap state (replaces legacy variables) let selectedMapTile = null; let activePaletteGroup = 0; // Helper functions function getCurrentTilemap() { return tilemaps[currentTilemapIndex]; } /** * Initialize the map data array for current tilemap */ function initializeMapData() { const tilemap = getCurrentTilemap(); tilemap.data = new Array(tilemap.width * tilemap.height).fill(0); } /** * Create a new tilemap */ function createNewTilemap() { const name = prompt("Enter tilemap name:", `Map ${tilemaps.length + 1}`); if (!name) return; const width = parseInt(prompt("Enter width (5-100):", "20")); const height = parseInt(prompt("Enter height (5-100):", "15")); if (isNaN(width) || isNaN(height) || width < 5 || width > 100 || height < 5 || height > 100) { alert("Invalid dimensions. Must be between 5 and 100."); return; } const newTilemap = { id: nextTilemapId++, name: name, width: width, height: height, tileSize: 32, data: new Array(width * height).fill(0) }; tilemaps.push(newTilemap); currentTilemapIndex = tilemaps.length - 1; // Refresh the interface updateTilemapTabs(); updateControls(); createMapGrid(); createTilePalette(); } /** * Switch to a different tilemap */ function switchTilemap(index) { if (index >= 0 && index < tilemaps.length) { currentTilemapIndex = index; updateTilemapTabs(); updateControls(); createMapGrid(); createTilePalette(); } } /** * Remove a tilemap */ function removeTilemap(index) { if (tilemaps.length <= 1) { alert("Cannot delete the last tilemap"); return; } if (confirm(`Delete "${tilemaps[index].name}"?`)) { tilemaps.splice(index, 1); if (currentTilemapIndex >= tilemaps.length) { currentTilemapIndex = tilemaps.length - 1; } else if (currentTilemapIndex > index) { currentTilemapIndex--; } updateTilemapTabs(); updateControls(); createMapGrid(); createTilePalette(); } } /** * Update tilemap tabs */ function updateTilemapTabs() { const container = document.getElementById('tilemapTabs'); if (!container) return; container.innerHTML = ''; tilemaps.forEach((tilemap, index) => { const tab = document.createElement('button'); tab.textContent = tilemap.name; tab.style.cssText = ` background: ${index === currentTilemapIndex ? '#6cf' : '#555'}; color: ${index === currentTilemapIndex ? '#000' : '#fff'}; border: none; padding: 4px 8px; margin: 0 2px; border-radius: 4px; cursor: pointer; font-size: 11px; position: relative; `; tab.addEventListener('click', () => switchTilemap(index)); // Add delete button for inactive tabs (only if more than one map exists) if (tilemaps.length > 1 && index !== currentTilemapIndex) { const deleteBtn = document.createElement('span'); deleteBtn.textContent = '×'; deleteBtn.style.cssText = ` position: absolute; top: -2px; right: -2px; background: #f44; color: white; border-radius: 50%; width: 14px; height: 14px; font-size: 8px; display: flex; align-items: center; justify-content: center; cursor: pointer; `; deleteBtn.addEventListener('click', (e) => { e.stopPropagation(); removeTilemap(index); }); tab.appendChild(deleteBtn); } container.appendChild(tab); }); // Add new tilemap button const addBtn = document.createElement('button'); addBtn.textContent = '+'; addBtn.style.cssText = ` background: #4a4; color: white; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; font-size: 11px; margin-left: 5px; `; addBtn.addEventListener('click', createNewTilemap); container.appendChild(addBtn); } /** * Update controls to reflect current tilemap */ function updateControls() { const tilemap = getCurrentTilemap(); const tileSizeSelect = document.getElementById('tileSizeSelect'); const gridWidth = document.getElementById('gridWidth'); const gridHeight = document.getElementById('gridHeight'); if (tileSizeSelect) tileSizeSelect.value = tilemap.tileSize; if (gridWidth) gridWidth.value = tilemap.width; if (gridHeight) gridHeight.value = tilemap.height; } /** * Resize the grid with data preservation */ function resizeGrid(newWidth, newHeight) { const tilemap = getCurrentTilemap(); if (newWidth === tilemap.width && newHeight === tilemap.height) { return; // No change needed } // Ask for confirmation if grid has data const hasData = tilemap.data.some(tile => tile !== 0); if (hasData && !confirm(`Resize grid from ${tilemap.width}×${tilemap.height} to ${newWidth}×${newHeight}? This may crop or clear some tiles.`)) { return; } // Save existing data const oldData = [...tilemap.data]; const oldWidth = tilemap.width; const oldHeight = tilemap.height; // Update dimensions tilemap.width = newWidth; tilemap.height = newHeight; // Create new data array tilemap.data = new Array(newWidth * newHeight).fill(0); // Copy existing tiles that fit in new dimensions for (let y = 0; y < Math.min(oldHeight, newHeight); y++) { for (let x = 0; x < Math.min(oldWidth, newWidth); x++) { const oldIndex = y * oldWidth + x; const newIndex = y * newWidth + x; tilemap.data[newIndex] = oldData[oldIndex]; } } // Recreate the grid createMapGrid(); // Update the input fields updateControls(); } /** * Set a tile in the current map */ function setMapTile(x, y, tileId) { const tilemap = getCurrentTilemap(); if (x >= 0 && x < tilemap.width && y >= 0 && y < tilemap.height) { const index = y * tilemap.width + x; tilemap.data[index] = tileId; // Update the visual grid cell const cell = document.querySelector(`[data-map-x="${x}"][data-map-y="${y}"]`); if (cell) { updateMapCell(cell, tileId); } } } /** * Get a tile from the current map */ function getMapTile(x, y) { const tilemap = getCurrentTilemap(); if (x >= 0 && x < tilemap.width && y >= 0 && y < tilemap.height) { const index = y * tilemap.width + x; return tilemap.data[index]; } return 0; } /** * Update a visual map cell with tile data */ function updateMapCell(cell, tileId) { const tilemap = getCurrentTilemap(); const x = parseInt(cell.dataset.mapX); const y = parseInt(cell.dataset.mapY); // Clear previous content but preserve coordinate label const coordLabel = cell.querySelector('.coord-label'); cell.innerHTML = ''; // Restore or create coordinate label if (coordLabel) { cell.appendChild(coordLabel); } else { const label = document.createElement('span'); label.className = 'coord-label'; label.textContent = `${x},${y}`; label.style.cssText = ` position: absolute; top: 2px; left: 2px; font-size: 8px; color: rgba(255,255,255,0.5); pointer-events: none; z-index: 1; `; cell.appendChild(label); } cell.style.backgroundColor = ''; if (tileId === 0) { cell.style.backgroundColor = 'transparent'; return; } // Check if groups exist before trying to find tiles if (typeof groups === 'undefined' || !groups) { console.warn('No tile groups available'); return; } // Find the tile in our groups for (const group of groups) { const tile = group.tiles.find(t => t.uniqueId === tileId); if (tile) { // Create canvas to display the tile const canvas = document.createElement('canvas'); canvas.width = tilemap.tileSize; canvas.height = tilemap.tileSize; canvas.style.cssText = 'width: 100%; height: 100%; position: absolute; top: 0; left: 0;'; const ctx = canvas.getContext('2d'); // Scale the tile to fit the map cell const tempCanvas = document.createElement('canvas'); tempCanvas.width = tile.size; tempCanvas.height = tile.size; const tempCtx = tempCanvas.getContext('2d'); tempCtx.putImageData(tile.data, 0, 0); ctx.drawImage(tempCanvas, 0, 0, tile.size, tile.size, 0, 0, tilemap.tileSize, tilemap.tileSize); cell.appendChild(canvas); // Add ID badge const idBadge = document.createElement('span'); idBadge.className = 'id-badge'; idBadge.textContent = tileId; idBadge.style.cssText = ` position: absolute; bottom: 2px; right: 2px; font-size: 8px; color: #fff; background: rgba(0,0,0,0.7); padding: 1px 3px; border-radius: 3px; pointer-events: none; z-index: 2; `; cell.appendChild(idBadge); break; } } } /** * Create the map grid */ function createMapGrid() { const container = document.getElementById('mapGrid'); if (!container) return; const tilemap = getCurrentTilemap(); container.innerHTML = ''; container.style.cssText = ` display: grid; grid-template-columns: repeat(${tilemap.width}, ${tilemap.tileSize}px); grid-template-rows: repeat(${tilemap.height}, ${tilemap.tileSize}px); gap: 1px; background: #333; padding: 10px; overflow: auto; max-height: 400px; `; // Create grid cells for (let y = 0; y < tilemap.height; y++) { for (let x = 0; x < tilemap.width; x++) { const cell = document.createElement('div'); cell.className = 'map-cell'; cell.dataset.mapX = x; cell.dataset.mapY = y; cell.style.cssText = ` width: ${tilemap.tileSize}px; height: ${tilemap.tileSize}px; border: 1px solid rgba(255,255,255,0.2); background: #222; cursor: pointer; position: relative; `; // Add coordinate label const label = document.createElement('span'); label.className = 'coord-label'; label.textContent = `${x},${y}`; label.style.cssText = ` position: absolute; top: 2px; left: 2px; font-size: 8px; color: rgba(255,255,255,0.5); pointer-events: none; z-index: 1; `; cell.appendChild(label); // Add click handler cell.addEventListener('click', () => { if (selectedMapTile !== null) { setMapTile(x, y, selectedMapTile); } }); // Add hover effect cell.addEventListener('mouseenter', () => { cell.style.backgroundColor = '#444'; }); cell.addEventListener('mouseleave', () => { if (getMapTile(x, y) === 0) { cell.style.backgroundColor = '#222'; } }); container.appendChild(cell); } } // Load existing map data for (let y = 0; y < tilemap.height; y++) { for (let x = 0; x < tilemap.width; x++) { const tileId = getMapTile(x, y); if (tileId !== 0) { const cell = container.querySelector(`[data-map-x="${x}"][data-map-y="${y}"]`); if (cell) { updateMapCell(cell, tileId); } } } } } /** * Create the tile palette from picked tiles */ function createTilePalette() { const container = document.getElementById('tilePalette'); if (!container) return; const tilemap = getCurrentTilemap(); container.innerHTML = ''; // Check if groups exist if (typeof groups === 'undefined' || !groups || groups.length === 0) { container.innerHTML = '<div style="color: #888; padding: 10px;">No tile groups available. Use the Tile Picker first.</div>'; return; } // Create controls container const controlsContainer = document.createElement('div'); controlsContainer.style.cssText = 'margin-bottom: 5px; display: flex; align-items: center; gap: 10px;'; // Group tabs const tabContainer = document.createElement('div'); tabContainer.style.cssText = 'display: flex; gap: 2px;'; groups.forEach((group, index) => { const tab = document.createElement('button'); tab.textContent = `G${index + 1}`; tab.style.cssText = ` background: ${index === activePaletteGroup ? '#6cf' : '#444'}; color: ${index === activePaletteGroup ? '#000' : '#fff'}; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; font-size: 11px; `; tab.addEventListener('click', () => { activePaletteGroup = index; createTilePalette(); }); tabContainer.appendChild(tab); }); // Clear button const clearBtn = document.createElement('button'); clearBtn.textContent = 'Clear All'; clearBtn.style.cssText = ` background: #d44; color: white; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; font-size: 11px; `; clearBtn.addEventListener('click', () => clearMap()); controlsContainer.appendChild(tabContainer); controlsContainer.appendChild(clearBtn); container.appendChild(controlsContainer); // Create tiles container const tilesContainer = document.createElement('div'); tilesContainer.style.cssText = 'display: flex; align-items: center; gap: 3px; flex-wrap: wrap;'; // Add eraser tool const eraserBtn = document.createElement('div'); eraserBtn.className = 'palette-tile'; eraserBtn.style.cssText = ` width: ${tilemap.tileSize}px; height: ${tilemap.tileSize}px; border: 2px solid #666; cursor: pointer; background: #333; color: #fff; display: flex; align-items: center; justify-content: center; font-size: 12px; position: relative; `; eraserBtn.textContent = 'X'; eraserBtn.title = 'Eraser'; eraserBtn.addEventListener('click', () => { selectedMapTile = 0; document.querySelectorAll('.palette-tile').forEach(t => t.classList.remove('selected')); eraserBtn.classList.add('selected'); }); tilesContainer.appendChild(eraserBtn); // Add tiles from active group if (activePaletteGroup < groups.length) { const activeGroup = groups[activePaletteGroup]; activeGroup.tiles.forEach(tile => { const tileDiv = document.createElement('div'); tileDiv.className = 'palette-tile'; tileDiv.style.cssText = ` width: ${tilemap.tileSize}px; height: ${tilemap.tileSize}px; border: 2px solid #666; cursor: pointer; overflow: hidden; position: relative; `; // Create canvas for tile preview const canvas = document.createElement('canvas'); canvas.width = tilemap.tileSize; canvas.height = tilemap.tileSize; canvas.style.cssText = 'width: 100%; height: 100%; position: absolute; top: 0; left: 0;'; const ctx = canvas.getContext('2d'); // Draw scaled tile const tempCanvas = document.createElement('canvas'); tempCanvas.width = tile.size; tempCanvas.height = tile.size; const tempCtx = tempCanvas.getContext('2d'); tempCtx.putImageData(tile.data, 0, 0); ctx.drawImage(tempCanvas, 0, 0, tile.size, tile.size, 0, 0, tilemap.tileSize, tilemap.tileSize); tileDiv.appendChild(canvas); // Add ID badge const idBadge = document.createElement('span'); idBadge.textContent = tile.uniqueId; idBadge.style.cssText = ` position: absolute; bottom: 1px; right: 1px; font-size: 7px; color: #fff; background: rgba(0,0,0,0.8); padding: 1px 2px; border-radius: 2px; pointer-events: none; z-index: 2; line-height: 1; `; tileDiv.appendChild(idBadge); // Add selection handler tileDiv.addEventListener('click', () => { selectedMapTile = tile.uniqueId; document.querySelectorAll('.palette-tile').forEach(t => t.classList.remove('selected')); tileDiv.classList.add('selected'); }); tilesContainer.appendChild(tileDiv); }); } container.appendChild(tilesContainer); } /** * Open the tilemap overlay - main entry point */ function openTilemapOverlay() { const overlayContent = document.getElementById('overlayContent'); overlayContent.innerHTML = ` <div style="margin-bottom: 10px; padding: 0 10px;"> <div id="tilemapTabs" style="margin-bottom: 10px;"></div> <div style="display: flex; justify-content: flex-start; align-items: center; gap: 10px;"> <select id="tileSizeSelect" style="background: #333; color: #fff; border: 1px solid #666; padding: 4px 8px; border-radius: 4px;"> <option value="8">8px</option> <option value="16">16px</option> <option value="32" selected>32px</option> <option value="64">64px</option> <option value="128">128px</option> </select> <input type="number" id="gridWidth" min="5" max="100" style="width: 50px; background: #333; color: #fff; border: 1px solid #666; padding: 4px; border-radius: 4px;"> <span style="color: #888;">×</span> <input type="number" id="gridHeight" min="5" max="100" style="width: 50px; background: #333; color: #fff; border: 1px solid #666; padding: 4px; border-radius: 4px;"> <button id="resizeGrid" style="background: #666; color: white; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; font-size: 11px;">Resize</button> </div> </div> <div style="margin-bottom: 10px; padding: 0 10px;"> <div id="tilePalette"></div> </div> <div style="height: calc(100% - 120px); overflow: auto; padding: 0 10px;"> <div id="mapGrid"></div> </div> `; // Set up event handlers setupEventHandlers(); // Initialize interface updateTilemapTabs(); updateControls(); // Initialize the map data if needed const tilemap = getCurrentTilemap(); if (!tilemap.data || tilemap.data.length === 0) { initializeMapData(); } createMapGrid(); createTilePalette(); } /** * Set up event handlers for controls */ function setupEventHandlers() { // Tile size change handler document.getElementById('tileSizeSelect').addEventListener('change', (e) => { const tilemap = getCurrentTilemap(); tilemap.tileSize = parseInt(e.target.value); createMapGrid(); createTilePalette(); }); // Grid resize handler document.getElementById('resizeGrid').addEventListener('click', () => { const newWidth = parseInt(document.getElementById('gridWidth').value); const newHeight = parseInt(document.getElementById('gridHeight').value); if (newWidth >= 5 && newWidth <= 100 && newHeight >= 5 && newHeight <= 100) { resizeGrid(newWidth, newHeight); } else { alert('Grid size must be between 5 and 100'); } }); // Allow Enter key to resize ['gridWidth', 'gridHeight'].forEach(id => { document.getElementById(id).addEventListener('keypress', (e) => { if (e.key === 'Enter') document.getElementById('resizeGrid').click(); }); }); } /** * Clear the entire current map */ function clearMap() { if (confirm('Clear the entire map?')) { const tilemap = getCurrentTilemap(); tilemap.data.fill(0); // Refresh each cell for (let y = 0; y < tilemap.height; y++) { for (let x = 0; x < tilemap.width; x++) { const cell = document.querySelector(`[data-map-x="${x}"][data-map-y="${y}"]`); if (cell) { updateMapCell(cell, 0); } } } } } // Add CSS for selected palette tiles const style = document.createElement('style'); style.textContent = ` .palette-tile.selected { border-color: #6cf !important; box-shadow: 0 0 5px #6cf; } .map-cell:hover { border-color: #6cf !important; } `; document.head.appendChild(style); // Debug alert for mobile debugging - success if (typeof debugAlert === 'function') { debugAlert('tilemap.js loaded successfully'); }