🍀소스코드
<!DOCTYPE html>
<html>
<head>
<title>Basic Missile Command HTML Game</title>
<meta charset="UTF-8">
<style>
html, body {
height: 100%;
margin: 0;
}
body {
background: #0c0c0c;
display: flex;
align-items: center;
justify-content: center;
}
canvas {
cursor: crosshair;
border: 1px solid white;
}
</style>
</head>
<body>
<canvas width="800" height="550" id="game"></canvas>
<script>
const canvas = document.getElementById('game');
const context = canvas.getContext('2d');
const groundY = 500; // y position of where the ground starts
const cityWidth = 45; // how wide a city rect is
const cityHeight = 25; // how tall a city rect is
const cityY = groundY - cityHeight; // y position of the city
const siloY = groundY - 30; // y position of the top of the silo
const missileSize = 4; // the radius/size of a missile
const missileSpeed = 1; // how fast a missile moves
const counterMissileSpeed = 15; // how fast a counter-missile moves
// information about each missile
let missiles = [];
let counterMissiles = [];
// information about each explosion
let explosions = [];
// how many missiles to spawn at each interval of the level (in this
// case spawn 4 missiles at the start of level 1 and 4 more missiles
// at the next interval of level 1)
const levels = [ [4, 4] ];
let currLevel = 0;
let currInterval = 0;
// the x/y position of all cities and if the city is currently alive
let cities = [
{ x: 140, y: cityY, alive: true },
{ x: 220, y: cityY, alive: true },
{ x: 300, y: cityY, alive: true },
{ x: 500, y: cityY, alive: true },
{ x: 580, y: cityY, alive: true },
{ x: 660, y: cityY, alive: true }
];
// the x position of each of the 3 silos
const siloPos = [ 55, canvas.width / 2, 745 ];
// the x/y position of each silo, the number of missiles left, and if
// it is still alive
let silos = [
{ x: siloPos[0], y: siloY, missiles: 10, alive: true },
{ x: siloPos[1], y: siloY, missiles: 10, alive: true },
{ x: siloPos[2], y: siloY, missiles: 10, alive: true }
];
// the x/y position of each missile spawn point. missiles spawn
// directly above each city and silo plus the two edges
const missileSpawns = cities
.concat(silos)
.concat([{ x: 0, y: 0 }, { x: canvas.width, y: 0 }])
.map(pos => ({ x: pos.x, y: 0 }));
// return a random integer between min (inclusive) and max (inclusive)
// @see https://stackoverflow.com/a/1527820/2124254
function randInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// get the angle between two points
function angleBetweenPoints(source, target) {
// atan2 returns the counter-clockwise angle in respect to the
// x-axis, but the canvas rotation system is based on the y-axis
// (rotation of 0 = up).
// so we need to add a quarter rotation to return a
// counter-clockwise rotation in respect to the y-axis
return Math.atan2(target.y - source.y, target.x - source.x) + Math.PI / 2;
}
// distance between two points
function distance(source, target) {
return Math.hypot(source.x - target.x, source.y - target.y);
}
// spawn a missile by choosing a spawn point and a target.
// a missile can target any city or silo
function spawnMissile() {
const targets = cities.concat(silos);
const randSpawn = randInt(0, missileSpawns.length - 1);
const randTarget = randInt(0, targets.length - 1);
const start = missileSpawns[randSpawn];
const target = targets[randTarget];
const angle = angleBetweenPoints(start, target);
missiles.push({
start, // where the missile started
target, // where the missile is going
pos: { x: start.x, y: start.y }, // current position
alive: true, // if we should still draw the missile
// used to update the position every frame
dx: missileSpeed * Math.sin(angle),
dy: missileSpeed * -Math.cos(angle)
});
}
// game loop
// start at -2 seconds (time is in milliseconds) to give the player 1
// second before the missiles start
let lastTime = -2000;
function loop(time) {
requestAnimationFrame(loop);
context.clearRect(0,0,canvas.width,canvas.height);
// spawn missiles every interval of 3 seconds (if the level allows
// more missiles)
if (time - lastTime > 3000 && currInterval < levels[currLevel].length) {
for (let i = 0; i < levels[currLevel][currInterval]; i++) {
spawnMissile();
}
currInterval++;
lastTime = time;
}
// draw cities
context.fillStyle = 'blue';
cities.forEach(city => {
// center the city on the x position
context.fillRect(city.x - cityWidth / 2, city.y, cityWidth, cityHeight);
});
// draw ground and silos
context.fillStyle = 'yellow';
context.beginPath();
context.moveTo(0, canvas.height);
context.lineTo(0, groundY);
// draw each silo hill
siloPos.forEach(x => {
context.lineTo(x - 40, groundY);
context.lineTo(x - 20, siloY);
context.lineTo(x + 20, siloY);
context.lineTo(x + 40, groundY);
});
context.lineTo(canvas.width, groundY);
context.lineTo(canvas.width, canvas.height);
context.fill();
// draw the number of counter-missiles each silo
context.fillStyle = 'black';
silos.forEach(silo => {
// draw missiles in a triangular shape by incrementing how many
// missiles we can draw per row
let missilesPerRow = 1;
let count = 0;
let x = silo.x;
let y = silo.y + 5;
for (let i = 0; i < silo.missiles; i++) {
context.fillRect(x, y, 4, 10);
x += 12;
if (++count === missilesPerRow) {
x = silo.x - 6 * count;
missilesPerRow++;
y += 7;
count = 0;
}
}
});
// update and draw missiles
context.strokeStyle = 'red';
context.lineWidth = 2;
// update color based on time so it "blinks"
// by dividing by a number and seeing if it's odd or even we can
// change the speed of the blinking
context.fillStyle = 'white';
if (Math.round(time / 2) % 2 === 0) {
context.fillStyle = 'black';
}
missiles.forEach(missile => {
missile.pos.x += missile.dx;
missile.pos.y += missile.dy;
// check if the missile hit an explosion by doing a circle-circle
// collision check
explosions.forEach(explosion => {
const dist = distance(explosion, missile.pos);
if (dist < missileSize + explosion.size) {
missile.alive = false;
}
});
// if missile is close the the target we blow it up
const dist = distance(missile.pos, missile.target);
if (dist < missileSpeed) {
missile.alive = false;
missile.target.alive = false;
}
if (missile.alive) {
context.beginPath();
context.moveTo(missile.start.x, missile.start.y);
context.lineTo(missile.pos.x, missile.pos.y);
context.stroke();
// center the head of the missile to the x/y position
context.fillRect(missile.pos.x - missileSize / 2, missile.pos.y - missileSize / 2, missileSize, missileSize);
}
// a dead missile spawns an explosion
else {
explosions.push({
x: missile.pos.x,
y: missile.pos.y,
size: 2,
dir: 1,
alive: true
});
}
});
// update and draw counter missiles
context.strokeStyle = 'blue';
context.fillStyle = 'white';
counterMissiles.forEach(missile => {
missile.pos.x += missile.dx;
missile.pos.y += missile.dy;
// if missile is close the the target we blow it up
const dist = distance(missile.pos, missile.target);
if (dist < counterMissileSpeed) {
missile.alive = false;
explosions.push({
x: missile.pos.x,
y: missile.pos.y,
size: 2,
dir: 1,
alive: true
});
}
else {
context.beginPath();
context.moveTo(missile.start.x, missile.start.y);
context.lineTo(missile.pos.x, missile.pos.y);
context.stroke();
context.fillRect(missile.pos.x - 2, missile.pos.y - 2, 4, 4);
}
});
// update and draw explosions
explosions.forEach(explosion => {
explosion.size += 0.35 * explosion.dir;
// change the direction of the explosion to wane
if (explosion.size > 30) {
explosion.dir = -1;
}
// remove the explosion
if (explosion.size <= 0) {
explosion.alive = false;
}
else {
context.fillStyle = 'white';
if (Math.round(time / 3) % 2 === 0) {
context.fillStyle = 'blue';
}
context.beginPath();
context.arc(explosion.x, explosion.y, explosion.size, 0, 2 * Math.PI);
context.fill();
}
});
// remove dead missiles, explosions, cities, and silos
missiles = missiles.filter(missile => missile.alive);
counterMissiles = counterMissiles.filter(missile => missile.alive);
explosions = explosions.filter(explosion => explosion.alive);
cities = cities.filter(city => city.alive);
silos = silos.filter(silo => silo.alive);
}
// listen to mouse events to fire counter-missiles
canvas.addEventListener('click', e => {
// get the x/y position of the mouse pointer by subtracting the x/y
// position of the canvas element from the x/y position of the
// pointer
const x = e.clientX - e.target.offsetLeft;
const y = e.clientY - e.target.offsetTop;
// determine which silo is closest to the pointer and fire a
// counter-missile from it
let launchSilo = null;
let siloDistance = Infinity; // start at the largest number
silos.forEach(silo => {
const dist = distance({ x, y }, silo);
if (dist < siloDistance && silo.missiles) {
siloDistance = dist;
launchSilo = silo;
}
});
if (launchSilo) {
const start = { x: launchSilo.x, y: launchSilo.y };
const target = { x, y };
const angle = angleBetweenPoints(start, target);
launchSilo.missiles--;
counterMissiles.push({
start,
target,
pos: { x: launchSilo.x, y: launchSilo. y},
dx: counterMissileSpeed * Math.sin(angle),
dy: counterMissileSpeed * -Math.cos(angle),
alive: true
});
}
});
// start the game
requestAnimationFrame(loop);
</script>
</body>
</html>
'Peace be with you.'
by Learnmore'