重力流变:基于物理的飞行躲避游戏
来自 Wikiprompt,自由的提示词百科全书
重力流变:基于物理的飞行躲避游戏 一个用于生成单文件HTML游戏的详细提示,该游戏包含基于物理的玩法、程序化贝塞尔曲线障碍物、Web Audio合成,以及禅意柔和色调的美学风格。
提示词内容收藏
🌐
<!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: #1e1e2f;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Segoe UI', system-ui, sans-serif;
}
.canvas-wrapper {
background: #2a2a3b;
border-radius: 2rem;
padding: 0.6rem;
box-shadow: 0 20px 40px rgba(0,0,0,0.6);
}
canvas {
display: block;
width: 100%;
max-width: 900px;
height: auto;
border-radius: 1.5rem;
background: #f2ede4; /* pastel paper */
box-shadow: inset 0 0 30px rgba(0,0,0,0.05);
cursor: none;
}
.info {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 0.8rem;
color: #b9b3c7;
font-weight: 300;
letter-spacing: 1px;
padding: 0 0.5rem;
}
.info span {
background: #2e2e42;
padding: 0.3rem 1rem;
border-radius: 30px;
font-size: 0.8rem;
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
}
.status {
color: #cdc6e0;
}
.reset-btn {
background: none;
border: 1px solid #5d5b70;
color: #b9b3c7;
padding: 0.3rem 1.2rem;
border-radius: 30px;
font-size: 0.8rem;
cursor: pointer;
transition: 0.2s;
}
.reset-btn:hover {
background: #3a3a52;
border-color: #8f8aa8;
color: white;
}
</style>
</head>
<body>
<div style="display: flex; flex-direction: column; width: min(900px, 95vw);">
<div class="canvas-wrapper">
<canvas id="gameCanvas" width="900" height="600"></canvas>
</div>
<div class="info">
<span>🌫️ GRAVITY FLUX</span>
<span class="status" id="statusDisplay">◌ drift</span>
<button class="reset-btn" id="resetButton">↻ reset</button>
</div>
</div>
<script>
(function(){
// ---------- CANVAS & CONTEXT ----------
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const statusEl = document.getElementById('statusDisplay');
// ---------- FIXED DIMENSIONS ----------
const W = 900, H = 600;
// ---------- GLOBAL STATE ----------
let time = 0; // global time for sine morph
let gameActive = true;
let score = 0;
let gatePassCooldown = 0; // frames to avoid double count
// ---------- SOFT-BODY SPHERE (player) ----------
const player = {
x: 180, y: 300,
vx: 0, vy: 0,
radius: 18, // base radius
squash: 0.0, // dynamic squash factor (visual)
phase: 0.0
};
// ---------- GATE / OBSTACLE SYSTEM ----------
// we use a set of vertical "gates" (upper and lower bezier walls)
// each gate has a base gap center and gap size, morphing over time
const GATE_COUNT = 5;
const GATE_SPACING = 200; // x distance between gates
const FIRST_GATE_X = 500; // first gate appears ahead
// each gate: { baseY, gapHalfRange, gapSize, speed, phaseOffset }
let gates = [];
function initGates() {
gates = [];
for (let i = 0; i < GATE_COUNT; i++) {
const baseY = 200 + Math.sin(i * 1.7) * 100 + 100; // varied heights
gates.push({
baseY: baseY,
gapHalfRange: 60 + 20 * Math.sin(i * 2.3), // vertical drift range
gapSize: 70 + 25 * Math.sin(i * 1.2), // base gap size
speed: 0.6 + 0.3 * Math.cos(i * 1.1),
phaseOffset: i * 1.8,
});
}
}
initGates();
// ---------- RESET FUNCTION ----------
function resetGame() {
player.x = 180;
player.y = 300;
player.vx = 0;
player.vy = 0;
player.squash = 0;
time = 0;
score = 0;
gameActive = true;
gatePassCooldown = 0;
initGates();
statusEl.innerText = '◌ drift';
}
// ---------- AUDIO (Web Audio API) ----------
let audioCtx = null;
let ambientGain = null;
let ambientOsc = null;
let sweepGain = null;
function initAudio() {
if (audioCtx) return; // already created
try {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
} catch(e) {
return;
}
// ambient pulse: low sine + slow LFO
ambientGain = audioCtx.createGain();
ambientGain.gain.value = 0.04;
ambientGain.connect(audioCtx.destination);
ambientOsc = audioCtx.createOscillator();
ambientOsc.type = 'sine';
ambientOsc.frequency.value = 55; // low drone
const lfo = audioCtx.createOscillator();
lfo.type = 'sine';
lfo.frequency.value = 0.3;
const lfoGain = audioCtx.createGain();
lfoGain.gain.value = 12;
lfo.connect(lfoGain);
lfoGain.connect(ambientOsc.frequency);
lfo.start();
ambientOsc.connect(ambientGain);
ambientOsc.start();
// sweep gain (for gate pass)
sweepGain = audioCtx.createGain();
sweepGain.gain.value = 0;
sweepGain.connect(audioCtx.destination);
}
// reactive acoustic sweep (called on gate pass)
function playSweep() {
if (!audioCtx || !sweepGain) return;
const now = audioCtx.currentTime;
const osc = audioCtx.createOscillator();
osc.type = 'sine';
osc.frequency.setValueAtTime(180, now);
osc.frequency.exponentialRampToValueAtTime(720, now + 0.25);
const gain = audioCtx.createGain();
gain.gain.setValueAtTime(0.12, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.4);
osc.connect(gain);
gain.connect(sweepGain);
osc.start(now);
osc.stop(now + 0.4);
}
// ---------- HELPER: BEZIER POINT (cubic) ----------
function cubicBezier(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;
}
// ---------- GATE GEOMETRY (MORPHING) ----------
// returns upper and lower boundary y at given x (for a gate index)
// uses two cubic beziers: upper wall and lower wall
function getGateBoundaries(gate, gateX, time) {
const t = time * 0.5 + gate.phaseOffset;
// vertical oscillation of the whole gate
const driftY = Math.sin(t * 0.9) * 25;
const centerY = gate.baseY + driftY;
// gap size morphs with sine
const gapHalf = gate.gapSize * 0.5 + Math.sin(t * 1.3) * 12;
// control points for upper/lower walls (smooth curves)
// we create a gentle S‑curve using control points offset
const x0 = gateX - 45;
const x1 = gateX - 15;
const x2 = gateX + 15;
const x3 = gateX + 45;
// upper wall: from left to right, y goes slightly up/down
const upperBase = centerY - gapHalf;
const lowerBase = centerY + gapHalf;
// control points for upper wall (add curvature)
const cp1u = upperBase - 18 + Math.sin(t * 1.1) * 10;
const cp2u = upperBase + 12 + Math.cos(t * 0.7) * 8;
const cp1l = lowerBase + 15 + Math.cos(t * 0.8) * 10;
const cp2l = lowerBase - 10 + Math.sin(t * 1.2) * 8;
// return boundary functions (y at given x)
return {
upperY: (x) => {
if (x <= x0) return upperBase;
if (x >= x3) return upperBase;
const tt = (x - x0) / (x3 - x0);
return cubicBezier(upperBase, cp1u, cp2u, upperBase, tt);
},
lowerY: (x) => {
if (x <= x0) return lowerBase;
if (x >= x3) return lowerBase;
const tt = (x - x0) / (x3 - x0);
return cubicBezier(lowerBase, cp1l, cp2l, lowerBase, tt);
},
x0, x3
};
}
// ---------- COLLISION DETECTION (sphere vs gate) ----------
function checkCollision() {
// player sphere vs each gate
for (let i = 0; i < gates.length; i++) {
const gateX = FIRST_GATE_X + i * GATE_SPACING;
// only check if player is near gate horizontally
if (Math.abs(player.x - gateX) < 50 + player.radius) {
const gate = gates[i];
const bounds = getGateBoundaries(gate, gateX, time);
// sample at player.x
const upper = bounds.upperY(player.x);
const lower = bounds.lowerY(player.x);
// sphere collision: if player.y - radius < upper OR player.y + radius > lower
if (player.y - player.radius < upper || player.y + player.radius > lower) {
return true; // collision
}
}
}
return false;
}
// ---------- GATE PASS DETECTION (score) ----------
function checkGatePass() {
if (gatePassCooldown > 0) return;
for (let i = 0; i < gates.length; i++) {
const gateX = FIRST_GATE_X + i * GATE_SPACING;
// if player just crossed the gate center (within a small window)
if (player.x > gateX - 6 && player.x < gateX + 6) {
// check if inside gap (no collision)
const gate = gates[i];
const bounds = getGateBoundaries(gate, gateX, time);
const upper = bounds.upperY(player.x);
const lower = bounds.lowerY(player.x);
if (player.y - player.radius > upper && player.y + player.radius < lower) {
// passed through!
score++;
gatePassCooldown = 20; // frames
playSweep();
statusEl.innerText = `✦ ${score}`;
break;
}
}
}
}
// ---------- PHYSICS & UPDATE ----------
function update() {
if (!gameActive) return;
// simple gravity + damping
player.vy += 0.12; // gravity
player.vx *= 0.98;
player.vy *= 0.98;
// mouse control (target velocity based on pointer)
// we use mouse position to steer (stored in mouse)
const targetX = mouse.x;
const targetY = mouse.y;
const dx = targetX - player.x;
const dy = targetY - player.y;
// gentle spring force
player.vx += dx * 0.002;
player.vy += dy * 0.002;
// clamp velocity
const maxSpeed = 4.5;
const sp = Math.hypot(player.vx, player.vy);
if (sp > maxSpeed) {
player.vx = (player.vx / sp) * maxSpeed;
player.vy = (player.vy / sp) * maxSpeed;
}
// integrate
player.x += player.vx;
player.y += player.vy;
// soft boundaries (canvas)
if (player.x < 20) { player.x = 20; player.vx *= -0.3; }
if (player.x > W-20) { player.x = W-20; player.vx *= -0.3; }
if (player.y < 20) { player.y = 20; player.vy *= -0.3; }
if (player.y > H-20) { player.y = H-20; player.vy *= -0.3; }
// squash & stretch based on vertical velocity
player.squash = Math.max(-0.15, Math.min(0.15, player.vy * 0.01));
player.phase += 0.05;
// collision detection
if (checkCollision()) {
gameActive = false;
statusEl.innerText = '✖ impact';
// soft reset after short delay? we keep frozen
}
// gate pass scoring
if (gameActive) {
checkGatePass();
if (gatePassCooldown > 0) gatePassCooldown--;
}
// global time advance
time += 0.02;
}
// ---------- RENDERING ----------
function draw() {
ctx.clearRect(0, 0, W, H);
// pastel background
ctx.fillStyle = '#f2ede4';
ctx.fillRect(0, 0, W, H);
// soft shadow layer for depth
ctx.shadowColor = 'rgba(80, 70, 90, 0.25)';
ctx.shadowBlur = 24;
ctx.shadowOffsetY = 6;
// draw all gates (organic bezier walls)
for (let i = 0; i < gates.length; i++) {
const gateX = FIRST_GATE_X + i * GATE_SPACING;
const gate = gates[i];
const bounds = getGateBoundaries(gate, gateX, time);
// draw upper wall (filled shape)
ctx.beginPath();
ctx.moveTo(bounds.x0 - 20, -10);
ctx.lineTo(bounds.x0 - 20, bounds.upperY(bounds.x0 - 20));
// sample bezier
const steps = 20;
for (let s = 0; s <= steps; s++) {
const x = bounds.x0 + (bounds.x3 - bounds.x0) * (s / steps);
const y = bounds.upperY(x);
ctx.lineTo(x, y);
}
ctx.lineTo(bounds.x3 + 20, bounds.upperY(bounds.x3 + 20));
ctx.lineTo(bounds.x3 + 20, -10);
ctx.closePath();
ctx.fillStyle = '#d9d2c0';
ctx.fill();
// lower wall
ctx.beginPath();
ctx.moveTo(bounds.x0 - 20, H+10);
ctx.lineTo(bounds.x0 - 20, bounds.lowerY(bounds.x0 - 20));
for (let s = 0; s <= steps; s++) {
const x = bounds.x0 + (bounds.x3 - bounds.x0) * (s / steps);
const y = bounds.lowerY(x);
ctx.lineTo(x, y);
}
ctx.lineTo(bounds.x3 + 20, bounds.lowerY(bounds.x3 + 20));
ctx.lineTo(bounds.x3 + 20, H+10);
ctx.closePath();
ctx.fillStyle = '#d9d2c0';
ctx.fill();
// subtle inner glow lines
ctx.shadowBlur = 12;
ctx.strokeStyle = '#b8af9a';
ctx.lineWidth = 1.5;
ctx.beginPath();
for (let s = 0; s <= steps; s++) {
const x = bounds.x0 + (bounds.x3 - bounds.x0) * (s / steps);
const y = bounds.upperY(x);
if (s === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.stroke();
ctx.beginPath();
for (let s = 0; s <= steps; s++) {
const x = bounds.x0 + (bounds.x3 - bounds.x0) * (s / steps);
const y = bounds.lowerY(x);
if (s === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.stroke();
}
// reset shadow for player
ctx.shadowBlur = 30;
ctx.shadowColor = 'rgba(120, 100, 130, 0.3)';
// draw soft-body sphere (self-inflating effect)
const rad = player.radius * (1 + 0.1 * Math.sin(time * 2.5));
const squashX = 1 + player.squash;
const squashY = 1 - player.squash;
ctx.save();
ctx.translate(player.x, player.y);
ctx.scale(squashX, squashY);
// gradient for soft volume
const grad = ctx.createRadialGradient(-4, -4, 3, 0, 0, rad+6);
grad.addColorStop(0, '#fff9f0');
grad.addColorStop(0.7, '#e3d7c5');
grad.addColorStop(1, '#b7aa99');
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(0, 0, rad, 0, Math.PI*2);
ctx.fill();
// highlight
ctx.shadowBlur = 8;
ctx.beginPath();
ctx.arc(-3, -3, rad*0.3, 0, Math.PI*2);
ctx.fillStyle = 'rgba(255,255,240,0.6)';
ctx.fill();
ctx.restore();
// reset shadow
ctx.shadowBlur = 0;
ctx.shadowColor = 'transparent';
// draw gate markers (subtle)
ctx.font = '12px monospace';
ctx.fillStyle = '#8f8aa8';
ctx.fillText('GRAVITY FLUX', 30, 40);
}
// ---------- MOUSE TRACKING ----------
const mouse = { x: W/2, y: H/2 };
function onMouseMove(e) {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
mouse.x = (e.clientX - rect.left) * scaleX;
mouse.y = (e.clientY - rect.top) * scaleY;
}
// ---------- ANIMATION LOOP ----------
function animate() {
update();
draw();
requestAnimationFrame(animate);
}
// ---------- EVENT LISTENERS ----------
canvas.addEventListener('mousemove', onMouseMove);
canvas.addEventListener('mouseenter', () => {
initAudio(); // start audio on first interaction
});
document.getElementById('resetButton').addEventListener('click', () => {
resetGame();
initAudio(); // ensure audio context
});
// initial audio init on first click/touch
canvas.addEventListener('click', function initOnce() {
initAudio();
}, { once: true });
// start animation
resetGame(); // ensure clean start
animate();
// also init audio on first keydown (just in case)
window.addEventListener('keydown', () => initAudio(), { once: true });
})();
</script>
</body>
</html>
用法
此提示词专为 coding 设计。复制上方内容并粘贴到你常用的 AI 工具中。
为获得最佳效果,可将占位符(方括号或大写字母标示)替换为你的具体需求。
讨论
0 条评论