import React, { useState, useEffect, useRef } from 'react'; const ExplosionGenerator = () => { const [frames, setFrames] = useState([]); const [frameSize] = useState(48); const [generating, setGenerating] = useState(false); const [spriteSheetCanvas, setSpriteSheetCanvas] = useState(null); const [spriteSheetReady, setSpriteSheetReady] = useState(false); const canvasRef = useRef(null); const spriteSheetRef = useRef(null); // Generate a single explosion frame const generateExplosionFrame = (frameIndex, totalFrames) => { const canvas = document.createElement('canvas'); canvas.width = frameSize; canvas.height = frameSize; const ctx = canvas.getContext('2d'); // Clear background ctx.clearRect(0, 0, frameSize, frameSize); // Calculate explosion progress (0 to 1) const progress = frameIndex / (totalFrames - 1); // Size starts small and gets bigger, then smaller let size; if (progress < 0.7) { // Expansion phase size = frameSize * 0.1 + (frameSize * 0.8) * (progress / 0.7); } else { // Fade out phase size = frameSize * 0.9 - (frameSize * 0.6) * ((progress - 0.7) / 0.3); } // Center of explosion const centerX = frameSize / 2; const centerY = frameSize / 2; // Draw explosion shape const points = 8 + Math.floor(progress * 4); const innerRadius = size * 0.5; const outerRadius = size * 0.8; // Colors change throughout the explosion const colors = [ `rgba(255, ${Math.floor(255 * (1 - progress * 0.7))}, 0, ${1 - Math.pow(progress, 2)})`, // Red to yellow, fading `rgba(255, ${Math.floor(150 + 105 * (1 - progress))}, ${Math.floor(200 * progress)}, ${1 - Math.pow(progress, 1.5)})`, // Yellow-orange `rgba(255, 255, 255, ${1 - Math.pow(progress, 1.2)})` // White, fading ]; // Add some randomness to the explosion shape ctx.beginPath(); for (let i = 0; i < points * 2; i++) { const angle = (Math.PI * 2 * i) / (points * 2); const randomness = 0.2 + Math.random() * 0.3; const radius = i % 2 === 0 ? outerRadius : innerRadius * randomness; const x = centerX + Math.cos(angle) * radius; const y = centerY + Math.sin(angle) * radius; if (i === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } } ctx.closePath(); // Create gradient fill const gradient = ctx.createRadialGradient(centerX, centerY, innerRadius * 0.3, centerX, centerY, outerRadius); gradient.addColorStop(0, colors[2]); // White in center gradient.addColorStop(0.4, colors[1]); // Yellow in middle gradient.addColorStop(1, colors[0]); // Red at edge ctx.fillStyle = gradient; ctx.fill(); // Add outline ctx.strokeStyle = `rgba(0, 0, 0, ${1 - Math.pow(progress, 1.5)})`; ctx.lineWidth = 1.5; ctx.stroke(); // Add some particles if (progress < 0.9) { const particleCount = Math.floor(10 + progress * 20); for (let i = 0; i < particleCount; i++) { const angle = Math.random() * Math.PI * 2; const distance = Math.random() * size * (0.7 + progress * 0.5); const particleSize = 1 + Math.random() * 2; const x = centerX + Math.cos(angle) * distance; const y = centerY + Math.sin(angle) * distance; ctx.beginPath(); ctx.arc(x, y, particleSize, 0, Math.PI * 2); // Random color between white and yellow const particleColor = Math.random() > 0.5 ? colors[1] : colors[2]; ctx.fillStyle = particleColor; ctx.fill(); } } return canvas; }; // Generate all explosion frames const generateExplosion = async () => { setGenerating(true); const totalFrames = 16; const newFrames = []; for (let i = 0; i < totalFrames; i++) { const frameCanvas = generateExplosionFrame(i, totalFrames); newFrames.push(frameCanvas.toDataURL()); } setFrames(newFrames); await createSpriteSheet(newFrames); setGenerating(false); }; // Create sprite sheet from frames const createSpriteSheet = async (frameDataUrls) => { const canvas = document.createElement('canvas'); canvas.width = frameSize * 4; // 4 columns canvas.height = frameSize * 4; // 4 rows const ctx = canvas.getContext('2d'); // Clear the canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Fill with a background color so we can see it ctx.fillStyle = '#1f2937'; // dark gray ctx.fillRect(0, 0, canvas.width, canvas.height); // Create a promise for each image load const loadImages = frameDataUrls.map((dataUrl, index) => { return new Promise((resolve) => { const img = new Image(); img.onload = () => { const col = index % 4; const row = Math.floor(index / 4); ctx.drawImage(img, col * frameSize, row * frameSize, frameSize, frameSize); resolve(); }; img.src = dataUrl; }); }); // Wait for all images to load and draw before setting the sprite sheet canvas await Promise.all(loadImages); setSpriteSheetCanvas(canvas); setSpriteSheetReady(true); }; // Download sprite sheet - Simple approach const downloadSpriteSheet = () => { if (spriteSheetRef.current) { try { // Simply open the image in a new tab where it can be right-clicked and saved const imgData = spriteSheetRef.current.toDataURL('image/png'); window.open(imgData, '_blank'); } catch (error) { console.error("Error preparing sprite sheet:", error); alert("There was an error displaying the sprite sheet. Please try again."); } } else { alert("Please generate an explosion first."); } }; // Preview animation useEffect(() => { if (frames.length > 0 && canvasRef.current) { const ctx = canvasRef.current.getContext('2d'); let currentFrame = 0; let animationId; const animateExplosion = () => { ctx.clearRect(0, 0, frameSize, frameSize); const img = new Image(); img.onload = () => { ctx.drawImage(img, 0, 0, frameSize, frameSize); }; img.src = frames[currentFrame]; currentFrame = (currentFrame + 1) % frames.length; animationId = setTimeout(animateExplosion, 100); // ~10 fps }; animateExplosion(); return () => clearTimeout(animationId); } }, [frames, frameSize]); // Update sprite sheet canvas when it changes useEffect(() => { if (spriteSheetCanvas && spriteSheetRef.current) { const ctx = spriteSheetRef.current.getContext('2d'); ctx.clearRect(0, 0, frameSize * 4, frameSize * 4); ctx.drawImage(spriteSheetCanvas, 0, 0); } }, [spriteSheetCanvas, frameSize, spriteSheetReady]); return (

Cartoon Explosion Generator

Preview Animation

Sprite Sheet (4x4)

{spriteSheetCanvas ? (
) : (
Generate explosion to see sprite sheet
)}
{frames.length > 0 && (

Individual Frames

{frames.map((frame, index) => (
{`Frame
))}
)}
); }; export default ExplosionGenerator;