dh_0e

[Project] 풋살 온라인 게임 (팀 프로젝트) 본문

내일배움캠프/Project

[Project] 풋살 온라인 게임 (팀 프로젝트)

dh_0e 2024. 6. 26. 21:26

블로그 정리중 2주 전에 진행했던 팀 프로젝트를 빼먹어 지금이라도 정리하여 포스팅한다..!

이번(저번) 프로젝트는 피파 온라인 4를 오마주한 풋살 온라인이라는 게임을 서버로 제작하는 것이다.

 

필수 기능 정의

더보기
  • 회원가입 / 로그인
    • 이건 뭐 당연한 기능이니 굳이 설명을 하지는 않겠습니다.
    • 회원가입 시 기본적으로 캐시를 충전해주세요. (캐시의 기본값은 여러분들이 정해주세요!)
      • 캐시는 선수 뽑기를 할 때 소모가 되어야 하며 캐시가 없으면 선수를 더 뽑을 수 없어야 합니다!
  • 캐시 구매 기능
    • 결제 연동을 하는 것이 아니라 단순 API 호출을 하면 일정량의 캐시 구매를 할 수 있게끔만 해주세요!
    • 이건 코어 기능이 아니기 때문에 최대한 단순하게 마무리를 해주세요.
      • 선수 뽑기 시 캐시가 부족할 때를 대비해서 만드는 기능입니다!
  • 선수 데이터 준비
    • 풋살 온라인에서 실제로 사용할 선수들의 데이터 포맷을 정의하고 이에 맞추어 선수 데이터를 데이터베이스에 미리 준비를 시켜야합니다.
    • 물론, 이것을 API화 시키는 방법도 있습니다만 데이터를 사전에 미리 준비하고 한 번에 넣는게 효율적이긴 할 거에요!
    • 선수 데이터 예시
      • 선수 이름: 리오넬 메시
      • 속력: 86
      • 골 결정력: 98
      • 슛 파워: 94
      • 수비: 75
      • 스태미너: 85
    • 이렇게 뽑기 전에 뽑기 씬(Scene)을 먼저 연출해준 다음에요.
      • 이걸 게임 업계에선 보통 Gacha(가챠)라고 표현하죠.
      • 당연히, 뽑기 1번에 일정량의 캐시가 소비되어야 합니다. (이 소비되는 캐시값은 여러분들이 정해주세요!)
      • 선수를 뽑으려면 당연히 미리 선수 데이터가 데이터베이스에 준비되어야 하는 것은 기본이겠죠?
        • 위에서 정의한 선수 데이터 준비 단계가 완료가 되어야 해당 기능을 구현할 수 있어요!
      • 게임에서 필수 BM(비지니스 모델) 중 하나이기 때문에 이 기능에 상당히 공을 들이곤 한답니다.선수 뽑기 기능
    • 좋은 스펙의 선수가 뽑히면 이렇게 화려한 연출을 해서 내가 좋은 아이템을 얻었구나라고 인식합니다.
  • *이거 눌러도 재생 안됩니다. 낚이지 마세요(..)*
  • 나만의 팀 꾸리기 기능
    • 피파 온라인 4는 결국 좋은 선수들을 모아서 보유한 선수 중 우수한 선수들로 나만의 팀을 꾸린 다음에 상대방과 온라인 축구 대전을 하는 것이 핵심 콘텐츠입니다.
    • 따라서, 선수 뽑기 기능으로 선수들을 확보하면 그 중 우수한 선수들로 팀을 꾸리는 것입니다!
    • 하지만, 우리가 만드는 게임은 풋살 온라인이기 때문에 편의상 1팀은 최대 3명의 선수로 구성될 수 있다고 하겠습니다.
    • 또한, 포메이션 및 포지션과 같은 개념은 과감히 생략하도록 하겠습니다.
    • 팀을 꾸리지 않으면 축구 게임을 진행할 수 없으니 유의해주세요
  • 축구 게임 기능
    • 게임 로직을 디테일하게 구성하기 위해서는 리얼 게임 서버가 필요하겠죠?
      • 클라이언트 유저가 입력한 데이터를 클라이언트 → 게임 서버로 전송하죠!
        • 이동, 패스, 슛, …
      • 그리고 게임 서버에서는 최신으로 도착한 데이터를 기반으로 상태 동기화를 합니다!
        • 고도의 동기화 기술이 구현이 되어있어야 랙 현상을 느끼지 않고 게임을 플레이 할 수 있어요!
      • 상태 동기화 시에 선수들끼리 충돌을 하면 어떻게 될까요?
        • 충돌 처리 테크닉을 사용해서 중요한 판정을 수행해야 됩니다! 이것도 게임 서버가 하죠!
        • 누군가 태클에 걸려서 넘어지거나 태클을 무시하거나 오히려 태클 건 친구가 날아가거나…
    • 🤔 리얼 게임 서버가 필요한지는 그냥 느낌적으로 알 것 같긴한데… 서버가 하는게 뭐에요?
    • 하지만, 우리는 지금 이러한 로직까지 구현할 수 있는 상황이 아니기 때문에 게임 로직을 매우 심플하게 가져가겠습니다. 하지만, 이건 예시일뿐! 여러분들이 스스로 게임 로직을 고도화시키셔도 무방해요!
      • 단, 가이드 된 게임 로직대로 구현을 안하셨다면 Github Readme에 꼭 게임 로직을 묘사해주세요!
    • 게임 로직 구성 전 정의해야 할 것 - 선수 스탯
      • 선수 스탯 예시
        • 속력
        • 골 결정력
        • 슛 파워
        • 수비
        • 스태미너
    • 예시 게임 로직 (A 유저 VS B 유저)
      • A 유저 팀 구성
        1. 선수 1: 공격수
          • 속력: 85
          • 골 결정력: 90
          • 슛 파워: 88
          • 수비: 60
          • 스태미너: 80
        2. 선수 2: 미드필더
          • 속력: 78
          • 골 결정력: 75
          • 슛 파워: 80
          • 수비: 70
          • 스태미너: 85
        3. 선수 3: 수비수
          • 속력: 70
          • 골 결정력: 60
          • 슛 파워: 65
          • 수비: 90
          • 스태미너: 82
      • B 유저 팀 구성
        1. 선수 1: 공격수
          • 속력: 88
          • 골 결정력: 85
          • 슛 파워: 90
          • 수비: 55
          • 스태미너: 78
        2. 선수 2: 미드필더
          • 속력: 80
          • 골 결정력: 70
          • 슛 파워: 75
          • 수비: 72
          • 스태미너: 88
        3. 선수 3: 수비수
          • 속력: 72
          • 골 결정력: 65
          • 슛 파워: 68
          • 수비: 92
          • 스태미너: 83
      1. 선수 스탯마다 게임 승리와 패배를 결정지을 수 있는 가중치를 임의로 부여하겠습니다. 각 가중치의 총합은 1이어야 합니다. 가중치 부여 예시입니다.
        1. 속력 - 0.1
        2. 골 결정력 - 0.25
        3. 슛 파워 - 0.15
        4. 수비 - 0.3
        5. 스태미너 - 0.2
      2. 그러면, 이 가중치 부여 예시 기반으로 각 선수의 스탯을 정규화해보겠습니다.
        • 정규화된 스탯의 A 유저 팀 구성
          1. 선수 1: 공격수
            • 속력: 85
            • 골 결정력: 90
            • 슛 파워: 88
            • 수비: 60
            • 스태미너: 80
            • 총 점수 계산: (85 * 0.1) + (90 * 0.25) + (88 * 0.15) + (60 * 0.3) + (80 * 0.2)
              • = 8.5 + 22.5 + 13.2 + 18 + 16
              • = 78.2
          2. 선수 2: 미드필더
            • 속력: 78
            • 골 결정력: 75
            • 슛 파워: 80
            • 수비: 70
            • 스태미너: 85
            • 총 점수 계산: (78 * 0.1) + (75 * 0.25) + (80 * 0.15) + (70 * 0.3) + (85 * 0.2)
              • = 7.8 + 18.75 + 12 + 21 + 17
              • = 76.55
          3. 선수 3: 수비수
            • 속력: 70
            • 골 결정력: 60
            • 슛 파워: 65
            • 수비: 90
            • 스태미너: 82
            • 총 점수 계산: (70 * 0.1) + (60 * 0.25) + (65 * 0.15) + (90 * 0.3) + (82 * 0.2)
              • = 7 + 15 + 9.75 + 27 + 16.4
              • = 75.15
        • 정규화된 스탯의 B 유저 팀 구성
          1. 선수 1: 공격수
            • 속력: 88
            • 골 결정력: 85
            • 슛 파워: 90
            • 수비: 55
            • 스태미너: 78
            • 총 점수 계산: (88 * 0.1) + (85 * 0.25) + (90 * 0.15) + (55 * 0.3) + (78 * 0.2)
              • = 8.8 + 21.25 + 13.5 + 16.5 + 15.6
              • = 75.65
          2. 선수 2: 미드필더
            • 속력: 80
            • 골 결정력: 70
            • 슛 파워: 75
            • 수비: 72
            • 스태미너: 88
            • 총 점수 계산: (80 * 0.1) + (70 * 0.25) + (75 * 0.15) + (72 * 0.3) + (88 * 0.2)
              • = 8 + 17.5 + 11.25 + 21.6 + 17.6
              • = 75.95
          3. 선수 3: 수비수
            • 속력: 72
            • 골 결정력: 65
            • 슛 파워: 68
            • 수비: 92
            • 스태미너: 83
            • 총 점수 계산: (72 * 0.1) + (65 * 0.25) + (68 * 0.15) + (92 * 0.3) + (83 * 0.2)
              • = 7.2 + 16.25 + 10.2 + 27.6 + 16.6
              • = 77.85
        이제 위와 같이 정규화 된 팀 구성 기반으로 팀의 총 점수를 매기면 다음과 같습니다.
        • A 유저 팀의 총 점수: 229.9
        • B 유저 팀의 총 점수: 229.45
      3. 각 팀의 총 점수들을 구했으면 이제 이것을 기반으로 룰렛을 돌리면 됩니다. 예를 들어, 총 점수의 소수점은 일단 내림처리(Math.floor)로 한다고 하면 두 팀의 총 점수는 동일합니다. 하지만, A는 총 점수가 90인데 B는 10인 경우도 있겠죠. 이러면 확실히 B의 승리 확률은 희박해야 공평해집니다. 나름 팀 스탯 기반의 룰렛이니 어느정도 공평한 측면이 있죠.
      4. 예시 코드를 작성해보면 다음과 같습니다.
      5. // A 유저 팀과 B 유저 팀의 총 점수 const scoreA = 229.9; const scoreB = 229.45; // 최대 점수는 두 팀의 총 점수의 합으로 하시면 됩니다! const maxScore = scoreA + scoreB; const randomValue = Math.random() * maxScore; if (randomValue < scoreA) { // A 유저 승리 처리 const aScore = Math.floor(Math.random() * 4) + 2; // 2에서 5 사이 const bScore = Math.floor(Math.random() * Math.min(3, aScore)); // aScore보다 작은 값을 설정 result = `A 유저 승리: A ${aScore} - ${bScore} B`; } else { // B 유저 승리 처리 const bScore = Math.floor(Math.random() * 4) + 2; // 2에서 5 사이 const aScore = Math.floor(Math.random() * Math.min(3, bScore)); // bScore보다 작은 값을 설정 result = `B 유저 승리: B ${bScore} - ${aScore} A`; }
    • 준비된 게임 로직대로 승패가 갈리면 이제 게임 결과 테이블에 기록을 하면 됩니다! 제가 예시 코드에 작성한 것처럼 스코어도 어느정도 랜덤하게 생성해서 리얼하게 결과를 기록할 수 있으면 되죠!
    • 게임을 할 때는 항상 상대의 온라인/오프라인 여부는 따지지 않고 무조건 게임 신청을 할 수 있다고 가정을 하겠습니다! 그래서 만만한 유저(?)를 발견하셨다면 계속 공략해서 어뷰징도 가능한 구조(..)
      • POST /api/games/play/:user_id 와 같이 API를 호출하면 게임이 바로 시작 후 종료된 후 게임 결과를 받는 느낌이라고 생각하시면 되겠어요!

 

도전 기능 정의

더보기

위의 필수 기능을 다 구현하고나면 이제 조금 더 리얼한 온라인 게임을 표방할 수 있는 장치들을 심어서 더욱 더 우리의 풋살 온라인 프로젝트 자체의 몰입도를 높여보는 시간을 가져보도록 하겠습니다!

  • 승리/패배 시 게임 점수 조정 기능
    • LOL 하시는 분들은 너무 많이 본걸거라 자세한 설명은 생략합니다만 승리/패배 시 게임 점수는 항상 올라가거나 떨어져야 하는 것은 기본입니다.
    • 우선, 기본 게임 점수는 1000점으로 하겠습니다.
    • 일단은 심플하게 이기면 +10점, 지면 -10점과 같이 하고 뒤에서 조금 더 고도화하는 방향으로 해볼게요!
    • 이 단계까지 왔다면 유저는 본인들의 게임 점수가 정의된 후 관리가 되고 있어야 한다는 뜻입니다!
  • 유저 랭킹 조회 기능
    • 게임 점수가 생겼다면 이제 이걸로 유저 랭킹을 조회할 수 있겠죠?!
    • 승률과 승/무/패까지 적어주면 정말 있어보이긴 할 것 같습니다!
  • 점수 기반 자동 매치 메이킹 기능
    • 제가 필수 기능에서 얘기했듯이 지나치게 팀 스탯이 낮은 유저들만 골라서 반복적으로 이기면 유저들 점수에 거품도 끼고 게임 자체도 매우 재미없어지며 있던 경쟁심도 사라질겁니다.
    • 이러한 요소를 방지하기 위해 점수 기반으로 매치 메이킹을 자동으로 하는 API를 만들어볼게요!
    • 이 기능에서는 유저 ID를 직접 지정해서 게임을 하는게 아니라 랜덤하게 본인의 점수와 얼추 비슷한 유저들 중 한명을 랜덤으로 골라서 게임을 하는 것이라고 생각하면 됩니다!
      • 이 얼추 비슷한 정도는 여러분들이 한 번 생각해보시고 코드에 녹여내시면 되겠어요!
  • ⭐⭐⭐ 스페셜: 선수 강화 기능 ⭐⭐⭐
    • 사실상 피파 온라인 4의 핵심 BM이자 게임을 대표하는 기능이 바로 이 악마같은 강화 기능입니다.
    • 이 기능을 어떻게 구현해야되는지는 제가 디테일하게 가이드는 하지 않겠습니다!
    • 다만, 이 기능을 구현하신 팀은 어떻게 구현했는지 Github Readme에 꼭 작성을 해주세요!
    • 구현할 때 어떤식으로 강화 성공/실패를 판정할 것인지 성공했을 때 능력치는 어떻게 변화시킬 것인지 재료는 어떻게 넣어야 성공 확률을 높일 것인지 등 생각해야 될 것이 참 많은 기능이므로 스페셜 기능으로 지칭했습니다!위의 필수 기능을 다 구현하고나면 이제 조금 더 리얼한 온라인 게임을 표방할 수 있는 장치들을 심어서 더욱 더 우리의 풋살 온라인 프로젝트 자체의 몰입도를 높여보는 시간을 가져보도록 하겠습니다!
      •  

 

 

 

시연 영상

 

프로젝트 Github URL: https://github.com/znfnfns0365/Futsal_online

 

GitHub - znfnfns0365/Futsal_online: 풋살 온라인 프로젝트

풋살 온라인 프로젝트. Contribute to znfnfns0365/Futsal_online development by creating an account on GitHub.

github.com

  • Readme 파일에 데이터베이스 ORM, API 명세서, 파일구조, 게임 매치메이킹 및 점수 계산 로직 등이 나와있다.

 

Trouble Shooting

게임 매치메이킹 및 점수 계산 로직을 짤 때 개발자가 아닌 기획자의 입장에서 재미성과 형평성을 보장하기 위해 로직을 짜보았는데, 게임을 계속해서 해보고, 팀원들의 피드백을 받으면서 계속해서 밸런스를 맞추다 보니 코드가 복잡해졌고, 이를 처음부터 계획적으로 진행했다면 어떨까 생각이 들었다.

확률적인 값들을 데이터베이스에 저장해놓고 기획자가 DB만 수정하여 기획적인 면을 수정할 수 있게끔 하는게 좋다는 피드백을 받았다. 이는 게임 서버 개발자들이 기획에 관여하지 않고, 기획자들에게 이를 맡기는 부분이라고 하셨다.

 

소감

팀원분들과 협업이 잘 돼서 프로젝트 진행하는 1주일 내내 즐거웠다. 특히 팀장님 주도하에 팀원분들과 소통이 원활하게 진행되었던게 큰 역할을 했던 것 같다!