개발/소프트웨어 마에스트로

SWM에서 게임 개발로 살아남기(8. Manyfest)

잠수돌침대 2022. 12. 13. 17:30

시리즈물로 제작 중입니다. 이전 내용과 이어집니다.

https://songmin9813.tistory.com/8(7. Novelists)

살아남기 시리즈의 마지막 시리즈가 될 것 같습니다. 내실 더 다지면서 다음에 뵙겠습니다.

 


 

8. Manyfest

1. 최초 게임 기획 의도

 

지금까지의 게임들을 되돌아봤을 때, 싱글 플레이를 제외하고 전부 하나의 공통점을 가지고 있는 것을 발견했다. 그것은 바로 ‘2인 전용’을 가정하고 만든 게임이라는 점이었는데, 만들기 전에는 그 사실을 모르고 제작했다가 게임이 점점 많아지자 하나의 제작 목표가 되었던 것 같다.

 

이번엔 진짜 진짜 마지막으로 멀티용 게임 하나 만들고 끝낸다!!! 😜

 

마침 시기도 소프트웨어 마에스트로의 마지막을 달리고 있었기 때문에 해당 게임을 마지막으로 게임 기획과 제작을 종료하기로 마음먹었던 것 같다.

 

여러 게임들을 만들면서 코드를 재활용하고, 어느 부분에서 어떠한 방식으로 사용해야 하는지는 감이 잡혀있는 상태였기 때문에 최초 게임 기획/뼈대를 잡고 난 이후에는 꽤 수월하게 게임 제작에 들어갔었다.

 

여러 사용자를 가정하고 만든 Manyfest

해당 게임은 Maestro 기간 동안 5번째로 만든 게임이자, 여러 인원(최대 12명)을 가정하고 제작한 이미지 비대칭 게임이다.

사용자가 방에 입장하게 되면 프로그램은 각 Pair를 만들어 그 Pair에 맞는 같은 사진을 출력시키고, 대화를 통해서 누가 자신과 같은 사진인지 추론하는 게임이다. 만약 플레이어들이 Pair가 맞춰지지 않는 홀수일 경우, 사진만 사진을 가질 수도 있는 구조로 만들어 선택의 가능성을 높였다.

 

해당 게임에 대해서는 어뷰징의 요소가 적으면서 개인 입장에서 사진 한 장을 설명하는 것이라 난이도가 그렇게 어렵지 않을 것이라 판단. 여러 명이서 즐기기 적합한 파티 게임으로 구상하고 게임을 제작하였다.

 

게임은 많은 사람이(Many) 즐길 수 있다는 페스티벌(Festival)의 약자로 Manyfest라는 이름을 붙이게 되었다.

 

Manifest라는 안드로이드 스튜디오 설정 파일에서도 많은 영감을 받았다. 아는 사람만 아는 파일 이름이라고나 할까.

 

Picoke 게임과의 차이점을 들자면 Picoke은 각 플레이어가 6장+1장의 사진을 말해야 한다는 점인 반면에, Manyfest는 그 역할을 더욱 세분화시켜 여러 명이 참가하지만 사진의 사진만을 말하면 된다는 점에서 그 차별성을 가진다.

 

게임의 방향성과 콘셉트에서 예상하듯 기존 게임들(Twigitizer, Picoke 등)에서 정말 많은 코드와 소스, 함수를 참고하며 만들었기에 다른 게임이 가지고 있는 디자인과 유사한 구조를 가지고 있는 것을 알 수 있다.

 

2. 제약사항과 직면한 문제

 

추가적으로 해당 게임을 만들기 위해 요구된 가장 큰 제약사항은 아래의 내용과 같다.

 

💡 BE,FE 간의 추가 API 제작 없이 현재 존재하는 API 만으로 구현이 가능한 게임을 만들어야 한다.

 

실제로 개발 당시 BE와 FE의 추가 작업과 버그 리포팅을 실시간으로 진행하며 다른 태스크에 열중했던 것으로 기억한다. Game 입장에서는 추가적인 API를 구현하는 것이 다른 팀원에게 있어 큰 부담으로 다가올 수 있을 것 같아 Picoke의 받아오는 정보를 기반으로 게임을 제작하되, 최소한의 추가 정보만을 더하여 게임을 제작하였다.

 

Manyfest를 제작하기 위해 필요한 입력 정보는 다음과 같다.

 

let inputData = {
    images: [
      "random images 1",
      "random images 2",
      "random images 3",
      "random images 4",
      "random images 5",
      "random images 6",
    ],
    currentUser:"B",
    totalUser:5
  }

 

같은 방에 있는 플레이어로 하여금 완전히 같은 순서와 파일로 되어있는 image set. 그리고 현재 방에 몇 명이 있고, 현재 플레이어는 어떤 유저인지 다른 유저와 구분이 되는 currentUser 정보가 추가적으로 필요했다.

 

그리고 프로그램 상에서 이를 이용하여 같은 그림을 출력하게 하는 Pair를 맞추되, 초기 Pair 규칙은 인접한 플레이어로 상정하였다.

 

if(currentUser==="A"||currentUser==="B") picture.src=pictures.get("question")[0];
else if(currentUser==="C"||currentUser==="D") picture.src=pictures.get("question")[1];
else if(currentUser==="E"||currentUser==="F") picture.src=pictures.get("question")[2];
else if(currentUser==="G"||currentUser==="H") picture.src=pictures.get("question")[3];
else if(currentUser==="I"||currentUser==="J") picture.src=pictures.get("question")[4];
else picture.src=pictures.get("question")[5];

 

위의 코드는 currentUser를 기반으로 인접한 두 Player가 같은 이미지를 저장하게 하는 것을 의미한다. 하지만 해당 코드로만 프로그램을 실행하게 하면 사용자는 몇 번의 플레이로 자신이 어떤 플레이어와 Pair를 이루고 있는지 알게 되는 치명적인 단점이 있었다. 이는 해당 로직을 사용하기에는 로직에서 사용하는 User 정보와 실제로 유저에게 유저 정보를 알려주는 User 정보를 다르게 하는 Filtering 과정을 거쳐야 함을 의미했다.

 

어… 난 3번 유저네? 그러면 무조건 4번 유저하고 짝일 테니까 4번을 눌러야겠다.

 

위의 내용을 몇 번의 플레이를 통해 충분히 유추가 가능하다는 소리이기도 하다.

 

3. 핵심 알고리즘 - Seed를 이용한 고정 랜덤 배열 생성

 

이에 입력된 currentUser 정보를 바탕으로 다른 유저와 완전히 똑같은 정보를 주고받는 Image set(inputData.imgaes)를 일종의 시드로 작동하게 하여 같은 방의 있는 유저별로 똑같은 정보의 랜덤 출력 유저 정보를 생성하는 것으로 해결하였다.

 

아래의 코드는 실제 Filtering을 위해 생성되는 outputUser 데이터 배열을 생성하는 코드이다.

 

const makeOutputUser=(images, totalUser)=>{
    //length-5가 실제 숫자
    const result=[];
    for(let i=0;i<totalUser;i++){
      const index=i%images.length;
      let number=(Number(images[index][images[index].length-5])%totalUser)+65;
      //Use images as seed number
      while(true){
        const alphabet=String.fromCharCode(number);
        if(result.includes(alphabet))
          number = number + 1 > 64 + totalUser ? 65 : number + 1;
        else{
          result.push(alphabet);
          break;
        }
      }
    }
    return result;
  }

 

상기 코드에서 images의 끝자리 숫자 정보를 이용하여 랜덤 하면서도 같은 방 유저 입장에서는 완전히 같은 랜덤 정보를 가져오는 데 성공하였다.

 

전반적인 Pair 로직을 그림으로 표현하면 다음과 같다.

 

Filtering 로직과 current, output User 사이의 관계

추가적으로 인원이 많은 것을 가정하기에 RTM을 이용하여 실시간 정답 처리는 하는 것보다는 해당 플레이어가 틀렸는지 맞았는지 여부만을 체크하여 Client로 보내는 방식이 적당한 것 같아 해당 로직으로 게임을 기획하였다.

 

Picoke에서의 정답채택 방식인 ‘성공/실패 여부만을 Client로 보낸다’ 방식

Deleterow에서의 정답채택 방식인 ‘실시간으로 정답 여부를 처리하여 Client로 보낸다’ 방식

 

결국 이 둘의 로직 처리 방식의 장단점이 확실하다는 것을 새삼 깨닫게 되었다. Deleterow를 제작할 당시만 해도 전자의 방식이 그렇게 좋은 방식이 아니라 생각했지만, 전자의 방식이 근본적으로 간편하기 때문에 사용하기 편한 방식이라는 장점 또한 무시하지 못한다는 것을 알게 해 준 게임 기획이었다.

 

if (judge(selected)) {
          //Correct!
      personalCorrect=true;
      window.ReactNativeWebView.postMessage(toJSON("CORRECT","TRUE"));
else if (selected !== 0) {
          //Wrong!
      personalCorrect=false;
      window.ReactNativeWebView.postMessage(toJSON("CORRECT","FALSE"));
}

 

모든 코드에는 장단점이 뚜렷히 존재한다.