軸方向ドリフト:モバイル対応物理迷路ゲーム
フリーのプロンプト百科事典 Wikiprompt より
軸方向ドリフト:モバイル対応物理迷路ゲーム 重力シフト機構、モバイル傾きコントロール、バウハウス視覚スタイルを備えた単一ファイルHTML5迷路ゲームを生成するための包括的なプロンプト。
プロンプト内容保存
🌐
<!doctype html>
<html lang="ja">
<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; }
html, body {
width: 100%; height: 100%;
overflow: hidden;
background: #1a1a1a;
font-family: 'Helvetica Neue', Arial, sans-serif;
touch-action: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
}
#gameCanvas {
display: block;
width: 100%; height: 100%;
background: #f4f4f0;
}
#ui-overlay {
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
pointer-events: none;
z-index: 10;
}
#hud {
position: absolute;
top: 16px; left: 16px;
color: #000;
font-size: 14px;
font-weight: 700;
letter-spacing: 1px;
background: #ffd500;
padding: 8px 12px;
border: 3px solid #000;
box-shadow: 4px 4px 0 #000;
}
#status {
position: absolute;
bottom: 16px; left: 16px;
color: #fff;
font-size: 12px;
font-weight: 700;
background: #e63946;
padding: 6px 10px;
border: 2px solid #000;
box-shadow: 3px 3px 0 #000;
}
#gravity-indicator {
position: absolute;
top: 16px; right: 16px;
width: 60px; height: 60px;
background: #fff;
border: 3px solid #000;
box-shadow: 4px 4px 0 #000;
display: flex;
align-items: center;
justify-content: center;
}
#gravity-arrow {
width: 0; height: 0;
border-left: 12px solid transparent;
border-right: 12px solid transparent;
border-bottom: 24px solid #e63946;
transform-origin: center;
transition: transform 0.1s;
}
#win-screen {
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,0.85);
display: none;
align-items: center;
justify-content: center;
flex-direction: column;
z-index: 20;
color: #fff;
}
#win-screen h1 {
font-size: 48px;
color: #ffd500;
text-shadow: 4px 4px 0 #000;
margin-bottom: 20px;
letter-spacing: 4px;
}
#win-screen p {
font-size: 18px;
margin-bottom: 30px;
color: #fff;
}
#restart-btn {
pointer-events: auto;
background: #e63946;
color: #fff;
border: 3px solid #000;
padding: 12px 24px;
font-size: 16px;
font-weight: 700;
cursor: pointer;
box-shadow: 4px 4px 0 #000;
transition: transform 0.1s;
}
#restart-btn:hover {
transform: translate(2px, 2px);
box-shadow: 2px 2px 0 #000;
}
#restart-btn:active {
transform: translate(4px, 4px);
box-shadow: 0 0 0 #000;
}
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<div id="ui-overlay">
<div id="hud">AXIAL DRIFT</div>
<div id="status">VORTEX: 未到達</div>
<div id="gravity-indicator">
<div id="gravity-arrow"></div>
</div>
</div>
<div id="win-screen">
<h1>VORTEX 到達</h1>
<p>軸のドリフトを制御しました</p>
<button id="restart-btn">再起動</button>
</div>
<script>
(function() {
'use strict';
// ===== キャンバス設定 =====
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const winScreen = document.getElementById('win-screen');
const restartBtn = document.getElementById('restart-btn');
const statusEl = document.getElementById('status');
const gravityArrow = document.getElementById('gravity-arrow');
// ===== 物理定数 =====
const PHYSICS = {
GRAVITY_MAX: 1500, // 最大重力加速度 (px/s^2)
FRICTION: 0.985, // 摩擦減衰係数
ANGULAR_DAMPING: 0.95, // 角速度減衰
MOMENTUM_DAMPING: 0.99, // 運動量減衰
MARBLE_RADIUS: 12,
BOUNCE: 0.4, // 跳ね返り係数
MAX_SPEED: 800
};
// ===== 迷路定義 =====
// 壁は [x1, y1, x2, y2] の線分
const mazeWalls = [
// 外枠
[50, 50, 750, 50],
[750, 50, 750, 550],
[750, 550, 50, 550],
[50, 550, 50, 50],
// 内部構造 (バウハウス風の幾何学)
[150, 50, 150, 200],
[150, 200, 300, 200],
[300, 200, 300, 100],
[300, 100, 450, 100],
[450, 100, 450, 250],
[450, 250, 250, 250],
[250, 250, 250, 400],
[250, 400, 400, 400],
[400, 400, 400, 300],
[400, 300, 550, 300],
[550, 300, 550, 450],
[550, 450, 650, 450],
[650, 450, 650, 150],
[650, 150, 500, 150],
[500, 150, 500, 50],
[100, 350, 100, 500],
[100, 500, 200, 500],
[200, 500, 200, 350],
[200, 350, 350, 350],
[350, 350, 350, 500],
[600, 200, 700, 200],
[700, 200, 700, 350],
[700, 350, 600, 350],
[600, 350, 600, 200]
];
// 動的トラップドア (時間で開閉)
const trapDoors = [
{ x: 200, y: 280, w: 80, h: 12, phase: 0, speed: 0.8, active: true },
{ x: 450, y: 180, w: 80, h: 12, phase: 2, speed: 0.6, active: true },
{ x: 320, y: 450, w: 60, h: 12, phase: 4, speed: 0.7, active: true },
{ x: 580, y: 380, w: 70, h: 12, phase: 1, speed: 0.9, active: true }
];
// 中心のボルテックス (目標)
const vortex = { x: 400, y: 300, radius: 35 };
// ===== マーブル状態 =====
let marble = {
x: 100, y: 100,
vx: 0, vy: 0,
angle: 0,
angularVel: 0,
radius: PHYSICS.MARBLE_RADIUS
};
// ===== 重力ベクトル =====
let gravity = { x: 0, y: 0 }; // 正規化 (-1 〜 1)
let targetGravity = { x: 0, y: 0 };
// ===== 入力状態 =====
let isDragging = false;
let lastMouseX = 0, lastMouseY = 0;
let deviceOrientationSupported = false;
// ===== ゲーム状態 =====
let gameWon = false;
let lastTime = 0;
let elapsedTime = 0;
// ===== リサイズ処理 =====
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// ===== マウス入力 =====
canvas.addEventListener('mousedown', (e) => {
if (gameWon) return;
isDragging = true;
lastMouseX = e.clientX;
lastMouseY = e.clientY;
});
window.addEventListener('mousemove', (e) => {
if (!isDragging || gameWon) return;
const dx = e.clientX - lastMouseX;
const dy = e.clientY - lastMouseY;
// ドラッグ方向と逆に重力を傾ける
targetGravity.x = Math.max(-1, Math.min(1, targetGravity.x - dx / 150));
targetGravity.y = Math.max(-1, Math.min(1, targetGravity.y - dy / 150));
lastMouseX = e.clientX;
lastMouseY = e.clientY;
});
window.addEventListener('mouseup', () => {
isDragging = false;
});
// ===== タッチ入力 =====
canvas.addEventListener('touchstart', (e) => {
if (gameWon) return;
e.preventDefault();
const touch = e.touches[0];
isDragging = true;
lastMouseX = touch.clientX;
lastMouseY = touch.clientY;
});
canvas.addEventListener('touchmove', (e) => {
if (!isDragging || gameWon) return;
e.preventDefault();
const touch = e.touches[0];
const dx = touch.clientX - lastMouseX;
const dy = touch.clientY - lastMouseY;
targetGravity.x = Math.max(-1, Math.min(1, targetGravity.x - dx / 150));
targetGravity.y = Math.max(-1, Math.min(1, targetGravity.y - dy / 150));
lastMouseX = touch.clientX;
lastMouseY = touch.clientY;
});
canvas.addEventListener('touchend', (e) => {
e.preventDefault();
isDragging = false;
});
// ===== デバイスオリエンテーション =====
function handleOrientation(e) {
if (gameWon) return;
if (e.gamma === null || e.beta === null) return;
deviceOrientationSupported = true;
// gamma: 左右 (-90 〜 90), beta: 前後 (-180 〜 180)
const xTilt = Math.max(-1, Math.min(1, e.gamma / 45));
const yTilt = Math.max(-1, Math.min(1, e.beta / 45));
targetGravity.x = xTilt;
targetGravity.y = yTilt;
}
if (window.DeviceOrientationEvent) {
window.addEventListener('deviceorientation', handleOrientation);
}
// ===== 壁衝突判定 =====
function checkWallCollision() {
for (let i = 0; i < mazeWalls.length; i++) {
const wall = mazeWalls[i];
const x1 = wall[0], y1 = wall[1], x2 = wall[2], y2 = wall[3];
// 線分と円の最短距離を計算
const dx = x2 - x1;
const dy = y2 - y1;
const lenSq = dx * dx + dy * dy;
if (lenSq === 0) continue;
let t = ((marble.x - x1) * dx + (marble.y - y1) * dy) / lenSq;
t = Math.max(0, Math.min(1, t));
const closestX = x1 + t * dx;
const closestY = y1 + t * dy;
const distX = marble.x - closestX;
const distY = marble.y - closestY;
const dist = Math.sqrt(distX * distX + distY * distY);
if (dist < marble.radius) {
// 衝突解決
const overlap = marble.radius - dist;
const nx = distX / (dist || 1);
const ny = distY / (dist || 1);
marble.x += nx * overlap;
marble.y += ny * overlap;
// 反射
const dot = marble.vx * nx + marble.vy * ny;
marble.vx -= (1 + PHYSICS.BOUNCE) * dot * nx;
marble.vy -= (1 + PHYSICS.BOUNCE) * dot * ny;
// 摩擦
marble.vx *= 0.8;
marble.vy *= 0.8;
// 角速度更新
marble.angularVel += (marble.vx * ny - marble.vy * nx) * 0.05;
}
}
}
// ===== トラップドア衝突 =====
function checkTrapCollision() {
for (let i = 0; i < trapDoors.length; i++) {
const door = trapDoors[i];
if (!door.active) continue;
// ドアの矩形と円の衝突
const closestX = Math.max(door.x, Math.min(marble.x, door.x + door.w));
const closestY = Math.max(door.y, Math.min(marble.y, door.y + door.h));
const distX = marble.x - closestX;
const distY = marble.y - closestY;
const dist = Math.sqrt(distX * distX + distY * distY);
if (dist < marble.radius) {
// 衝突: ドアを押しのける
const overlap = marble.radius - dist;
const nx = distX / (dist || 1);
const ny = distY / (dist || 1);
marble.x += nx * overlap;
marble.y += ny * overlap;
const dot = marble.vx * nx + marble.vy * ny;
marble.vx -= (1 + PHYSICS.BOUNCE) * dot * nx;
marble.vy -= (1 + PHYSICS.BOUNCE) * dot * ny;
}
}
}
// ===== ボルテックス到達判定 =====
function checkVortexReached() {
const dx = marble.x - vortex.x;
const dy = marble.y - vortex.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < vortex.radius + marble.radius) {
gameWon = true;
winScreen.style.display = 'flex';
statusEl.textContent = 'VORTEX: 到達';
}
}
// ===== 物理更新 =====
function updatePhysics(dt) {
if (gameWon) return;
// 重力をスムーズに補間
gravity.x += (targetGravity.x - gravity.x) * 0.1;
gravity.y += (targetGravity.y - gravity.y) * 0.1;
// 重力加速度を適用
const gx = gravity.x * PHYSICS.GRAVITY_MAX;
const gy = gravity.y * PHYSICS.GRAVITY_MAX;
marble.vx += gx * dt;
marble.vy += gy * dt;
// 最大速度制限
const speed = Math.sqrt(marble.vx * marble.vx + marble.vy * marble.vy);
if (speed > PHYSICS.MAX_SPEED) {
marble.vx = (marble.vx / speed) * PHYSICS.MAX_SPEED;
marble.vy = (marble.vy / speed) * PHYSICS.MAX_SPEED;
}
// 位置更新
marble.x += marble.vx * dt;
marble.y += marble.vy * dt;
// 摩擦と減衰
marble.vx *= PHYSICS.FRICTION;
marble.vy *= PHYSICS.FRICTION;
marble.angularVel *= PHYSICS.ANGULAR_DAMPING;
// 角速度を位置に反映 (転がり)
marble.angle += marble.angularVel * dt;
// 衝突判定
checkWallCollision();
checkTrapCollision();
// トラップドアの開閉更新
for (let i = 0; i < trapDoors.length; i++) {
const door = trapDoors[i];
door.phase += door.speed * dt;
door.active = Math.sin(door.phase) > -0.3;
}
// ボルテックス到達チェック
checkVortexReached();
}
// ===== 描画 =====
function draw() {
const w = canvas.width;
const h = canvas.height;
// 背景
ctx.fillStyle = '#f4f4f0';
ctx.fillRect(0, 0, w, h);
// スケーリング (迷路を中央に配置)
const scale = Math.min(w / 800, h / 600);
const offsetX = (w - 800 * scale) / 2;
const offsetY = (h - 600 * scale) / 2;
ctx.save();
ctx.translate(offsetX, offsetY);
ctx.scale(scale, scale);
// 影の設定
ctx.shadowColor = 'rgba(0,0,0,0.3)';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
// 迷路の床 (バウハウス風の色ブロック)
ctx.fillStyle = '#ffd500';
ctx.fillRect(50, 50, 700, 500);
ctx.fillStyle = '#e63946';
ctx.fillRect(100, 100, 150, 150);
ctx.fillStyle = '#2a9d8f';
ctx.fillRect(450, 350, 200, 150);
ctx.fillStyle = '#264653';
ctx.fillRect(300, 150, 100, 100);
// 影をリセット (壁用)
ctx.shadowColor = 'rgba(0,0,0,0.5)';
ctx.shadowBlur = 8;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
// 壁を描画
ctx.strokeStyle = '#000';
ctx.lineWidth = 6;
ctx.lineCap = 'round';
ctx.beginPath();
for (let i = 0; i < mazeWalls.length; i++) {
const wall = mazeWalls[i];
ctx.moveTo(wall[0], wall[1]);
ctx.lineTo(wall[2], wall[3]);
}
ctx.stroke();
// トラップドアを描画
for (let i = 0; i < trapDoors.length; i++) {
const door = trapDoors[i];
ctx.shadowColor = 'rgba(0,0,0,0.4)';
ctx.shadowBlur = 6;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
if (door.active) {
ctx.fillStyle = '#e63946';
ctx.strokeStyle = '#000';
ctx.lineWidth = 3;
ctx.fillRect(door.x, door.y, door.w, door.h);
ctx.strokeRect(door.x, door.y, door.w, door.h);
} else {
ctx.fillStyle = 'rgba(230, 57, 70, 0.3)';
ctx.strokeStyle = 'rgba(0,0,0,0.3)';
ctx.lineWidth = 2;
ctx.fillRect(door.x, door.y, door.w, door.h);
ctx.strokeRect(door.x, door.y, door.w, door.h);
}
}
// ボルテックスを描画
ctx.shadowColor = 'rgba(42, 157, 143, 0.8)';
ctx.shadowBlur = 20;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
const vortexPulse = 1 + Math.sin(elapsedTime * 3) * 0.1;
ctx.fillStyle = '#2a9d8f';
ctx.beginPath();
ctx.arc(vortex.x, vortex.y, vortex.radius * vortexPulse, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#000';
ctx.lineWidth = 4;
ctx.stroke();
// ボルテックス内側の渦
ctx.fillStyle = '#264653';
ctx.beginPath();
ctx.arc(vortex.x, vortex.y, vortex.radius * 0.5 * vortexPulse, 0, Math.PI * 2);
ctx.fill();
// マーブルを描画
ctx.shadowColor = 'rgba(0,0,0,0.6)';
ctx.shadowBlur = 15;
ctx.shadowOffsetX = 4;
ctx.shadowOffsetY = 4;
ctx.save();
ctx.translate(marble.x, marble.y);
ctx.rotate(marble.angle);
// マーブルのグラデーション
const grad = ctx.createRadialGradient(-3, -3, 2, 0, 0, marble.radius);
grad.addColorStop(0, '#ffffff');
grad.addColorStop(0.3, '#cccccc');
grad.addColorStop(1, '#333333');
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(0, 0, marble.radius, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.stroke();
// マーブルの模様 (回転を可視化)
ctx.strokeStyle = '#e63946';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(marble.radius * 0.7, 0);
ctx.stroke();
ctx.restore();
ctx.restore();
// 重力インジケーター更新
const angle = Math.atan2(gravity.y, gravity.x) + Math.PI / 2;
gravityArrow.style.transform = `rotate(${angle}rad)`;
}
// ===== メインループ =====
function gameLoop(timestamp) {
const dt = Math.min((timestamp - lastTime) / 1000, 0.05);
lastTime = timestamp;
elapsedTime += dt;
updatePhysics(dt);
draw();
requestAnimationFrame(gameLoop);
}
// ===== リスタート =====
function restartGame() {
marble.x = 100;
marble.y = 100;
marble.vx = 0;
marble.vy = 0;
marble.angle = 0;
marble.angularVel = 0;
gravity = { x: 0, y: 0 };
targetGravity = { x: 0, y: 0 };
gameWon = false;
winScreen.style.display = 'none';
statusEl.textContent = 'VORTEX: 未到達';
// トラップドアをリセット
for (let i = 0; i < trapDoors.length; i++) {
trapDoors[i].phase = i * 1.5;
trapDoors[i].active = true;
}
}
restartBtn.addEventListener('click', restartGame);
// ===== 初期化 =====
lastTime = performance.now();
requestAnimationFrame(gameLoop);
})();
</script>
</body>
</html>
ログインして完全なプロンプトを表示
次で続行:
ログインすると、次に同意したことになります: 利用規約 と プライバシーポリシー
使い方
このプロンプトは coding 向けに設計されています。上の内容をコピーして、お好みの AI ツールに貼り付けてください。
最良の結果を得るには、プレースホルダー(角括弧や大文字で示された部分)を具体的な要件に置き換えてください。
ノート
0 件のコメント