상세 컨텐츠

본문 제목

Selecolor - 프론트엔드 게임 제작 챌린지

WEB

by Yoonsang's Log 2022. 2. 13. 22:05

본문

넘블 로고

[React] 상태관리 라이브러리를 사용하지 않고 다른 색깔 찾기 게임 제작

Numble에서 열리는 2월 4일부터 2월 13일까지 10일간 게임을 만드는 챌린지에 참여했다.

지인들이 참여하는게 재밌어보이길래 참여했는데

하필 딱 2월 4일부터 다른 일정이 겹쳐서 시간이 많이 부족했다.

조금 급하게 개발한 감이 있지만 게임은 성공적으로 완성했고, 일단 무엇보다 재밌다..

제작한 게임 링크 : https://selecolor.vercel.app

 

Selecolor | Home

Select a different color box. Very simple game!

selecolor.vercel.app

아래에는 프로젝트 과정과 느낀점 및 개선할 점 등을 정리했다.

 

챌린지 소개

우선 챌린지에 대해 간단하게 소개하자면,

게임에 대한 룰을 넘블에서 정해주면 그것에 맞게 게임을 제작해서 배포까지 해보는 챌린지다.

  1. React 사용하기
  2. Function Component 사용
  3. TypeScript 사용
  4. 서버에 배포
  5. Context, Redux, Mobx, Recoil 등 상태관리 도구 사용하지 않기

조건 5번에 상태관리 라이브러리를 사용하지 말라는 규칙이 있는데, 이번 챌린지를 통해서 props drilling을 경험해보고

어디에 상태관리 라이브러리가 사용되면 좋을지 확실히 판단할 수 있겠다 싶었는데,

생각보다 규모가 크지 않은 프로젝트여서 5번 조건이 없었어도 상태관리 라이브러리를 사용하지는 않았을 것 같다.

React를 더 React 스럽게 사용하고 싶은 0~1년차 프론트엔드 개발자 에게 권장한다고 되어 있는데,

컴포넌트를 잘게 분할해서 재활용성 높은 컴포넌트를 제작해 사용하는 것을 의미하는듯하다.

그리고 코드에 대한 리뷰를 받아볼 수 있어 좋은 취지의 챌린지에 참여한 것 같다.

게임에 대한 상세 스펙은 아래와 같다.

  1. 한개의 색깔만 다른 박스들이 표시
  2. 제한 시간 15초, 정답이 아닌 박스를 클릭하면 3초 감소
  3. 남은시간 비례 score 누적
  4. 남은 시간이 0초가 되면 게임 종료. 최종 stage와 누적 score를 출력하고 새로운 게임 시작.
  5. stage가 올라갈수록 난이도 증가

박스 개수 계산식

Math.pow(Math.round((stage + 0.5) / 2) + 1, 2)

스코어 누적 계산식

Math.pow(stage, 3) * 남은시간

개발 시작

프로젝트 이름 설정

프로젝트를 시작하기 전에, 해당 프로젝트에 대한 이름이 없어서 직접 지어주었다.

후보군에는 picolor(pick+color), selecolor(select+color), 등 많이 있었는데, 이미 사용중인 이름을 피해서 selecolor로 이름지었다.

 

프로젝트 설정

라우팅이 파일 이름으로 곧바로 설정되는게 너무나도 편리한데 SSR, SSG까지 적용되는 next.js를 최근에 알게 되어 공부중이다.

프로젝트 생성은 create-next-app로 하였다.

폴더 구조는 개인적으로 아토믹 디자인 패턴이 제일 유지보수성이 좋다고 생각하지만 현재는 아래와 같이 구조화하였다.

추후 리팩토링을 한다면 아토믹하게 쪼개서 재활용성을 극대로 높일 수 있을 것이다.

src
   ㄴ pages
   ㄴ components
   ㄴ containers
   ㄴ utils

 

CSS

CSS는 next.js 프레임워크를 제작한 vercel의 CSS-in-JS 라이브러리인 Styled JSX를 사용했다.

https://nextjs.org/blog/styling-next-with-styled-jsx

하지만, 최근 버전의 next에서는 CSS Module을 디폴트로 적용되어 있다.

 

Styling Next.js with Styled JSX

Styled JSX is a CSS-in-JS library that allows you to write encapsulated and scoped CSS to style your components. This blog post will help you get started with using Styled JSX in Next.js.

nextjs.org

 

Hooks

시간을 체크해주어야 하는데, setInterval을 쓰니 사이드이펙트가 있었다.

react custom hook인 useInterval 사용을 위해 usehook-ts를 Install해서 사용했다. 

https://usehooks-ts.com/react-hook/use-interval

 

useInterval() react hook - usehooks-ts

Use setInterval in functional React component with the same API. Set your callback function as a first parameter and a delay (in milliseconds) for the…

usehooks-ts.com

 

프로젝트 세부사항

https://github.com/yanggak12/selecolor

 

GitHub - yanggak12/selecolor: 🌈 Select different color box !

🌈 Select different color box ! Contribute to yanggak12/selecolor development by creating an account on GitHub.

github.com

components

더보기

Layout.tsx

_app.tsx 페이지에서 호출해서 사용하며 모든 페이지를 감싸는 layout 컴포넌트이다. 모든 페이지에 적용하고 싶은 PageLink.tsx와 같은 컴포넌트가 들어가기 적절한 컴포넌트이다. 

 

PageLink.tsx

깃허브로 이동하는 Link가 있는 컴포넌트. 추후 다른 페이지도 연결 가능.

 

Seo.tsx

SEO 최적화를 위해 실제 html의 head에 들어갈 내용을 next/Head를 사용해 page 가장 상단에 들어갈 컴포넌트. title을 props로 받아 페이지마다 다른 <title> 적용 가능.

 

Modal.tsx

modal 컴포넌트를 여러개 만들 수 있는 모달 컴포넌트이다. 모달창이 나오는 백그라운드를 어둡게 처리해주는 용도이다.

 

AlertModal.tsx

위 모달 컴포넌트를 사용해 만든 알림 컴포넌트이다. 시간이 다 지났을때 display되어 누적 스코어와 게임을 다시할지 종료할지를 표시한다. 넘블 조건에는 없었지만 고도화해서 랭크 기능(RankModal.tsx) 추가 중임.

 

ImgButton.tsx

AlertModal 혹은 다른 곳에 사용되는 이미지와 함께 있는 버튼 컴포넌트이다. 이미지와 텍스트 그리고 버튼 클릭 핸들러를 props로 받는다.

 

StageTime.tsx

game 페에지에 stage와 time이 표시하는 컴포넌트. 

 

BoxItem.tsx

Game에 사용되는 박스 컴포넌트이다. 해당 박스가 목표로 하는 박스가 맞는지 내부에서 판단해서 props로 내려받은 것들이 적용된다.

 

* 컴포넌트는 가능한 재활용이 가능하게 짜야한다고 하는데, 이렇게 재활용성 없는 컴포넌트의 경우 BoxContainer에서 반복해서 호출하는 하위 컴포넌트로 이렇게 권한을 부여해주는것이 맞는지,  아니면 상단에서 기능들을 다 정해놓고 내려주기만 해야하는건지(targetBoxHandler, nonTargetBoxHandler로 나누어서 내려주는식) 헷갈린다.

 

BoxContainer.tsx

위에서 보았던 BoxItem.tsx를 반복해서 디스플레이하는 컴포넌트이다.

display: grid 를 통해 컴포넌트를 제작했다. 

참고 레퍼런스 - https://studiomeal.com/archives/533

 

이번에야말로 CSS Grid를 익혀보자

이 포스트에는 실제 코드가 적용된 부분들이 있으므로, 해당 기능을 잘 지원하는 최신 웹 브라우저로 보시는게 좋습니다. (대충 인터넷 익스플로러로만 안보면 된다는 이야기) 이 튜토리얼은 “

studiomeal.com

 


RankModal.tsx

Rank를 저장할 수 있도록 input으로 닉네임을 받는 모달을 띄운다.

닉네임을 입력하지 않으면 alert를 띄워 입력하게 한다.

정상적으로 저장하였으면 rank 페이지로 이동시킨다.

* TypeScript를 사용할때 input 값 e의 타입

JS를 쓸 때는 생각하지 못했던 e의 타입에 대해 생각해볼 수 있었다.

e의 타입은 React.ChangeEvent<HTMLInputElement>이다.

 

containers

더보기

getBoxCnt.ts

박스 개수를 반환하는 함수

 

getBoxRange.ts

박스 개수에 맞는 배열을 반환하는 함수

 

getGridCnt.ts

BoxContainer에 grid로 반복될 수를 반환하는 함수

 

getTargetIndex.ts

정답이 될 박스의 인덱스를 반환하는 함수

 

getColorPercent.ts

정답이 될 박스의 색을 조정하여 반환하는 함수

stage*0.1 씩 더해주는 연산을 거치고, 0.8 이상이 될 경우 0.8로 고정을 시켜주었다.

 

getRandomColor.ts

랜덤으로 색을 반환하는 함수


getRank.ts

firebase에 저장한 데이터베이스를 받아와서 점수 순으로 정렬해주는 함수

arr.sort((a, b) => b.score - a.score);
return arr.slice(0, 30);

 

setColorByStage

색상을 단계별로 조정해주는 함수 - 단계가 올라갈 수록 어려워지는 규칙을 만족

 

getRGBA.ts

rgb 조정과 색상 정도 조정을 위해 분리했던 괄호와 rgba문자열을 결합하는 함수

 

storeRank.ts

firebase에 랭킹정보 JSON 객체를 저장하는 함수.

JSON -  { nickname, score, time }

pages

더보기

index.tsx

가장 먼저 사용자가 마주하게 될 index페이지.

Selecolor의 로고와 게임을 시작할 수 있는 페이지로의 링크 버튼과

랭크를 확인할 수 있는 페이지로의 링크 버튼이 있다.

game.tsx

게임이 이루어지는 페이지.

index를 거쳐서 온 것이 아니라 /game 으로 바로 접속한 경우 비정상 접속으로 간주하고 index페이지로 보낸다.

useRouter를 통해 props를 전달받는 것처럼 query를 통해 전달하였다. (url에서는 표시하지 않음 - as 사용)

시간이 다 지나면 AlertModal를 표시해 Time out을 알린다.

rank.tsx

배포 후 친구들 반응이 너무 괜찮아서 간단하게 닉네임만 받아서 랭크해보았다.

간단히 파이어베이스 데이터베이스를 사용해 저장된 JSON 객체를 받아와 디스플레이 해주는 페이지.

Troubleshooting

가독성 좋은 코드 작성

박스 개수를 계산한다던지, 정답이 될 박스의 인덱스를 랜덤하게 구하는 등 그 목적이 명확한 기능들을 네이밍에 신경써서 무엇을 기능으로 하는지 쉽게 이해할 수 있도록 함수로 다 빼내는 등 추상화하였다.

- getBoxRange.ts 에서 for문을 사용한 부분

C언어식의 반복문을 사용해서 구현했는데, 반복문이 있으면 코드를 읽는 흐름을 방해할 수 있어 추상화하여 함수로 빼내었다.

 

CSS

CSS-in-JS를 사용하면 JS 코드를 수정하면서 필요한 적용사항들을 props로 받아서 처리하는 등 유연하게 CSS를 작성할 수 있다.

즉, 정적이었던 CSS를 동적으로 처리할 수 있는 것이다.

Next.js는 Vercel사에서 만든 CSS-in-JS 라이브러리인 styled-jsx를 기본으로 포함하였었다.

하지만, 9.2버전 이상의 Next.js에서는 기본으로 CSS Module이 적용되었다.

아직까지는 테마 등의 공통 영역, 일부 유틸리티 등은 CSS-in-JS로 쉽게 해결이 어렵기 때문이다. 그리고 규모가 큰 프로젝트일수록 마크업 개발자(퍼블리셔)가 분리되어 있는데 마크업 개발자는 페이지 단위로 작업하지만, 프론트엔드 개발자는 컴포넌트 단위로 작업한다. CSS-in-JS이라면 이미 만들어진 코드를 재배치해야 하기 때문에 비효율적이며 유지보수에도 영향을 미친다. 

CSS Module은 CSS-in-JS의 장점처럼 컴포넌트 단위로 추상화가 가능하며, 렌더링 속도에도 영향을 준다.

하지만 이번 프로젝트의 경우 퍼블리셔가 분리되어 있지 않고 1인이 개발하였기 때문에 CSS-in-JS 방식을 사용하였다.

하지만 규모가 있고 안정적인 서비스를 구축하게 된다면 CSS Module을 사용하면 좋을 것이다.

[참고] https://fe-developers.kakaoent.com/2022/220210-css-in-kakaowebtoon/

 

카카오웹툰은 CSS를 어떻게 작성하고 있을까?

카카오엔터테인먼트 FE 기술블로그

fe-developers.kakaoent.com

- 색상 다르게

타겟이 되는 박스의 색상은 다르게 설정해주어야 하는데, 단계별로 어려워져야 하는 조건을 만족하려면 rgba를 조작하여야 겠다고 생각했다. 그래서 (a,b,c,d) 로 이루어져 있는 rgba의 숫자에 d에 속하는 정도를 단계별로 조정해서 구현했다. 그 이후 슬랙 채널에 같은 질문을 올려주신 분의 글과 댓글을 확인해보니, 기존 rgb가 (a,b,c)라면 타겟은 단계별로 작아지는 수 x로 해서 (a-x, b-x, c-x) 와 같은 방식으로 구현했다는 글을 보게 되어 해당 방식을 참고하여 아래와 같이 작성하였다.

   - numberByStage : 단계별로 숫자가 작아짐. 100에서 현재 단계를 나눈 값

   - rgbArr : rgb 문자열을 (",")로 잘라 해당 배열로 저장하고, 그 배열을 하나씩 순회하며 numberByStage보다 작으면 더해주고 크면 빼주는 형식으로 저장한 배열

const setColorByStage = (color: string, stage: number) => {
  const numberByStage = 100 / stage;
  const rgbArr = color
    .split(",")
    .map((c) =>
      Number(c) > numberByStage
        ? Number(c) - numberByStage
        : Number(c) + numberByStage
    );
  return `${rgbArr[0] + "," + rgbArr[1] + "," + rgbArr[2]}`;
};

export default setColorByStage;

 

 

 

느낀점

Redux, Mobx 등과 같은 상태관리 라이브러리를 언제 쓰는게 좋을까 깨닫는 계기가 되었음 했는데,

프로젝트 규모가 거대하지는 않아서 불편할 만큼 props drilling을 경험하지 못해서 막 피부로 와닿지는 않았다.

추가적으로 공부해보고 싶은 부분으로 react의 state가 변경될 때 렌더링 과정과 동작원리를 이해하고는 있으나, 머리속에 구조화된 그림이 그려지지는 않는다는 것을 깨달았다. 

CSS도 작성하면서 아직 의도한 대로 완벽하게 나오는 것이 아니라, 한 두번 삽질(?)을 해야 원하는 결과물을 볼 수 있었다.

CSS를 어떤 방식으로 사용할지 고민하는 것도 좋지만, 당장 CSS를 사용하는 숙련도가 더욱 필요하다고 생각했다.

프로젝트를 통해서 새로 공부할 것들 그리고 필요한 것들을 많이 알게 되어 당장 공부해나가야 할 방향을 잡아주는 느낌이다.

다음 프로젝트 전까지 부족했던 부분을 모두 채우고 성장한 모습으로 참여하고 싶다.

관련글 더보기

댓글 영역