2024-10-01 CH-4 개인과제 (리얼 타임 과제) 시작 1

MOON·2024년 10월 2일
0

내일배움캠프 과제

목록 보기
14/36
post-thumbnail

안녕하세요 요즘 날씨가 갑자기 추워졌네요. 항상 건강 조심하시고 저도 건강관리 체력관리를 해야 될 것 같네요 하하 그럼 이제 본 내용으로 작성해 보겠습니다.

이번에 웹소켓 강의와 함께 개인과제로 바로 주셨습니다. 개인과제 내용은 실시간으로 움직이고 점수 획득을 통해 게임을 진행하는 크롬 다이노 게임을 웹소켓을 이해하고 사용하여 구현해보는 과제입니다. 강의 내용과 함꼐 이어지는 개인과제 입니다. 강의 내용에서 어느정도 코드를 제공해 주면서 실제 구현해보는 과정은 직접 개인과제로 만들어보는 내용입니다.

실제 크롬 다이노 게임 링크
https://dinorunner.com/ko/

필수 기능 내용

제공된 기획 내용들입니다.
    **📝 기획한 내용들**

    - [x]  시간에 따른 점수 획득 // 여기까지 강의내용을 따라 구현하였습니다.
        - [ ]  스테이지 구분
        - [ ]  스테이지에 따른 점수 획득 구분
    - [ ]  아이템 획득
        - [ ]  아이템 획득 시 점수 획득
        - [ ]  스테이지 별 아이템 생성 구분
        - [ ]  아이템 별 획득 점수 구분

위에 기획에 맞게 구현하는 것이 이번 필수 기능 구현과제입니다.

도전 기능 내용

1. Broadcast 기능 추가

2. 가장 높은 점수 Record 관리

  • 가장 높은 점수를 서버에서 관리하는 것으로 수정하고, 만약 점수가 갱신된다면 현재 게임에 참가하고 있는 유저와 새로 참가할 유저에게 새롭게 쓰인 가장 높은 점수를 알려줘야 할 것입니다.

3. 유저 정보 연결

  • 만약 유저 ID를 클라이언트에서 알고 있다면 최초 접속 이후 uuid를 생성, 발급받고 그 다음 접속부터는 이전에 했던 게임 기록에 대해 연동 가능 할 것입니다.

4. Redis 연동, 게임 정보 저장

  • 유저, 스테이지에 대한 정보를 redis에 저장해봅시다.
  • 디렉토리 구조, 파일들 간의 관계 등을 고려하여 효율적인 최선의 방법을 고려하여 적용합니다.

먼저 파일의 디렉토리 구조입니다.

├── assets                     // 게임 데이터
│   ├── item.json
│   ├── item_unlock.json
│   └── stage.json
├── package-lock.json
├── package.json
├── public                     // 프론트엔드 
├── readme.md
└── src                        // 서버 코드
    ├── app.js
    ├── constants.js
    ├── handlers               // 비즈니스 로직 
    │   ├── game.handler.js
    │   ├── handlerMapping.js
    │   ├── helper.js
    │   ├── regiser.handler.js
    │   └── stage.handler.js
    ├── init                   // 필수 데이터, 기능 로드 (load)
    │   ├── assets.js
    │   └── socket.js
    └── models                 // 세션 모델 관리
        ├── stage.model.js
        └── user.model.js

현재 서버와 프론트엔드 파일을 같이 관리하면서 작업하고 있습니다.

assets 폴더에는 게임데이터가 json형태로 있습니다. 이 assets폴더에 있는 게임 데이터를 통해서 서버와 클라이언트가 기획에 맞게 이용할 수 있습니다.

주의

절대 헷갈려서는 안되는 것은 ‘서버와 클라이언트는 같은 게임 데이터 테이블을 보고 있다’ 라는 사실입니다.
클라이언트에서는 데이터 테이블을 기준으로 계산해 게임 유저들에게 보여줍니다.
서버에서는 데이터 테이블을 기준으로 클라이언트에게서 받은 데이터를 검증, 처리합니다.

주의사항도 말씀해주셔서 "서버와 클라이언트는 같은 데이터 테이블을 봐야하는구나" 처음에는 사실 이해가 잘되었지만 계속 생각해 보니 서버와 클라이언트가 각자 다른 데이터를 이용하여 각자 계산하면 당연히 값이 다를것이니 오류가 일어날 수 밖에 없구나라는 사실을 생각하는데 시간이 좀 걸렸습니다. 하핫

서버에선 assets 파일을 바로 확인할 수 있었습니다.

import fs from 'fs'; // 파일 시스템
import path from 'path'; //
import { fileURLToPath } from 'url';
import { filename } from '../../public/assets/fileName.js';

let gaemAssets = {};

const __filename = fileURLToPath(import.meta.url); // 현재 이 파일에 절대 경로를 찾는다.
const __dirname = path.dirname(__filename); // 현재 파일의 이름을 빼고 경로 위치만을 찾는다
// 최상위 경로 + assets 폴더
const basePath = path.join(__dirname, '../../public/assets'); // __dirname가 경로 위치니깐 여기 위치 기준으로 '../../assets' 를 통해 찾을 파일의 경로를 찾는다

// 파일 읽는 함수
// 비동기 병렬로 파일을 읽는다.
const readFileAsync = (filename) => {
  return new Promise((resolve, reject) => {
    fs.readFile(path.join(basePath, filename), 'utf8', (err, data) => {
      if (err) {
        reject(err);
        return;
      }
      resolve(JSON.parse(data));
    });
  });
};

// Promise.all()을 통해 한번에 pomise함수를 실행하여 여러 파일을 한번에 읽기 시작하고 파일들이 전부 다운 받을때 까지 기다린다 즉 안에 함수들이 비동기 적으로 동시에 실행이 일어나고 모든 함수가 끝날때 까지 기다린다.
// Promise.all()
export const loadGameAssets = async () => {
  try {
    const [stages, items, itemUnlocks] = await Promise.all([
      readFileAsync(filename.stage_Json),
      readFileAsync(filename.item_Json),
      readFileAsync(filename.itemUnlock_Json),
    ]);

    gaemAssets = { stages, items, itemUnlocks };
    return gaemAssets;
  } catch (err) {
    throw new Error('Failed to load game assets: ' + err.message);
  }
};

export const getGameAssets = () => {
  return gaemAssets;
};

주석을 적어 놓것과 같이 "fs"라는 파일 시스템을 이용해 해당 경로로 찾아가 assets폴더에서 해당 파일들을 찾아 값을 읽어올 수 있었습니다.

그럼 이제 클라이언트에서도 assets폴더에 있는 게임 데이터들을 읽어올려고 하였으나 저에겐 쉽지는 않은 과정들이 있었습니다.

// public폴더안에 있는 Score.js라는 파일입니다.

import { sendEvent } from './Socket.js';
import stageAssetData from '../assets/stage.json' with { type: 'json' };
console.log(stageAssetData);

class Score {
  score = 0;
  scorePerSecond = 1;
  HIGH_SCORE_KEY = 'highScore'; // 로컬로 최종 점수 저장할 키
  stageChange = true;
  currentStageId = stageAssetData.data[0].id;
...
...

import stageAssetData from '../assets/stage.json' with { type: 'json' }; 이 코드에서 with { type: 'json' }; 이용해 json형태의 파일을 읽어 올 수 있다고해서 사용해 보았습니다.

여기서 문제들 발생

위에 코드처럼 작성할때 브라우저에서 계속 오류가 발생 하였습니다. 왜지? 왜이러지? 그래서 with 대신 assert도 사용해보고, fetch함수도 이용해 값을 찾아볼려 했으나 전부 실패하였습니다.

그래서 그럼 혹시 public 프론트엔드 파일안에 assets 파일을 포함시켜서 import stageAssetData from './assets/stage.json' with { type: 'json' }; 경로를 재설정하고 실행해 봤더니 잘 작동이 되는 것이 였습니다.

"이게 아까는 왜 오류가 발생하고 이번엔 왜 잘되는거지?"이유를 알수 없어 찾고 또 찾고 계속 찾아보니 브라우저(클라이언트)에서 실행되는 코드는 경로가 다르게 해석된다고 합니다.

서버는 현재 아무 이상없이 경로를 찾아 읽어 올 수 있었고, 브라우저(클라이언트)는 HTTP 요청을 통해 파일을 불러오기 때문에 ../과 같은 상위 폴더를 참조하는 경로가 제대로 작동하지 않는다고 합니다. 와 이유를 알고 보니 왜 오류없이 잘 실행이 될 수 있었는지에 대해 알 수 있었습니다.

오늘의 회고

강의도 다시 듣고 웹소켓에 대해 간단하게 찾아보면서 제공된 파일들의 코드를 해석하는데 집중했던 시간이였습니다. 그리고 위에서 설명한 내용처럼 몰랐던 내용도 알아보면서 직접 찾아보니 경로에 대해 좀 더 이해할 수 있었던 시간이었던거 같습니다. 이유를 알고보니 어찌보면 당연한 이야기 였던거 같다는 생각도 드네요. 하하...

그럼 오늘도 화이팅!

profile
안녕하세요

0개의 댓글