게임엔진 없이 게임을 만들었다

허형준·2023년 9월 20일
12

기술

목록 보기
7/9
post-thumbnail

이 포스트는 Archery 게임을 개발하며 얻은 기술적 인사이트를 담고있습니다. 양궁게임으로 기타 상용 게임엔진(유니티, 언리얼) 오로지 JavaScript,ThreeJS(3D 라이브러리) 만으로 게임을 개발했습니다.

1. 게임을 왜 개발했나

수시 접수 기간동안 할 일이 없어서 개발했다. 대학 전부를 수능최저 없는 전형으로 지원했고 그에 따라 수능공부를 할 필요가 없으니 남는 시간에는 놀기만 하면 되었다. 남은 시간이 거의 하루 24시간이라 뭐 할거없나 찾아보다 게임을 개발하기로 결정했다.

게임을 만들기 위한 몇 가지 조건을 나열해보면 다음과 같다.

우선 설치형 게임이 아니면서 접근성이 좋아야 한다. 답은 웹게임. 개발하기도 간단하고 무엇보다 플레이스토어 출시 없이 바로 웹에 배포만 하면 끝이다. 물론 플레이스토어 개발자 계정은 있지만 따로 포팅하기 귀찮아서 딱히 앱으로 개발하지는 않았다.

다음으로 상당히 가벼워야 한다. 번들링, 모델 파일은 1MB 이상 넘어가지 않도록 설계했고 넘어간다 싶으면 폴리곤 수를 줄이거나 아예 추가하지 않았다. 또한 연산이 무거운 collision detection을 최대한 적게 사용해야 했다.

마지막으로 간단하게 개발할 수 있어야 한다. 긴 시간이 소요되는건 원치 않았다. 적어도 1주일 이내에 끝낼 수 있는 분량으로 핵심 코드를 작성하고 이후에 기능을 더 추가하는 확장 개발 방식을 택했다. 최소 MVP를 만들어 놓고 구글 애널리틱스로 분석해보며 개발을 지속할지 말지 결정하는 최소 개발 방법론을 중심으로 진행하기로 했다.

2. 기술스택 선정

3D를 웹에서 다룰때 ThreeJS를 쓰는게 편하다. 게임엔진은 아니지만 이걸로 게임 만들지 말란법은 없으니까 ThreeJS로 3차원 공간을 표현해주었다.

UI는 React/TypeScript를 사용했다. 어짜피 가벼운 프로젝트라 Web Components를 사용해도 무방했는데, 웬지 React를 쓰는게 확장성 측면에서 용이할거 같아 React로 결정했다. 이건 최적의 선택이었고 후회하지 않는다.

웹 게임은 성능과 최적화가 중요하다. ThreeJS에는 별도의 물리엔진(ammo.js/cannon-es 등)을 추가하지 않았고 따라서 성능상의 이점을 가져올 수 있었다.

3. 기획

게임의 전제 조건은 다음과 같다.

  1. 조작은 한 손으로도 가능할것
  2. 모바일을 지원할 것
  3. 무거운 폴리곤이나 연산을 수행하지 않을 것
  4. 필요한 음향이 단조로울 것
  5. UI는 간편할 것
  6. 재미있어야...

지금 와서 생각해보니 기술적으로 치중했던 부분이 없지 않아 있다. 게임의 본질인 재미의 요소를 고려하지 않고 만들었다는게 지금 와서 돌이켜보니 실책이었다. 그러나 개발할 당시에는 일단 만들고 보자라는 생각이 강해서인지 게임의 본질적 요소보다는 기술적 구현 가능성에 중심을 두었다.

아무튼 이 포스트는 게임 기획에 대한 내용은 아니다. 기술적 한계 내에서 최상의 그래픽과 성능을 뽑아내기 위한 기술적 요구사항에 가깝다.

4. 개발

기술스택 선정도 끝났고, 기획도 어느정도 마련되었으니 개발에 들어갈 시간이다. 우선 화살은 포물선 공식을 따른다. t초가 지난 후 x, y 좌표를 구해주는 포물선 공식이다.

60 프레임 기준 1초에 60번 저 위치를 계산해주기 위해 t는 매 프레임당 1/60 씩 더해주어야 한다. 참고로 저건 회전량은 고려하지 않았기 때문에 z축에는 세타h=가로축 회전값 만큼 곱해주어 계산해준다.

그럼 플레이어의 위치에서 방향값만 있으면 3차원 공간 어디든 날라가는 화살을 구현할 수 있다.

필요한 공식은 이게 끝이다. 나머지는 그래픽스 영역과 최적화다. 위 구문은 코드로 옮기면 아래와 같다.

const thisTime = this.clock.getElapsedTime() - this.archery.arrow.shotTime
const windSpeed = this.takeWindSpeedToXY({ ...this.windSpeed  })

const v0 = this.archery.v0
const theta = this.archery.rotation.p 
const g = 9.8
const t = thisTime
const x = - v0 * Math.cos(theta) * t + ((windSpeed.y ) * thisTime)
const y = 3 + v0 * Math.sin(theta) * t - 1/2 * g * (t*t)
const z = - v0 * (this.archery.rotation.h*1.4) * t + ((windSpeed.x ) * thisTime)

추가로 몇 가지 디테일을 추가했는데, 예를들면 화살이 명중했을때 떨리듯이 진동하는 효과이다. 진동은 sin과 cos파로 구현했으며 코드는 다음과 같다.

private vibrateArrow() {
  let count = 0
  const arrowModel = this.arrow[this.shotCount]
  const rotationY = arrowModel.model.rotation.y
  const rotationX = arrowModel.model.rotation.x

  this.animateInterval = setInterval(() => {
    arrowModel.model.rotation.y = rotationY + (Math.cos(count) / (200))
    arrowModel.model.rotation.x = rotationX + (Math.sin(count) / (200))
    count += 0.8
    if (count > 16) {
      clearInterval(this.animateInterval)
    }
  }, 20)
}

sin cos 파로 회전하며 원형 진동을 구현했다. 이러한 그래픽스 요소 외에 추가로 들어간 기능은,

  1. 실시간 멀티플레이
  2. 스코어 및 통계 계산
  3. 좀비출현
  4. i18n 다국어 지원
  5. 스테이지

가 있다.

4-1. ThreeJS와 React간 상태 공유

사실 ThreeJS를 react-three-fiber 라이브러리로 바꿔 사용하면 최적화나 기타 여러 부문에서 이점을 가져올 수 있었다. 그러나 필자는 위 라이브러리 문법에 익숙하지 않고 ThreeJS의 공식 문법에 익숙하다. 최대한 짧은 시간내에 MVP를 만들기 원했고 react-three-fiber가 아닌 ThreeJS 기본 문법으로 작성했다. 따라서 ThreeJS와 React는 각각 분리된 환경으로 구성되며 데이터(상태) 공유는 어려워진다.

이 문제를 해결하기 위해 Redux를 도입했다. Redux는 범용 라이브러리로 React 뿐 아니라 Web Components, ThreeJS Class에서도 사용 가능해 확장성 있는 라이브러리다.

4-2. UI 라이브러리를 사용하지 않았..

필자는 Material UI를 자주 사용한다. 비교적 최근까지 개인적으로 만들어 사용한 bootstrap 기반 devent design system을 사용하고 있었지만, React에서 사용하기에 어려움이 많아 이참에 UI 라이브러리를 바꿔 사용중이었다. 그러나 이번 프로젝트에서는 UI 라이브러리를 일절 사용하지 않고 only css만 사용해서 개발했다.

특히 호불호가 갈리는 css in js 방식을 사용했는데, 큰 프로젝트가 아니고 기본적인 UI만 들어가면 가능해서 이 방식을 사용했다. 예를들어 버튼 컴포넌트 설계는 다음과 같다. style={{}} 내부에 CSS 스타일을 집어넣는 방식으로 랜더링 타임이 길다는 단점과 스타일링 관리가 어렵다는 굉장한 단점이 있으나, 한 파일에서 집중적으로 관리할 수 있고 동적으로 스타일 변경이 가능하다는 점이 이 스타일 설계 방식의 장점이다.

4-3. Webpack

사실 앞서 Material UI를 사용하지 않은 주된 원인이다. Webpack Bundling시 2000ms 가까운 최악의 성능을 보여주었고 이는 Material UI 자체가 모든 기능을 한 번에 담고있기 때문이다. Tailwind처럼 사용한 부분만 불러오는게 아닌 모든 의존성을 불러와서 굳이 필요없는 부분도 웹팩에서 압축되는 셈이다.

그럼 TailwindCSS를 쓰는게 맞지 않냐고 반문할 수 있다. Tailwind는 프로토타이핑에 적합하고 진입장벽도 어렵지 않으며 무엇보다 가볍다는 점에서 말이다.

그러나 필자는 Tailwind 같은 문법이 비효율적이라고 생각하는 쪽이다. 매번 공식문서 찾아가며 문법을 알아야하는 것도 시간낭비고 동적인 스타일 변경에서 문제가 있으며 상황에 따라서는 CSS 문법을 이용하는게 시간 절약이 도움이 된다.

4-4. 배포

역시 Docker + GoCD 조합으로 마무리 지었다. 이와 관련해서는 필자의 다른 포스트 GoCD Pipeline 구축 포스트를 읽어보길 권한다.

레포 브렌치를 분기별로 나눠 merge 이벤트를 GoCD에서 트래킹하고 자동으로 배포 반영되는 전략을 취했다.

배포는 역시나 홈서버다. 필자의 경우 홈서버 수용인원에 제한이 생기면 AWS로 넘어가려 생각중인데, 예전에 테스트 해봤을때 10000MAU 이상은 멀쩡하다. 즉, 웬만큼 뜨는 프로젝트 아니면 굳이 AWS로 넘어가는건 손해라고 본다.

5. 상용 게임 엔진을 쓰지 말고 개발하는건

(사실 이거 이야기 하려고) 유명한 상용 게임엔진을 나열해보자.

  1. 유니티
  2. 언리얼엔진

생각나는게 이거 딱 두개밖에 없다. 그만큼 게임 엔진 시장이 독과점 구조라는 뜻이다. 상용 게임 엔진은 개발자 친화적인 구조에 초보자들도 비교적 쉽게 게임을 만들도록 돕는 막강한 툴이다. 결제모듈, 모바일 개발, 최적화, 에셋 추가등 여러 방면에서 직접 개발이 필요한 요소를 줄여주고 개발 시간을 단축시킨다.

반면 이런 편의성에는 단점도 존재한다. 게임엔진을 개발하는 회사도 마친가지로 먹고 살아야 하니 시장이 침체될때는 과금 정책이 심해진다.

언리얼 엔진의 경우 유료엔진이었다가 유니티를 견제하기 위해 4버전 부터 무료로 전환했다. 만약 시장에서 유니티가 사라진다면 언리얼엔진은 다시 유료로 전환할지도 모른다.

게임엔진에서의 독과점 악용에 대표적인 사례로 이번 유니티 사태가 있다. 잠깐 유니티 사태를 모르는 사람을 위해 짚고 넘어가자. 유니티엔진을 이용해 만든 게임은 유저가 설치할 때 마다 과금을 해야하는 구조로 정책이 변경됬는데, 이 과금 정책이 정신 나간 정책이라고 인디 게임 개발사들은 물론 개인 게임 개발자들도 반발하고 있다. 만일 악성유저가 게임을 설치했다 지우고 다시 설치하기를 반복한다면 게임사는 파산해버린지도 모른다.

이러한 독과점으로 인한 피해를 줄이기 위해 대형 게임사들은 자체적으로 게임 엔진을 구축해서 만든다. 그러나 대부분의 인디 게임사들의 경우 자체적으로 구축할 여력이 안되기 때문에 상용 엔진을 가져다 쓴다.

사실 필자는 독과점을 굉장히 혐오한다. 그래서 오픈소스 정책을 지향하고 또 필자도 오픈소스로 개발해 누구나 사용할 수 있도록 깃허브에 공개하고 있다. 게임 개발도 마찬가지여야 한다고 생각한다. 어느 한 기업이 독점적인 지위를 누리지 않게 하기 위해 엔진의 오픈소스화도 필요하다고 본다.

오픈소스 게임 엔진의 대표격은 고도엔진이다. 유니티에서 고도엔진으로 갈아타고 있다는 게임 개발자들이 한 둘이 아니다. 그만큼 독과점은 게임 개발에 있어 치명적으로 변질될 우려가 있다.

하나의 툴에 익숙해지지 말자. 여러 툴을 익혀 의존성을 줄여두면 분명 요긴하게 쓰이는 순간이 온다.

6. 결과

학교 친구들, 주변 지인들에게 물어보니 공통된 반응이 나왔다.

핵심 콘텐츠가 없다

이 게임을 해야하는 강력한 동기도 없고, 재미도 없다. 물론 초기 작품이고 기술적인 부분에 치중하는 경향이 있어 합리적인 반응이라 예상했다. 어쨌든 프로젝트를 통해 달성하고자 하는 목표는 모두 달성했다.

이후 이대로 끝내기는 아쉬워서 커뮤니티 몇 곳에 링크를 첨부해 홍보해보았다.

170WAU 정도 나왔고 초반 공개 수치로는 아쉬운 결과다. 일단 실시간 멀티플레이 기능을 추가하고 있고 테스트 해봐야 알겠지만 DAU를 올리는컨텐츠를 기획하는데에 우선할 계획이다.

profile
소프트웨어 개발자.

1개의 댓글

comment-user-thumbnail
2023년 10월 26일

재밌따... 20분 동안 즐겼네요 ㅋㅋㅋ 9스테이지가 한계 ㅠ
중간마다 검은 좀비는 무찔러줘야 되는건가요..?

혹시 오픈소스라면 소스를 볼 수 있을까요?

답글 달기