dh_0e

[Project] 타워 디펜스 게임 (팀 프로젝트) 본문

내일배움캠프/Project

[Project] 타워 디펜스 게임 (팀 프로젝트)

dh_0e 2024. 6. 21. 21:07

이번 팀 프로젝트의 주제는 튜터님께서 만드신 타워 디펜스 게임 클라이언트의 서버를 만들어 적용시키는 것이다. 웹소켓으로 서버를 열고, 검증 로직, 다양한 기능을 추가하여 서버를 구성하였다.

 

서버 기능 명세 (필수 구현)

더보기
  1. 회원가입 / 로그인 기능 (REST API로 통신)
    1. 로그인 성공 시 Access Token을 발급받도록 해주세요.
      1. 이후에 WebSocket 통신에서 해당 Access Token을 기반으로 유저 인증을 할 때 쓸 것입니다.
  2. 유저 별 게임 데이터 관리
    • 클라이언트 코드에 있는 해당 변수들은 서버로부터 동기화를 받아야 하며 유저별로 관리가 되어야 해요!
      • 공통 데이터
        • 기지 체력
        • 최초 유저 골드
        • 타워 구입 비용
        • 초기 타워 개수
      • 유저 데이터
        • 몬스터 레벨
        • 몬스터 생성 주기
        • 게임 점수
        • 기존 최고 점수
        • 타워 좌표들 (배열)
  3. let userGold = 0; // 유저 골드 let baseHp = 0; // 기지 체력 let towerCost = 0; // 타워 구입 비용 let numOfInitialTowers = 0; // 초기 타워 개수 let monsterLevel = 0; // 몬스터 레벨 let monsterSpawnInterval = 0; // 몬스터 생성 주기 let score = 0; // 게임 점수 let highScore = 0; // 기존 최고 점수
  4. 클라이언트가 서버로부터 수신하는 이벤트 종류 정의 및 코드 구현 (WebSocket으로 통신)
    1. 커넥션 성공 이벤트
    2. 커넥션 실패 이벤트
    3. 상태 동기화 이벤트
      1. 해당 이벤트를 통해 게임에서 필요한 데이터들을 서버로부터 받고 상태를 동기화 합니다.
      2. 우선은, 최초에 게임이 시작될 때 필요한 모든 게임 데이터들을 전달 받아야겠죠?
      3. 이후에는 상태 동기화를 해야 될 때마다 필요한 게임 데이터만 선택적으로 전달을 받으면 됩니다.
        1. 예를 들어, 몬스터를 한 번 죽였을 때 스코어가 100점이 올라야겠죠?
        2. 서버는 해당 유저의 스코어를 +100점 한 후 현재 스코어를 클라이언트에게 전달합니다.
      4. 타워 디펜스 게임 규칙 섹션을 잘 숙지하시고 언제 어떤 데이터들로 상태 동기화가 되어야하는지는 여러분들이 스스로 잘 판단을 해보시길 바랍니다!
  5. 클라이언트가 서버로 송신하는 이벤트 종류 정의 및 코드 구현 (WebSocket으로 통신)
    1. 게임 시작 이벤트
      1. 커넥션 성공했다고 서버로부터 이벤트를 수신하면 해당 이벤트를 바로 보내시면 됩니다.
    2. 최초 타워 추가 이벤트
      1. 최초 타워는 numOfInitialTowers 값 만큼 공짜로 추가를 해줘야 합니다.
      2. 자세한 것은 game.js의 placeInitialTowers 함수를 참고해주세요!
      3. 해당 이벤트에는 { x, y } 좌표를 추가적으로 보내주세요.
        1. 서버에서는 유저 별로 최소한 어떤 좌표들에 타워가 있는지를 체크하기 위함입니다!
    3. 타워 구입 이벤트
      1. 타워 구입은 골드를 지불해야겠죠? 근데, 돈이 모자라면 구입이 되면 안됩니다.
      2. 자세한 것은 game.js의 placeNewTower 함수를 참고해주세요!
      3. 해당 이벤트에는 { x, y } 좌표를 추가적으로 보내주세요.
        1. 서버에서는 유저 별로 최소한 어떤 좌표들에 타워가 있는지를 체크하기 위함입니다!
    4. 몬스터 죽이는 이벤트
      1. 몬스터가 죽으면 클라이언트가 서버에 몬스터를 죽였다라고 이벤트를 보내줘야 합니다!
    5. 게임 오버 이벤트
      1. 기지가 파괴가 되면 게임 오버 이벤트도 보내줘야겠죠!
  6. 유저 별 최고 기록 스코어 저장
    1. 유저 정보와 최고 기록 스코어를 기록할 최소한의 데이터베이스는 당연히 있어야겠죠?

 

더 재밌는 게임을 만들기 위한 도전! (도전 과제)

더보기
  • 타워 환불 기능
    • 타워 위치가 너무 별로이면 타워 환불을 할 수도 있으면 좋겠네요!
    • 랜덤 배치라는 특성을 고려하면 판매시 골드는 구입 골드로 환불을 해주고요!
    • 환불이 되면 타워는 당연히 맵상에서 사라지고 서버상에서도 사라져야겠죠?
  • 특정 타워 업그레이드 기능
    • 타워 업그레이드를 하면 새로운 타워 스프라이트도 적용해서 진짜 업그레이드 느낌이 나는거죠!
    • 공격력이 오르고 레이저 빔 발사 주기도 조금 더 짧아질 수 있겠네요.
    • 물론, 업그레이드도 돈이 들어야겠죠?
    • 랜덤 배치된 타워에서 특정 타워를 어떻게 선택해서 업그레이드 할 수 있을까요?
      • 만약, 이게 힘들다면 랜덤하게 업그레이드를 하는 기능도 방법입니다!
  • 보물 고블린 몬스터 출연 기능
    • 이따금씩 몬스터 웨이브 사이에 보물 고블린 몬스터를 출연시켜서 이 친구를 잡게 된다면 그 즉시 골드 보상을 해주거나 타워 보상을 해주면 그것도 재밌을 것 같습니다!

 

게임 작동 화면

 

 

서버와 통신 로그

 

 

게임 시연 영상

 

프로젝트 Github URL: https://github.com/znfnfns0365/tower-defense-game

 

GitHub - znfnfns0365/tower-defense-game

Contribute to znfnfns0365/tower-defense-game development by creating an account on GitHub.

github.com

  • Readme 파일에 게임 설명, AWS 배포 링크, 기능, 패킷 구조, 데이터 테이블, 폴더 구조 등이 나와있다.

 

Trouble Shooting

처음엔 전 프로젝트까지 쓰던 Prisma로 사용자의 정보(아이디, 패스워드)와 uuid, 유저 최고기록을 저장하였다. 이 과정에서 사용자의 정보같은 가벼운 데이터를 굳이 Prisma에 저장하지 말고 가벼운 데이터들을 저장할 때 주로 쓰는 redis에 저장하자는 의견이 나왔다.

 

 Problem: Prisma를 redis로 리팩토링하는 과정에서, 관계형 데이터베이스인 Prisma에서 find할 때 where구문으로 일치하는 데이터를 찾아서 가져오는 과정을 "key-value" 형식의 비관계형 데이터베이스인 redis에서는 직접 구현해야하는 상황이 발생했다. (key값으로 설정한 데이터(=username)를 찾을 때는 바로 찾을 수 있어서 편했지만, value에 들어가있는 데이터(=uuid)로 유저 데이터를 불러와야하는 상황)

 

Solve: redis의 keys('*') 메소드를 이용해서(모든 key 값을 불러옴) 모든 value를 불러온 뒤, uuid(key가 아닌 데이터)가 일치하는 key값을 찾아 return해주는 함수를 짰고, 이를 다른 redis 함수들과 함께 userData.handle.js 파일에 함수만 따로 저장해서 혼잡할 수 있는 코드를 깔끔하게 정리하여 해결했다.

 

But !!

Redis에서 *(와일드카드)로 키 목록을 스캔하는 것은 매우 위험한 행위이다. 서버의 유저가 많아질 수록 몇 명의 유저가 본인의 이름을 조회만 하려고 해도 이 게임 서버는 마비가 될 수 있다는 튜터님의 피드백이 있었고, 해결 방안으로 위와 같은 안티패턴 대신 username에 맞는 키 값을 미리 캐싱을 해놓거나, username 자체를 이용하여 키를 만들고 바로 조회되도록 수정하라 하셨다.

서버에서 유저의 정보를 가져올 때, Prima를 사용할 당시에 where 구문을 사용했기 때문에, find할 데이터가 username, uuid 등 여러가지를 사용했는데 이 부분을 처음부터 하나의 데이터로 통일했으면 이런 문제가 일어나지 않았을 것이다.

앞으로 프로젝트를 진행할 때 이 부분을 팀원들과 미리 조율하고, 구현 전에 통일시키는 것이 중요할 것 같다!

 

 

소감

사다리타기를 통해 결정된 팀장을 정하고 진행했던 프로젝트라 처음엔 걱정이 됐다. 하지만 팀원 모두가 열심히 참여하며 조원들끼리 똘똘 뭉쳐 역할을 분담한 뒤 진행한 결과, 프로젝트를 성공적으로 완성하였고, 이는 오히려 긍정적인 영향을 많이 미친 것 같다. 누구 한 명의 일방적인 캐리없이 모두가 자신의 역량을 발휘하여 완성한 결과물이라 조원들 모두가 뿌듯했고, 이번 프로젝트를 통해 다 함께 성장했다는 느낌을 받았다.