[TIL] 24.10.03. THU

GDORI·2024년 10월 3일
0

TIL

목록 보기
60/79
post-thumbnail

이제야 시작한 개인과제...

발제는 지난주 금요일이었는데 너무 늦게 시작했다. 소켓이 너무 어려워서 복습하는데 집중도 잘 안돼가지고 시간을 너무 낭비했다.
어여어여 가자..

스켈레톤 디렉토리 구조

.
├── 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

패킷 구조

필수 기능

  1. 스테이지 구분
  2. 스테이지에 따른 점수 획득 구분
  3. 스테이지에 따라 아이템이 생성
  4. 아이템 획득 시 점수 획득
  5. 아이템 별 획득 점수 구분

도전 기능

  1. Broadcast 기능 추가
  2. 가장 높은 점수 Record 관리
  3. 유저 정보 연결
  4. Redis 연동, 게임 정보 저장

스테이지 구분

스테이지 구분의 경우 스켈레톤 코드에 데이터 테이블은 구현이 되어있고, 게임 진행 시 스테이지 전환 관련해서는 구현되어 있지 않다. 일단은 스켈레톤 코드를 거의 건들이지 않고 연동 먼저 하고 손을 댈 생각이다.

서버 측 스켈레톤 코드

export const moveStageHandler = (userId, payload) => {
  let currentStage = getStage(userId);

  if (!currentStage.length) {
    return { status: "fail", message: "No stages found for user" };
  }

  currentStage.sort((a, b) => a.id - b.id);
  const currentStageId = currentStage[currentStage.length - 1].id;
  if (currentStageId !== payload.currentStage)
    return { status: "fail", message: "current stage mismatch" };

  // 점수 검증
  const serverTime = Date.now();
  const elapsedTime =
    (serverTime - currentStage[currentStage.length - 1].timestamp) / 1000;
  console.log(currentStage.timestamp);
  console.log(elapsedTime);
  
  // 여기부터 바뀔 부분
  // 1스테이지 -> 2스테이지로 넘어가는 가정
  if (elapsedTime < 100 || elapsedTime > 105) {
    return { status: "fail", message: "Invalid elapsed time" };
  }

  const { stage } = getGameAssets();
  if (!stage.data.some((stage) => stage.id === payload.targetStage))
    return { status: "fail", message: "Target stage not found" };

  setStage(userId, payload.targetStage, serverTime);

  return { status: "success" };
};

현재 스켈레톤 코드에서는 1스테이지에서 2스테이지 넘어갈때만 점수검증을 통해 처리해주고 있다는 것을 알 수 있다.
여기에 데이터 테이블을 참고하여 각 스테이지마다 필요로 하는 점수를 확인하고 검증할 수 있는 과정을 추가하면 되겠다.

수정된 서버 코드

// 기존 스테이지 점수를 구함
  const curStage = stage.data.find((stage) => stage.id === currentStageId);

  // 다음 타켓 스테이지의 점수를 구함.
  const targetStage = stage.data.find(
    (stage) => stage.id === payload.targetStage
  );
  if (!targetStage)
    return { status: "fail", message: "Target stage not found" };
  // 타겟 스테이지 점수와 기존 스테이지 점수의 차이를 구함.
  const intervalScore = targetStage.score - curStage.score;

  // 타겟 스테이지 점수 네트워크 시간 오차범위
  if (elapsedTime < intervalScore - 0.5 || elapsedTime > intervalScore + 0.5) {
    return { status: "fail", message: "Invalid elapsed time" };
  }

  setStage(userId, payload.targetStage, serverTime);

  return { status: "success", message: "Move to target stage" };

클라이언트 측 스켈레톤 코드

update(deltaTime) {
    this.score += deltaTime * 0.001;
    // 점수가 100점 이상이 될 시 서버에 메세지 전송
    if (Math.floor(this.score) === 100 && this.stageChange) {
      this.stageChange = false;
      sendEvent(11, { currentStage: 1000, targetStage: 1001 });
    }
  }

현재는 하드코딩으로 100점 이상이 되었을 경우 다음 스테이지 넘어가게 이벤트를 보내고 있다. 데이터 테이블에서 정보를 받아서
각 점수마다 이벤트를 송출하도록 바꾸면 될 것 같다.

수정된 클라이언트 코드

   update(deltaTime) {
    this.score += deltaTime * 0.001;
    // 등록된 스테이지 데이터 테이블 점수 내에서만 구동
    if (Math.floor(this.score) <= stageScore[stageScore.length - 1].score) {

      // 다음 스테이지 조건 점수와 현재 점수가 같으면서 stageChance가 true일 때 스테이지 변경 시도 
      if (
        Math.floor(this.score) === stageScore[this.stage + 1].score &&
        this.stageChange
      ) {
        // 프레임단위 호출이기 때문에 여러번 호출되는 것을 방지
        this.stageChange = false;
        // 현재 스테이지 아이디와 타겟 스테이지 아이디를 서버로 전송함
        sendEvent(11, {
          currentStage: stageScore[this.stage].id,
          targetStage: stageScore[this.stage + 1].id,
        });
        // 해당 점수가 지나갈 때 stageChange가 true로 바뀌게 1초 setTimeOut 설정
        setTimeout(() => {
          this.stage++;
          this.stageChange = true;
        }, 1000);
      }
    }
  }

데이터 테이블을 어떻게 받아왔는가

파일, CDN, DB 의 방법이 있지만 서버에서 api로 fetch 하는 방향으로 설정하였다. 작업하다보면 바뀔 수 있지만 지금 당장은 fetch로...

서버 코드

app.get("/api/getAssets", (req, res) => {
  const data = getGameAssets();
  if (!data) return res.status(404).json({ message: "Not found assets" });
  return res.json(data);
});

클라이언트 코드

let stageScore;
async function fetchData() {
  try {
    const res = await fetch("/api/getAssets");
    const data = await res.json();
    stageScore = data.stage.data;
  } catch (error) {
    console.error("Error fetch:", error);
  }
}

fetchData();

스테이지에 따른 점수 구분

현재 위에서 스테이지를 변경하는 방법은 시간초에 따른 점수 획득으로 서버에 저장된 시간과 비교하여 변경되고 있다. 만약 스테이지에 따라 1초당 얻는 점수를 변경하려면 계산하는 과정과 데이터 테이블에 컬럼을 추가적으로 넣어주어야 할 것 같다.
데이터 테이블에 perSecond를 추가해주고 클라이언트 측 초당 올라가는 점수를 perSecond만큼 곱해주고, 서버측은 나눠주면 될 것 같다.

서버 코드

  const intervalScore =
    (targetStage.score - curStage.score) / curStage.perSecond;

클라이언트 코드

update(deltaTime) {
    this.score += deltaTime * this.perSecond * 0.001;
    // 등록된 스테이지 데이터 테이블 점수 내에서만 구동
    if (Math.floor(this.score) <= stageScore[stageScore.length - 1].score) {
      // 다음 스테이지 조건 점수와 현재 점수가 같으면서 stageChance가 true일 때 스테이지 변경 시도
      if (
        Math.floor(this.score) === stageScore[this.stage + 1].score &&
        this.stageChange
      ) {
        // 프레임단위 호출이기 때문에 여러번 호출되는 것을 방지
        this.stageChange = false;
        // 현재 스테이지 아이디와 타겟 스테이지 아이디를 서버로 전송함
        sendEvent(11, {
          currentStage: stageScore[this.stage].id,
          targetStage: stageScore[this.stage + 1].id,
        });
        // 해당 점수가 지나갈 때 stageChange가 true로 바뀌게 1초 setTimeOut 설정
        setTimeout(() => {
          this.stage++;
          // 스테이지 증가와 동시에 초당 점수 변경
          this.perSecond = stageScore[this.stage].perSecond;

          this.stageChange = true;
        }, 1000);
      }
    }
  }
profile
하루 최소 1시간이라도 공부하자..

0개의 댓글