Team-dev.recre-frontend.README.md {swjungle}


META README

본 파일은 깃허브 리포지토리 recre-frontend README.md 파일에 작성할 내용의 초안을 담았습니다. 서비스 소개, 게임 소개, 배포 방법, 포스터, 최종발표영상 까지는 recre-backend와 동일한 내용을 담을 예정이고, 그 뒤에는 발표나 포스터에서 못다 이야기한 구체적인 프론트엔드쪽 기술적인 챌린지들을 정리할 예정입니다.

RecRe

Screenshots

나만무_중간발표_최승현

나만무_중간발표_최승현

Architectures

최종 수정.png

Build With NextJS

먼저 필요한 의존성들을 설치해줍니다.

npm i

환경변수에 필요한 .env.production과 .env.development를 생성합니다. 아래는 필요한 환경변수들을 정의한 예제입니다.

NEXT_PUBLIC_RECRE_URL=프론트엔드 서버 URL
NEXT_PUBLIC_BACK_API=백엔드 서버 URL
NEXT_PUBLIC_SOCKET_API=웹소켓 게이트웨이 URL

그리고 다음 명령어를 통해 각각 개발용과 프로덕션용 모드로 실행할 수 있습니다.

npm run dev -- -p <port>
npm run start -- -p <port>

포스터

최승현-RecRe-2

Project Directory Structure

app/

app
├── catch
│   └── page.tsx
├── catchAnswer
│   └── page.tsx
├── favicon.ico
├── gamePage
│   └── page.tsx
├── gameSelect
│   └── page.tsx
├── globals.css
├── layout.tsx
├── login
│   └── login.tsx
├── modules
│   ├── answerAtom.tsx
│   ├── backApi.tsx
│   ├── catchStartAtom.tsx
│   ├── gameAtoms.tsx
│   ├── loginAtoms.tsx
│   ├── loginTryNumAtom.tsx
│   ├── popoverAtom.tsx
│   ├── redGreenAtoms.tsx
│   ├── redGreenStartAtom.tsx
│   ├── socketApi.tsx
│   ├── tokenAtoms.tsx
│   └── userInfoAtom.tsx
├── page.tsx
├── player
│   └── page.tsx
├── playerComponent
│   ├── catchPlayer.tsx
│   └── redGreenPlayer.tsx
├── profile
│   └── profile.tsx
├── provider.tsx
├── redGreen
│   └── page.tsx
└── token
    └── page.tsx

component/

component
├── MyModal.tsx
├── MyPopover.tsx
├── OauthButtons.tsx
├── Particle.tsx
├── QRpage.tsx
├── footer.tsx
├── header.tsx
├── oauthButtonsStyle.module.css
├── rankingBoard.tsx
├── snackBar.tsx
└── youngHee.tsx

기술적 챌린지

호스트와 플레이어 간 캔버스 동기화

호스트가 호스트 화면에서 캔버스에 그림을 그리면 플레이어의 화면에도 동시에 그림이 그려지게 하고 싶었다.

첫번째 방법

처음에는 캔버스의 기능 중 하나인 그림을 이미지로 바꿔서 보내는 방법을 사용해보려 했었다.

useEffect(() => {
    console.log(234234)
    const canvas: HTMLCanvasElement | null = canvasRef.current;
    if (canvas) {
        const context = canvas.getContext('2d');
        if (context) {
          context.beginPath();
          // 서버에 그림 데이터 및 캔버스 정보 전송
          const canvasData = {
            data: context.getImageData(0, 0, canvas.width, canvas.height).data,
            width: canvas.width,
            height: canvas.height,
          };
          socket.emit('draw', canvasData);
        }
    }
  }, [isPainting]);

그러나 위와 같은 방식은 호스트가 마우스로 그림을 그리다가 마우스를 뗐을 때만 그림으로 저장되어 플레이어에게 보내지며, 플레이어는 실시간 그림이 아닌 마우스를 뗐을 때의 그림만 갱신되게 되어 실시간성이 떨어지게 된다.

두번째 방법

useCallback을 사용하여 호스트가 onmousemove 일 때 실시간으로 그림의 시작 지점과 그려지는 좌표를 emit하게 하여 플레이어한테도 실시간으로 호스트의 그림이 그려지게 방법을 바꾸었다.

const paint = useCallback(
    (event: MouseEvent) => {
      event.preventDefault();   // drag 방지
      event.stopPropagation();  // drag 방지

      const newMousePosition = getCoordinates(event);
      if (isPainting) {
        if (mousePosition && newMousePosition) {
          drawLine(mousePosition, newMousePosition);
          setMousePosition(newMousePosition);
          socket.emit('draw', {
            room_id : userInfo.id,
            x : newMousePosition.x,
            y : newMousePosition.y,
            color : isEraser?'white':curColor,
            lineWidth : isEraser ? eraserWidth : lineWidth,
            first_x : mousePosition.x,
            first_y : mousePosition.y,
          });
        }
      } else {

      }
    },
    [isPainting, mousePosition]
  );

이렇게 수정하니 이전보다는 소켓의 emit 횟수는 늘었지만 이미지를 보내는게 아닌 좌표 정보면 보내는 것이므로 소켓에 큰 무리 없이 정보를 실시간으로 보낼 수 있었다.

또한 이런 방식으로 현재 100명까지 문제 없이 길지 않은 반응속도로 호스트의 그림이 플레이어의 화면에 동기화되는 것을 확인했으며 1000명까지도 문제 없이 소켓이 감당할 수 있는 것으로 보인다.

고려해볼 부분

다만 네트워크 환경에서의 문제가 있을 수 있어 보인다.

예를 들어 100명의 플레이어와 1명의 호스트가 같은 와이파이 공유기에 접속하여 게임을 진행한다고 봤을 때, 그 네트워크가 과연 100명에게 무사히 그림 정보를 보낼 수 있을 지 고려해 봐야 할 것 같다.

핸드폰을 흔들어서 화면 속 캐릭터가 앞으로 나아가게 만드는 방법