안녕하세요!
지난 글에서는 비행기와 적의 충돌을 처리하고 게임 오버 상태를 구현하는 방법을 설명드렸는데요.
오늘은 지난 시간에 이어서, 점수 시스템, 목숨 시스템, 게임 오버 후 다시 시작 기능을 구현해서 게임을 더 재미있게 만들어보겠습니다.
이전 코드는 아래 링크를 참고해주세요.
2024.09.15 - [JavaScript] - [JavaScript] 비행기 슈팅 게임 만들기 - (4) 충돌 감지와 게임 오버
1. 점수 시스템 구현하기
먼저, 적을 총알로 맞췄을 때 점수가 올라가는 기능을 추가할 것입니다.
1-1. 점수 변수 추가
let score = 0; // 현재 점수를 저장할 변수
1-2. 충돌 감지 함수 handleCollisions() 수정
충돌 감지 함수 handleCollisions() 내에서 총알이 적과 충돌하면 score 변수를 10점씩 증가시켜줍니다.
function handleCollisions() {
bullets.forEach((bullet, bulletIndex) => {
enemies.forEach((enemy, enemyIndex) => {
// 총알과 적의 충돌 처리
if (checkCollision(bullet, enemy)) {
// 충돌 시 적과 총알을 배열에서 제거
bullets.splice(bulletIndex, 1); // 총알 제거
enemies.splice(enemyIndex, 1); // 적 제거
score += 10; // !!점수 증가!!
}
});
});
// 생략
}
1-3. 화면에 점수 표시하기
gameLoop() 함수 내에서 다음과 같이 점수를 화면에 그려주는 코드를 추가하여, 현재 플레이어의 진행 상황을 쉽게 알 수 있도록 해줍니다.
function gameLoop() {
if (isGameOver) {
gameOver(); // 게임 오버 시 메시지 표시
return; // 더 이상 게임을 진행하지 않음
}
ctx.clearRect(0, 0, canvas.width, canvas.height); // 캔버스 전체를 지워서 이전 프레임의 그림을 지움
drawPlane(); // 비행기 그리기
update(); // 비행기 위치 업데이트
drawEnemies(); // 적 그리기
updateEnemies(); // 적 위치 업데이트
drawBullets(); // 총알 그리기
updateBullets(); // 총알 위치 업데이트
handleCollisions(); // 충돌 감지 및 처리
ctx.fillStyle = 'white';
ctx.font = '20px Arial';
ctx.fillText('Score: ' + score, 10, 30); // 점수 표시
requestAnimationFrame(gameLoop); // 다음 프레임 호출
}
2. 목숨 시스템 추가하기
게임을 더 도전적으로 만들기 위해, 비행기가 적과 충돌하면 목숨이 줄어들도록 하는 시스템을 추가해보겠습니다.
충돌 후에는 잠시간 무적상태가 되어 추가 충돌을 방지하며, 비행기가 깜빡이는 효과를 주려고 합니다.
2-1. 목숨 변수 추가
let lives = 3; // 비행기의 목숨 개수를 저장할 변수
2-2. 비행기 객체 속성 추가
비행기 객체 속성으로 무적 상태 여부를 나타내는 isInvincible 변수와 깜빡임 효과를 위해 비행기가 보이는지 여부를 나타내는 isVisible 변수를 추가합니다.
// 비행기 객체 설정
const plane = {
x: canvas.width / 2 - 20, // 비행기의 초기 x 위치를 캔버스 중앙에 설정
y: canvas.height - 60, // 비행기의 초기 y 위치를 캔버스 하단에 설정
width: 40, // 비행기의 너비
height: 40, // 비행기의 높이
speed: 5, // 비행기의 이동 속도
moveLeft: false, // 왼쪽으로 이동 중인지 여부
moveRight: false, // 오른쪽으로 이동 중인지 여부
isInvincible: false, // 무적 상태 여부
isVisible: true, // 비행기가 보이는지 여부
};
2-3. 충돌 감지 함수 handleCollisions() 수정
충돌 감지 함수 handleCollisions() 내에서 적과 비행기가 충돌할 경우 다음과 같이 적용되도록 수정합니다.
- 목숨이 1 감소합니다.
- 무적 상태로 전환되며, 무적 상태인 동안은 다른 적과 닿아도 충돌로 인식하지 않습니다.
- 무적 상태일 동안 비행기가 화면에서 깜빡이는 것처럼 보이도록, 0.2초마다 isVisible 변수 값을 변경합니다.
- 목숨이 0이 되면, 게임오버 됩니다.
function handleCollisions() {
//생략
enemies.forEach((enemy) => {
// 적과 비행기의 충돌 처리
if (checkCollision(enemy, plane) && !plane.isInvincible) {
lives -= 1; // 목숨 감소
plane.isInvincible = true; // 무적 상태로 전환
let blinkCount = 0;
const blinkInterval = setInterval(() => {
plane.isVisible = !plane.isVisible; // 깜빡임 효과
blinkCount++;
if (blinkCount > 5) { // 깜빡임 횟수 설정
clearInterval(blinkInterval);
plane.isVisible = true; // 원래 상태로 복구
plane.isInvincible = false; // 무적 상태 해제
}
}, 200); // 0.2초마다 깜빡임
// 목숨이 0이 되면 게임 오버
if (lives <= 0) {
isGameOver = true;
}
}
});
}
2-4. 화면에 목숨과 깜빡임 표시
gameLoop() 함수를 다음과 같이 수정해서, 화면에 남은 목숨을 표시하고, isVisible 변수에 따라 비행기에 깜빡임 효과가 적용되도록 합니다.
function gameLoop() {
if (isGameOver) {
gameOver(); // 게임 오버 시 메시지 표시
return; // 더 이상 게임을 진행하지 않음
}
ctx.clearRect(0, 0, canvas.width, canvas.height); // 캔버스 전체를 지워서 이전 프레임의 그림을 지움
if (plane.isVisible !== false) {
drawPlane(); // 비행기 그리기
}
update(); // 비행기 위치 업데이트
drawEnemies(); // 적 그리기
updateEnemies(); // 적 위치 업데이트
drawBullets(); // 총알 그리기
updateBullets(); // 총알 위치 업데이트
handleCollisions(); // 충돌 감지 및 처리
// 점수 및 목숨 표시
ctx.fillStyle = 'white';
ctx.font = '20px Arial';
ctx.fillText('Score: ' + score, 10, 30); // 점수 표시
ctx.fillText('Lives: ' + lives, 10, 60); // 목숨 표시
requestAnimationFrame(gameLoop); // 다음 프레임 호출
}
3. 게임 오버 처리 및 다시 시작 기능
이전에 구현했던 gameOver() 함수를 다음과 같이 수정하여, 플레이어가 게임을 다시 시작할 수 있도록 다시 시작 버튼을 추가합니다.
function gameOver() {
ctx.fillStyle = 'red'; // 게임 오버 메시지 색상 설정
ctx.font = '48px Arial'; // 폰트 설정
ctx.fillText('Game Over', canvas.width / 2 - 120, canvas.height / 2); // 화면에 메시지 표시
ctx.fillText('Score: ' + score, canvas.width / 2 - 80, canvas.height / 2 + 50); // 최종 점수 표시
// 다시 시작 버튼 생성
const restartButton = document.createElement('button');
restartButton.innerText = '다시 시작';
restartButton.style.position = 'absolute';
restartButton.style.top = canvas.getBoundingClientRect().top + canvas.height / 2 + 100 + 'px';
restartButton.style.left = canvas.getBoundingClientRect().left + canvas.width / 2 - 50 + 'px';
document.body.appendChild(restartButton);
restartButton.addEventListener('click', () => {
// 게임을 다시 시작하는 함수
score = 0;
lives = 3;
isGameOver = false;
enemies.length = 0;
bullets.length = 0;
plane.x = canvas.width / 2 - 20; // 비행기의 위치 초기화
document.body.removeChild(restartButton); // 버튼 제거
gameLoop(); // 게임 루프 재시작
});
}
전체 코드
const canvas = document.getElementById('gameCanvas'); // HTML에서 캔버스를 가져옵니다.
const ctx = canvas.getContext('2d'); // 캔버스에서 2D 그리기 기능을 사용할 수 있게 합니다.
canvas.width = 400; // 캔버스의 너비를 400px로 설정
canvas.height = 600; // 캔버스의 높이를 600px로 설정
// 비행기 객체 설정
const plane = {
x: canvas.width / 2 - 20, // 비행기의 초기 x 위치를 캔버스 중앙에 설정
y: canvas.height - 60, // 비행기의 초기 y 위치를 캔버스 하단에 설정
width: 40, // 비행기의 너비
height: 40, // 비행기의 높이
speed: 5, // 비행기의 이동 속도
moveLeft: false, // 왼쪽으로 이동 중인지 여부
moveRight: false, // 오른쪽으로 이동 중인지 여부
isInvincible: false, // 무적 상태 여부
isVisible: true, // 비행기가 보이는지 여부
};
let score = 0; // 현재 점수를 저장할 변수
let lives = 3; // 비행기의 목숨 개수를 저장할 변수
let isGameOver = false; // 게임 오버 상태를 추적할 변수
// 비행기 그리기 함수
function drawPlane() {
ctx.fillStyle = plane.isInvincible ? 'rgba(255, 255, 255, 0.5)' : 'white'; // 무적 상태일 때 비행기를 반투명하게 그리기
ctx.fillRect(plane.x, plane.y, plane.width, plane.height); // 비행기를 직사각형으로 그립니다.
}
// 비행기 위치 업데이트 함수
function update() {
if (plane.moveLeft && plane.x > 0) { // 왼쪽으로 이동 중이고 캔버스를 벗어나지 않았을 때
plane.x -= plane.speed; // 왼쪽으로 이동
}
if (plane.moveRight && plane.x + plane.width < canvas.width) { // 오른쪽으로 이동 중이고 캔버스를 벗어나지 않았을 때
plane.x += plane.speed; // 오른쪽으로 이동
}
}
const enemies = []; // 적 배열을 선언하여 다수의 적을 관리
// 적 객체를 생성하는 함수
function createEnemy() {
const enemy = {
x: Math.random() * (canvas.width - 40), // 적의 X 위치를 랜덤으로 설정
y: 0, // 적의 초기 Y 위치는 화면 위
width: 40, // 적의 너비
height: 40, // 적의 높이
speed: 2, // 적이 내려오는 속도
};
enemies.push(enemy); // 적을 배열에 추가
}
// 적을 그리는 함수
function drawEnemies() {
enemies.forEach((enemy) => {
ctx.fillStyle = 'red'; // 적의 색상을 빨간색으로 설정
ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height); // 적을 그립니다.
});
}
// 적의 위치를 업데이트하는 함수
function updateEnemies() {
enemies.forEach((enemy, index) => {
enemy.y += enemy.speed; // 적이 아래로 이동
// 적이 화면 아래로 나가면 배열에서 제거
if (enemy.y > canvas.height) {
enemies.splice(index, 1); // 적 제거
}
});
}
const bullets = []; // 비행기에서 발사할 총알을 담을 배열
// 총알을 발사하는 함수
function shootBullet() {
const bullet = {
x: plane.x + plane.width / 2 - 5, // 비행기 중앙에서 발사
y: plane.y, // 비행기 위치에서 발사
width: 5, // 총알 너비
height: 10, // 총알 높이
speed: 7, // 총알 속도
};
bullets.push(bullet); // 총알 배열에 추가
}
// 총알을 그리는 함수
function drawBullets() {
bullets.forEach((bullet) => {
ctx.fillStyle = 'yellow'; // 총알 색상 설정
ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height); // 총알 그리기
});
}
// 총알의 위치를 업데이트하는 함수
function updateBullets() {
bullets.forEach((bullet, index) => {
bullet.y -= bullet.speed; // 총알이 위로 올라감
// 총알이 화면을 벗어나면 배열에서 제거
if (bullet.y < 0) {
bullets.splice(index, 1); // 총알 제거
}
});
}
// 일정 시간마다 적이 생성되도록 설정 (적이 계속 등장)
setInterval(createEnemy, 1000); // 1초마다 적을 생성
// 키보드 입력 처리: 키를 눌렀을 때
window.addEventListener('keydown', function(e) {
if (e.code === 'ArrowLeft') { // 왼쪽 화살표 키가 눌렸을 때
plane.moveLeft = true; // 왼쪽 이동을 시작
}
if (e.code === 'ArrowRight') { // 오른쪽 화살표 키가 눌렸을 때
plane.moveRight = true; // 오른쪽 이동을 시작
}
if (e.code === 'Space') { // 스페이스바를 누르면 총알 발사
shootBullet(); // 총알 발사 함수 호출
}
});
// 키보드 입력 처리: 키를 뗐을 때
window.addEventListener('keyup', function(e) {
if (e.code === 'ArrowLeft') { // 왼쪽 화살표 키를 뗐을 때
plane.moveLeft = false; // 왼쪽 이동을 멈춤
}
if (e.code === 'ArrowRight') { // 오른쪽 화살표 키를 뗐을 때
plane.moveRight = false; // 오른쪽 이동을 멈춤
}
});
// 충돌 감지 함수 (총알과 적의 충돌, 적과 비행기의 충돌을 처리)
function checkCollision(obj1, obj2) {
// 두 객체가 겹쳤는지 여부를 반환
return (
obj1.x < obj2.x + obj2.width &&
obj1.x + obj1.width > obj2.x &&
obj1.y < obj2.y + obj2.height &&
obj1.y + obj1.height > obj2.y
);
}
// 적과 총알 및 비행기의 충돌을 확인하고 처리하는 함수
function handleCollisions() {
bullets.forEach((bullet, bulletIndex) => {
enemies.forEach((enemy, enemyIndex) => {
// 총알과 적의 충돌 처리
if (checkCollision(bullet, enemy)) {
bullets.splice(bulletIndex, 1); // 총알 제거
enemies.splice(enemyIndex, 1); // 적 제거
score += 10; // 점수 증가
}
});
});
enemies.forEach((enemy) => {
// 적과 비행기의 충돌 처리
if (checkCollision(enemy, plane) && !plane.isInvincible) {
lives -= 1; // 목숨 감소
plane.isInvincible = true; // 무적 상태로 전환
let blinkCount = 0;
const blinkInterval = setInterval(() => {
plane.isVisible = !plane.isVisible; // 깜빡임 효과
blinkCount++;
if (blinkCount > 5) { // 깜빡임 횟수 설정
clearInterval(blinkInterval);
plane.isVisible = true; // 원래 상태로 복구
plane.isInvincible = false; // 무적 상태 해제
}
}, 200); // 0.2초마다 깜빡임
// 목숨이 0이 되면 게임 오버
if (lives <= 0) {
isGameOver = true;
}
}
});
}
// 게임 오버를 처리하는 함수
function gameOver() {
ctx.fillStyle = 'red'; // 게임 오버 메시지 색상 설정
ctx.font = '48px Arial'; // 폰트 설정
ctx.fillText('Game Over', canvas.width / 2 - 120, canvas.height / 2); // 화면에 메시지 표시
ctx.fillText('Score: ' + score, canvas.width / 2 - 80, canvas.height / 2 + 50); // 최종 점수 표시
// 다시 시작 버튼 생성
const restartButton = document.createElement('button');
restartButton.innerText = '다시 시작';
restartButton.style.position = 'absolute';
restartButton.style.top = canvas.getBoundingClientRect().top + canvas.height / 2 + 100 + 'px';
restartButton.style.left = canvas.getBoundingClientRect().left + canvas.width / 2 - 50 + 'px';
document.body.appendChild(restartButton);
restartButton.addEventListener('click', () => {
// 게임을 다시 시작하는 함수
score = 0;
lives = 3;
isGameOver = false;
enemies.length = 0;
bullets.length = 0;
plane.x = canvas.width / 2 - 20; // 비행기의 위치 초기화
document.body.removeChild(restartButton); // 버튼 제거
gameLoop(); // 게임 루프 재시작
});
}
// 게임 루프 함수
function gameLoop() {
if (isGameOver) {
gameOver(); // 게임 오버 시 메시지 표시
return; // 더 이상 게임을 진행하지 않음
}
ctx.clearRect(0, 0, canvas.width, canvas.height); // 캔버스 전체를 지워서 이전 프레임의 그림을 지움
if (plane.isVisible !== false) {
drawPlane(); // 비행기 그리기
}
update(); // 비행기 위치 업데이트
drawEnemies(); // 적 그리기
updateEnemies(); // 적 위치 업데이트
drawBullets(); // 총알 그리기
updateBullets(); // 총알 위치 업데이트
handleCollisions(); // 충돌 감지 및 처리
// 점수 및 목숨 표시
ctx.fillStyle = 'white';
ctx.font = '20px Arial';
ctx.fillText('Score: ' + score, 10, 30); // 점수 표시
ctx.fillText('Lives: ' + lives, 10, 60); // 목숨 표시
requestAnimationFrame(gameLoop); // 다음 프레임 호출
}
gameLoop();
'JavaScript' 카테고리의 다른 글
[JavaScript] 스네이크 게임 완성도 올리기 (0) | 2024.10.25 |
---|---|
[JavaScript] 스네이크 게임 만들기 (4) | 2024.10.19 |
[JavaScript] 비행기 슈팅 게임 만들기 - (4) 충돌 감지와 게임 오버 (2) | 2024.10.04 |
[JavaScript] 비행기 슈팅 게임 만들기 - (3) 총알 발사하기 (2) | 2024.09.28 |
[JavaScript] 비행기 슈팅 게임 만들기 - (2) 적 생성하기 (2) | 2024.09.21 |