Web Dev/JavaScript :: 자바스크립트

자바스크립트로 게임만들기 (4/7) 뱀 게임 - 뭘 배웠지?

HJPlumtree 2020. 6. 4. 23:35

이 글을 먼저 읽어보는걸 추천 드리고, 초보자라면 첫 번째 게임부터 따라서 만들어 보는걸 추천합니다.

https://forgottenknowledge.tistory.com/entry/%EA%B2%8C%EC%9E%84-%EB%A7%8C%EB%93%A4%EB%A9%B0-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%B0%B0%EC%9A%B0%EA%B8%B007

 

게임 만들며 자바스크립트 배우기(0/7)

웹 개발을 독학하며 배우고 있습니다. FreeCodeCamp 커리큘럼을 따라가고 있고, 6개의 인증서를 다 받으면 풀스택이 될 수 있다고 하네요. 2번째 인증서까지 받았고 다음 부트스트랩으로 넘어가기전

forgottenknowledge.tistory.com

 

이번에는 뱀(Snake) 게임 입니다.

이런결과물이 나옵니다.

 

앞선 게임들과 동일하게 CSS는 최소화 합니다.

원하는 테마를 생각하고 만들어보는게 좋은 연습이 될 것 같습니다.

저는 고흐의 시선(Gogh's Vision)으로 바꿔서 만들었습니다.

 

여기서는 처음 뱀 게임의 코드를 소개하고, 주석으로 각각 설명을 달았습니다.

사용하는 에디터(VSCode, Sublime 등)에 복사해서 보면 훨씬 보기 싶습니다.

 

고흐의 시각(Gogh's Vision)은 Github에서 코드를 확인할 수 있습니다.

링크 ▶ https://github.com/ermis0704/Goghs-Vision

 

코딩에 들어가기전 준비물

snake.html / snake.css / snake.js 만 있으면 됩니다.

 

밑의 함수를 사용합니다

미리 알고 있으면 편하게 볼 수 있을겁니다.

• querySelector()

• addEventListener()

• setInterval()

• keyCodes

• pop()

• unshift()

• push()

• classList.contains()

• classList.add()

• classList.remove()

 

그럼 HTML부터 시작합니다!

 

HTML

<html>
  <head>
    <meta charset="utf-8">
    <!--스타일시트, 자바스크립트 연결-->
    <script src="snake.js"></script>
    <link rel="stylesheet" href="snake.css">
    <title>Snake Game</title>
  </head>
  <body>

    <!--게임시작 버튼-->
    <button class='start'>Start/Restart</button>

    <div class='score'>Score:<span>0</span></div>
    
    <!-- 전체 100개의 div필요, 10x10 크기 -->
    <div class='grid'>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
    </div>
  </body>
</html>

 

 

CSS

//게임 판을 flex, 넓이, 높이를 정해 만든다
.grid {
  display:flex;
  width: 200px;
  height: 200px;
  flex-flow: row wrap;
  border: solid;
}

//앞에서 만든 div 100개의 칸 크기 지정
.grid div{
  width: 20px;
  height: 20px;
}

//뱀의 색상
.snake {
  background-color: blue;
}

사과 색상
.apple {
  background-color: red;
}​

 

 

JAVASCRIPT

//기본 HTML이 구성되면 실행되도록 모든 자바스크립트 코드를 감싼다
document.addEventListener('DOMContentLoaded', () =>{

  //게임의 칸 100개 가져오기
  const squares = document.querySelectorAll('.grid div');
  
  //스코어 가져오고
  const scoreDisplay = document.querySelector('.score span');
  
  //버튼 가져오기
  const startBtn = document.querySelector('.start');

  //밑으로 한 칸 가기 위한 숫자. 한 줄에 div가 10개 있기때문에 1칸 밑으로 가려면 10을 더해주면 된다. 
  const width = 10;
  
  //사과 위치가 저장될 곳
  let appleIndex = 0;
  
  //뱀의 시작 위치
  let currentSnake = [2, 1, 0]
  
  //방향을 정하는 변수
  let direction = 1;
  
  //점수 변수
  let score = 0;
  
  //스피드 변수, 점점 빨라 질것이기 때문에 1보다 낮은 0.9 사용
  let speed = 0.9;
  
  //뱀을 정해진 시간마다 움직이게 할 변수
  let intervalTime = 0;
  
  //setInterval을 위한 변수
  let interval = 0;

  //게임시작시 실행될 함수
  function startGame() {
  
    //이 함수 주로 값들을 기본값으로 바꿔주는 내용을 가지고 있다.
    //처음 시작할 때는 괜찮지만, 재시작을 하면 남아있는 사과, 뱀을 없애준다.
    currentSnake.forEach(index => squares[index].classList.remove('snake'))
    squares[appleIndex].classList.remove('apple');
    clearInterval(interval);
    score = 0;
    
    //사과배치 함수 실행
    randomApple();
    direction = 1;
    scoreDisplay.innerHTML = score;
    intervalTime = 1000;
    currentSnake = [2, 1, 0];
    currentIndex = 0;
    currentSnake.forEach(index => squares[index].classList.add('snake'));
    
    //intervalTime 즉 1초마다 moveOutComes() 함수를 실행한다.
    interval = setInterval(moveOutComes, intervalTime);
  }

  //밑의 control() 함수부터 보는 걸 추천합니다
  //중요한 뱀의 움직임을 담당할 함수
  function moveOutComes() {
  
    //벽에 부딪히면 게임이 게임이 끝나도록 하는 if문
    if(
    
      //뱀이 바닥을 부딪혔을때
      //맨 밑에 div 10개는 90부터 99, 여기에 10을 더하면 100~109 즉 div 밖이다
      //그리고 방향이 밑을 향하고 있을때 성립할 때 죽는다.
      (currentSnake[0] + width >= (width * width) && (direction === width)) ||
      
      //오른쪽 벽에 부딪혔을때
      //div의 맨 오른쪽은 9, 19, 29, ... 99,
      //여기에 나머지를 구하는 '%'를 사용해서 값이 9(width -1)가 나오고 &&
      //방향이 오른쪽(1)이면 죽음
      (currentSnake[0] % width === width -1 && direction === 1) ||
      
      //같은 방식으로 왼쪽,
      (currentSnake[0] % width === 0 && direction === -1) ||
      
      //위의 벽도 부딪히면 죽는다
      (currentSnake[0] - width < 0 && direction === -width) ||
      
      //한 가지더 뱀의 머리가 뱀의 몸통을 터치하면 죽는 방식도 넣어둔다.
      squares[currentSnake[0] + direction].classList.contains('snake')
    ) {
    
      //위의 if문에 해당되면 게임을 중지(종료)
      return clearInterval(interval);
    }

    //↓↓↓여기부턴 뱀이 움직이는 동작
    
    //시작시 뱀은 [2,1,0] 즉 div 0, 1, 2에 있다.
    //tail 변수에 pop()으로 0을 떼어서 넣고, 뱀의 배열도 [2, 1]로 만든다
    const tail = currentSnake.pop();
    
    //div의 tail 자리인 0에 뱀 클래스를 제거함으로 화면에 없어진다
    squares[tail].classList.remove('snake');
    
    //뱀의 배열 맨 앞에 머리와 방향(direction)을 더해 unshift()로 집어넣는다.
    //예를들어 오른쪽으로 가면 현재 머리(2)와 방향(1)을 더해 3을 집어넣어 [3, 2, 1]이 된다
    //아직 배열에서 넣었을 뿐, 화면에 표시하지는 않았다.
    currentSnake.unshift(currentSnake[0] + direction);

    //뱀의 머리가 사과를 만났을 때 if문
    //머리가 사과를 가진 div에 도달 했으면 실행
    if(squares[currentSnake[0]].classList.contains('apple')) {
    
      //우선 사과를 div에서 없애 화면에 사라지게 하고
      squares[currentSnake[0]].classList.remove('apple');
      
      //div의 tail이 있던 자리에 뱀을 하나 추가한다
      squares[tail].classList.add('snake');
      
      //뱀 배열에도 tail자리를 넣어준다
      currentSnake.push(tail);
      
      //새로 사과 배치
      randomApple()
      
      //점수를 1점 올리고
      score++;
      
      //스코어에 표시
      scoreDisplay.textContent = score;
      
      //게임을 잠깐 중단을 하고
      clearInterval(interval);
      
      //1000이었던 intervalTime에 앞에서 정한 0.9의 speed 값을 곱해 900로 만든다.
      //1초에서 0.9초로 줄어든다
      intervalTime *= speed;
      
      //빨라진 시간으로 현재 함수인 moveOutComes()을 실행 -> 뱀이 빨리 움직인다 
      interval = setInterval(moveOutComes, intervalTime);
    }
    
    //앞서 unshift()로 넣었던 머리 부분에 div에 뱀의 클래스를 추가함으로 화면에 표시
    squares[currentSnake[0]].classList.add('snake');
  }

  //사과를 랜덤하게 배치하는 함수
  function randomApple() {
    
    //처음 한번 실행해야 되서, do {} while 반복문을 사용
    do {
      
      //div의 길이(100개)와 0~1의 랜덤숫자를 곱하고, Math.floor로 내림해준다.
      //그래서 나올 수 있는 수는 0~99
      appleIndex = Math.floor(Math.random() * squares.length);
      
      //사과 인덱스가 snake 클래스를 가지고 있으면 또 실행된다.
      //결국 snake 클래스를 가지고 있지않은 div를 찾기 위함이다
    } while(squares[appleIndex].classList.contains('snake'));
    
    //나온 위치에 사과를 표시
    squares[appleIndex].classList.add('apple');
  }

  //게임키를 받아서 뱀의 방향을 정해주는 함수
  //키보드의 모든 문자, 숫자에는 번호가 정해져있다.
  //사이트 https://keycode.info에서 확인가능
  function control(e) {

    //오른쪽
    //오른쪽의 code는 39다 그래서 39가 눌리면 방향을 1로 바꿔준다.
    //밑의 위, 왼쪽, 아래도 같은 방식이다.
    if(e.keyCode === 39) {
      direction = 1;

      //up
    } else if(e.keyCode === 38) {
      direction = -width;

      //left
    } else if(e.keyCode === 37) {
      direction = -1;

      //down
    } else if(e.keyCode === 40) {
      direction = +width;
    }
  }
  
  //키가 눌렸다 올라갈 때 마다 control() 함수가 실행되서 방향(direction)이 바뀐다.
  document.addEventListener('keyup', control);
  
  //게임시작 버튼을 클릭되면 startGame() 함수가 실행
  startBtn.addEventListener('click', startGame);




})