šŸ“œ
movement.js
← Back
šŸ“ Javascript ⚔ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
// Debug alert for mobile debugging if (typeof debugAlert === 'function') { debugAlert('movement.js loaded'); } // Movement settings for each group let movementSettings = {}; let currentMovementGroup = 0; // Movement configuration options const MOVEMENT_SETTINGS = { physics: { gravity: { label: 'Gravity', type: 'number', default: 980, min: 0, max: 2000, step: 10, unit: 'px/s²' }, friction: { label: 'Friction', type: 'number', default: 0.8, min: 0, max: 1, step: 0.05, unit: '' }, bounce: { label: 'Bounce', type: 'number', default: 0.2, min: 0, max: 1, step: 0.05, unit: '' }, mass: { label: 'Mass', type: 'number', default: 1, min: 0.1, max: 10, step: 0.1, unit: 'kg' } }, movement: { walkSpeed: { label: 'Walk Speed', type: 'number', default: 150, min: 10, max: 500, step: 5, unit: 'px/s' }, runSpeed: { label: 'Run Speed', type: 'number', default: 250, min: 10, max: 800, step: 5, unit: 'px/s' }, jumpHeight: { label: 'Jump Height', type: 'number', default: 300, min: 0, max: 1000, step: 10, unit: 'px' }, acceleration: { label: 'Acceleration', type: 'number', default: 1000, min: 100, max: 3000, step: 50, unit: 'px/s²' } }, collision: { canCollide: { label: 'Can Collide', type: 'boolean', default: true }, canPush: { label: 'Can Push Others', type: 'boolean', default: false }, canBePushed: { label: 'Can Be Pushed', type: 'boolean', default: true }, solid: { label: 'Solid', type: 'boolean', default: true } }, special: { canClimb: { label: 'Can Climb', type: 'boolean', default: false }, canSwim: { label: 'Can Swim', type: 'boolean', default: false }, takeDamage: { label: 'Takes Damage', type: 'boolean', default: false }, dealDamage: { label: 'Deals Damage', type: 'boolean', default: false }, damageAmount: { label: 'Damage Amount', type: 'number', default: 10, min: 1, max: 100, step: 1, unit: 'hp' } } }; /** * Open the movement overlay - main entry point called by index.html */ function openMovementOverlay() { const overlayContent = document.getElementById('overlayContent'); overlayContent.innerHTML = ` <h2>Movement System šŸƒ</h2> <div id="movementTabs"></div> <div id="movementSettings"></div> `; // Initialize movement settings for existing groups initializeMovementSettings(); // Render the interface renderMovementTabs(); renderMovementSettings(); } /** * Initialize movement settings for all existing groups */ function initializeMovementSettings() { if (typeof groups !== 'undefined' && groups) { groups.forEach((group, index) => { if (!movementSettings[group.id]) { movementSettings[group.id] = createDefaultMovementSettings(group.category); } }); } } /** * Create default movement settings based on group category * @param {string} category - The tile group category * @returns {Object} Default movement settings */ function createDefaultMovementSettings(category) { const settings = {}; // Copy default values from MOVEMENT_SETTINGS for (const categoryKey in MOVEMENT_SETTINGS) { settings[categoryKey] = {}; for (const settingKey in MOVEMENT_SETTINGS[categoryKey]) { settings[categoryKey][settingKey] = MOVEMENT_SETTINGS[categoryKey][settingKey].default; } } // Customize defaults based on tile category switch (category) { case 'Ground': settings.collision.solid = true; settings.collision.canCollide = true; settings.physics.friction = 0.9; break; case 'Platform': settings.collision.solid = false; // One-way collision settings.collision.canCollide = true; break; case 'Pushable': settings.collision.canBePushed = true; settings.collision.canPush = false; settings.physics.mass = 2; break; case 'Passable': settings.collision.canCollide = false; settings.collision.solid = false; break; case 'Hazard': settings.collision.solid = false; settings.special.dealDamage = true; settings.special.damageAmount = 25; break; case 'Conveyor': settings.collision.solid = true; settings.movement.walkSpeed = 200; // Affects player when standing on it break; case 'Climbable': settings.collision.solid = false; settings.special.canClimb = true; break; case 'Player': settings.collision.canPush = true; settings.special.takeDamage = true; settings.movement.walkSpeed = 150; settings.movement.jumpHeight = 300; break; case 'NPC': settings.collision.canCollide = true; settings.movement.walkSpeed = 100; settings.movement.jumpHeight = 200; break; } return settings; } /** * Get category color for visual coding (matches tilepicker.js) * @param {string} category - The category value * @returns {string} CSS color value */ function getCategoryColor(category) { const colors = { 'None': '#666', 'Ground': '#8B4513', 'Platform': '#DEB887', 'Pushable': '#CD853F', 'Passable': '#90EE90', 'Hazard': '#FF4500', 'Conveyor': '#4169E1', 'Climbable': '#228B22', 'Sensor': '#9370DB', 'Door': '#B8860B', 'SwitchableToggle': '#FF69B4', 'SwitchableOnce': '#FF1493', 'AnimationGround': '#FF6347', 'AnimationPassable': '#20B2AA', 'Player': '#FFD700', 'NPC': '#87CEEB' }; return colors[category] || '#666'; } /** * Render the group tabs */ function renderMovementTabs() { const tabContainer = document.getElementById('movementTabs'); if (!tabContainer) return; tabContainer.innerHTML = ''; tabContainer.style.cssText = 'margin-bottom: 15px; display: flex; gap: 5px; align-items: center; flex-wrap: wrap;'; if (typeof groups === 'undefined' || !groups || groups.length === 0) { tabContainer.innerHTML = '<div style="color: #888; padding: 10px;">No tile groups found. Create groups in the Tile Picker first.</div>'; return; } groups.forEach((group, index) => { const btn = document.createElement('button'); const groupName = group.name || `Group ${index + 1}`; const categoryColor = getCategoryColor(group.category); btn.textContent = groupName; btn.style.cssText = ` background: ${index === currentMovementGroup ? '#6cf' : '#555'}; color: ${index === currentMovementGroup ? '#000' : '#fff'}; border: 3px solid ${categoryColor}; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-size: 12px; position: relative; max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; `; btn.onclick = () => { currentMovementGroup = index; renderMovementTabs(); renderMovementSettings(); }; btn.title = `${groupName}\nCategory: ${group.category}\nTiles: ${group.tiles ? group.tiles.length : 0}`; // Add category indicator const categoryIndicator = document.createElement('div'); categoryIndicator.style.cssText = ` position: absolute; bottom: -2px; left: 50%; transform: translateX(-50%); width: 80%; height: 3px; background: ${categoryColor}; border-radius: 2px; `; btn.appendChild(categoryIndicator); tabContainer.appendChild(btn); }); } /** * Render movement settings for the current group */ function renderMovementSettings() { const settingsContainer = document.getElementById('movementSettings'); if (!settingsContainer) return; settingsContainer.innerHTML = ''; if (typeof groups === 'undefined' || !groups || groups.length === 0) { settingsContainer.innerHTML = '<div style="color: #888; padding: 20px; text-align: center;">No groups available</div>'; return; } const currentGroup = groups[currentMovementGroup]; if (!currentGroup) return; const groupSettings = movementSettings[currentGroup.id] || createDefaultMovementSettings(currentGroup.category); movementSettings[currentGroup.id] = groupSettings; settingsContainer.style.cssText = 'padding: 15px; background: #2a2a2a; border-radius: 8px; max-height: 70vh; overflow-y: auto;'; // Group header with category selector const header = document.createElement('div'); header.style.cssText = 'margin-bottom: 20px; padding-bottom: 15px; border-bottom: 2px solid #555;'; const titleRow = document.createElement('div'); titleRow.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;'; const titleDiv = document.createElement('div'); titleDiv.innerHTML = ` <h3 style="color: #6cf; margin: 0 0 5px 0;">${currentGroup.name || 'Unnamed Group'}</h3> <div style="color: #ccc; font-size: 12px;"> Tiles: ${currentGroup.tiles ? currentGroup.tiles.length : 0} | ID: ${currentGroup.id} </div> `; const categoryDiv = document.createElement('div'); categoryDiv.style.cssText = 'display: flex; align-items: center; gap: 10px;'; const categoryLabel = document.createElement('label'); categoryLabel.textContent = 'Block Type:'; categoryLabel.style.cssText = 'color: #ccc; font-weight: bold; font-size: 14px;'; const categorySelect = document.createElement('select'); categorySelect.style.cssText = ` background: #555; color: white; border: 1px solid #777; border-radius: 4px; padding: 5px 8px; font-size: 12px; min-width: 150px; `; const categories = [ 'None', 'Ground', 'Platform', 'Pushable', 'Passable', 'Hazard', 'Conveyor', 'Climbable', 'Sensor', 'Door', 'SwitchableToggle', 'SwitchableOnce', 'AnimationGround', 'AnimationPassable', 'Player', 'NPC' ]; categories.forEach(cat => { const option = document.createElement('option'); option.value = cat; option.textContent = cat; option.selected = cat === currentGroup.category; categorySelect.appendChild(option); }); categorySelect.onchange = () => { currentGroup.category = categorySelect.value; // Update movement settings based on new category const newDefaults = createDefaultMovementSettings(categorySelect.value); movementSettings[currentGroup.id] = newDefaults; renderMovementSettings(); // Re-render to show updated settings }; categoryDiv.appendChild(categoryLabel); categoryDiv.appendChild(categorySelect); titleRow.appendChild(titleDiv); titleRow.appendChild(categoryDiv); header.appendChild(titleRow); // Add tile previews if tiles exist if (currentGroup.tiles && currentGroup.tiles.length > 0) { const tilesDiv = document.createElement('div'); tilesDiv.style.cssText = 'margin-top: 10px;'; const tilesLabel = document.createElement('div'); tilesLabel.textContent = 'Tiles in this group:'; tilesLabel.style.cssText = 'color: #ccc; font-size: 12px; margin-bottom: 8px;'; tilesDiv.appendChild(tilesLabel); const tilesContainer = document.createElement('div'); tilesContainer.style.cssText = 'display: flex; gap: 5px; flex-wrap: wrap;'; currentGroup.tiles.forEach(tile => { const tileWrapper = document.createElement('div'); tileWrapper.style.cssText = 'position: relative; display: inline-block;'; // Create canvas for tile display const canvas = document.createElement('canvas'); canvas.width = 32; canvas.height = 32; canvas.style.cssText = `border: 2px solid ${getCategoryColor(currentGroup.category)}; border-radius: 4px; background: #000;`; // Draw the tile const ctx = canvas.getContext('2d'); const tempCanvas = document.createElement('canvas'); tempCanvas.width = tile.size; tempCanvas.height = tile.size; const tempCtx = tempCanvas.getContext('2d'); tempCtx.putImageData(tile.data, 0, 0); // Scale to 32x32 for preview ctx.drawImage(tempCanvas, 0, 0, tile.size, tile.size, 0, 0, 32, 32); // Create ID badge const idBadge = document.createElement('span'); idBadge.textContent = tile.uniqueId; idBadge.style.cssText = 'position: absolute; bottom: -2px; right: -2px; background: rgba(0,0,0,0.8); color: #fff; font-size: 8px; padding: 1px 3px; border-radius: 2px; border: 1px solid #6cf;'; tileWrapper.appendChild(canvas); tileWrapper.appendChild(idBadge); tilesContainer.appendChild(tileWrapper); }); tilesDiv.appendChild(tilesContainer); header.appendChild(tilesDiv); } settingsContainer.appendChild(header); // Render setting categories for (const categoryKey in MOVEMENT_SETTINGS) { const categoryDiv = document.createElement('div'); categoryDiv.style.cssText = 'margin-bottom: 20px;'; const categoryTitle = document.createElement('h4'); categoryTitle.textContent = categoryKey.charAt(0).toUpperCase() + categoryKey.slice(1); categoryTitle.style.cssText = 'color: #4a4; margin: 0 0 10px 0; font-size: 14px; text-transform: capitalize;'; categoryDiv.appendChild(categoryTitle); const categorySettings = document.createElement('div'); categorySettings.style.cssText = 'background: #333; padding: 15px; border-radius: 6px; display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px;'; for (const settingKey in MOVEMENT_SETTINGS[categoryKey]) { const setting = MOVEMENT_SETTINGS[categoryKey][settingKey]; const settingDiv = createSettingControl(categoryKey, settingKey, setting, groupSettings[categoryKey][settingKey]); categorySettings.appendChild(settingDiv); } categoryDiv.appendChild(categorySettings); settingsContainer.appendChild(categoryDiv); } // Export button const exportBtn = document.createElement('button'); exportBtn.textContent = 'Export Movement Data'; exportBtn.style.cssText = ` background: #4a4; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 14px; margin-top: 15px; width: 100%; `; exportBtn.onclick = exportMovementData; settingsContainer.appendChild(exportBtn); } /** * Create a setting control element * @param {string} categoryKey - The category key * @param {string} settingKey - The setting key * @param {Object} setting - The setting definition * @param {*} currentValue - The current value * @returns {HTMLElement} The setting control element */ function createSettingControl(categoryKey, settingKey, setting, currentValue) { const settingDiv = document.createElement('div'); settingDiv.style.cssText = 'display: flex; flex-direction: column; gap: 5px;'; const label = document.createElement('label'); label.textContent = setting.label + (setting.unit ? ` (${setting.unit})` : ''); label.style.cssText = 'color: #ccc; font-size: 12px; font-weight: bold;'; settingDiv.appendChild(label); let input; if (setting.type === 'boolean') { input = document.createElement('input'); input.type = 'checkbox'; input.checked = currentValue; input.style.cssText = 'transform: scale(1.2);'; } else if (setting.type === 'number') { input = document.createElement('input'); input.type = 'number'; input.value = currentValue; input.min = setting.min || 0; input.max = setting.max || 1000; input.step = setting.step || 1; input.style.cssText = 'background: #222; color: white; border: 1px solid #555; padding: 5px; border-radius: 3px; font-size: 12px;'; } input.onchange = () => { const newValue = setting.type === 'boolean' ? input.checked : parseFloat(input.value); updateMovementSetting(categoryKey, settingKey, newValue); }; settingDiv.appendChild(input); return settingDiv; } /** * Update a movement setting * @param {string} categoryKey - The category key * @param {string} settingKey - The setting key * @param {*} value - The new value */ function updateMovementSetting(categoryKey, settingKey, value) { const currentGroup = groups[currentMovementGroup]; if (!currentGroup) return; if (!movementSettings[currentGroup.id]) { movementSettings[currentGroup.id] = createDefaultMovementSettings(currentGroup.category); } movementSettings[currentGroup.id][categoryKey][settingKey] = value; if (typeof debugAlert === 'function') { debugAlert(`Updated ${currentGroup.name}.${categoryKey}.${settingKey} = ${value}`); } } /** * Export movement data as JSON */ function exportMovementData() { try { // Create complete movement configuration const movementData = { metadata: { exportDate: new Date().toISOString(), editor: "Tile Game Editor - Movement System", version: "1.0.0" }, globalSettings: { defaultPhysics: MOVEMENT_SETTINGS.physics, defaultMovement: MOVEMENT_SETTINGS.movement, defaultCollision: MOVEMENT_SETTINGS.collision, defaultSpecial: MOVEMENT_SETTINGS.special }, groupMovementSettings: {} }; // Add settings for each group if (typeof groups !== 'undefined' && groups) { groups.forEach(group => { if (movementSettings[group.id]) { movementData.groupMovementSettings[group.id] = { groupName: group.name, category: group.category, tileCount: group.tiles ? group.tiles.length : 0, settings: movementSettings[group.id] }; } }); } const jsonString = JSON.stringify(movementData, null, 2); // Try to copy to clipboard if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(jsonString).then(() => { if (typeof debugAlert === 'function') { debugAlert('Movement data copied! ' + Math.round(jsonString.length / 1024) + 'KB'); } }).catch(() => { fallbackCopy(jsonString); }); } else { fallbackCopy(jsonString); } function fallbackCopy(text) { const textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'fixed'; textarea.style.opacity = '0'; document.body.appendChild(textarea); textarea.select(); try { const successful = document.execCommand('copy'); if (successful && typeof debugAlert === 'function') { debugAlert('Movement data copied (fallback)! ' + Math.round(text.length / 1024) + 'KB'); } } catch (err) { if (typeof debugAlert === 'function') { debugAlert('Copy failed. Check console for data.'); } console.log('Movement data:', text); } finally { document.body.removeChild(textarea); } } } catch (error) { if (typeof debugAlert === 'function') { debugAlert('Export failed: ' + error.message); } } } if (typeof debugAlert === 'function') { debugAlert('movement.js loaded successfully'); }