Axiale Drift: Mobil-responsives Physik-Labyrinth-Spiel
Von Wikiprompt, der freien Prompt-Enzyklopädie
Axiale Drift: Mobil-responsives Physik-Labyrinth-Spiel Ein umfassender Prompt zur Erzeugung eines einseitigen HTML5-Labyrinthspiels mit Gravitationswechsel-Mechanik, mobilen Neigungssteuerungen und Bauhaus-Visualstil.
Prompt-InhaltSpeichern
🌐
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Axial Drift</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-user-select: none;
user-select: none;
touch-action: none;
}
body {
background: #1a1a1a;
overflow: hidden;
font-family: 'Arial Black', 'Helvetica Neue', sans-serif;
}
#gameCanvas {
display: block;
width: 100vw;
height: 100vh;
background: #f4f4f4;
}
#ui-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
}
#title {
position: absolute;
top: 20px;
left: 20px;
color: #ff3b30;
font-size: 28px;
font-weight: 900;
letter-spacing: 4px;
text-shadow: 3px 3px 0 #000;
text-transform: uppercase;
}
#status {
position: absolute;
bottom: 20px;
left: 20px;
color: #000;
font-size: 14px;
font-weight: bold;
background: #ffcc00;
padding: 8px 12px;
border: 3px solid #000;
box-shadow: 4px 4px 0 #000;
}
#controls-hint {
position: absolute;
bottom: 20px;
right: 20px;
color: #fff;
font-size: 12px;
background: #007aff;
padding: 6px 10px;
border: 3px solid #000;
box-shadow: 4px 4px 0 #000;
text-align: right;
}
@media (max-width: 768px) {
#title { font-size: 20px; top: 10px; left: 10px; }
#status { font-size: 11px; bottom: 10px; left: 10px; }
#controls-hint { font-size: 10px; bottom: 10px; right: 10px; max-width: 150px; }
}
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<div id="ui-overlay">
<div id="title">AXIAL DRIFT</div>
<div id="status">GRAVITY: X:0.0 Y:0.0</div>
<div id="controls-hint">DRAG / TILT TO SHIFT GRAVITY</div>
</div>
<script>
(function() {
'use strict';
// ========== CANVAS SETUP ==========
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const statusEl = document.getElementById('status');
let W = window.innerWidth;
let H = window.innerHeight;
let DPR = Math.min(window.devicePixelRatio || 1, 2);
function resizeCanvas() {
W = window.innerWidth;
H = window.innerHeight;
DPR = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = W * DPR;
canvas.height = H * DPR;
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
buildMaze();
}
// ========== MAZE DEFINITION ==========
// Grid-based maze: 0 = empty, 1 = wall, 2 = trapdoor (dynamic), 3 = vortex (goal)
const GRID_SIZE = 20; // base grid cells
let mazeGrid = [];
let cellSize = 0;
let mazeOffsetX = 0;
let mazeOffsetY = 0;
let vortexPos = { x: 0, y: 0 };
let trapdoors = [];
// Marble physics
let marble = {
x: 0, y: 0,
vx: 0, vy: 0,
radius: 0,
onGround: false
};
// Gravity vector (normalized, -1 to 1)
let gravity = { x: 0, y: 0 };
let targetGravity = { x: 0, y: 0 };
// Game state
let gameWon = false;
let gameLost = false;
let winTimer = 0;
// ========== MAZE GENERATION ==========
function buildMaze() {
// Calculate cell size based on viewport
const cols = Math.floor(W / 40);
const rows = Math.floor(H / 40);
const gridCols = Math.max(12, Math.min(20, cols));
const gridRows = Math.max(12, Math.min(20, rows));
cellSize = Math.min(W / gridCols, H / gridRows);
mazeOffsetX = (W - cellSize * gridCols) / 2;
mazeOffsetY = (H - cellSize * gridRows) / 2;
// Generate maze grid (simple procedural generation)
mazeGrid = [];
for (let r = 0; r < gridRows; r++) {
mazeGrid[r] = [];
for (let c = 0; c < gridCols; c++) {
// Border walls
if (r === 0 || c === 0 || r === gridRows - 1 || c === gridCols - 1) {
mazeGrid[r][c] = 1;
} else {
// Random walls with some structure
const rand = Math.random();
if (rand < 0.25) {
mazeGrid[r][c] = 1;
} else {
mazeGrid[r][c] = 0;
}
}
}
}
// Clear area around start (top-left) and vortex (center)
const startR = 1, startC = 1;
const vortexR = Math.floor(gridRows / 2);
const vortexC = Math.floor(gridCols / 2);
// Clear start area
for (let r = startR; r <= startR + 1; r++) {
for (let c = startC; c <= startC + 1; c++) {
if (r < gridRows - 1 && c < gridCols - 1) mazeGrid[r][c] = 0;
}
}
// Clear vortex area
for (let r = vortexR - 1; r <= vortexR + 1; r++) {
for (let c = vortexC - 1; c <= vortexC + 1; c++) {
if (r > 0 && c > 0 && r < gridRows - 1 && c < gridCols - 1) mazeGrid[r][c] = 0;
}
}
// Set vortex position
vortexPos = {
x: mazeOffsetX + (vortexC + 0.5) * cellSize,
y: mazeOffsetY + (vortexR + 0.5) * cellSize
};
// Place trapdoors (dynamic elements)
trapdoors = [];
const numTrapdoors = Math.floor(gridCols * gridRows * 0.05);
for (let i = 0; i < numTrapdoors; i++) {
const r = Math.floor(Math.random() * (gridRows - 2)) + 1;
const c = Math.floor(Math.random() * (gridCols - 2)) + 1;
if (mazeGrid[r][c] === 0 && !(r === startR && c === startC) && !(r === vortexR && c === vortexC)) {
mazeGrid[r][c] = 2;
trapdoors.push({
r: r,
c: c,
x: mazeOffsetX + (c + 0.5) * cellSize,
y: mazeOffsetY + (r + 0.5) * cellSize,
state: 'closed', // 'closed', 'open', 'closing'
timer: 0,
openTime: 0
});
}
}
// Place marble at start
marble.x = mazeOffsetX + (startC + 0.5) * cellSize;
marble.y = mazeOffsetY + (startR + 0.5) * cellSize;
marble.radius = cellSize * 0.3;
marble.vx = 0;
marble.vy = 0;
gameWon = false;
gameLost = false;
winTimer = 0;
}
// ========== PHYSICS ==========
const FRICTION = 0.98;
const DAMPING = 0.95;
const MAX_SPEED = 8;
const GRAVITY_FORCE = 0.5;
function updatePhysics(dt) {
if (gameWon || gameLost) return;
// Smooth gravity transition
gravity.x += (targetGravity.x - gravity.x) * 0.1;
gravity.y += (targetGravity.y - gravity.y) * 0.1;
// Apply gravity
marble.vx += gravity.x * GRAVITY_FORCE * dt * 60;
marble.vy += gravity.y * GRAVITY_FORCE * dt * 60;
// Apply friction and damping
marble.vx *= FRICTION;
marble.vy *= FRICTION;
marble.vx *= DAMPING;
marble.vy *= DAMPING;
// Clamp speed
const speed = Math.sqrt(marble.vx * marble.vx + marble.vy * marble.vy);
if (speed > MAX_SPEED) {
marble.vx = (marble.vx / speed) * MAX_SPEED;
marble.vy = (marble.vy / speed) * MAX_SPEED;
}
// Move marble
marble.x += marble.vx * dt * 60;
marble.y += marble.vy * dt * 60;
// Collision detection with maze walls
const gridCols = mazeGrid[0].length;
const gridRows = mazeGrid.length;
// Check collision with grid cells
const minCol = Math.floor((marble.x - marble.radius - mazeOffsetX) / cellSize);
const maxCol = Math.floor((marble.x + marble.radius - mazeOffsetX) / cellSize);
const minRow = Math.floor((marble.y - marble.radius - mazeOffsetY) / cellSize);
const maxRow = Math.floor((marble.y + marble.radius - mazeOffsetY) / cellSize);
for (let r = Math.max(0, minRow); r <= Math.min(gridRows - 1, maxRow); r++) {
for (let c = Math.max(0, minCol); c <= Math.min(gridCols - 1, maxCol); c++) {
if (mazeGrid[r][c] === 1) {
// Wall collision - resolve
const wallLeft = mazeOffsetX + c * cellSize;
const wallRight = wallLeft + cellSize;
const wallTop = mazeOffsetY + r * cellSize;
const wallBottom = wallTop + cellSize;
// Find closest point on wall to marble center
const closestX = Math.max(wallLeft, Math.min(marble.x, wallRight));
const closestY = Math.max(wallTop, Math.min(marble.y, wallBottom));
const dx = marble.x - closestX;
const dy = marble.y - closestY;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < marble.radius) {
// Push marble out
if (dist === 0) {
// Marble center is inside wall - push out based on penetration
const overlapX = Math.min(marble.x - wallLeft, wallRight - marble.x);
const overlapY = Math.min(marble.y - wallTop, wallBottom - marble.y);
if (overlapX < overlapY) {
marble.x = (marble.x < wallLeft + cellSize / 2) ? wallLeft - marble.radius : wallRight + marble.radius;
} else {
marble.y = (marble.y < wallTop + cellSize / 2) ? wallTop - marble.radius : wallBottom + marble.radius;
}
} else {
const pushX = (dx / dist) * (marble.radius - dist);
const pushY = (dy / dist) * (marble.radius - dist);
marble.x += pushX;
marble.y += pushY;
}
// Reflect velocity
const normalX = dx / (dist || 1);
const normalY = dy / (dist || 1);
const dot = marble.vx * normalX + marble.vy * normalY;
if (dot < 0) {
marble.vx -= 2 * dot * normalX;
marble.vy -= 2 * dot * normalY;
marble.vx *= 0.8;
marble.vy *= 0.8;
}
}
} else if (mazeGrid[r][c] === 2) {
// Trapdoor - check if open
const trap = trapdoors.find(t => t.r === r && t.c === c);
if (trap && trap.state === 'open') {
// Check if marble is over the trapdoor
const trapLeft = mazeOffsetX + c * cellSize;
const trapRight = trapLeft + cellSize;
const trapTop = mazeOffsetY + r * cellSize;
const trapBottom = trapTop + cellSize;
if (marble.x > trapLeft && marble.x < trapRight && marble.y > trapTop && marble.y < trapBottom) {
// Marble falls through - game over
gameLost = true;
return;
}
}
}
}
}
// Check vortex collision (win condition)
const dx = marble.x - vortexPos.x;
const dy = marble.y - vortexPos.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < marble.radius + cellSize * 0.3) {
gameWon = true;
winTimer = 0;
}
// Update trapdoors
for (const trap of trapdoors) {
if (trap.state === 'closed') {
trap.timer += dt;
if (trap.timer > 3) {
trap.state = 'open';
trap.openTime = 0;
}
} else if (trap.state === 'open') {
trap.openTime += dt;
if (trap.openTime > 1.5) {
trap.state = 'closing';
trap.timer = 0;
}
} else if (trap.state === 'closing') {
trap.timer += dt;
if (trap.timer > 0.5) {
trap.state = 'closed';
trap.timer = 0;
}
}
}
}
// ========== RENDERING ==========
function drawMaze() {
ctx.clearRect(0, 0, W, H);
// Background
ctx.fillStyle = '#f4f4f4';
ctx.fillRect(0, 0, W, H);
// Draw grid cells
const gridCols = mazeGrid[0].length;
const gridRows = mazeGrid.length;
for (let r = 0; r < gridRows; r++) {
for (let c = 0; c < gridCols; c++) {
const x = mazeOffsetX + c * cellSize;
const y = mazeOffsetY + r * cellSize;
if (mazeGrid[r][c] === 1) {
// Wall - Bauhaus style: bold primary color blocks
ctx.fillStyle = '#007aff'; // Blue
ctx.fillRect(x, y, cellSize, cellSize);
ctx.strokeStyle = '#000';
ctx.lineWidth = 3;
ctx.strokeRect(x, y, cellSize, cellSize);
// Drop shadow
ctx.shadowColor = 'rgba(0,0,0,0.3)';
ctx.shadowBlur = 8;
ctx.shadowOffsetX = 4;
ctx.shadowOffsetY = 4;
ctx.fillStyle = '#007aff';
ctx.fillRect(x, y, cellSize, cellSize);
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
// Re-stroke for clean edges
ctx.strokeStyle = '#000';
ctx.lineWidth = 3;
ctx.strokeRect(x, y, cellSize, cellSize);
} else if (mazeGrid[r][c] === 2) {
// Trapdoor
const trap = trapdoors.find(t => t.r === r && t.c === c);
if (trap) {
if (trap.state === 'closed') {
ctx.fillStyle = '#ff3b30'; // Red
ctx.fillRect(x, y, cellSize, cellSize);
ctx.strokeStyle = '#000';
ctx.lineWidth = 3;
ctx.strokeRect(x, y, cellSize, cellSize);
// X pattern
ctx.beginPath();
ctx.moveTo(x + 5, y + 5);
ctx.lineTo(x + cellSize - 5, y + cellSize - 5);
ctx.moveTo(x + cellSize - 5, y + 5);
ctx.lineTo(x + 5, y + cellSize - 5);
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.stroke();
} else if (trap.state === 'open') {
// Open - dark hole
ctx.fillStyle = '#1a1a1a';
ctx.fillRect(x, y, cellSize, cellSize);
ctx.strokeStyle = '#000';
ctx.lineWidth = 3;
ctx.strokeRect(x, y, cellSize, cellSize);
} else {
// Closing - yellow warning
ctx.fillStyle = '#ffcc00';
ctx.fillRect(x, y, cellSize, cellSize);
ctx.strokeStyle = '#000';
ctx.lineWidth = 3;
ctx.strokeRect(x, y, cellSize, cellSize);
}
}
}
}
}
// Draw vortex (goal)
const vortexRadius = cellSize * 0.4;
const pulse = 0.5 + 0.5 * Math.sin(Date.now() * 0.005);
// Outer glow
ctx.shadowColor = '#ffcc00';
ctx.shadowBlur = 20 + pulse * 20;
ctx.fillStyle = '#ffcc00';
ctx.beginPath();
ctx.arc(vortexPos.x, vortexPos.y, vortexRadius, 0, Math.PI * 2);
ctx.fill();
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
// Inner circle
ctx.fillStyle = '#ff3b30';
ctx.beginPath();
ctx.arc(vortexPos.x, vortexPos.y, vortexRadius * 0.6, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#000';
ctx.lineWidth = 3;
ctx.stroke();
// Spiral effect
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
for (let i = 0; i < 3; i++) {
ctx.beginPath();
ctx.arc(vortexPos.x, vortexPos.y, vortexRadius * (0.2 + i * 0.2), i * 2, i * 2 + 1.5);
ctx.stroke();
}
// Draw marble
ctx.shadowColor = 'rgba(0,0,0,0.4)';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
// Marble gradient
const gradient = ctx.createRadialGradient(
marble.x - marble.radius * 0.3,
marble.y - marble.radius * 0.3,
marble.radius * 0.1,
marble.x,
marble.y,
marble.radius
);
gradient.addColorStop(0, '#ffffff');
gradient.addColorStop(0.5, '#cccccc');
gradient.addColorStop(1, '#666666');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(marble.x, marble.y, marble.radius, 0, Math.PI * 2);
ctx.fill();
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.stroke();
// Marble highlight
ctx.fillStyle = 'rgba(255,255,255,0.6)';
ctx.beginPath();
ctx.arc(marble.x - marble.radius * 0.3, marble.y - marble.radius * 0.3, marble.radius * 0.2, 0, Math.PI * 2);
ctx.fill();
// Draw gravity vector indicator
const indicatorX = W - 60;
const indicatorY = H - 60;
ctx.strokeStyle = '#000';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(indicatorX, indicatorY, 30, 0, Math.PI * 2);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(indicatorX, indicatorY);
ctx.lineTo(indicatorX + gravity.x * 25, indicatorY + gravity.y * 25);
ctx.strokeStyle = '#ff3b30';
ctx.lineWidth = 4;
ctx.stroke();
// Arrowhead
const angle = Math.atan2(gravity.y, gravity.x);
const arrowLen = 10;
ctx.beginPath();
ctx.moveTo(indicatorX + gravity.x * 25, indicatorY + gravity.y * 25);
ctx.lineTo(indicatorX + gravity.x * 25 - arrowLen * Math.cos(angle - 0.4), indicatorY + gravity.y * 25 - arrowLen * Math.sin(angle - 0.4));
ctx.moveTo(indicatorX + gravity.x * 25, indicatorY + gravity.y * 25);
ctx.lineTo(indicatorX + gravity.x * 25 - arrowLen * Math.cos(angle + 0.4), indicatorY + gravity.y * 25 - arrowLen * Math.sin(angle + 0.4));
ctx.stroke();
// Update status
statusEl.textContent = 'GRAVITY: X:' + gravity.x.toFixed(1) + ' Y:' + gravity.y.toFixed(1);
// Win/Lose overlays
if (gameWon) {
ctx.fillStyle = 'rgba(0,0,0,0.6)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#ffcc00';
ctx.font = 'bold 48px Arial Black';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.shadowColor = '#000';
ctx.shadowBlur = 10;
ctx.fillText('VORTEX CAPTURED!', W / 2, H / 2 - 30);
ctx.font = '20px Arial';
ctx.fillStyle = '#fff';
ctx.fillText('Click / tap to restart', W / 2, H / 2 + 30);
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
} else if (gameLost) {
ctx.fillStyle = 'rgba(0,0,0,0.6)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#ff3b30';
ctx.font = 'bold 48px Arial Black';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.shadowColor = '#000';
ctx.shadowBlur = 10;
ctx.fillText('TRAPPED!', W / 2, H / 2 - 30);
ctx.font = '20px Arial';
ctx.fillStyle = '#fff';
ctx.fillText('Click / tap to restart', W / 2, H / 2 + 30);
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
}
}
// ========== INPUT HANDLING ==========
let isDragging = false;
let lastMouseX = 0;
let lastMouseY = 0;
function handleMouseDown(e) {
if (gameWon || gameLost) {
buildMaze();
return;
}
isDragging = true;
lastMouseX = e.clientX;
lastMouseY = e.clientY;
}
function handleMouseMove(e) {
if (!isDragging) return;
const dx = e.clientX - lastMouseX;
const dy = e.clientY - lastMouseY;
lastMouseX = e.clientX;
lastMouseY = e.clientY;
// Map mouse movement to gravity
targetGravity.x = Math.max(-1, Math.min(1, targetGravity.x + dx * 0.02));
targetGravity.y = Math.max(-1, Math.min(1, targetGravity.y + dy * 0.02));
}
function handleMouseUp() {
isDragging = false;
}
// Touch events
function handleTouchStart(e) {
e.preventDefault();
const touch = e.touches[0];
if (gameWon || gameLost) {
buildMaze();
return;
}
isDragging = true;
lastMouseX = touch.clientX;
lastMouseY = touch.clientY;
}
function handleTouchMove(e) {
e.preventDefault();
if (!isDragging) return;
const touch = e.touches[0];
const dx = touch.clientX - lastMouseX;
const dy = touch.clientY - lastMouseY;
lastMouseX = touch.clientX;
lastMouseY = touch.clientY;
targetGravity.x = Math.max(-1, Math.min(1, targetGravity.x + dx * 0.02));
targetGravity.y = Math.max(-1, Math.min(1, targetGravity.y + dy * 0.02));
}
function handleTouchEnd(e) {
e.preventDefault();
isDragging = false;
}
// Device orientation
function handleOrientation(e) {
if (e.gamma !== null && e.beta !== null) {
// gamma: left/right tilt (-90 to 90)
// beta: front/back tilt (-180 to 180)
const x = Math.max(-1, Math.min(1, e.gamma / 45));
const y = Math.max(-1, Math.min(1, e.beta / 45));
targetGravity.x = x;
targetGravity.y = y;
}
}
// ========== GAME LOOP ==========
let lastTime = 0;
function gameLoop(timestamp) {
const dt = Math.min((timestamp - lastTime) / 1000, 0.05);
lastTime = timestamp;
updatePhysics(dt);
drawMaze();
requestAnimationFrame(gameLoop);
}
// ========== EVENT LISTENERS ==========
window.addEventListener('resize', resizeCanvas);
canvas.addEventListener('mousedown', handleMouseDown);
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
canvas.addEventListener('touchstart', handleTouchStart, { passive: false });
canvas.addEventListener('touchmove', handleTouchMove, { passive: false });
canvas.addEventListener('touchend', handleTouchEnd, { passive: false });
// Request device orientation permission for iOS
if (typeof DeviceOrientationEvent !== 'undefined' && typeof DeviceOrientationEvent.requestPermission === 'function') {
DeviceOrientationEvent.requestPermission()
.then(state => {
if (state === 'granted') {
window.addEventListener('deviceorientation', handleOrientation);
}
})
.catch(err => console.log('Orientation permission denied'));
} else {
window.addEventListener('deviceorientation', handleOrientation);
}
// ========== INIT ==========
resizeCanvas();
requestAnimationFrame(gameLoop);
})();
</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