<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Frakktus - 霓虹几何节奏游戏</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
neon: {
pink: '#ff2a6d',
blue: '#00f2ff',
purple: '#a855f7',
green: '#10b981'
},
dark: {
base: '#0f172a',
layer: '#1e293b'
}
},
fontFamily: {
neon: ['Orbitron', 'sans-serif']
},
animation: {
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
'float': 'float 6s ease-in-out infinite',
'glow': 'glow 2s ease-in-out infinite alternate'
},
keyframes: {
float: {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-20px)' }
},
glow: {
'0%': { boxShadow: '0 0 5px rgba(255, 42, 109, 0.7)' },
'100%': { boxShadow: '0 0 20px rgba(255, 42, 109, 0.9), 0 0 30px rgba(255, 42, 109, 0.7)' }
}
}
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.text-shadow-neon {
text-shadow: 0 0 8px currentColor, 0 0 12px currentColor;
}
.shape-border {
position: relative;
}
.shape-border::before {
content: '';
position: absolute;
inset: -2px;
border-radius: inherit;
background: linear-gradient(45deg, #ff2a6d, #00f2ff, #a855f7, #10b981);
z-index: -1;
animation: glow 3s linear infinite;
background-size: 400% 400%;
animation: gradientMove 8s ease infinite;
}
@keyframes gradientMove {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.grid-pattern {
background-size: 40px 40px;
background-image:
linear-gradient(to right, rgba(255,255,255,0.05) 1px, transparent 1px),
linear-gradient(to bottom, rgba(255,255,255,0.05) 1px, transparent 1px);
}
}
</style>
</head>
<body class="bg-dark-base grid-pattern min-h-screen font-sans text-white overflow-hidden">
<!-- 背景几何图形 -->
<div class="fixed inset-0 pointer-events-none">
<div class="absolute top-1/4 left-1/3 w-64 h-64 rounded-full bg-neon-pink/20 blur-3xl animate-pulse-slow"></div>
<div class="absolute bottom-1/3 right-1/4 w-80 h-80 rounded-full bg-neon-blue/20 blur-3xl animate-pulse-slow" style="animation-delay: 1s;"></div>
<div class="absolute top-1/2 left-1/5 w-72 h-72 rounded-full bg-neon-purple/20 blur-3xl animate-pulse-slow" style="animation-delay: 2s;"></div>
<!-- 动态几何边框 -->
<div class="absolute top-20 left-10 w-20 h-20 rounded-lg shape-border animate-float" style="animation-delay: 0s;"></div>
<div class="absolute bottom-20 right-10 w-32 h-32 rounded-lg shape-border animate-float" style="animation-delay: 1s;"></div>
<div class="absolute top-1/3 right-1/3 w-28 h-28 rounded-lg shape-border animate-float" style="animation-delay: 2s;"></div>
</div>
<div class="relative z-10 flex flex-col items-center justify-between min-h-screen p-4">
<!-- 游戏标题 -->
<header class="text-center mt-8">
<h1 class="text-[clamp(2rem,8vw,4rem)] font-neon text-neon-pink text-shadow-neon mb-2 tracking-wider">FRAKKTUS</h1>
<p class="text-neon-blue/80 text-[clamp(1rem,3vw,1.2rem)]">霓虹几何 · 节奏挑战</p>
</header>
<!-- 游戏主界面 -->
<main class="flex-1 w-full max-w-4xl flex flex-col items-center justify-center">
<!-- 分数显示 -->
<div class="flex items-center justify-between w-full max-w-md mb-8">
<div class="bg-dark-layer/80 backdrop-blur-sm p-3 rounded-lg border border-neon-pink/30">
<p class="text-neon-pink text-sm">分数</p>
<p id="score" class="text-2xl font-bold text-neon-pink">0</p>
</div>
<div class="bg-dark-layer/80 backdrop-blur-sm p-3 rounded-lg border border-neon-blue/30">
<p class="text-neon-blue text-sm">连击</p>
<p id="combo" class="text-2xl font-bold text-neon-blue">0</p>
</div>
</div>
<!-- 游戏区域 -->
<div id="gameArea" class="w-full max-w-md aspect-square relative">
<div class="absolute inset-0 bg-dark-layer/60 backdrop-blur-sm rounded-2xl border-2 border-gradient-to-r from-neon-pink to-neon-blue"></div>
<!-- 中央节奏指示器 -->
<div id="centerIndicator" class="absolute inset-1/4 flex items-center justify-center">
<div class="w-full h-full rounded-full bg-neon-pink/20 border-4 border-neon-pink/50 animate-pulse-slow"></div>
<div class="w-1/2 h-1/2 rounded-full bg-dark-base border-2 border-neon-pink"></div>
</div>
<!-- 提示信息 -->
<div id="gameMessage" class="absolute inset-0 flex flex-col items-center justify-center text-center">
<p class="text-2xl mb-4 text-neon-blue">点击开始游戏</p>
<button id="startButton" class="bg-neon-pink hover:bg-neon-pink/80 text-white font-bold py-3 px-8 rounded-full transition-all duration-300 transform hover:scale-105 shape-border">
开始游戏
</button>
</div>
</div>
<!-- 难度选择 -->
<div class="w-full max-w-md mt-8 grid grid-cols-3 gap-4">
<button id="easyMode" class="difficulty-btn bg-dark-layer/80 hover:bg-neon-green/20 border border-neon-green/30 py-2 rounded-lg transition-all duration-300">
<i class="fa fa-smile-o mr-2"></i>简单
</button>
<button id="mediumMode" class="difficulty-btn bg-dark-layer/80 hover:bg-neon-blue/20 border border-neon-blue/30 py-2 rounded-lg transition-all duration-300">
<i class="fa fa-meh-o mr-2"></i>中等
</button>
<button id="hardMode" class="difficulty-btn bg-dark-layer/80 hover:bg-neon-pink/20 border border-neon-pink/30 py-2 rounded-lg transition-all duration-300">
<i class="fa fa-frown-o mr-2"></i>困难
</button>
</div>
</main>
<!-- 游戏控制与信息 -->
<footer class="w-full max-w-4xl flex flex-col md:flex-row justify-between items-center mt-8 mb-4">
<div class="flex items-center mb-4 md:mb-0">
<i class="fa fa-volume-up text-neon-purple mr-2"></i>
<input type="range" id="volumeControl" min="0" max="1" step="0.1" value="0.7" class="w-32 h-2 bg-dark-layer rounded-lg appearance-none cursor-pointer">
</div>
<div class="text-center text-neon-purple/70 text-sm">
<p>按节奏点击出现的几何图形</p>
</div>
<div class="flex items-center">
<button id="muteButton" class="p-2 rounded-full bg-dark-layer/80 hover:bg-neon-purple/20 border border-neon-purple/30 transition-all duration-300">
<i class="fa fa-volume-up"></i>
</button>
</div>
</footer>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// 游戏状态
const gameState = {
isPlaying: false,
score: 0,
combo: 0,
maxCombo: 0,
difficulty: 'medium', // easy, medium, hard
beatInterval: 1000, // 基础节拍间隔(毫秒)
noteSpeed: 1, // 音符移动速度
nextNoteTime: 0,
notes: [],
activeNote: null,
audio: null,
audioContext: null,
analyser: null,
frequencyData: new Uint8Array(128),
animationFrameId: null
};
// DOM元素
const elements = {
gameArea: document.getElementById('gameArea'),
centerIndicator: document.getElementById('centerIndicator'),
gameMessage: document.getElementById('gameMessage'),
startButton: document.getElementById('startButton'),
scoreDisplay: document.getElementById('score'),
comboDisplay: document.getElementById('combo'),
easyMode: document.getElementById('easyMode'),
mediumMode: document.getElementById('mediumMode'),
hardMode: document.getElementById('hardMode'),
volumeControl: document.getElementById('volumeControl'),
muteButton: document.getElementById('muteButton')
};
// 初始化游戏
function initGame() {
setupAudio();
setupEventListeners();
updateDifficultyUI();
}
// 设置音频
function setupAudio() {
gameState.audio = new Audio('https://assets.mixkit.co/music/preview/mixkit-neon-dance-beat-900.mp3');
gameState.audio.volume = gameState.volume || 0.7;
if (typeof(AudioContext) !== 'undefined') {
gameState.audioContext = new AudioContext();
const source = gameState.audioContext.createMediaElementSource(gameState.audio);
gameState.analyser = gameState.audioContext.createAnalyser();
gameState.analyser.fftSize = 256;
source.connect(gameState.analyser);
gameState.analyser.connect(gameState.audioContext.destination);
}
}
// 设置事件监听器
function setupEventListeners() {
// 开始游戏按钮
elements.startButton.addEventListener('click', startGame);
// 难度选择
elements.easyMode.addEventListener('click', () => setDifficulty('easy'));
elements.mediumMode.addEventListener('click', () => setDifficulty('medium'));
elements.hardMode.addEventListener('click', () => setDifficulty('hard'));
// 音量控制
elements.volumeControl.addEventListener('input', (e) => {
gameState.audio.volume = e.target.value;
});
// 静音按钮
elements.muteButton.addEventListener('click', toggleMute);
}
// 切换静音
function toggleMute() {
gameState.audio.muted = !gameState.audio.muted;
elements.muteButton.innerHTML = gameState.audio.muted
? '<i class="fa fa-volume-off"></i>'
: '<i class="fa fa-volume-up"></i>';
}
// 设置难度
function setDifficulty(diff) {
gameState.difficulty = diff;
// 根据难度调整参数
switch(diff) {
case 'easy':
gameState.beatInterval = 1200;
gameState.noteSpeed = 0.8;
break;
case 'medium':
gameState.beatInterval = 1000;
gameState.noteSpeed = 1;
break;
case 'hard':
gameState.beatInterval = 800;
gameState.noteSpeed = 1.2;
break;
}
updateDifficultyUI();
}
// 更新难度UI
function updateDifficultyUI() {
elements.easyMode.classList.remove('bg-neon-green/20', 'border-neon-green/50');
elements.mediumMode.classList.remove('bg-neon-blue/20', 'border-neon-blue/50');
elements.hardMode.classList.remove('bg-neon-pink/20', 'border-neon-pink/50');
switch(gameState.difficulty) {
case 'easy':
elements.easyMode.classList.add('bg-neon-green/20', 'border-neon-green/50');
break;
case 'medium':
elements.mediumMode.classList.add('bg-neon-blue/20', 'border-neon-blue/50');
break;
case 'hard':
elements.hardMode.classList.add('bg-neon-pink/20', 'border-neon-pink/50');
break;
}
}
// 开始游戏
function startGame() {
if (gameState.isPlaying) return;
gameState.isPlaying = true;
elements.gameMessage.style.display = 'none';
elements.startButton.disabled = true;
// 重置游戏状态
gameState.score = 0;
gameState.combo = 0;
gameState.maxCombo = 0;
gameState.notes = [];
gameState.nextNoteTime = Date.now() + gameState.beatInterval;
// 更新分数显示
updateScoreDisplay();
// 播放音乐
gameState.audio.currentTime = 0;
gameState.audio.play().catch(e => console.log('音频播放需要用户交互:', e));
// 开始游戏循环
gameLoop();
}
// 游戏循环
function gameLoop() {
if (!gameState.isPlaying) return;
// 处理音频分析
if (gameState.analyser) {
gameState.analyser.getByteFrequencyData(gameState.frequencyData);
}
// 生成新音符
const now = Date.now();
if (now >= gameState.nextNoteTime) {
spawnNote();
gameState.nextNoteTime = now + gameState.beatInterval;
}
// 更新音符位置
updateNotes();
// 绘制游戏画面
renderGame();
// 继续循环
gameState.animationFrameId = requestAnimationFrame(gameLoop);
}
// 生成新音符
function spawnNote() {
const note = {
id: Date.now(),
x: Math.random() * elements.gameArea.offsetWidth,
y: -50,
size: 40 + Math.random() * 20,
shape: Math.floor(Math.random() * 3), // 0: shape: Math.floor(Math.random() * 3), // 0: 圆形, 1: 正方形, 2: 三角形
color: Math.floor(Math.random() * 4), // 0: 粉色, 1: 蓝色, 2: 紫色, 3: 绿色
targetX: elements.gameArea.offsetWidth / 2,
targetY: elements.gameArea.offsetHeight / 2,
speed: gameState.noteSpeed * (0.8 + Math.random() * 0.4),
isHit: false,
hitTimer: 0,
lifeTime: 0,
maxLifeTime: 3000 // 音符最大存在时间(毫秒)
};
gameState.notes.push(note);
}
// 更新音符状态
function updateNotes() {
const now = Date.now();
const gameAreaRect = elements.gameArea.getBoundingClientRect();
const centerX = gameAreaRect.width / 2;
const centerY = gameAreaRect.height / 2;
gameState.notes = gameState.notes.filter(note => {
// 计算到中心的距离
const dx = note.targetX - note.x;
const dy = note.targetY - note.y;
const distance = Math.sqrt(dx * dx + dy * dy);
// 移动音符
if (!note.isHit) {
note.x += (dx / distance) * note.speed;
note.y += (dy / distance) * note.speed;
note.lifeTime = now - note.id;
} else {
note.hitTimer = now - note.id;
}
// 检查是否到达中心
if (distance < 10 && !note.isHit) {
// 未击中
note.isHit = true;
handleMissedNote(note);
}
// 检查是否超出生命周期
return note.hitTimer < 500 || note.lifeTime < note.maxLifeTime;
});
}
// 渲染游戏画面
function renderGame() {
// 清除之前的音符
while (elements.gameArea.firstChild) {
if (elements.gameArea.firstChild.id !== 'centerIndicator' &&
elements.gameArea.firstChild.id !== 'gameMessage') {
elements.gameArea.removeChild(elements.gameArea.firstChild);
}
}
// 绘制音符
gameState.notes.forEach(note => {
const noteElement = document.createElement('div');
noteElement.id = `note-${note.id}`;
noteElement.style.position = 'absolute';
noteElement.style.transition = 'all 0.2s ease';
// 设置形状和颜色
const colors = ['neon-pink', 'neon-blue', 'neon-purple', 'neon-green'];
const shapeClasses = {
0: 'rounded-full', // 圆形
1: 'rounded-lg', // 正方形
2: 'triangle' // 三角形
};
// 三角形样式
if (note.shape === 2) {
noteElement.style.width = '0';
noteElement.style.height = '0';
noteElement.style.borderLeft = `${note.size / 2}px solid transparent`;
noteElement.style.borderRight = `${note.size / 2}px solid transparent`;
noteElement.style.borderBottom = `${note.size}px solid ${colors[note.color]}`;
} else {
noteElement.style.width = `${note.size}px`;
noteElement.style.height = `${note.size}px`;
noteElement.classList.add(shapeClasses[note.shape]);
noteElement.style.backgroundColor = `rgba(255,255,255,0.8)`;
noteElement.style.border = `2px solid ${colors[note.color]}`;
}
// 击中效果
if (note.isHit) {
const hitProgress = note.hitTimer / 500;
noteElement.style.opacity = 1 - hitProgress;
noteElement.style.transform = `scale(${1 + hitProgress * 0.5})`;
}
// 设置位置
noteElement.style.left = `${note.x}px`;
noteElement.style.top = `${note.y}px`;
elements.gameArea.appendChild(noteElement);
});
}
// 处理音符点击
elements.gameArea.addEventListener('click', (e) => {
if (!gameState.isPlaying) return;
const gameAreaRect = elements.gameArea.getBoundingClientRect();
const clickX = e.clientX - gameAreaRect.left;
const clickY = e.clientY - gameAreaRect.top;
const closestNote = findClosestNote(clickX, clickY);
if (closestNote) {
handleHitNote(closestNote);
}
});
// 寻找最近的音符
function findClosestNote(x, y) {
let closestNote = null;
let closestDistance = Infinity;
gameState.notes.forEach(note => {
if (note.isHit) return;
const dx = x - note.x;
const dy = y - note.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < note.size / 2 && distance < closestDistance) {
closestDistance = distance;
closestNote = note;
}
});
return closestNote;
}
// 处理击中音符
function handleHitNote(note) {
note.isHit = true;
// 计算击中分数 (距离中心越近分数越高)
const dx = note.targetX - note.x;
const dy = note.targetY - note.y;
const distance = Math.sqrt(dx * dx + dy * dy);
const centerDistance = Math.sqrt(
(note.targetX - elements.gameArea.offsetWidth / 2) *
(note.targetX - elements.gameArea.offsetWidth / 2) +
(note.targetY - elements.gameArea.offsetHeight / 2) *
(note.targetY - elements.gameArea.offsetHeight / 2)
);
// 计算击中评价
let points = 0;
let评价 = '';
if (distance < centerDistance * 0.2) {
points = 100;
评价 = '完美!';
gameState.combo++;
} else if (distance < centerDistance * 0.4) {
points = 70;
评价 = '良好!';
gameState.combo++;
} else if (distance < centerDistance * 0.6) {
points = 40;
评价 = '一般!';
gameState.combo = Math.max(1, gameState.combo);
} else {
points = 10;
评价 = '勉强!';
gameState.combo = Math.max(1, gameState.combo);
}
gameState.score += points;
gameState.maxCombo = Math.max(gameState.maxCombo, gameState.combo);
// 显示击中效果和分数
showHitEffect(note.x, note.y, points, 评价);
// 更新分数显示
updateScoreDisplay();
}
// 处理未击中音符
function handleMissedNote(note) {
gameState.combo = 0;
// 显示未击中效果
showMissEffect(note.x, note.y);
// 更新分数显示
updateScoreDisplay();
}
// 显示击中效果
function showHitEffect(x, y, points, 评价) {
const effect = document.createElement('div');
effect.style.position = 'absolute';
effect.style.left = `${x}px`;
effect.style.top = `${y}px`;
effect.style.color = 'white';
effect.style.fontSize = '24px';
effect.style.fontWeight = 'bold';
effect.style.textShadow = '0 0 10px currentColor';
effect.style.zIndex = '100';
effect.style.transform = 'translate(-50%, -50%)';
effect.style.opacity = '0';
// 根据评价设置颜色
if (评价 === '完美!') {
effect.style.color = 'neon-green';
} else if (评价 === '良好!') {
effect.style.color = 'neon-blue';
} else {
effect.style.color = 'neon-pink';
}
elements.gameArea.appendChild(effect);
// 动画效果
setTimeout(() => {
effect.style.opacity = '1';
effect.style.transform = 'translate(-50%, -50%) scale(1)';
}, 10);
setTimeout(() => {
effect.style.transform = 'translate(-50%, -100%) scale(1.5)';
effect.style.opacity = '0';
}, 300);
setTimeout(() => {
elements.gameArea.removeChild(effect);
}, 500);
// 播放击中音效 (这里使用音频分析的节奏来模拟音效)
if (gameState.analyser) {
const maxFrequency = Math.max(...gameState.frequencyData);
if (maxFrequency > 50) {
// 这里可以添加实际的音效播放逻辑
// 简化处理,使用音频分析的峰值来模拟节奏反馈
}
}
}
// 显示未击中效果
function showMissEffect(x, y) {
const effect = document.createElement('div');
effect.style.position = 'absolute';
effect.style.left = `${x}px`;
effect.style.top = `${y}px`;
effect.style.color = 'neon-pink';
effect.style.fontSize = '24px';
effect.style.fontWeight = 'bold';
effect.style.textShadow = '0 0 10px currentColor';
effect.style.zIndex = '100';
effect.style.transform = 'translate(-50%, -50%)';
effect.style.opacity = '0';
effect.innerHTML = 'MISS';
elements.gameArea.appendChild(effect);
// 动画效果
setTimeout(() => {
effect.style.opacity = '1';
effect.style.transform = 'translate(-50%, -50%) scale(1)';
}, 10);
setTimeout(() => {
effect.style.transform = 'translate(-50%, -100%) scale(1.5)';
effect.style.opacity = '0';
}, 300);
setTimeout(() => {
elements.gameArea.removeChild(effect);
}, 500);
}
// 更新分数显示
function updateScoreDisplay() {
elements.scoreDisplay.textContent = gameState.score;
elements.comboDisplay.textContent = gameState.combo;
// 连击颜色变化
if (gameState.combo > 10) {
elements.comboDisplay.classList.remove('text-neon-blue');
elements.comboDisplay.classList.add('text-neon-green');
} else if (gameState.combo > 5) {
elements.comboDisplay.classList.remove('text-neon-pink');
elements.comboDisplay.classList.add('text-neon-blue');
} else {
elements.comboDisplay.classList.remove('text-neon-green', 'text-neon-blue');
elements.comboDisplay.classList.add('text-neon-pink');
}
}
// 暂停游戏
function pauseGame() {
gameState.isPlaying = false;
cancelAnimationFrame(gameState.animationFrameId);
}
// 游戏结束
function gameOver() {
pauseGame();
// 显示游戏结束画面
elements.gameMessage.style.display = 'flex';
elements.gameMessage.innerHTML = `
<p class="text-2xl mb-2 text-neon-blue">游戏结束!</p>
<p class="text-xl mb-4">最终分数: ${gameState.score}</p>
<p class="text-lg mb-6">最高连击: ${gameState.maxCombo}</p>
<button id="restartButton" class="bg-neon-pink hover:bg-neon-pink/80 text-white font-bold py-3 px-8 rounded-full transition-all duration-300 transform hover:scale-105 shape-border">
重新开始
</button>
`;
// 重新开始按钮事件
document.getElementById('restartButton').addEventListener('click', () => {
initGame();
startGame();
});
}
// 窗口大小变化时调整游戏
window.addEventListener('resize', () => {
if (gameState.isPlaying) {
// 重新计算音符目标位置
gameState.notes.forEach(note => {
note.targetX = elements.gameArea.offsetWidth / 2;
note.targetY = elements.gameArea.offsetHeight / 2;
});
}
});
// 初始化游戏
initGame();
});
</script>
</body>
</html>
index.html
style.css
index.js
index.html