僕、GEOMETRY DASHに最近ハマってて、それを作ろうと思ったんですね。(??)
なので、AIにコードを書かせて、それっぽいのを作りました。
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OG Circle Movement</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #87CEEB;
/* スカイブルー */
user-select: none;
}
#player {
width: 50px;
height: 50px;
position: absolute;
left: 100px;
perspective: 600px;
/* 3D効果 */
}
/* 尾 (雫形 90度反時計回り -> 左向き) */
#player::before {
content: '';
position: absolute;
top: 50%;
left: -20px;
width: 40px;
height: 40px;
background: linear-gradient(135deg, #e040fb, transparent);
/* Purple */
border-radius: 0 50% 50% 50%;
/* 左上が尖る */
transform: translateY(-50%) rotate(-45deg);
/* 左向きに */
z-index: -1;
opacity: 0.8;
filter: blur(2px);
}
.tetra {
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
transform-style: preserve-3d;
animation: spin 3s linear infinite;
/* オーラ: drop-shadowで発光表現 */
filter: drop-shadow(0 0 8px #d500f9) drop-shadow(0 0 15px #aa00ff);
}
@keyframes spin {
0% {
transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg);
}
100% {
transform: rotateX(360deg) rotateY(360deg) rotateZ(360deg);
}
}
.face {
position: absolute;
width: 50px;
height: 43.3px;
/* 50 * sqrt(3) / 2 */
background-color: #aa00ff;
/* Purple Base */
opacity: 0.9;
clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
/* 重心基準で回転 */
transform-origin: 50% 66.67%;
top: -28.87px;
/* 43.3 * 2/3 = 28.86... */
left: -25px;
}
.face::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.4), transparent);
}
/*
正四面体ジオメトリ:
r = 10.2px
rotateX(19.47deg) (Positive for gapless)
*/
.face:nth-child(1) {
transform: rotateY(0deg) rotateX(19.47deg) translateZ(10.2px);
background-color: #d500f9;
/* Purple Accent 1 */
}
.face:nth-child(2) {
transform: rotateY(120deg) rotateX(19.47deg) translateZ(10.2px);
background-color: #e040fb;
/* Purple Accent 2 */
}
.face:nth-child(3) {
transform: rotateY(240deg) rotateX(19.47deg) translateZ(10.2px);
background-color: #ea80fc;
/* Purple Accent 3 */
}
.face:nth-child(4) {
transform: rotateX(90deg) translateZ(10.2px);
background-color: #4a148c;
/* Dark Purple */
}
</style>
</head>
<body>
<div id="player">
<div class="tetra">
<div class="face"></div>
<div class="face"></div>
<div class="face"></div>
<div class="face"></div>
</div>
</div>
<script>
const player = document.getElementById('player');
// 設定
const speed = 8; // 移動速度
const radius = 25; // 半径
const obstacleWidth = 30;
let obstacleSpeed = 5; // 時間とともに速くする
// 状態
let isMovingUp = false;
let y = window.innerHeight - (radius * 2);
let frameCount = 0;
let isGameOver = false;
const obstacles = [];
// マウス・タッチイベント
const startMovingUp = () => { isMovingUp = true; };
const stopMovingUp = () => { isMovingUp = false; };
window.addEventListener('mousedown', startMovingUp);
window.addEventListener('mouseup', stopMovingUp);
window.addEventListener('touchstart', (e) => { e.preventDefault(); startMovingUp(); }, { passive: false });
window.addEventListener('touchend', stopMovingUp);
function createObstacle() {
// タイプ決定 (0: 通常壁, 1: 浮遊ブロック, 2: 動く壁)
const type = Math.floor(Math.random() * 3);
const obstacle = document.createElement('div');
obstacle.style.position = 'absolute';
obstacle.style.backgroundColor = '#ff5722';
obstacle.style.width = obstacleWidth + 'px';
obstacle.style.left = window.innerWidth + 'px';
let height, isTop, yPos;
const moveData = { type: type, dir: 1, speed: 2 }; // 移動用
if (type === 0) {
// 通常壁
height = Math.random() * (window.innerHeight - 250) + 50;
isTop = Math.random() > 0.5;
yPos = isTop ? 0 : window.innerHeight - height;
obstacle.style.height = height + 'px';
obstacle.style.top = yPos + 'px';
} else if (type === 1) {
// 浮遊ブロック
height = Math.random() * 100 + 50;
yPos = Math.random() * (window.innerHeight - 100 - height) + 50;
obstacle.style.height = height + 'px';
obstacle.style.top = yPos + 'px';
} else if (type === 2) {
// 動く壁
height = Math.random() * 150 + 100;
yPos = Math.random() * (window.innerHeight - height);
obstacle.style.height = height + 'px';
obstacle.style.top = yPos + 'px';
obstacle.style.backgroundColor = '#ff1744';
}
document.body.appendChild(obstacle);
obstacles.push({
element: obstacle,
x: window.innerWidth,
width: obstacleWidth,
height: height,
y: yPos,
moveData: moveData
});
}
function update() {
if (isGameOver) return;
// プレイヤー移動
if (isMovingUp) {
y -= speed;
} else {
y += speed;
}
// 境界チェック
const maxTop = 0;
const maxBottom = window.innerHeight - (radius * 2);
if (y < maxTop) {
y = maxTop;
} else if (y > maxBottom) {
y = maxBottom;
}
player.style.top = y + 'px';
// 障害物管理
frameCount++;
// 時間経過で加速 (300フレーム=約5秒ごとに+0.2)
if (frameCount % 300 === 0) {
obstacleSpeed += 0.2;
}
if (frameCount % 100 === 0) {
createObstacle();
}
// 障害物移動と衝突判定
for (let i = obstacles.length - 1; i >= 0; i--) {
const obs = obstacles[i];
obs.x -= obstacleSpeed;
// 動く壁の処理
if (obs.moveData && obs.moveData.type === 2) {
obs.y += obs.moveData.speed * obs.moveData.dir;
if (obs.y <= 0 || obs.y + obs.height >= window.innerHeight) {
obs.moveData.dir *= -1;
}
obs.element.style.top = obs.y + 'px';
}
obs.element.style.left = obs.x + 'px';
// 出たら削除
if (obs.x + obs.width < 0) {
obs.element.remove();
obstacles.splice(i, 1);
continue;
}
// 衝突判定 (簡易矩形)
// プレイヤーの当たり判定を少し小さくして遊びやすくする
// 正四面体は50x50だが、見た目は中心寄りなので、40x40くらいで判定
const playerRect = {
x: 100 + 5,
y: y + 5,
width: 40,
height: 40
};
if (
playerRect.x < obs.x + obs.width &&
playerRect.x + playerRect.width > obs.x &&
playerRect.y < obs.y + obs.height &&
playerRect.y + playerRect.height > obs.y
) {
gameOver();
}
}
requestAnimationFrame(update);
}
function gameOver() {
isGameOver = true;
alert('GAME OVER');
location.reload();
}
// 初期化
update();
window.addEventListener('resize', () => {
// リサイズ対応(今回は省略)
});
</script>
</body>
</html>
ただ、ちゃんと機能するか分からないんですけどね。
とりあえず今回は、障害物をランダムに設定しました。