Mini Shooter - 2D Battle Game
by CyberEagle48618 lines21.5 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mini Shooter - 2D Battle Game</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background: #1a1a2e;
font-family: Arial, sans-serif;
overflow: hidden;
}
#gameContainer {
position: relative;
}
canvas {
border: 3px solid #e94560;
border-radius: 10px;
box-shadow: 0 0 30px rgba(233,69,96,0.3);
}
#ui {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-size: 16px;
font-weight: bold;
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
}
#controls {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
color: rgba(255,255,255,0.7);
font-size: 12px;
text-align: center;
text-shadow: 1px 1px 2px rgba(0,0,0,0.8);
}
</style>
</head>
<body>
<div id="gameContainer">
<canvas id="gameCanvas"></canvas>
<div id="ui">
<div>❤️ Health: <span id="health">100</span></div>
<div>🔫 Ammo: <span id="ammo">30</span></div>
<div>💀 Kills: <span id="kills">0</span></div>
<div>⭐ Score: <span id="score">0</span></div>
</div>
<div id="controls">
🎮 W/A/S/D - Move | Mouse - Aim & Shoot | R - Reload | Space - Jetpack
</div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
// Set canvas size
canvas.width = 800;
canvas.height = 500;
// Game variables
let gameRunning = true;
// Player object
const player = {
x: canvas.width / 2,
y: canvas.height - 100,
width: 30,
height: 40,
speed: 4,
health: 100,
maxHealth: 100,
ammo: 30,
maxAmmo: 30,
kills: 0,
score: 0,
angle: 0,
jetpackFuel: 100,
maxJetpackFuel: 100,
isJetpackActive: false,
vy: 0,
onGround: true
};
// Arrays for game objects
let bullets = [];
let enemies = [];
let particles = [];
let powerUps = [];
// Input handling
const keys = {};
let mouseX = player.x;
let mouseY = player.y - 100;
let mouseDown = false;
// Ground level
const groundY = canvas.height - 20;
// Platforms
const platforms = [
{ x: 100, y: groundY - 100, width: 150, height: 15 },
{ x: 350, y: groundY - 150, width: 150, height: 15 },
{ x: 550, y: groundY - 100, width: 150, height: 15 },
{ x: 250, y: groundY - 200, width: 100, height: 15 },
{ x: 500, y: groundY - 200, width: 100, height: 15 }
];
// Event listeners
window.addEventListener('keydown', (e) => {
keys[e.key.toLowerCase()] = true;
if (e.key.toLowerCase() === 'r') reload();
});
window.addEventListener('keyup', (e) => {
keys[e.key.toLowerCase()] = false;
if (e.key === ' ') {
player.isJetpackActive = false;
}
});
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
mouseX = e.clientX - rect.left;
mouseY = e.clientY - rect.top;
});
canvas.addEventListener('mousedown', (e) => {
if (e.button === 0) {
mouseDown = true;
shoot();
}
});
canvas.addEventListener('mouseup', (e) => {
if (e.button === 0) {
mouseDown = false;
}
});
canvas.addEventListener('contextmenu', (e) => e.preventDefault());
// Shooting function
function shoot() {
if (player.ammo > 0) {
const angle = Math.atan2(mouseY - player.y, mouseX - player.x);
bullets.push({
x: player.x,
y: player.y,
vx: Math.cos(angle) * 10,
vy: Math.sin(angle) * 10,
damage: 20
});
player.ammo--;
updateUI();
}
}
// Reload function
function reload() {
player.ammo = player.maxAmmo;
updateUI();
}
// Spawn enemy
function spawnEnemy() {
if (enemies.length < 5) {
const side = Math.random() < 0.5 ? 'left' : 'right';
const x = side === 'left' ? -50 : canvas.width + 50;
const y = groundY - 40;
enemies.push({
x: x,
y: y,
width: 30,
height: 40,
speed: 1.5 + Math.random() * 1.5,
health: 50,
maxHealth: 50,
shootTimer: 0,
shootCooldown: 60 + Math.random() * 40,
onGround: true,
vy: 0
});
}
}
// Spawn power-up
function spawnPowerUp() {
if (powerUps.length < 3 && Math.random() < 0.005) {
const types = ['health', 'ammo'];
powerUps.push({
x: Math.random() * (canvas.width - 40) + 20,
y: Math.random() * (groundY - 50) + 30,
width: 20,
height: 20,
type: types[Math.floor(Math.random() * types.length)]
});
}
}
// Update game state
function update() {
if (!gameRunning) return;
// Player movement
if (keys['a'] || keys['arrowleft']) player.x -= player.speed;
if (keys['d'] || keys['arrowright']) player.x += player.speed;
// Jetpack
if ((keys[' '] || keys['w'] || keys['arrowup']) && player.jetpackFuel > 0) {
player.isJetpackActive = true;
player.vy = -8;
player.jetpackFuel -= 0.5;
player.onGround = false;
// Jetpack particles
for (let i = 0; i < 3; i++) {
particles.push({
x: player.x + (Math.random() - 0.5) * 10,
y: player.y + player.height / 2,
vx: (Math.random() - 0.5) * 2,
vy: Math.random() * 3 + 2,
life: 20,
color: '#ff6b35'
});
}
} else {
player.isJetpackActive = false;
if (player.jetpackFuel < player.maxJetpackFuel) {
player.jetpackFuel += 0.2;
}
}
// Apply gravity to player
if (!player.onGround && !player.isJetpackActive) {
player.vy += 0.8;
}
player.y += player.vy;
player.onGround = false;
// Ground collision
if (player.y + player.height / 2 >= groundY) {
player.y = groundY - player.height / 2;
player.vy = 0;
player.onGround = true;
}
// Platform collision
platforms.forEach(platform => {
if (player.vy > 0 &&
player.x > platform.x &&
player.x < platform.x + platform.width &&
player.y + player.height / 2 >= platform.y &&
player.y + player.height / 2 <= platform.y + platform.height + 10) {
player.y = platform.y - player.height / 2;
player.vy = 0;
player.onGround = true;
}
});
// Keep player in bounds
if (player.x < player.width / 2) player.x = player.width / 2;
if (player.x > canvas.width - player.width / 2) player.x = canvas.width - player.width / 2;
if (player.y < player.height / 2) player.y = player.height / 2;
// Update bullets
bullets = bullets.filter(bullet => {
bullet.x += bullet.vx;
bullet.y += bullet.vy;
return bullet.x > -50 && bullet.x < canvas.width + 50 &&
bullet.y > -50 && bullet.y < canvas.height + 50;
});
// Update enemies
enemies.forEach(enemy => {
// Enemy AI - move towards player
if (enemy.x < player.x) enemy.x += enemy.speed;
if (enemy.x > player.x) enemy.x -= enemy.speed;
// Enemy shooting
enemy.shootTimer--;
if (enemy.shootTimer <= 0) {
const angle = Math.atan2(player.y - enemy.y, player.x - enemy.x);
bullets.push({
x: enemy.x,
y: enemy.y,
vx: Math.cos(angle) * 7,
vy: Math.sin(angle) * 7,
damage: 10,
isEnemy: true
});
enemy.shootTimer = enemy.shootCooldown;
}
// Enemy gravity and ground collision
enemy.vy += 0.8;
enemy.y += enemy.vy;
if (enemy.y + enemy.height / 2 >= groundY) {
enemy.y = groundY - enemy.height / 2;
enemy.vy = 0;
enemy.onGround = true;
}
});
// Check bullet collisions
bullets.forEach((bullet, bulletIndex) => {
if (bullet.isEnemy) {
// Enemy bullet hit player
const dx = bullet.x - player.x;
const dy = bullet.y - player.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < player.width / 2 + 5) {
player.health -= bullet.damage;
bullets.splice(bulletIndex, 1);
// Hit particles
for (let i = 0; i < 5; i++) {
particles.push({
x: player.x,
y: player.y,
vx: (Math.random() - 0.5) * 4,
vy: (Math.random() - 0.5) * 4,
life: 15,
color: '#ff0000'
});
}
if (player.health <= 0) {
gameRunning = false;
alert('Game Over! Final Score: ' + player.score);
location.reload();
}
updateUI();
}
} else {
// Player bullet hit enemy
enemies.forEach((enemy, enemyIndex) => {
const dx = bullet.x - enemy.x;
const dy = bullet.y - enemy.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < enemy.width / 2 + 5) {
enemy.health -= bullet.damage;
bullets.splice(bulletIndex, 1);
if (enemy.health <= 0) {
// Enemy death
for (let i = 0; i < 10; i++) {
particles.push({
x: enemy.x,
y: enemy.y,
vx: (Math.random() - 0.5) * 6,
vy: (Math.random() - 0.5) * 6,
life: 20,
color: '#ff4444'
});
}
enemies.splice(enemyIndex, 1);
player.kills++;
player.score += 100;
updateUI();
}
}
});
}
});
// Check power-up collisions
powerUps.forEach((powerUp, index) => {
const dx = player.x - powerUp.x;
const dy = player.y - powerUp.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 25) {
if (powerUp.type === 'health') {
player.health = Math.min(player.health + 30, player.maxHealth);
} else if (powerUp.type === 'ammo') {
player.ammo = player.maxAmmo;
}
powerUps.splice(index, 1);
updateUI();
}
});
// Update particles
particles = particles.filter(particle => {
particle.x += particle.vx;
particle.y += particle.vy;
particle.life--;
return particle.life > 0;
});
// Spawn enemies and power-ups
spawnEnemy();
spawnPowerUp();
// Continuous shooting
if (mouseDown && player.ammo > 0) {
shoot();
}
}
// Draw everything
function draw() {
// Clear canvas
ctx.fillStyle = '#0f3460';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw sky gradient
const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
gradient.addColorStop(0, '#16213e');
gradient.addColorStop(0.6, '#1a1a2e');
gradient.addColorStop(1, '#0f3460');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, groundY);
// Draw ground
ctx.fillStyle = '#533e2d';
ctx.fillRect(0, groundY, canvas.width, canvas.height - groundY);
ctx.fillStyle = '#6b4c3a';
for (let i = 0; i < canvas.width; i += 30) {
ctx.fillRect(i, groundY, 15, 5);
}
// Draw platforms
platforms.forEach(platform => {
ctx.fillStyle = '#8b7355';
ctx.fillRect(platform.x, platform.y, platform.width, platform.height);
ctx.fillStyle = '#a0522d';
ctx.fillRect(platform.x, platform.y, platform.width, 3);
});
// Draw power-ups
powerUps.forEach(powerUp => {
ctx.fillStyle = powerUp.type === 'health' ? '#ff4444' : '#ffaa00';
ctx.beginPath();
ctx.arc(powerUp.x, powerUp.y, 10, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'white';
ctx.font = '14px Arial';
ctx.textAlign = 'center';
ctx.fillText(powerUp.type === 'health' ? '+' : '🔫', powerUp.x, powerUp.y + 5);
});
// Draw enemies
enemies.forEach(enemy => {
// Enemy body
ctx.save();
ctx.translate(enemy.x, enemy.y);
const angle = Math.atan2(player.y - enemy.y, player.x - enemy.x);
ctx.rotate(angle);
// Body
ctx.fillStyle = '#ff4444';
ctx.fillRect(-15, -20, 30, 35);
// Head
ctx.fillStyle = '#cc3333';
ctx.beginPath();
ctx.arc(0, -25, 12, 0, Math.PI * 2);
ctx.fill();
// Gun
ctx.fillStyle = '#333333';
ctx.fillRect(10, -10, 15, 4);
// Eyes
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(3, -28, 3, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.arc(4, -28, 1.5, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
// Health bar
ctx.fillStyle = '#ff0000';
ctx.fillRect(enemy.x - 15, enemy.y - 35, 30, 4);
ctx.fillStyle = '#00ff00';
ctx.fillRect(enemy.x - 15, enemy.y - 35, 30 * (enemy.health / enemy.maxHealth), 4);
});
// Draw player
ctx.save();
ctx.translate(player.x, player.y);
const playerAngle = Math.atan2(mouseY - player.y, mouseX - player.x);
ctx.rotate(playerAngle);
// Player body
ctx.fillStyle = '#4488ff';
ctx.fillRect(-15, -20, 30, 35);
// Player head
ctx.fillStyle = '#3366cc';
ctx.beginPath();
ctx.arc(0, -25, 12, 0, Math.PI * 2);
ctx.fill();
// Helmet
ctx.fillStyle = '#2255aa';
ctx.beginPath();
ctx.arc(0, -27, 14, Math.PI, 0);
ctx.fill();
// Gun
ctx.fillStyle = '#555555';
ctx.fillRect(10, -10, 20, 5);
ctx.fillStyle = '#777777';
ctx.fillRect(25, -12, 5, 9);
// Eyes
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(3, -28, 3, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.arc(4, -28, 1.5, 0, Math.PI * 2);
ctx.fill();
// Jetpack
if (player.isJetpackActive) {
ctx.fillStyle = '#ff6b35';
ctx.beginPath();
ctx.arc(-8, 5, 8, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
// Draw bullets
bullets.forEach(bullet => {
ctx.fillStyle = bullet.isEnemy ? '#ff0000' : '#ffff00';
ctx.beginPath();
ctx.arc(bullet.x, bullet.y, 3, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(bullet.x, bullet.y, 1.5, 0, Math.PI * 2);
ctx.fill();
});
// Draw particles
particles.forEach(particle => {
ctx.fillStyle = particle.color;
ctx.globalAlpha = particle.life / 20;
ctx.beginPath();
ctx.arc(particle.x, particle.y, 2, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = 1;
});
// Draw crosshair
ctx.strokeStyle = 'white';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.arc(mouseX, mouseY, 15, 0, Math.PI * 2);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(mouseX - 20, mouseY);
ctx.lineTo(mouseX - 10, mouseY);
ctx.moveTo(mouseX + 10, mouseY);
ctx.lineTo(mouseX + 20, mouseY);
ctx.moveTo(mouseX, mouseY - 20);
ctx.lineTo(mouseX, mouseY - 10);
ctx.moveTo(mouseX, mouseY + 10);
ctx.lineTo(mouseX, mouseY + 20);
ctx.stroke();
}
// Update UI
function updateUI() {
document.getElementById('health').textContent = Math.max(0, Math.floor(player.health));
document.getElementById('ammo').textContent = player.ammo;
document.getElementById('kills').textContent = player.kills;
document.getElementById('score').textContent = player.score;
// Change health color based on value
const healthElement = document.getElementById('health');
if (player.health < 30) {
healthElement.style.color = '#ff4444';
} else if (player.health < 60) {
healthElement.style.color = '#ffaa00';
} else {
healthElement.style.color = '#44ff44';
}
}
// Game loop
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
// Start game
updateUI();
gameLoop();
</script>
</body>
</html>Game Source: Mini Shooter - 2D Battle Game
Creator: CyberEagle48
Libraries: none
Complexity: complex (618 lines, 21.5 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: mini-shooter-2d-battle-game-cybereagle48" to link back to the original. Then publish at arcadelab.ai/publish.