스네이크 게임이란 고전게임으로 뱀이 여기저기를 돌아다니면서 사과(먹이)를 먹으면 뱀의 길이가 점점 늘어나고, 뱀이 꼬여서 몸이 부딪히면 게임 오버되는 방식으로 진행됩니다. 이 스네이크 게임의 현대화된 버전이 slither.io 라고 보시면 됩니다.

유튜브를 보다 보니 이 게임을 만드는 영상이 있어서 따라해보았습니다. 이 영상은 자바스크립트보다는 알고리즘에 중점을 둔 영상이고 말하는 속도가 빨라 알아들을 수 없어 제 수준에서는 분석이 좀 많이 필요하다고 생각하였습니다. 그래서 시리즈로 나눠 스네이크 게임을 만들어보는 글을 연재해보도록 하겠습니다.

 

HTML5 뼈대 만들기

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>

</body>
</html>

 

캔버스 만들기

먼저 캔버스를 만들어보도록 하겠습니다. 외부 라이브러리를 사용하지 않는 이상 자바에서 대부분의 게임용 애니메이션은 캔버스 위에서 진행된다고 봐도 무방합니다. 캔버스를 이용해 점, 선, 면 등을 다양하게 그릴 수 있습니다. HTML 부분을 만들고 <body> 부분에 <canvas> 태그를 삽입합니다.

<body>
    <canvas id="game-canvas" width="400" height="400"></canvas>
</body>

위의 코드는 아이디로 game-canvas 를 가지는 가로, 세로 400px(픽셀)의 캔버스를 만들겠다는 의미입니다. 이 단계에서는 아무것도 보이지 않으며 이 캔버스를 자바스크립트에서 조작해 캔버스에 각종 그림을 그립니다.

 

캔버스를 자바스크립트로 가져오기

캔버스는 자바스크립트에서 다룰 수 있습니다. document.getElementById를 이용해 캔버스 DOM을 변수에 저장합니다. 그리고 그 캔버스에서 ‘2d 컨텍스트’라는 것을 가져와 변수로 저장합니다. 2d 컨텐츠를 다루는 렌더링 컨텍스트를 다룰 수 있게 합니다.3d 등 다른 컨텍스트도 있다고 합니다만 여기서 만드는 스네이크 게임은 2차원 게임이므로 2d 컨텐츠를 가져옵니다.

<body>
    <canvas id="game-canvas" width="400" height="400"></canvas>
    <script>
        
        const canvas = document.getElementById("game-canvas")
        const ctx = canvas.getContext("2d")
     
    </script>
</body>

 

키보드 이벤트 부여, 15fps로 게임 렌더링

먼저 블럭을 움직이려면 키보드 방향키에 이벤트가 부여되야 합니다. 이것은 addEventListener로 지정합니다. 그리고 2d 컨텍스트는 정지된 그림을 렌더링하므로 게임을 초당 15프레임으로 다시 그리도록 지정하여 애니메이션을 구현하도록 합니다. setInterval을 이용하면 해당 ms(밀리세컨드)마다 지정된 함수를 반복적으로 실행합니다.

window.onload = () => {
    document.addEventListener("keydown", keyPush)
    
    setInterval(game, 1000 / 15)
}

1000ms 는 1초이며, 이것을 15로 나눈 값은 1초당 15번 실행하겠다는 의미입니다.

keyPush, game 함수는 이후에 구현합니다.

 

x, y 포지션 변수 생성, 그리드 사이즈 및 타일 개수 변수 생성

현재 블럭의 위치를 저장하는 위치 변수를 생성합니다. 위치 변수는 값이 지속적으로 변해야 하므로 let으로 선언합니다.

또한 캔버스의 그리드 사이즈를 px로 선언하고, 캔버스의 타일 개수도 설정합니다. 20px의 그리드가 20개 있으면 20*20 = 400px가 됩니다.

 

let positionX = 0, positionY = 0
const gridSize = 20, tileCount = 20

 

키보드 이벤트의 keyPush 함수 구현

방향키를 누르면 블럭이 ‘한 칸’ 이동하는 이벤트 함수를 구현하도록 하겠습니다.

function keyPush(evt) {
    switch(evt.keyCode) {
        case 37:
            positionX += -1;
            positionY += 0;
            break;
        case 38:
            positionX += 0;
            positionY += -1;
            break;
        case 39:
            positionX += 1;
            positionY += 0;
            break;
        case 40:
            positionX += 0;
            positionY += 1;
            break;   
    }
}

evt.keyCode에는 현재 눌린 키코드가 들어 있습니다. 왼쪽 방향키부터 시계 방향으로 37(왼쪽), 38(위), 39(오른쪽), 40(아래)으로 할당되어 있습니다. switch 분기문을 이용해 각 코드에 대한 이벤트를 부여합니다.

왼쪽이면 x방향으로 한 칸 뒤로 가야 하므로 -1, 그 반대인 오른쪽은 x방향으로 1… 이런 식으로 움직이는 방향을 구현했습니다.

 

게임 함수 구현

15fps로 실행될 game 함수를 만듭니다.

function game() {
    
    // ........... //
}

 

캔버스 초기화 및 배경색 지정

game 함수 안에 다음 코드를 삽입합니다.

ctx.fillStyle = "black"
ctx.fillRect(0, 0, canvas.width, canvas.height)

검은색 배경의 사각형을 캔버스 전체에 그린다는 의미입니다.

이 코드는 반드시 game 함수 안에 삽입되어야 합니다. 바깥에 삽입하면 처음 한 번만 그리기 작업을 수행하는데, game 함수가 렌더링 될 때 아래와 같이 지워지지 않는 자국을 남깁니다.

 

블럭 그리기
ctx.fillStyle = "lime"
ctx.fillRect(positionX * gridSize, positionY * gridSize, gridSize - 2, gridSize - 2)

크기에서 각각-2를 하는 이유는 구분선을 표시하기 위함입니다.

via GIPHY

 

가장자리 처리하기

만약 블럭이 왼쪽 가장자리에서 왼쪽으로 이동한다면 오른쪽 끝으로, 위쪽 가장자리에서 위쪽으로 이동한다면 아래쪽 끝으로 하는 등 가장자리에서 수행할 동작을 지정합니다.

if(positionX < 0) {
    positionX = tileCount - 1
}
if(positionX > tileCount - 1) {
    positionX = 0
}
if(positionY < 0) {
    positionY = tileCount - 1
}
if(positionY > tileCount - 1) {
    positionY = 0
}

position X, Y0부터 시작하여 19(=tileCount - 1)에서 끝납니다.

via GIPHY

 

이번에 완성된 전체 코드는 다음과 같습니다.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>

<body>
    <canvas id="game-canvas" width="400" height="400"></canvas>
    <script>
        
        const canvas = document.getElementById("game-canvas")
        const ctx = canvas.getContext("2d")
            
        window.onload = () => {
            document.addEventListener("keydown", keyPush)
            
            setInterval(game, 1000 / 15)
        }
        
        
        let positionX = 0, positionY = 0
        const gridSize = 20, tileCount = 20
        
        function game() {
            
            if(positionX < 0) {
                positionX = tileCount - 1
            }
            if(positionX > tileCount - 1) {
                positionX = 0
            }
            if(positionY < 0) {
                positionY = tileCount - 1
            }
            if(positionY > tileCount - 1) {
                positionY = 0
            }
            
            ctx.fillStyle = "black"
            ctx.fillRect(0, 0, canvas.width, canvas.height)
            
            ctx.fillStyle = "lime"
            ctx.fillRect(positionX * gridSize, positionY * gridSize, gridSize - 2, gridSize - 2)
        }
        
        function keyPush(evt) {
            switch(evt.keyCode) {
                case 37:
                    positionX += -1;
                    positionY += 0;
                    break;
                case 38:
                    positionX += 0;
                    positionY += -1;
                    break;
                case 39:
                    positionX += 1;
                    positionY += 0;
                    break;
                case 40:
                    positionX += 0;
                    positionY += 1;
                    break;   
            }
        }
    </script>
</body>

</html>

 

문의 | 코멘트 또는 yoonbumtae@gmail.com


카테고리: WEB: Frontend


0개의 댓글

답글 남기기

Avatar placeholder

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다