시작하며


여태껏 리액트의 편안함에 길들여져있던 나..

위코드 사전스터디에 참여하면서 리액트는 잠시 내려놓고 자바스크립트를 다시 꼼꼼히 공부하기 시작했다. 

자바스크립트도 정말 여러 번 반복해서 보고 있는 것 같은데, 다시 보니 내가 몰랐던 부분이 정말 많았다.

어찌보면 이번이 가장 꼼꼼히 하고 있는듯!

 

몰랐던 문법을 알아가면서 직접 사용해보는 것도 재밌고, 아직은 기초적인 수준이지만 프로그래머스에서 문제들을 풀어나가는 것도 매일 느낄 수 있는 소소한 재미인 것 같다. 

나.. 자바스크립트 별로 안좋아하는 줄 알았는데.. 잘 몰라서 그런 거였어! 짱잼!

 

게다가 사전스터디 마지막 과제로 Ghost Rain 게임을 만드는데, 이 과제에서 재미와 공부를 동시에 잡을 수 있어 유익했다.

물론 처음에는 리액트와 각종 라이브러리에 의존하다 바닐라 자바스크립트로 프로젝트를 하려니 쉽지는 않았다. 

엄청 간단한 기능인 것 같은데 은근히 시간도 좀 걸리고..막힐 때도 있었지만 그걸 해결해냈을 때의 성취감이란!

 

처음에 과제로 받은 프로젝트의 예시는 아래 이미지와 같았다. 

사실 이 정도도 내가 해낼 수 있을까? 라며 걱정하기도 했지만, 그보다 "내가 게임을 만든다니!" 라는 기대감이 더 컸다.

그리고 기왕 게임을 만들 거 과제로 주어진 범위보다 추가 기능도 더 넣고, 나만의 버전으로 커스터마이징하고 싶었다.

 

만드는 과정은 쉽지 않았으나.. 암튼 우당탕탕 하다가 결과적으로 아래와 같은 이런 게임을 만들었다.

 

게임 화면


 

시작 화면

게임을 시작하기에 앞서 인트로 영상과 게임 타이틀이 나오는 시작 화면을 만들었다.

 

시작 화면을 만드는 건 처음이라서 어떻게 구현할까 고민했는데..

게임 화면 위에 z-index를 활용해 시작 화면을 올리고, start 버튼을 누르면 시작 화면이 사라지도록 구현했다.

라우터로 페이지를 이동하는 게 아닌 한 페이지 위에 얹고 사라지게 하는 방식은 다소 낯설긴 했지만,

한 화면 내에서 여러 모습을 구현할 수 있다는 게 장점으로 다가오기도 했다.

 

또, 오른쪽 윗부분에 볼륨 버튼을 넣었다.

볼륨 on 아이콘을 누르면 bgm이 플레이되면서 아이콘이 off로 바뀐다. 

그렇게 바뀐 off 아이콘을 다시 누르면 재차 bgm이 플레이된다.

배경음악을 자동으로 플레이되게 하고 pause 버튼만 넣으려고 했으나 크롬 정책상 자동재생이 막혀 있어서

플레이가 매끄럽지 않은 문제로 인해 수동으로 켰다 끄도록 구현했다.

 

오디오 플레이어는 초반에 html의 audio 태그를 사용했으나,

audio autoplay나 loop가 매끄럽게 동작하지 않기도 하고 코드 수정이나 재사용이 용이하게 하도록 하기 위해

sound.js 파일을 별도로 파서 audio.play()와 audio.pause() 의 방식으로 작성해 사용하였다. 

 

// sound.js

let audioOffBtn = document.querySelector(".icon_volume_off");
let audioOnBtn = document.querySelector(".icon_volume_on");

audioOnBtn.addEventListener("click", function () {
  var audio = new Audio("./audio/Tropical_Thunder.mp3");
  audio.loop = true;
  audio.volume = 0.5;
  audio.play();
  audioOnBtn.style.display = "none";
  audioOffBtn.style.display = "block";

  audioOffBtn.addEventListener("click", function () {
    audio.volume = 0;
    audioOffBtn.style.display = "none";
    audioOnBtn.style.display = "block";
  });
});

 

 

플레이 화면

게임 플레이 방식은 전반적으로 과제의 규정에 맞춰서 구현했다.

상단의 랜덤한 위치에서 유령이 비처럼 내려오는 것 + 지면에 서 있는 용사가 좌우로 움직이는 것 을 기본으로 구현하고,

용사가 유령과 접촉할 경우 유령이 죽는 모습을 보여주도록 했다.

용사의 좌우 모습과 유령의 살아있을 때 / 죽었을 때 모습은 각각 하나의 이미지를 자르는 image sprite 방식을 사용했다.

 

게임을 플레이할 때는 키보드의 좌우 키를 사용해서 플레이하도록 했다.

강의에서 안내한 것은 keyCode 방식이었으나, 찾아보니 브라우저 표준 방식이 아니어서 사용이 권장되지 않는 형태라고 한다.

이에 key 프로퍼티를 사용하는 방식으로 변경하고자 했으나, 이 역시 지원하지 않는 브라우저도 존재한다고 한다.

따라서 나는 keyCode와 key property를 동시에 사용하면서 여러 브라우저를 폭넓게 지원할 수 있도록 구현했다.

 

// hero.js

document.addEventListener("keydown", function (e) {
  let key = e.key || e.keyCode;

  let heroLeft = getComputedStyle(heroElement).left;
  let heroLeftNum = Number(heroLeft.split("px")[0]);

  if (key === "ArrowLeft" || key === 37) {
    heroElement.style.backgroundPosition = "-70px";
    heroElement.style.left = heroLeftNum - 10 + "px";
  } else if (key === "ArrowRight" || key === 39) {
    heroElement.style.backgroundPosition = "-105px";
    heroElement.style.left = heroLeftNum + 10 + "px";
  } else if (heroLeft < 0 && (key === "ArrowLeft" || key === 37)) {
    heroElement.style.left = 0;
  } else if (
    heroLeft > BG_WIDTH - HERO_WIDTH &&
    (key === "ArrowRight" || key === 39)
  ) {
    heroElement.style.left = 0;
  }
});

document.addEventListener("keyup", function () {
  heroElement.style.backgroundPosition = "0";
});

 

추가적으로 왼쪽 상단에 타이머를 만들어 놓기도 했다.

게임 플레이가 시작되면 타이머가 남은 게임 시간을 알려주며, 제한시간이 끝날 경우 timeout 글자가 표시된다.

이와 동시에 게임 화면에도 game over 영상이 보여진다.

 

// timer

function timerAction() {
  let time = 15;

  let timeCounter = setInterval(function () {
    timerElement.innerText = time--;

    if (time < 0) {
      clearInterval(timeCounter);
      timerElement.innerHTML = "TIMEOUT";
      timerElement.style.width = "35px";
      timerElement.style.height = "35px";
      timerElement.style.backgroundSize = "cover";

      bgElementStart.style.backgroundColor = "black";
      startScreen.style.display = "block";
      startScreen.style.background =
        'url("https://media1.giphy.com/media/Vd8V1PpBe1Jr3SNP1d/giphy.gif?cid=ecf05e47lsruk49n2nz0csfje8apmwf5d3iujcpxdt48pd4j&rid=giphy.gif&ct=g") no-repeat';
      startScreen.style.backgroundSize = "cover";
      startScreen.style.backgroundPosition = "-40px";
    }
  }, 1000);
}

startScreenBtn.addEventListener("click", timerAction);

 

 

 

정리하며


과제 기한에 맞춰서 진행을 하다 보니, 생각보다 시간이 부족했다.

멋진 게임을 만들고 싶다는 마음이 크다보니.. 정작 본질인 게임의 기능보다는 css에 집중하고 있는 나 자신을 발견...!

이게 좋을까 저게 좋을까, 하면서 디자인을 이래저래 실험하는 데에 꽤나 많은 시간을 소비하고 있었다.

 

그래서 이 사실을 깨달은 시점부터 디자인을 올스탑하고, 완전히 백지의 상태로 돌아갔다.

추가로 구현하고자 했던 이런저런 기능 역시 전부 주석 처리하고 0의 상태에서 주요 기능부터 만들기 시작했다.

기능 구현이 다 되지 않은 상태에서 css에 실험을 많이 하다보면 이런저런 요소가 엉킬 수 있다는 걸 이전에 한 차례 경험했기 때문에,

이번에는 같은 실수를 두 번 다시 하지 않으리라 다짐하고 게임의 주 기능을 먼저 완성했다.

 

게임 mvp를 완성한 후 -> 기본 디자인 입히기 -> 추가 기능 완성 -> 추가 기능 디자인 입히기

이런 방식으로 차근차근 진행했더니 css 우선순위가 꼬이거나 하는 일 없이 깔끔하게 마무리할 수 있었다.

또 꼼꼼히 만들어 나가다보니 중간중간 수정 혹은 재사용이 편한 코드는 무엇인가에 대해 충분히 고민하면서 구현할 수 있어 좋았다.

(조금이었지만 이전에 클린코드를 공부한 것도 나름 도움이 되는 것 같아 뿌듯..ㅎㅎ)

 

마지막으로는 디자인(특히 게임 콘솔 모양)을 하면서 방대하게 늘어난 css를 js로 옮기고, 또 옮긴 내용을 간결하게 정리하는 리팩토링 과정을 거쳤다.

사실 이전에는 css가 편하고 익숙한 나머지 js에서 스타일링하는 것을 피해왔었다.

그런데 이번에 리팩토링을 하는 과정에서 css의 기본 스타일링뿐만 아니라 :hover 같은 액션들을 js에서 이벤트리스너로 작성하니

여러 가지 기능이나 액션을 한 눈에 보고 관리하기 매우 편하다는 장점을 발견했다.

따라서 앞으로도 사용자의 액션과 관련된 부분은 css보다는 주로 js에서 처리해야겠다는 생각을 했다. 

추가로, 아직 js로 옮기는 방법을 몰라 미처 다 이동하지 못한 css 코드들이 있는데 이 부분도 어떻게 하면 좋을지 더 공부하고 찾아보고자 한다.