Gravity Flux: Physikbasiertes Flugausweichspiel
Von Wikiprompt, der freien Prompt-Enzyklopädie
Gravity Flux: Physikbasiertes Flugausweichspiel Ein detaillierter Prompt zur Erzeugung eines Ein-Datei-HTML-Spiels mit physikbasiertem Gameplay, prozeduralen Bezier-Hindernissen, Web-Audio-Synthese und einer zen-artigen Pastellästhetik.
Prompt-InhaltSpeichern
🌐
<!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 evasion</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #1e1e2f;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-family: 'Segoe UI', system-ui, sans-serif;
}
.canvas-wrapper {
background: #2b2b3c;
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: #f2efe9; /* soft pastel base */
}
.info {
display: flex;
justify-content: space-between;
margin-top: 14px;
color: #b9b3c7;
font-weight: 400;
letter-spacing: 0.5px;
padding: 0 8px;
}
.info span {
background: #2e2e42;
padding: 6px 16px;
border-radius: 30px;
font-size: 0.9rem;
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
}
.accent {
color: #d8d0e8;
}
</style>
</head>
<body>
<div>
<div class="canvas-wrapper">
<canvas id="gameCanvas" width="800" height="600"></canvas>
<div class="info">
<span>🌫️ <span class="accent">Gravity Flux</span></span>
<span id="gateCount">gates: 0</span>
<span>⬆️⬇️ / W·S</span>
</div>
</div>
</div>
<script>
(function() {
// ---------- canvas & context ----------
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const W = 800, H = 600;
// ---------- audio (Web Audio API) ----------
let audioCtx = null;
let masterGain = null;
let pulseOsc = null;
let pulseGain = null;
let sweepOsc = null;
let sweepGain = null;
function initAudio() {
if (audioCtx) return;
try {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
masterGain = audioCtx.createGain();
masterGain.gain.value = 0.18;
masterGain.connect(audioCtx.destination);
// ambient pulse: low sine, slow LFO
pulseOsc = audioCtx.createOscillator();
pulseOsc.type = 'sine';
pulseOsc.frequency.value = 55; // deep hum
pulseGain = audioCtx.createGain();
pulseGain.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(pulseGain.gain);
pulseOsc.connect(pulseGain);
pulseGain.connect(masterGain);
pulseOsc.start();
lfo.start();
// sweep oscillator (reactive, triggered on gate pass)
sweepOsc = audioCtx.createOscillator();
sweepOsc.type = 'sine';
sweepOsc.frequency.value = 180;
sweepGain = audioCtx.createGain();
sweepGain.gain.value = 0.0;
sweepOsc.connect(sweepGain);
sweepGain.connect(masterGain);
sweepOsc.start();
} catch (e) {
// audio not available
audioCtx = null;
}
}
// reactive sweep: short glissando
function playSweep() {
if (!audioCtx || !sweepOsc) return;
const now = audioCtx.currentTime;
sweepGain.gain.cancelScheduledValues(now);
sweepGain.gain.setValueAtTime(0.25, now);
sweepGain.gain.exponentialRampToValueAtTime(0.001, now + 0.4);
sweepOsc.frequency.setValueAtTime(220, now);
sweepOsc.frequency.exponentialRampToValueAtTime(720, now + 0.25);
}
// ---------- soft‑body sphere (self‑inflating) ----------
const ball = {
x: 200,
y: 300,
baseRadius: 16,
radius: 16,
vy: 0,
vx: 0,
squash: 0.0, // visual squash factor
phase: 0.0
};
// ---------- game state ----------
let gates = [];
let gateCount = 0;
let time = 0;
let gameOver = false;
let score = 0;
// ---------- procedural gate generation ----------
const GATE_SPACING = 260; // horizontal distance
const GATE_WIDTH = 130; // base width
const GATE_HEIGHT = 180; // vertical span
function generateGate(xPos, seedOffset) {
// each gate is a pair of bezier curves (upper and lower)
// they morph via sine functions
const baseY = H * 0.5 + Math.sin(seedOffset * 1.3) * 60;
return {
x: xPos,
baseY: baseY,
seed: seedOffset,
phaseOffset: Math.random() * Math.PI * 2,
width: GATE_WIDTH,
height: GATE_HEIGHT,
passed: false
};
}
// initial gates
for (let i = 0; i < 6; i++) {
gates.push(generateGate(300 + i * GATE_SPACING, i * 0.9 + 0.2));
}
// ---------- helper: get gate top/bottom curves at time t ----------
function getGateCurves(gate, t) {
const x = gate.x;
const baseY = gate.baseY;
const w = gate.width;
const h = gate.height;
const seed = gate.seed;
const ph = gate.phaseOffset;
// dynamic morph: sine waves shift control points
const wave1 = Math.sin(t * 0.8 + ph) * 22;
const wave2 = Math.cos(t * 0.6 + ph * 1.7) * 18;
const wave3 = Math.sin(t * 1.2 + ph * 0.3) * 14;
// upper curve (ceiling) – from left to right
const topY = baseY - h/2 + wave1 * 0.6;
const bottomY = baseY + h/2 + wave2 * 0.5;
// control points for upper bezier (cubic)
const cp1x = x - w * 0.2;
const cp1y = topY - 20 + wave3 * 0.8;
const cp2x = x + w * 0.2;
const cp2y = topY + 15 + wave2 * 0.7;
// lower bezier (floor)
const cp3x = x - w * 0.25;
const cp3y = bottomY + 18 + wave1 * 0.9;
const cp4x = x + w * 0.25;
const cp4y = bottomY - 12 + wave3 * 0.6;
return {
top: { start: {x: x - w/2, y: topY}, cp1: {x: cp1x, y: cp1y}, cp2: {x: cp2x, y: cp2y}, end: {x: x + w/2, y: topY} },
bottom: { start: {x: x - w/2, y: bottomY}, cp1: {x: cp3x, y: cp3y}, cp2: {x: cp4x, y: cp4y}, end: {x: x + w/2, y: bottomY} }
};
}
// ---------- collision detection (point vs bezier approx) ----------
function pointInGate(px, py, gate, t) {
const curves = getGateCurves(gate, t);
const top = curves.top;
const bottom = curves.bottom;
// sample bezier at 8 points for each curve
const steps = 10;
let minTop = Infinity;
let maxBottom = -Infinity;
for (let i = 0; i <= steps; i++) {
const u = i / steps;
// cubic bezier formula
const topY = bezierPoint(top.start.y, top.cp1.y, top.cp2.y, top.end.y, u);
const bottomY = bezierPoint(bottom.start.y, bottom.cp1.y, bottom.cp2.y, bottom.end.y, u);
if (topY < minTop) minTop = topY;
if (bottomY > maxBottom) maxBottom = bottomY;
}
// also check x range
const xLeft = gate.x - gate.width/2;
const xRight = gate.x + gate.width/2;
if (px < xLeft || px > xRight) return false;
// vertical check
return (py > minTop && py < maxBottom);
}
function bezierPoint(p0, p1, p2, p3, t) {
const mt = 1 - t;
return mt*mt*mt*p0 + 3*mt*mt*t*p1 + 3*mt*t*t*p2 + t*t*t*p3;
}
// ---------- update physics & logic ----------
function update(dt) {
if (gameOver) return;
// soft‑body inflation: radius oscillates gently
ball.phase += dt * 2.2;
ball.radius = ball.baseRadius + Math.sin(ball.phase) * 2.5;
// vertical control (gravity + thrust)
const gravity = 0.15;
const thrust = 0.30;
ball.vy += gravity * dt * 60;
if (keys['ArrowUp'] || keys['w'] || keys['W']) ball.vy -= thrust * dt * 60;
if (keys['ArrowDown'] || keys['s'] || keys['S']) ball.vy += thrust * dt * 60;
// damping
ball.vy *= 0.98;
ball.y += ball.vy * dt * 60;
ball.x += 0.8; // auto scroll
// boundaries
if (ball.y < 20) { ball.y = 20; ball.vy = 0; }
if (ball.y > H - 20) { ball.y = H - 20; ball.vy = 0; }
// collision with gates (only if not passed yet)
for (let gate of gates) {
if (gate.passed) continue;
// check if ball center is inside the gate opening (no contact)
const inside = pointInGate(ball.x, ball.y, gate, time);
// if ball is within x range and NOT inside opening -> collision
const xLeft = gate.x - gate.width/2 - ball.radius;
const xRight = gate.x + gate.width/2 + ball.radius;
if (ball.x > xLeft && ball.x < xRight) {
// check if center is outside the vertical opening (with radius margin)
const curves = getGateCurves(gate, time);
const topY = bezierPoint(curves.top.start.y, curves.top.cp1.y, curves.top.cp2.y, curves.top.end.y, 0.5);
const bottomY = bezierPoint(curves.bottom.start.y, curves.bottom.cp1.y, curves.bottom.cp2.y, curves.bottom.end.y, 0.5);
// approximate using mid point (more robust)
if (ball.y - ball.radius < topY || ball.y + ball.radius > bottomY) {
gameOver = true;
playSweep(); // low alert
return;
}
}
// check if ball passed the gate (x beyond gate center + width)
if (!gate.passed && ball.x > gate.x + gate.width/2) {
gate.passed = true;
gateCount++;
score++;
playSweep(); // success sweep
}
}
// recycle gates: remove far left, add new right
if (gates.length > 0 && gates[0].x < -200) {
gates.shift();
const lastX = gates.length > 0 ? gates[gates.length-1].x : 300;
const newSeed = gates.length * 0.7 + 1.3;
gates.push(generateGate(lastX + GATE_SPACING, newSeed));
}
// update gate x positions (move left)
for (let gate of gates) {
gate.x -= 0.8; // same as ball speed
}
// time for morphing
time += dt;
}
// ---------- rendering ----------
function draw() {
ctx.clearRect(0, 0, W, H);
// soft pastel background
ctx.fillStyle = '#f2efe9';
ctx.fillRect(0, 0, W, H);
// deep soft shadows for depth
ctx.shadowColor = 'rgba(60, 50, 70, 0.25)';
ctx.shadowBlur = 24;
ctx.shadowOffsetY = 6;
// draw gates (organic bezier)
for (let gate of gates) {
const curves = getGateCurves(gate, time);
const top = curves.top;
const bottom = curves.bottom;
ctx.beginPath();
ctx.moveTo(top.start.x, top.start.y);
ctx.bezierCurveTo(top.cp1.x, top.cp1.y, top.cp2.x, top.cp2.y, top.end.x, top.end.y);
ctx.lineTo(top.end.x, top.end.y - 18);
ctx.lineTo(top.start.x, top.start.y - 18);
ctx.closePath();
ctx.fillStyle = '#cbc3d8';
ctx.fill();
ctx.beginPath();
ctx.moveTo(bottom.start.x, bottom.start.y);
ctx.bezierCurveTo(bottom.cp1.x, bottom.cp1.y, bottom.cp2.x, bottom.cp2.y, bottom.end.x, bottom.end.y);
ctx.lineTo(bottom.end.x, bottom.end.y + 18);
ctx.lineTo(bottom.start.x, bottom.start.y + 18);
ctx.closePath();
ctx.fillStyle = '#b8b0c4';
ctx.fill();
// subtle outline
ctx.shadowBlur = 8;
ctx.strokeStyle = '#a39aad';
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.moveTo(top.start.x, top.start.y);
ctx.bezierCurveTo(top.cp1.x, top.cp1.y, top.cp2.x, top.cp2.y, top.end.x, top.end.y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(bottom.start.x, bottom.start.y);
ctx.bezierCurveTo(bottom.cp1.x, bottom.cp1.y, bottom.cp2.x, bottom.cp2.y, bottom.end.x, bottom.end.y);
ctx.stroke();
}
// draw soft‑body sphere (self‑inflating)
ctx.shadowBlur = 30;
ctx.shadowColor = 'rgba(80, 60, 80, 0.4)';
ctx.beginPath();
ctx.ellipse(ball.x, ball.y, ball.radius, ball.radius * 0.9, 0, 0, Math.PI * 2);
ctx.fillStyle = '#e8e0f0';
ctx.fill();
ctx.shadowBlur = 12;
ctx.strokeStyle = '#b7aec2';
ctx.lineWidth = 2;
ctx.stroke();
// inner highlight
ctx.shadowBlur = 0;
ctx.beginPath();
ctx.ellipse(ball.x - 3, ball.y - 3, ball.radius * 0.3, ball.radius * 0.2, 0, 0, Math.PI * 2);
ctx.fillStyle = '#ffffff90';
ctx.fill();
// reset shadow
ctx.shadowBlur = 0;
ctx.shadowOffsetY = 0;
// UI text
document.getElementById('gateCount').innerText = `gates: ${gateCount}`;
if (gameOver) {
ctx.font = 'bold 28px "Segoe UI", sans-serif';
ctx.fillStyle = '#5e4a5e';
ctx.shadowBlur = 20;
ctx.shadowColor = '#b0a0b0';
ctx.fillText('contact · flux', 240, 280);
ctx.shadowBlur = 0;
}
}
// ---------- input ----------
const keys = {};
window.addEventListener('keydown', (e) => {
keys[e.key] = true;
if (e.key === ' ' || e.key === 'ArrowUp' || e.key === 'ArrowDown') e.preventDefault();
initAudio(); // resume audio on first interaction
});
window.addEventListener('keyup', (e) => {
keys[e.key] = false;
});
// ---------- animation loop ----------
let lastTime = performance.now();
function loop(now) {
const dt = Math.min(0.05, (now - lastTime) / 1000);
lastTime = now;
update(dt);
draw();
requestAnimationFrame(loop);
}
// start
requestAnimationFrame(loop);
})();
</script>
</body>
</html>
Melde dich an, um den vollständigen Prompt zu sehen
Weiter mit:
Mit der Anmeldung akzeptierst du unsere Nutzungsbedingungen und Datenschutz
Verwendung
Dieser Prompt ist für die Verwendung mit coding gedacht. Kopiere den Inhalt oben und füge ihn in dein bevorzugtes KI-Tool ein.
Für beste Ergebnisse passe die Platzhalter (eckige Klammern oder Großbuchstaben) an deine Anforderungen an.
Diskussion
0 Kommentare