📜
gameObject.js
Back
📝 Javascript ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
// gameObject.js — Game Object Manager UI // - Reads sprite data from cutout.js localStorage // - Displays sprites, groups, and images in a management interface // - Allows creating and organizing game objects from sprites (function () { const CUTOUT_KEY = 'tile_holders_v4'; // ---------- Data Manager ---------- const DataManager = { sprites: [], groups: [], images: [], loadFromCutout() { try { const raw = localStorage.getItem(CUTOUT_KEY); if (!raw) { this.sprites = []; this.groups = []; this.images = []; return false; } const data = JSON.parse(raw); this.sprites = []; this.groups = []; this.images = []; // Process each image for (const [imageUrl, imageData] of Object.entries(data.images || {})) { const { tileSize, groups = [] } = imageData; // Store image info this.images.push({ url: imageUrl, name: imageUrl.split('/').pop() || 'image', tileSize, groupCount: groups.length, spriteCount: groups.reduce((sum, g) => sum + (g.tiles?.length || 0), 0) }); // Process groups and sprites for (const group of groups) { const groupData = { id: group.id, name: group.name, imageUrl, imageName: imageUrl.split('/').pop() || 'image', tileSize, sprites: [] }; // Process sprites in group for (const tile of group.tiles || []) { const sprite = { id: tile.spriteId, number: tile.spriteNumber, col: tile.col, row: tile.row, imageUrl, imageName: imageUrl.split('/').pop() || 'image', groupId: group.id, groupName: group.name, tileSize, x: tile.col * tileSize, y: tile.row * tileSize, width: tileSize, height: tileSize }; this.sprites.push(sprite); groupData.sprites.push(sprite); } this.groups.push(groupData); } } return true; } catch (error) { console.error('[gameObject] Failed to load cutout data:', error); return false; } }, getSpriteById(id) { return this.sprites.find(s => s.id === id) || null; }, getSpritesByGroup(groupId) { return this.sprites.filter(s => s.groupId === groupId); }, getStats() { return { totalSprites: this.sprites.length, totalGroups: this.groups.length, totalImages: this.images.length }; } }; // ---------- UI State ---------- let currentView = 'overview'; // overview, sprites, groups, images let selectedSprites = new Set(); // ---------- Render Functions ---------- function renderOverview() { const stats = DataManager.getStats(); return ` <div style="padding:1rem;"> <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:1rem;margin-bottom:1rem;"> <div style="padding:1rem;background:rgba(30,41,59,0.6);border-radius:.5rem;border:1px solid rgba(71,85,105,0.4);text-align:center;"> <div style="font-size:2rem;font-weight:bold;color:#3b82f6;">${stats.totalSprites}</div> <div style="color:#94a3b8;font-size:0.875rem;">Total Sprites</div> </div> <div style="padding:1rem;background:rgba(30,41,59,0.6);border-radius:.5rem;border:1px solid rgba(71,85,105,0.4);text-align:center;"> <div style="font-size:2rem;font-weight:bold;color:#10b981;">${stats.totalGroups}</div> <div style="color:#94a3b8;font-size:0.875rem;">Groups</div> </div> <div style="padding:1rem;background:rgba(30,41,59,0.6);border-radius:.5rem;border:1px solid rgba(71,85,105,0.4);text-align:center;"> <div style="font-size:2rem;font-weight:bold;color:#f59e0b;">${stats.totalImages}</div> <div style="color:#94a3b8;font-size:0.875rem;">Images</div> </div> </div> <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:.5rem;"> <button class="view-btn" data-view="sprites" style="padding:1rem;background:rgba(59,130,246,0.2);border:1px solid rgba(59,130,246,0.4);border-radius:.5rem;color:#3b82f6;cursor:pointer;transition:all 0.2s;"> 🎮 View Sprites </button> <button class="view-btn" data-view="groups" style="padding:1rem;background:rgba(16,185,129,0.2);border:1px solid rgba(16,185,129,0.4);border-radius:.5rem;color:#10b981;cursor:pointer;transition:all 0.2s;"> 📁 View Groups </button> <button class="view-btn" data-view="images" style="padding:1rem;background:rgba(245,158,11,0.2);border:1px solid rgba(245,158,11,0.4);border-radius:.5rem;color:#f59e0b;cursor:pointer;transition:all 0.2s;"> 🖼️ View Images </button> </div> ${stats.totalSprites === 0 ? ` <div style="margin-top:2rem;padding:1rem;border:1px dashed rgba(71,85,105,0.5);border-radius:.5rem;text-align:center;color:#94a3b8;"> No sprites found. Use the Cut tool to create sprites first. </div> ` : ''} </div> `; } function renderSprites() { if (DataManager.sprites.length === 0) { return ` <div style="padding:1rem;text-align:center;color:#94a3b8;"> No sprites available. Create some in the Cut tool first. </div> `; } return ` <div style="padding:1rem;"> <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;"> <button class="back-btn" data-view="overview" style="padding:.5rem 1rem;background:rgba(71,85,105,0.4);border:1px solid rgba(71,85,105,0.6);border-radius:.375rem;color:#e2e8f0;cursor:pointer;"> ← Back </button> <div style="color:#94a3b8;font-size:0.875rem;">${DataManager.sprites.length} sprites</div> </div> <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:.5rem;max-height:60vh;overflow-y:auto;"> ${DataManager.sprites.map(sprite => ` <div class="sprite-card" data-sprite-id="${sprite.id}" style="padding:.5rem;background:rgba(30,41,59,0.6);border:1px solid rgba(71,85,105,0.4);border-radius:.375rem;cursor:pointer;text-align:center;"> <div style="width:60px;height:60px;background:#0a0f1c;border-radius:.25rem;margin:0 auto .375rem;border:1px solid rgba(71,85,105,0.4);display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden;"> <div class="sprite-thumbnail" data-sprite-id="${sprite.id}" style="width:100%;height:100%;background-size:cover;background-position:center;image-rendering:pixelated;"> <div style="font-size:10px;color:#64748b;">…</div> </div> </div> <div style="color:#e2e8f0;font-size:0.8rem;font-weight:600;">#${sprite.number}</div> <div style="color:#94a3b8;font-size:0.75rem;">ID:${sprite.id}</div> <div style="color:#64748b;font-size:0.7rem;">${sprite.groupName}</div> </div> `).join('')} </div> </div> `; } function renderGroups() { if (DataManager.groups.length === 0) { return ` <div style="padding:1rem;text-align:center;color:#94a3b8;"> No groups available. </div> `; } return ` <div style="padding:1rem;"> <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;"> <button class="back-btn" data-view="overview" style="padding:.5rem 1rem;background:rgba(71,85,105,0.4);border:1px solid rgba(71,85,105,0.6);border-radius:.375rem;color:#e2e8f0;cursor:pointer;"> ← Back </button> <div style="color:#94a3b8;font-size:0.875rem;">${DataManager.groups.length} groups</div> </div> <div style="display:flex;flex-direction:column;gap:.5rem;max-height:60vh;overflow-y:auto;"> ${DataManager.groups.map(group => ` <div style="padding:1rem;background:rgba(30,41,59,0.6);border:1px solid rgba(71,85,105,0.4);border-radius:.5rem;"> <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:.5rem;"> <div> <div style="color:#e2e8f0;font-weight:600;margin-bottom:.25rem;">${group.name}</div> <div style="color:#94a3b8;font-size:0.875rem;">${group.imageName} • ${group.sprites.length} sprites • ${group.tileSize}px tiles</div> </div> </div> <div style="display:flex;gap:.375rem;overflow-x:auto;padding:.25rem 0;"> ${group.sprites.slice(0, 10).map(sprite => ` <div style="flex:0 0 auto;width:40px;height:40px;background:#0a0f1c;border-radius:.25rem;border:1px solid rgba(71,85,105,0.4);display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden;"> <div class="sprite-thumbnail" data-sprite-id="${sprite.id}" style="width:100%;height:100%;background-size:cover;background-position:center;image-rendering:pixelated;"> <div style="font-size:8px;color:#64748b;">#${sprite.number}</div> </div> </div> `).join('')} ${group.sprites.length > 10 ? ` <div style="flex:0 0 auto;width:40px;height:40px;background:rgba(71,85,105,0.4);border-radius:.25rem;display:flex;align-items:center;justify-content:center;color:#94a3b8;font-size:0.75rem;"> +${group.sprites.length - 10} </div> ` : ''} </div> </div> `).join('')} </div> </div> `; } function renderImages() { if (DataManager.images.length === 0) { return ` <div style="padding:1rem;text-align:center;color:#94a3b8;"> No images available. </div> `; } return ` <div style="padding:1rem;"> <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;"> <button class="back-btn" data-view="overview" style="padding:.5rem 1rem;background:rgba(71,85,105,0.4);border:1px solid rgba(71,85,105,0.6);border-radius:.375rem;color:#e2e8f0;cursor:pointer;"> ← Back </button> <div style="color:#94a3b8;font-size:0.875rem;">${DataManager.images.length} images</div> </div> <div style="display:flex;flex-direction:column;gap:.5rem;max-height:60vh;overflow-y:auto;"> ${DataManager.images.map(image => ` <div style="padding:1rem;background:rgba(30,41,59,0.6);border:1px solid rgba(71,85,105,0.4);border-radius:.5rem;"> <div style="display:flex;gap:1rem;align-items:center;"> <div style="width:80px;height:80px;background:#0a0f1c;border-radius:.375rem;border:1px solid rgba(71,85,105,0.4);overflow:hidden;flex-shrink:0;"> <img src="${image.url}" alt="" style="width:100%;height:100%;object-fit:cover;" loading="lazy"> </div> <div style="flex:1;"> <div style="color:#e2e8f0;font-weight:600;margin-bottom:.25rem;">${image.name}</div> <div style="color:#94a3b8;font-size:0.875rem;margin-bottom:.375rem;"> ${image.spriteCount} sprites in ${image.groupCount} groups • ${image.tileSize}px tiles </div> <div style="display:flex;gap:.5rem;"> <div style="padding:.25rem .5rem;background:rgba(59,130,246,0.2);border:1px solid rgba(59,130,246,0.4);border-radius:.25rem;color:#3b82f6;font-size:0.75rem;"> ${image.spriteCount} sprites </div> <div style="padding:.25rem .5rem;background:rgba(16,185,129,0.2);border:1px solid rgba(16,185,129,0.4);border-radius:.25rem;color:#10b981;font-size:0.75rem;"> ${image.groupCount} groups </div> </div> </div> </div> </div> `).join('')} </div> </div> `; } // ---------- Sprite Thumbnails ---------- function generateSpriteThumbnails() { document.querySelectorAll('.sprite-thumbnail[data-sprite-id]').forEach(thumbEl => { const spriteId = parseInt(thumbEl.getAttribute('data-sprite-id')); const sprite = DataManager.getSpriteById(spriteId); if (!sprite) return; const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = () => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const thumbSize = 60; canvas.width = thumbSize; canvas.height = thumbSize; ctx.imageSmoothingEnabled = false; ctx.drawImage(img, sprite.x, sprite.y, sprite.width, sprite.height, 0, 0, thumbSize, thumbSize); thumbEl.style.backgroundImage = `url(${canvas.toDataURL()})`; thumbEl.innerHTML = ''; }; img.onerror = () => { thumbEl.innerHTML = '<div style="font-size:10px;color:#64748b;">?</div>'; }; img.src = sprite.imageUrl; }); } // ---------- Update Content ---------- function updateContent() { const container = document.getElementById('gameobject-content'); if (!container) return; DataManager.loadFromCutout(); let content = ''; switch (currentView) { case 'sprites': content = renderSprites(); break; case 'groups': content = renderGroups(); break; case 'images': content = renderImages(); break; default: content = renderOverview(); break; } container.innerHTML = content; wireUpControls(); // Generate thumbnails after DOM update setTimeout(() => generateSpriteThumbnails(), 100); } function wireUpControls() { // View navigation document.querySelectorAll('.view-btn, .back-btn').forEach(btn => { btn.addEventListener('click', () => { const view = btn.getAttribute('data-view'); if (view) { currentView = view; updateContent(); } }); }); // Sprite card interactions document.querySelectorAll('.sprite-card').forEach(card => { card.addEventListener('click', () => { const spriteId = parseInt(card.getAttribute('data-sprite-id')); const sprite = DataManager.getSpriteById(spriteId); if (sprite) { console.log('Selected sprite:', sprite); // Could open detailed view or add to selection } }); }); } // ---------- Initial Mount ---------- const initialHtml = ` <div id="gameobject-content" style="height:100%;overflow-y:auto;"> <div style="padding:1rem;text-align:center;color:#94a3b8;">Loading game objects...</div> </div> `; window.AppItems = window.AppItems || []; window.AppItems.push({ title: '🎮 Objects', html: initialHtml, onRender() { // Always refresh data when the overlay is opened setTimeout(() => updateContent(), 0); } }); // Listen for cutout data changes window.addEventListener('storage', (e) => { if (e.key === CUTOUT_KEY) { updateContent(); } }); })();