JS Project 플러피 버드 제작 과정

김민찬·2022년 4월 8일
1

JS Project

목록 보기
2/3
post-thumbnail

javaScript로 간단한 게임을 만들면서 로직 구현도 해보고 js에 더 익숙해질 겸 플러피 버드를 만들기로 했다.

플러피 버드는 클릭만으로 플레이하는 간단한 게임으로 오래 전 굉장히 유행했었다.

게임 제작에 입문하거나 가볍게 만드는 작품으로 많이 이용되서 리소스도 많은 편이나, 직접 만드는 게 더 재밌을 거 같아서 직접 그렸다.

닭 캐릭터와 배경, 위/아래 장애물만 그리면 되서 그림은 금방 그렸다. html로 게임화면으로 사용할 태그를 넣어주고, 그 안에 캐릭터와 장애물을 배치했다.

<div class="background">
      <div class="chiken"></div>

      <div class="down-wall"></div>
      <div class="down-wall"></div>
      <div class="down-wall"></div>

      <div class="top-wall"></div>
      <div class="top-wall"></div>
      <div class="top-wall"></div>
</div>

css로 각각, background에 배경 이미지, chiken에 캐릭터 이미지, wall에 각각 위 아래 이미지를 적용시켰다.

중력 적용

닭 캐릭터가 중력을 받아 밑으로 떨어지도록 하는 코드를 작성, 자연스럽게 떨어지는 것을 구현하기 위해 닭의 bottom에 계속 점프력을 담은 변수를 더해준다. 그리고 점프력에 중력값을 빼서 중력을 받는 것처럼 보일 수 있게 코드를 작성했다.

//이런식으로 작성
let jumpPower = 3;
let gravity = 0.3;
chikenBottom += jumpPower;
jumpPower -= gravity;
chiken.style.bottom = `${chikenBottom}px`;

점프 구현

클릭 이벤트를 받아서 클릭 시 점프력에 다시 값을 재할당하면 캐릭터가 위로 점프하듯이 위치가 올라간다.

  function jump() {
    jumpPower = 6.5;
  }
  document.addEventListener("click", () => {
      jump();
  });

장애물이 움직이고, 캐릭터가 그 사이를 넘어가면 점수를 획득 할 수 있게 html을 추가로 작성했다.

      <div class="chiken"></div>

      <div class="down-wall anim"></div>
      <div class="down-wall anim"></div>
      <div class="down-wall anim"></div>

      <div class="top-wall check anim"></div>
      <div class="top-wall check anim"></div>
      <div class="top-wall check anim"></div>
      <span class="score">Score : </span>
      <div class="start">Touch!</div>

장애물 애니메이션 구현

장애물은 css 애니메이션으로 움직이게 하고, js를 통해 위치를 랜덤하게 바꿔줬다.

.top-wall.anim,
.down-wall.anim {
  animation: wallmove 3s infinite linear;
}
@keyframes wallmove {
  0% {
  }
  100% {
    left: -300px;
  }
}

animationiteration속성을 이용해 애니메이션이 끝날때, 랜덤하게 위치를 바꾸도록 한다.

 topWalls.forEach((topWall, idx) => {
    topWall.addEventListener("animationiteration", () => {
      randomPos = Math.floor(Math.random() * 160 - 40) + 40;
      topWall.style.top = `-${randomPos}px`;
      downWalls[idx].style.top = `${-randomPos + 530}px`;
      if (topWall.classList.value === "top-wall anim") {
        topWall.classList.add("check");
      }
    });
  });

위에 달린 장애물을 기준으로 애니메이션이 끝나면 랜덤한 위치로 바꿔준다. 그 뒤, 매칭되는 밑 장애물의 위치를 특정 길이만큼 움직인다. 아래 check는 점수를 위한 클래스이다.

게임 오버와 점수 획득 구현

요소의 위치값을 얻기 위해 offset속성을 사용할 수 있다. 장애물에는 check라는 클래스를 줘서 점수 획득 후, check를 remove로 없애주어 중복 점수를 방지했다. 그리고 애니메이션이 끝나면 다시 check속성을 add시켜준다. (위 코드 참고)

 topWalls.forEach((topWall) => {
        if (
          chiken.offsetLeft + chiken.offsetWidth >
          topWall.offsetLeft + topWall.offsetWidth
        ) {
          if (topWall.classList.contains("check")) {
            Score++;
            scoreBoard.innerText = `Score : ${Score}`;
            topWall.classList.remove("check");
          }
        }
      });
    }

캐릭터의 right값과 장애물의 right값을 구해서, 캐릭터의 right값이 더 커지면(즉 캐릭터가 장애물을 지나갔다면) Score를 증가 시킨다.

게임 오버

offset속성으로 각각의 위치를 체크할 수 있으니 장애물과 닿았을 때 게임오버가 되도록 구현해줄 수 있다.

ex) 캐릭터의 top이 위 장애물의 bottom보다 작으면서 
캐릭터의 right가 위 장애물의 left보다 크다면 충돌했음을 알 수 있다.
topWalls.forEach((topwall) => {
        if (
          chiken.offsetLeft <= topwall.offsetLeft + topwall.offsetWidth &&
          chiken.offsetLeft + chiken.offsetWidth >= topwall.offsetLeft &&
          chiken.offsetTop <= topwall.offsetTop + topwall.offsetHeight &&
          chiken.offsetTop + chiken.offsetHeight >= topwall.offsetTop
        ) {
          gameOver();
        }
      });

추락하거나, 화면 밖으로 나가게 되면 게임오버

    if (chiken.offsetTop + chiken.offsetHeight > 600 || chiken.offsetTop < 0) {
      gameOver();
    }

게임결과

게임 종료 시 점수와 재시작을 할 수 있는 팝업을 만들어 게임 로직을 완성

  <body>
    <div class="background">
      <div class="chiken"></div>
      <div class="down-wall anim"></div>
      <div class="down-wall anim"></div>
      <div class="down-wall anim"></div>
      <div class="top-wall check anim"></div>
      <div class="top-wall check anim"></div>
      <div class="top-wall check anim"></div>
      <span class="score">Score : </span>
      <div class="start">Touch!</div>
      <div class="dimd">
        <div class="text-game-over">
          <p>게임종료!</p>
        </div>
        <div class="score-board">
          <p class="best-score"></p>
          <p class="text-score">점수 :</p>
          <div class="wrap-btn">
            <button type="button" class="btn-restart">재시작</button>
          </div>
        </div>
      </div>
    </div>
  </body>

게임 종료 시 최고 점수 갱신

  if (bestScore < Score) {
      bestScore = Score;
    }

animationPlayState로 애니메이션 멈춤

topWalls.forEach((topwall) => {
      topwall.style.animationPlayState = "paused";
    });
downWalls.forEach((downwall) => {
      downwall.style.animationPlayState = "paused";
    });

종료 팝업켜기

document.querySelector(".dimd").classList.add("on");
document.querySelector(".text-score").innerText = `SCORE : ${Score}`;
document.querySelector(".best-score").innerText = `BEST : ${bestScore}`;

그 외 종료 팝업에서 sns 공유를 추가하고 프로젝트를 마무리했다. 다 만들고나서 과정을 쓰려니 쓰기 애매한 것들도 많고 그 때 어떻게 했는지 기억이 안나서 고생했다..

전체적인 코드를 적지는 않았지만 만약 플러피버드를 만들고 싶다면 참고할 부분이 있을 것 같다.

간단한 게임이지만 신경쓸게 많고 생각보다 쉽지 않았다. 일단은 프로젝트를 종료했지만 추가적으로 랭크를 구현하는 것을 최종 목표로 생각 중이다.

게임하러 가기
https://flappychiken.netlify.app/

profile
프론트엔드 개발자로 나아가고 있는 김민찬입니다.

0개의 댓글