var canvas = document.querySelector('canvas'); var ctx = canvas.getContext('2d'); var scoreboard = document.querySelector('.scoreboard'); var height = 400; var width = 600; // I/O var leftDown, rightDown, spaceDown; // PLAYER var playerX = width - 21; var playerY = height - 21; var playerSpeed = 10; var livesLeft = 3; // BULLETS var bullets = [] var maxBullets = Number.MAX_SAFE_INTEGER; var bulletSpeed = 20; var minTicksBetween = 5; var ticksSinceLastBullet = minTicksBetween; // ENEMIES var enemies = []; var maxEnemies = 5; var minTicksBetweenEnemies = 10; var maxWitcksBetweenEnemies = 100; var timeSinceLastSpawnEnemy = 0; var enemySpeedDown = 5; var enemySpeedSide = 5; // STARS var stars = []; var maxStars = Number.MAX_SAFE_INTEGER; var minTicksBetweenStars = 0; var maxTicksBetweenStars = 40; var timeSinceLastSpawnStar = 0; var starSpeedDown = 1; var starSpeedSide = 0; // SCORE var time = 0; var enemiesShot = 0; document.addEventListener('keydown', (event) => { const keyName = event.key; switch(keyName) { case 'ArrowRight': leftDown = false; rightDown = true; break; case 'ArrowLeft': rightDown = false; leftDown = true; break; case ' ': spaceDown = true; break; } }, false); document.addEventListener('keyup', (event) => { const keyName = event.key; switch(keyName) { case 'ArrowRight': rightDown = false; break; case 'ArrowLeft': leftDown = false; break; case ' ': spaceDown = false; break; } }, false); var intervalId = setInterval(function tick() { var text = ''; text += 'Time: ' + time; text += ', Enemies down: ' + enemiesShot; text += ', Score: ' + (time + (enemiesShot * 100)); text += ', Lives left: ' + livesLeft; time++; scoreboard.innerText = text; if(livesLeft === 0) { clearInterval(intervalId); } // Player ship placement if(leftDown) { playerX = Math.max(0, playerX - playerSpeed); } else if(rightDown) { playerX = Math.min(600 - 21, playerX + playerSpeed); } // Clear canvas ctx.fillStyle = '#000'; ctx.fillRect(0,0,600,400); stars.forEach(function(star) { if(star.level === 1) { star.y = star.y + starSpeedDown; ctx.fillStyle = '#FFC'; ctx.fillRect(star.x, star.y, 1, 1); } else if(star.level === 2) { star.y = star.y + starSpeedDown * 1.5; ctx.fillStyle = '#FFA'; ctx.fillRect(star.x, star.y, 2, 2); } else { star.y = star.y + starSpeedDown * 3; ctx.fillStyle = '#FF0'; ctx.fillRect(star.x, star.y, 5, 5); } }); // Draw player ctx.fillStyle = '#FFF'; ctx.fillRect(playerX, playerY, 21, 21); // Bullets placement if(bullets.length < maxBullets && spaceDown && ticksSinceLastBullet > minTicksBetween) { ticksSinceLastBullet = 0; bullets.push({ x: playerX + 11, y: playerY - 20 }) } ticksSinceLastBullet++; bullets = bullets.filter(b => b.y > 0); bullets.forEach(function(bullet) { bullet.y = bullet.y - bulletSpeed; // Draw bullet ctx.fillStyle = '#999'; ctx.fillRect(bullet.x, bullet.y, 3, 10); }); // Check if bullet hit enemy here! Also remove enemy if its // outside of screen (below) var dead = false; enemies = enemies.filter(function(enemy) { var shot = false; bullets.forEach(function(bullet) { if(bullet.x >= enemy.x && bullet.x <= enemy.x + 22) { if(bullet.y - 2 >= enemy.y && bullet.y <= enemy.y + 22) { shot = true; enemiesShot++; } } }); if(shot) return false; if(enemy.y > height) { return false; } // høyre siden på fiende: enemy.x + 21 // Må være lik eller høyere venstre side på meg: playerX // Venstre side på fiende: enemy.x // Må være lik eller lavere høyre side på meg: playerX + 21 if(enemy.x + 21 >= playerX && enemy.x <= playerX +21) { if(enemy.y >= playerY && enemy.y <= playerY + 21) { dead = true; } } return true; }); stars = stars.filter(function(star) { if(star.y > height) { return false; } return true; }); if(dead) { enemies = []; playerX = width - 21; playerY = height - 21; timeSinceLastSpawnEnemy = 0; livesLeft--; } // Enemy placements if(enemies.length < maxEnemies) { // Spawn enemy if it has gone too long time since last one // or if it has gone some time: 20% random spawning if(timeSinceLastSpawnEnemy > maxWitcksBetweenEnemies || (timeSinceLastSpawnEnemy > minTicksBetweenEnemies && Math.random() < 0.2)) { timeSinceLastSpawnEnemy = 0; enemies.push({ x: Math.floor(Math.random() * (width) - 21), y: -21, movement: true//true: left, false: right }) } } timeSinceLastSpawnEnemy++; // Star placements if(stars.length < maxStars) { // Spawn star if it has gone too long time since last one // or if it has gone some time: 20% random spawning if(timeSinceLastSpawnStar > maxTicksBetweenStars || (timeSinceLastSpawnStar > minTicksBetweenStars && Math.random() < 0.2)) { timeSinceLastSpawnStar = 0; stars.push({ x: Math.floor(Math.random() * (width)), y: -1, level: Math.random() > 0.6 ? (Math.random() > 0.85 ? 3 : 2) : 1 }) } } timeSinceLastSpawnStar++; function enemyDirection(enemy) { // Move towards player unless it is right above if(enemy.x + 13 >= playerX && enemy.x <= playerX + 13) { enemy.movement = enemy.x < playerX } else if(Math.random() < 0.4) { enemy.movement = enemy.x > playerX } // change of changing direction if(Math.random() < 0.01) { enemy.movement = !enemy.movement; } if(enemy.x < 0) { enemy.movement = false; } else if(enemy.x > (width - 21)) { enemy.movement = true; } } enemies.forEach(function(enemy) { enemy.y = enemy.y + enemySpeedDown; // Make enemy vertical movement more smooth. // The closer enemy is player vertically, thus the slower // vertical movement. Starts slowing down by 100px distance. var distance = Math.abs(enemy.x - playerX); var beta = (Math.min(100, distance) / 100); enemyDirection(enemy); if(enemy.movement) { enemy.x = enemy.x - enemySpeedSide * beta; } else { enemy.x = enemy.x + enemySpeedSide * beta; } // Draw enemy ctx.fillStyle = '#666'; ctx.fillRect(enemy.x, enemy.y, 21, 21); }); // Check if enemy hit player here! }, 20);
<div class="scoreboard"></div> <canvas height="400" width="600"> </canvas> <div> <ul> <li>Left, Right to steer</li> <li>Space to shoot</li> <li>F5 to start again</li> </ul> </div>
/* TODO: 1. Shoot and kill sounds. 2. Colors, perhaps gifs? 3. One more type of enemy: Shoots when above player then goes sharp left or right out of screen. 4. Place scores etc within canvas? At least more stylish rendering. 6. Black background, white/colored ships 7. Yellow background stars moving down slowly. */ .scoreboard { height: 20px; } body { background: #aaa; text-align: center; } canvas { background: white; margin: auto; } li { list-style: none; }