본문 바로가기
JavaScript

[JavaScript] 비행기 슈팅 게임 만들기 - (5) 점수, 목숨, 다시 시작 구현

by teamnova 2024. 10. 10.
728x90

안녕하세요!

지난 글에서는 비행기와 적의 충돌을 처리하고 게임 오버 상태를 구현하는 방법을 설명드렸는데요.

오늘은 지난 시간에 이어서, 점수 시스템, 목숨 시스템, 게임 오버 후 다시 시작 기능을 구현해서 게임을 더 재미있게 만들어보겠습니다.

 

 

 

이전 코드는 아래 링크를 참고해주세요.

2024.09.15 - [JavaScript] - [JavaScript] 비행기 슈팅 게임 만들기 - (4) 충돌 감지와 게임 오버

 

[JavaScript] 비행기 슈팅 게임 만들기 - (4) 충돌 감지와 게임 오버

안녕하세요.오늘은 지난 시간에 이어서, 적과 총알이 충돌하면 적을 제거하고, 적과 비행기가 충돌하면 게임 오버가 되는 기능을 구현하겠습니다.    이전 코드는 다음 링크를 통해 확인할 수

stickode.tistory.com

 

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();