popn.html
popn.html
DOCTYPE html>
<html>
<head>
<title>Pop'n Music Clone</title>
<style>
canvas {
border: 2px solid #fff;
display: block;
margin: 20px auto;
}
.game-container {
text-align: center;
font-family: Arial, sans-serif;
background: #1a1a1a;
color: white;
position: relative;
}
.buttons {
margin: 20px;
}
.game-button {
width: 80px;
height: 80px;
margin: 0 15px;
font-size: 24px;
cursor: pointer;
border: 3px solid #fff;
border-radius: 50%;
color: white;
text-shadow: 1px 1px 2px #000;
transition: transform 0.1s;
}
.game-button:active { transform: scale(0.9); }
#btn1 { background: #ff4d4d; }
#btn2 { background: #4dff4d; }
#btn3 { background: #4d4dff; }
#btn4 { background: #ffff4d; }
#btn5 { background: #ff4dff; }
#startScreen, #difficultyScreen {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.9);
color: white;
padding: 20px;
border-radius: 10px;
z-index: 10;
}
.menu-button {
padding: 10px 20px;
font-size: 18px;
margin: 5px;
cursor: pointer;
background: #666;
border: none;
color: white;
border-radius: 5px;
}
.menu-button:hover { background: #888; }
#character {
position: absolute;
top: 50px;
left: 50px;
width: 100px;
height: 100px;
}
</style>
</head>
<body>
<div class="game-container">
<canvas id="gameCanvas" width="600" height="400"></canvas>
<div class="buttons">
<button class="game-button" id="btn1">1</button>
<button class="game-button" id="btn2">2</button>
<button class="game-button" id="btn3">3</button>
<button class="game-button" id="btn4">4</button>
<button class="game-button" id="btn5">5</button>
</div>
<div>Score: <span id="score">0</span> | Combo: <span id="combo">0</span> |
Max Combo: <span id="maxCombo">0</span></div>
<img id="character" src="https://via.placeholder.com/100/00FF00/FFFFFF?
text=Char" alt="Character">
<audio id="bgm" loop>
<!-- Host locally: <source src="music/bgm.mp3" type="audio/mpeg"> -->
<source src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-
1.mp3" type="audio/mpeg">
</audio>
<audio id="hitSound">
<!-- Host locally: <source src="sfx/hit.mp3" type="audio/mpeg"> -->
<source src="https://www.myinstants.com/media/sounds/popn_hit.mp3"
type="audio/mpeg">
</audio>
<audio id="missSound">
<!-- Host locally: <source src="sfx/miss.mp3" type="audio/mpeg"> -->
<source src="https://www.myinstants.com/media/sounds/popn_miss.mp3"
type="audio/mpeg">
</audio>
<div id="startScreen">
<h1>Pop'n Music Clone</h1>
<button class="menu-button" id="startButton">Start Game</button>
</div>
<div id="difficultyScreen" style="display: none;">
<h2>Select Difficulty</h2>
<button class="menu-button" onclick="startGame(1)">Easy</button>
<button class="menu-button" onclick="startGame(2)">Normal</button>
<button class="menu-button" onclick="startGame(3)">Hard</button>
</div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreDisplay = document.getElementById('score');
const comboDisplay = document.getElementById('combo');
const maxComboDisplay = document.getElementById('maxCombo');
const bgm = document.getElementById('bgm');
const hitSound = document.getElementById('hitSound');
const missSound = document.getElementById('missSound');
const startScreen = document.getElementById('startScreen');
const difficultyScreen = document.getElementById('difficultyScreen');
const character = document.getElementById('character');
let score = 0;
let combo = 0;
let maxCombo = 0;
let notes = [];
let hitEffects = [];
const laneWidth = canvas.width / 5;
const noteSize = 50;
const hitZone = canvas.height - 100;
let gameStarted = false;
let difficulty = 1;
let bgOffset = 0;
// Load assets
const noteImage = new Image();
noteImage.src = 'https://via.placeholder.com/50/FF6666/FFFFFF?text=♪'; //
Replace with note.png
const bgImage = new Image();
bgImage.src = 'https://via.placeholder.com/600x400/333333/666666?
text=Stage'; // Replace with bg.png
const charIdle = new Image();
charIdle.src = 'https://via.placeholder.com/100/00FF00/FFFFFF?
text=Idle'; // Replace with idle.png
const charDance = new Image();
charDance.src = 'https://via.placeholder.com/100/00FFFF/FFFFFF?text=Dance';
// Replace with dance.png
class Note {
constructor(lane) {
this.x = lane * laneWidth + laneWidth/2 - noteSize/2;
this.y = -noteSize;
this.lane = lane;
}
update() {
this.y += 3 + difficulty;
}
draw() {
ctx.drawImage(noteImage, this.x, this.y, noteSize, noteSize);
}
}
class HitEffect {
constructor(x, y) {
this.x = x;
this.y = y;
this.life = 20; // Frames of animation
}
update() {
this.life--;
}
draw() {
ctx.fillStyle = `rgba(255, 255, 0, ${this.life / 20})`;
ctx.beginPath();
ctx.arc(this.x + noteSize/2, this.y + noteSize/2, noteSize, 0,
Math.PI * 2);
ctx.fill();
}
}
function spawnNote() {
if (!gameStarted) return;
const lane = Math.floor(Math.random() * 5);
notes.push(new Note(lane));
}
function drawBackground() {
bgOffset += 1.5;
if (bgOffset >= bgImage.height) bgOffset = 0;
ctx.drawImage(bgImage, 0, bgOffset - bgImage.height, canvas.width,
bgImage.height);
ctx.drawImage(bgImage, 0, bgOffset, canvas.width, bgImage.height);
}
function drawLanes() {
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
for (let i = 1; i < 5; i++) {
ctx.beginPath();
ctx.moveTo(i * laneWidth, 0);
ctx.lineTo(i * laneWidth, canvas.height);
ctx.stroke();
}
const gradient = ctx.createLinearGradient(0, hitZone, 0, hitZone +
100);
gradient.addColorStop(0, 'rgba(255, 105, 180, 0.4)');
gradient.addColorStop(1, 'rgba(255, 105, 180, 0.1)');
ctx.fillStyle = gradient;
ctx.fillRect(0, hitZone, canvas.width, 100);
}
function checkHit(lane) {
for (let i = notes.length - 1; i >= 0; i--) {
const note = notes[i];
if (note.lane === lane &&
note.y > hitZone - 50 &&
note.y < hitZone + 100) {
score += 10 + combo * difficulty;
combo++;
maxCombo = Math.max(maxCombo, combo);
scoreDisplay.textContent = score;
comboDisplay.textContent = combo;
maxComboDisplay.textContent = maxCombo;
hitSound.currentTime = 0;
hitSound.play();
hitEffects.push(new HitEffect(note.x, note.y));
notes.splice(i, 1);
character.src = charDance.src; // Dance on hit
setTimeout(() => { if (combo > 0) character.src =
charDance.src; else character.src = charIdle.src; }, 200);
return;
}
}
score -= 5;
combo = 0;
missSound.currentTime = 0;
missSound.play();
character.src = charIdle.src; // Idle on miss
scoreDisplay.textContent = score;
comboDisplay.textContent = combo;
}
// Keyboard controls
document.addEventListener('keydown', (e) => {
if (!gameStarted) return;
const key = parseInt(e.key);
if (key >= 1 && key <= 5) {
checkHit(key - 1);
}
});
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBackground();
drawLanes();
if (gameStarted) {
for (let i = notes.length - 1; i >= 0; i--) {
notes[i].update();
notes[i].draw();
if (notes[i].y > canvas.height) {
notes.splice(i, 1);
score -= 5;
combo = 0;
missSound.currentTime = 0;
missSound.play();
character.src = charIdle.src;
scoreDisplay.textContent = score;
comboDisplay.textContent = combo;
}
}
for (let i = hitEffects.length - 1; i >= 0; i--) {
hitEffects[i].update();
hitEffects[i].draw();
if (hitEffects[i].life <= 0) hitEffects.splice(i, 1);
}
}
requestAnimationFrame(gameLoop);
}
function startGame(diff) {
difficulty = diff;
gameStarted = true;
difficultyScreen.style.display = 'none';
bgm.play();
const bpm = 120;
const spawnInterval = (60 / bpm) * 1000 / (difficulty * 0.5 + 0.5);
setInterval(spawnNote, spawnInterval);
}
gameLoop();
</script>
</body>
</html>