본문 바로가기
JavaScript

[JavaScript] 스네이크 게임 완성도 올리기

by teamnova 2024. 10. 25.
728x90

안녕하세요.

오늘은 지난 시간에 만들었던 스네이크 게임에 몇가지 요소들을 추가해서 완성도를 높여보겠습니다.

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

2024.10.19 - [JavaScript] - [JavaScript] 스네이크 게임 만들기

 

[JavaScript] 스네이크 게임 만들기

안녕하세요!오늘은 자바스크립트를 사용해 클래식한 스네이크 게임을 만드는 방법을 알려드리겠습니다. 1. index.html 작성  canvas 태그를 사용해 스네이크 게임을 그릴 공간을 마련합니다.id를 통

stickode.tistory.com

1단계: 점수 시스템 추가하기

스네이크 게임의 재미 중 하나는 얼마나 높은 점수를 기록할 수 있는지 도전하는 것입니다. 이번에는 점수 시스템을 추가해 게임을 한층 더 흥미롭게 만들어보겠습니다.

 

1. index.html 수정

div 태그를 사용해 점수를 표시할 영역을 추가합니다.

<body>
    <canvas id="gameCanvas" width="400" height="400"></canvas>
    <div id="score">Score: 0</div>
    <script src="snake.js"></script>
</body>

 

 

2. snake.js 에 점수 관련 코드 추가

score 변수를 추가해 점수를 관리합니다. 음식을 먹을 때마다 score를 10점씩 증가시키고, updateScore() 함수로 점수를 화면에 표시합니다.

let score = 0; // 점수 초기화

function updateScore() {
    document.getElementById('score').innerText = 'Score: ' + score;
}

function checkFoodCollision() {
    if (snake[0].x === food.x && snake[0].y === food.y) {
        growing = true;
        score += 10; // 점수 증가
        updateScore(); // 점수 업데이트
        placeFood();
    }
}

2단계: 속도 조절하기

스네이크 게임은 시간이 지날수록 어려워져야 재미있겠죠? 이제 스네이크의 속도가 점점 빨라지도록 만들어봅시다.

  • speed 변수를 사용해 게임 루프의 속도를 관리합니다. 음식을 먹을 때마다 speed를 감소시켜 스네이크가 더 빨리 움직이도록 하였습니다.
  • 속도가 너무 빨라져 게임이 너무 어려워지는 것을 방지하기 위해 최소 속도를 50ms로 제한합니다.

 

 

let speed = 100; // 초기 속도 (0.1초)

function gameLoop() {
    if (gameOver) {
        displayGameOver();
        return;
    }

    moveSnake();
    checkFoodCollision();
    checkWallCollision();
    draw();
    checkSelfCollision();

    setTimeout(gameLoop, speed);
}

function checkFoodCollision() {
    if (snake[0].x === food.x && snake[0].y === food.y) {
        growing = true;
        score += 10;
        updateScore();
        placeFood();
        if (speed > 50) speed -= 5; // 속도를 점점 빠르게
    }
}

 

 

3단계: 시각적 효과 개선하기

게임의 시각적 효과를 개선하여 좀 더 재미있게 만들어볼까요? 스네이크의 머리를 더 눈에 띄게 하고, 음식을 특별한 모양으로 만들어봅시다.

  • 스네이크의 머리를 더 진한 초록색으로 그려 방향을 쉽게 알 수 있도록 했습니다.
  • drawFood() 함수에서는 사각형 대신 원형으로 음식을 그려 시각적인 변화를 주었습니다.
unction drawSnake() {
    snake.forEach((part, index) => {
        if (index === 0) {
            ctx.fillStyle = 'darkgreen'; // 머리는 더 진한 색으로
        } else {
            ctx.fillStyle = 'green';
        }
        ctx.fillRect(part.x, part.y, gridSize, gridSize);
    });
}

function drawFood() {
    ctx.fillStyle = 'orange';
    ctx.beginPath();
    ctx.arc(food.x + gridSize / 2, food.y + gridSize / 2, gridSize / 2, 0, 2 * Math.PI); // 원 모양의 음식
    ctx.fill();
}

 

4단계: 게임 오버 시 리스타트 기능 추가

게임 오버 후에 다시 게임을 시작할 수 있는 기능을 추가해볼까요? 사용자가 게임을 여러 번 도전할 수 있게 해줍니다.

 

function displayGameOver() {
    ctx.fillStyle = 'black';
    ctx.font = '30px Arial';
    ctx.fillText('Game Over', canvas.width / 2 - 80, canvas.height / 2);
    ctx.font = '20px Arial';
    ctx.fillText('Press R to Restart', canvas.width / 2 - 100, canvas.height / 2 + 30);
}

function restartGame() {
    snake = [{ x: 100, y: 100 }];
    food = { x: 200, y: 200 };
    dx = gridSize;
    dy = 0;
    score = 0;
    speed = 100;
    gameOver = false;
    updateScore();
    gameLoop();
}

document.addEventListener('keydown', function(event) {
    if (event.keyCode === 82 && gameOver) { // R 키로 게임 재시작
        restartGame();
    }
});

 

 

  • 게임 오버 시 Press R to Restart 메시지를 추가했습니다.
  • restartGame() 함수는 게임을 초기화하고 다시 시작하도록 설정합니다. keydown 이벤트 리스너에서 R 키를 눌렀을 때 게임이 리셋되도록 했습니다.

 

최종 코드

// 캔버스 설정
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');

// 게임 설정
const gridSize = 20; // 한 칸의 크기
let snake = [{ x: 100, y: 100 }]; // 스네이크의 초기 위치
let food = { x: 200, y: 200 }; // 음식의 초기 위치
let dx = gridSize; // x축 이동 방향 (오른쪽으로 시작)
let dy = 0; // y축 이동 방향 (세로 이동 없음)
let gameOver = false; // 게임 종료 상태 변수
let growing = false; // 스네이크가 자라는 중인지 상태를 관리
let score = 0; // 점수 초기화
let speed = 100; // 초기 속도 (0.1초)

// 스네이크와 음식을 그리는 함수
function drawSnake() {
    snake.forEach((part, index) => {
        if (index === 0) {
            ctx.fillStyle = 'darkgreen'; // 머리는 더 진한 색으로
        } else {
            ctx.fillStyle = 'green';
        }
        ctx.fillRect(part.x, part.y, gridSize, gridSize);
    });
}

function drawFood() {
    ctx.fillStyle = 'orange';
    ctx.beginPath();
    ctx.arc(food.x + gridSize / 2, food.y + gridSize / 2, gridSize / 2, 0, 2 * Math.PI); // 원 모양의 음식
    ctx.fill();
}

// 초기 화면 그리기
function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height); // 화면 지우기
    drawSnake();
    drawFood();
}

// 스네이크 이동 함수
function moveSnake() {
    const head = { x: snake[0].x + dx, y: snake[0].y + dy };
    snake.unshift(head);

    // 스네이크가 자라지 않는다면 꼬리 제거
    if (!growing) {
        snake.pop();
    } else {
        growing = false; // 자라기를 완료했으니 다시 false로 설정
    }
}

// 방향 변경 함수
function changeDirection(event) {
    const keyPressed = event.keyCode;

    const LEFT = 37;
    const UP = 38;
    const RIGHT = 39;
    const DOWN = 40;

    if (keyPressed === LEFT && dx === 0) {
        dx = -gridSize;
        dy = 0;
    } else if (keyPressed === UP && dy === 0) {
        dx = 0;
        dy = -gridSize;
    } else if (keyPressed === RIGHT && dx === 0) {
        dx = gridSize;
        dy = 0;
    } else if (keyPressed === DOWN && dy === 0) {
        dx = 0;
        dy = gridSize;
    }
}

// 키보드 이벤트 리스너 추가
document.addEventListener('keydown', changeDirection);

document.addEventListener('keydown', function(event) {
    if (event.keyCode === 82 && gameOver) { // R 키로 게임 재시작
        restartGame();
    }
});

// 음식 먹기 함수
function checkFoodCollision() {
    if (snake[0].x === food.x && snake[0].y === food.y) {
        growing = true;
        score += 10; // 점수 증가
        updateScore(); // 점수 업데이트
        placeFood();
        if (speed > 50) speed -= 5; // 속도를 점점 빠르게
    }
}

// 점수 업데이트 함수
function updateScore() {
    document.getElementById('score').innerText = 'Score: ' + score;
}

// 랜덤 위치에 음식 배치
function placeFood() {
    food.x = Math.floor(Math.random() * canvas.width / gridSize) * gridSize;
    food.y = Math.floor(Math.random() * canvas.height / gridSize) * gridSize;
}

// 벽 충돌 확인 함수
function checkWallCollision() {
    const head = snake[0];
    if (
        head.x < 0 || head.x >= canvas.width ||
        head.y < 0 || head.y >= canvas.height
    ) {
        gameOver = true;
    }
}

// 몸 충돌 확인 함수
function checkSelfCollision() {
    const head = snake[0];
    for (let i = 1; i < snake.length; i++) {
        if (head.x === snake[i].x && head.y === snake[i].y) {
            gameOver = true;
        }
    }
}

// 게임 오버 메시지 표시 함수
function displayGameOver() {
    ctx.fillStyle = 'black';
    ctx.font = '30px Arial';
    ctx.fillText('Game Over', canvas.width / 2 - 80, canvas.height / 2);
    ctx.font = '20px Arial';
    ctx.fillText('Press R to Restart', canvas.width / 2 - 100, canvas.height / 2 + 30);
}

// 게임 재시작 함수
function restartGame() {
    snake = [{ x: 100, y: 100 }];
    food = { x: 200, y: 200 };
    dx = gridSize;
    dy = 0;
    score = 0;
    speed = 100;
    gameOver = false;
    updateScore();
    gameLoop();
}

// 게임 루프
function gameLoop() {
    if (gameOver) {
        displayGameOver();
        return;
    }

    moveSnake();
    checkFoodCollision();
    checkWallCollision();
    draw();
    checkSelfCollision();

    setTimeout(gameLoop, speed);
}

gameLoop();