Discussion

Flux gravitationnel : jeu d'évasion en vol basé sur la physique

De Wikiprompt, l’encyclopédie libre de prompts

loshu2000
Contribué parloshu2000Source

17 mai 2026

Flux gravitationnel : jeu d'évasion en vol basé sur la physique Invite détaillée pour générer un jeu HTML en fichier unique avec un gameplay basé sur la physique, des obstacles procéduraux en courbes de Bézier, une synthèse audio Web Audio, et une esthétique pastel zen.

Contenu du PromptEnregistrer

🌐
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Gravity Flux · soft‑body flight</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #1a1c1e; min-height: 100vh; display: flex; justify-content: center; align-items: center; font-family: 'Segoe UI', system-ui, sans-serif; } .canvas-wrapper { background: #2d3136; padding: 12px; border-radius: 32px; box-shadow: 0 20px 40px rgba(0,0,0,0.6); } canvas { display: block; width: 800px; height: 600px; border-radius: 24px; box-shadow: inset 0 0 30px rgba(0,0,0,0.5); background: #f2ede7; /* pastel base */ } .info { display: flex; justify-content: space-between; margin-top: 12px; color: #b9c2c9; font-size: 14px; letter-spacing: 1px; padding: 0 8px; } .info span { background: #2b2f33; padding: 6px 14px; border-radius: 40px; box-shadow: 0 2px 6px rgba(0,0,0,0.3); } .btn { background: #4e5a63; border: none; color: #eef2f5; padding: 6px 16px; border-radius: 40px; font-weight: 500; cursor: pointer; transition: all 0.2s; box-shadow: 0 2px 8px rgba(0,0,0,0.3); } .btn:hover { background: #6b7a85; transform: scale(1.02); } </style> </head> <body> <div> <div class="canvas-wrapper"> <canvas id="gameCanvas" width="800" height="600"></canvas> <div class="info"> <span>⚡ GRAVITY FLUX</span> <span>✦ gates: <span id="gateCount">0</span></span> <button class="btn" id="resetBtn">↻ reset</button> </div> </div> </div> <script> (function() { // ========== CANVAS & CONTEXT ========== const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); const gateCountSpan = document.getElementById('gateCount'); // ========== GAME STATE ========== let width = 800, height = 600; let time = 0; let passedGates = 0; let gameOver = false; // ========== SOFT-BODY SPHERE (self-inflating) ========== const sphere = { x: 400, y: 300, baseRadius: 18, currentRadius: 18, vx: 0, vy: 0, // soft body wobble (simulated via radius oscillation) wobblePhase: 0, // control inputs up: false, down: false, left: false, right: false }; // ========== GATE STRUCTURE ========== // each gate: { x, y, w, h, phase, type, active } // type: 'closing' or 'expanding' (organic morph) let gates = []; const GATE_COUNT = 6; const GATE_MIN_W = 60; const GATE_MAX_W = 180; const GATE_HEIGHT = 220; // vertical opening (gap) // ========== AUDIO (Web Audio API) ========== let audioCtx = null; let masterGain = null; let ambientOsc = null; let ambientGain = null; function initAudio() { if (audioCtx) return; try { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); masterGain = audioCtx.createGain(); masterGain.gain.value = 0.25; masterGain.connect(audioCtx.destination); // ambient pulse: low sine + slow LFO ambientOsc = audioCtx.createOscillator(); ambientOsc.type = 'sine'; ambientOsc.frequency.value = 72; // deep pulse ambientGain = audioCtx.createGain(); ambientGain.gain.value = 0.5; const lfo = audioCtx.createOscillator(); lfo.frequency.value = 0.2; const lfoGain = audioCtx.createGain(); lfoGain.gain.value = 0.3; lfo.connect(lfoGain); lfoGain.connect(ambientGain.gain); ambientOsc.connect(ambientGain); ambientGain.connect(masterGain); ambientOsc.start(); lfo.start(); } catch(e) { console.warn('Audio init failed', e); } } // reactive acoustic sweep when passing gate function playSweep() { if (!audioCtx || !masterGain) return; const now = audioCtx.currentTime; const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.type = 'sine'; osc.frequency.setValueAtTime(180, now); osc.frequency.exponentialRampToValueAtTime(720, now + 0.25); gain.gain.setValueAtTime(0.4, now); gain.gain.exponentialRampToValueAtTime(0.01, now + 0.4); osc.connect(gain); gain.connect(masterGain); osc.start(now); osc.stop(now + 0.45); } // ========== GATE GENERATION (procedural, morphing) ========== function generateGates() { gates = []; for (let i = 0; i < GATE_COUNT; i++) { const x = 120 + i * 120; // spread across width const yBase = 200 + (i % 3) * 80; // vary vertical const type = (i % 2 === 0) ? 'closing' : 'expanding'; gates.push({ x: x, y: yBase, w: 100, // base width h: GATE_HEIGHT, phase: i * 1.7, // phase offset for sine morph type: type, active: true, // for organic bezier we store control points offsets morphSpeed: 0.8 + (i * 0.12), }); } } // ========== RESET ========== function resetGame() { passedGates = 0; gameOver = false; sphere.x = 400; sphere.y = 300; sphere.vx = 0; sphere.vy = 0; sphere.currentRadius = sphere.baseRadius; generateGates(); updateGateCounter(); } // ========== INPUT ========== const keyState = {}; function handleKeyDown(e) { if (e.key === 'ArrowUp' || e.key === 'w') { sphere.up = true; e.preventDefault(); } if (e.key === 'ArrowDown' || e.key === 's') { sphere.down = true; e.preventDefault(); } if (e.key === 'ArrowLeft' || e.key === 'a') { sphere.left = true; e.preventDefault(); } if (e.key === 'ArrowRight' || e.key === 'd') { sphere.right = true; e.preventDefault(); } } function handleKeyUp(e) { if (e.key === 'ArrowUp' || e.key === 'w') { sphere.up = false; e.preventDefault(); } if (e.key === 'ArrowDown' || e.key === 's') { sphere.down = false; e.preventDefault(); } if (e.key === 'ArrowLeft' || e.key === 'a') { sphere.left = false; e.preventDefault(); } if (e.key === 'ArrowRight' || e.key === 'd') { sphere.right = false; e.preventDefault(); } } window.addEventListener('keydown', handleKeyDown); window.addEventListener('keyup', handleKeyUp); document.getElementById('resetBtn').addEventListener('click', () => { resetGame(); initAudio(); // ensure audio on user gesture }); // ========== PHYSICS & COLLISION ========== function updateSphere(dt) { // gentle acceleration const accel = 0.6; const damping = 0.92; if (sphere.up) sphere.vy -= accel * dt * 60; if (sphere.down) sphere.vy += accel * dt * 60; if (sphere.left) sphere.vx -= accel * dt * 60; if (sphere.right) sphere.vx += accel * dt * 60; // apply damping sphere.vx *= damping; sphere.vy *= damping; // integrate sphere.x += sphere.vx * dt * 60; sphere.y += sphere.vy * dt * 60; // soft body self-inflate (radius pulsates) sphere.wobblePhase += dt * 2.2; sphere.currentRadius = sphere.baseRadius + Math.sin(sphere.wobblePhase) * 2.5; // boundaries (soft) if (sphere.x < 20) sphere.x = 20; if (sphere.x > width - 20) sphere.x = width - 20; if (sphere.y < 20) sphere.y = 20; if (sphere.y > height - 20) sphere.y = height - 20; } // check collision with gate (organic shape approximated by rectangle + morph) function checkGateCollision() { const r = sphere.currentRadius; for (let gate of gates) { if (!gate.active) continue; // compute current opening width based on sine morph const t = time * gate.morphSpeed + gate.phase; let wOffset; if (gate.type === 'closing') { // oscillates between 0.3 and 1.0 of base width wOffset = 0.35 + 0.65 * (Math.sin(t) * 0.5 + 0.5); } else { // expanding: oscillates between 0.8 and 1.6 wOffset = 0.8 + 0.8 * (Math.sin(t) * 0.5 + 0.5); } const currentW = Math.max(30, gate.w * wOffset); const halfW = currentW / 2; const gateLeft = gate.x - halfW; const gateRight = gate.x + halfW; const gateTop = gate.y - gate.h / 2; const gateBottom = gate.y + gate.h / 2; // sphere AABB const sLeft = sphere.x - r; const sRight = sphere.x + r; const sTop = sphere.y - r; const sBottom = sphere.y + r; // if sphere overlaps the gate region -> collision (unless it's a pass-through? but spec: no contact) const overlapX = sRight > gateLeft && sLeft < gateRight; const overlapY = sTop < gateBottom && sBottom > gateTop; if (overlapX && overlapY) { // but we need to check if sphere is inside the gap (vertical opening) -> actually the gate is the gap itself // The gate is the opening: we must be inside the rectangle to pass. // So we check if sphere is fully inside the gate area -> pass, else collision. // But spec: "pass through dynamic structural gates without making contact" -> contact with the structure. // The structure is the walls around the gap. So we treat the gate as the opening. // If sphere is inside the opening, it's safe. If it touches the edges, it's collision. // We'll check if sphere is fully inside the opening (with margin). const margin = 2; const insideX = (sphere.x - r > gateLeft + margin) && (sphere.x + r < gateRight - margin); const insideY = (sphere.y - r > gateTop + margin) && (sphere.y + r < gateBottom - margin); if (insideX && insideY) { // safe zone: if previously not counted, count as pass if (!gate.passed) { gate.passed = true; passedGates++; updateGateCounter(); playSweep(); } } else { // contact with structure -> game over gameOver = true; return; } } else { // not overlapping, reset passed flag if outside gate.passed = false; } } } function updateGateCounter() { gateCountSpan.textContent = passedGates; } // ========== RENDERING (zen pastel, soft shadows, bezier morph) ========== function drawBackground() { // soft gradient const grad = ctx.createLinearGradient(0, 0, 0, height); grad.addColorStop(0, '#f6efe8'); grad.addColorStop(1, '#e3d9cf'); ctx.fillStyle = grad; ctx.fillRect(0, 0, width, height); } // draw a single gate as organic bezier curves (morphing) function drawGate(gate) { const t = time * gate.morphSpeed + gate.phase; // compute opening width let wFactor; if (gate.type === 'closing') { wFactor = 0.35 + 0.65 * (Math.sin(t) * 0.5 + 0.5); } else { wFactor = 0.8 + 0.8 * (Math.sin(t) * 0.5 + 0.5); } const currentW = Math.max(30, gate.w * wFactor); const halfW = currentW / 2; const x = gate.x; const yTop = gate.y - gate.h / 2; const yBottom = gate.y + gate.h / 2; // organic bezier: left and right edges are curved, top and bottom are wavy const waveAmp = 12 * Math.sin(t * 1.3 + gate.phase); const waveAmp2 = 10 * Math.cos(t * 0.9 + gate.phase * 2); // left edge (bezier from top-left to bottom-left) const leftX = x - halfW; const rightX = x + halfW; // draw as two filled shapes (left wall and right wall) with soft shadow ctx.save(); ctx.shadowColor = 'rgba(80, 70, 60, 0.35)'; ctx.shadowBlur = 18; ctx.shadowOffsetY = 6; // left wall (organic shape) ctx.beginPath(); ctx.moveTo(leftX, yTop); // top curve ctx.bezierCurveTo(leftX - waveAmp, yTop + 20, leftX + waveAmp2, yTop + 40, leftX, yTop + 60); // inner edge (towards gap) ctx.lineTo(leftX + 6, yTop + 60); ctx.bezierCurveTo(leftX + 6 - waveAmp2, yTop + 80, leftX + 6 + waveAmp, yTop + 120, leftX + 6, yTop + 160); ctx.lineTo(leftX + 6, yBottom - 20); ctx.bezierCurveTo(leftX + 6 + waveAmp, yBottom - 40, leftX + 6 - waveAmp2, yBottom - 60, leftX + 6, yBottom); // bottom edge ctx.lineTo(leftX, yBottom); ctx.bezierCurveTo(leftX - waveAmp2, yBottom - 20, leftX + waveAmp, yBottom - 40, leftX, yBottom - 60); ctx.lineTo(leftX, yTop + 60); ctx.bezierCurveTo(leftX + waveAmp, yTop + 40, leftX - waveAmp2, yTop + 20, leftX, yTop); ctx.closePath(); ctx.fillStyle = '#cbbeb2'; ctx.fill(); // right wall (mirror) ctx.beginPath(); ctx.moveTo(rightX, yTop); ctx.bezierCurveTo(rightX + waveAmp, yTop + 20, rightX - waveAmp2, yTop + 40, rightX, yTop + 60); ctx.lineTo(rightX - 6, yTop + 60); ctx.bezierCurveTo(rightX - 6 + waveAmp2, yTop + 80, rightX - 6 - waveAmp, yTop + 120, rightX - 6, yTop + 160); ctx.lineTo(rightX - 6, yBottom - 20); ctx.bezierCurveTo(rightX - 6 - waveAmp, yBottom - 40, rightX - 6 + waveAmp2, yBottom - 60, rightX - 6, yBottom); ctx.lineTo(rightX, yBottom); ctx.bezierCurveTo(rightX + waveAmp2, yBottom - 20, rightX - waveAmp, yBottom - 40, rightX, yBottom - 60); ctx.lineTo(rightX, yTop + 60); ctx.bezierCurveTo(rightX - waveAmp, yTop + 40, rightX + waveAmp2, yTop + 20, rightX, yTop); ctx.closePath(); ctx.fillStyle = '#cbbeb2'; ctx.fill(); // subtle top and bottom organic bars (to emphasize gap) ctx.beginPath(); ctx.moveTo(leftX, yTop + 2); ctx.bezierCurveTo(x - 20, yTop - 8, x + 20, yTop - 8, rightX, yTop + 2); ctx.lineTo(rightX, yTop + 12); ctx.bezierCurveTo(x + 20, yTop + 4, x - 20, yTop + 4, leftX, yTop + 12); ctx.closePath(); ctx.fillStyle = '#b3a69a'; ctx.fill(); ctx.beginPath(); ctx.moveTo(leftX, yBottom - 2); ctx.bezierCurveTo(x - 20, yBottom + 8, x + 20, yBottom + 8, rightX, yBottom - 2); ctx.lineTo(rightX, yBottom - 12); ctx.bezierCurveTo(x + 20, yBottom - 4, x - 20, yBottom - 4, leftX, yBottom - 12); ctx.closePath(); ctx.fillStyle = '#b3a69a'; ctx.fill(); ctx.restore(); } // draw soft-body sphere function drawSphere() { ctx.save(); ctx.shadowColor = 'rgba(60, 50, 40, 0.4)'; ctx.shadowBlur = 24; ctx.shadowOffsetY = 8; // radial gradient for soft volume const grad = ctx.createRadialGradient(sphere.x-6, sphere.y-6, 4, sphere.x, sphere.y, sphere.currentRadius+4); grad.addColorStop(0, '#faf6f2'); grad.addColorStop(0.7, '#e0d5cc'); grad.addColorStop(1, '#b8a99e'); ctx.beginPath(); ctx.arc(sphere.x, sphere.y, sphere.currentRadius, 0, Math.PI*2); ctx.fillStyle = grad; ctx.fill(); // inner highlight ctx.shadowBlur = 8; ctx.beginPath(); ctx.arc(sphere.x-3, sphere.y-3, sphere.currentRadius*0.3, 0, Math.PI*2); ctx.fillStyle = 'rgba(255,255,255,0.4)'; ctx.fill(); ctx.restore(); } // ========== MAIN LOOP ========== let lastTime = 0; function gameLoop(timestamp) { const dt = Math.min(0.05, (timestamp - lastTime) / 1000 || 0.016); lastTime = timestamp; time += dt; if (!gameOver) { updateSphere(dt); checkGateCollision(); } // render ctx.clearRect(0, 0, width, height); drawBackground(); // draw gates for (let gate of gates) { drawGate(gate); } // draw sphere (if not game over, else still draw but with dim) if (gameOver) { ctx.globalAlpha = 0.6; drawSphere(); ctx.globalAlpha = 1.0; // overlay message ctx.font = 'bold 28px "Segoe UI", sans-serif'; ctx.fillStyle = '#5a4e46'; ctx.shadowColor = 'rgba(0,0,0,0.2)'; ctx.shadowBlur = 12; ctx.textAlign = 'center'; ctx.fillText('contact · reset', width/2, height/2 - 20); ctx.shadowBlur = 0; } else { drawSphere(); } // small hint ctx.font = '12px monospace'; ctx.fillStyle = '#7a6e64'; ctx.textAlign = 'left'; ctx.fillText('arrow keys / wasd', 20, height-16); requestAnimationFrame(gameLoop); } // ========== INIT ========== function init() { resetGame(); // start audio on first interaction (user gesture) const startAudio = () => { initAudio(); window.removeEventListener('keydown', startAudio); window.removeEventListener('click', startAudio); }; window.addEventListener('keydown', startAudio); window.addEventListener('click', startAudio); requestAnimationFrame(gameLoop); } init(); })(); </script> </body> </html>

Connectez-vous pour voir le prompt complet

Continuer avec:

En vous connectant, vous acceptez nos Conditions et Confidentialité

Utilisation

Ce prompt est conçu pour être utilisé avec coding. Copiez le contenu ci-dessus et collez-le dans votre outil d’IA préféré.

Pour de meilleurs résultats, personnalisez les espaces réservés (indiqués par des crochets ou des majuscules) selon vos besoins.

Références

Catégories :coding| prompts.chat| game-development| physics-engine

Discussion