깃허브 주소 : https://github.com/yunu95/EndingFamily-Kimchi

유튜브 주소 : https://www.youtube.com/watch?v=mDR3Lyo6fZk&ab_channel=Floater 

목차

  1. 계획 단계
  2. 사전 작업- Yunuty 게임 엔진의 개발
  3. 프로젝트의 시작
  4. 소감

 

1. 계획단계

 2022년 11월 게임인재원 1학기, 약 1주~1주 반 정도의 시간을 들여 간단한 게임을 만들어 보는 시간이 있었다. 오후 5시까지 배정된 수업은 수업대로 진행하고 남는 시간에 게임을 개발하는 과제였다. 할당된 개발 시간이 절대적으로 부족하고, 팀원들도 모두 프로그래밍 초심자였기 때문에 개발역량의 견적이 나오지 않는 상황에서 떠올린 나의 전략은 "최소기능의 구현은 매우 간단하지만 확장이 용이한 게임을 만들자."였다. 이런 전략에 맞는 게임의 기획은 바로 클리커 게임이었다.

그저 쿠키를 많이 만들기만 하면 되는 게임, 쿠키 클리커

 유명한 웹 게임, 쿠키 클리커로부터 유래한 클리커 장르 게임은 생산 시설을 구입하고 시설의 효율을 증가시켜 자원 수치를 끝없이 올리는 것에서 재미를 찾게 만드는 게임이다. 사실상 이런 게임은 사용자가 스페이스 바를 눌렀을 때 어떤 숫자가 1씩 증가하는 시스템만 만들어도 게임이 성립되기 때문에 최소기능 구현이 매우 간단하다고 볼 수 있다. 이렇게 먼저 최소기능만 구현한 후, 시설, 업그레이드, 쿠키 생산량에 따라 출력되는 뉴스 내용과 같은 시스템들은 모두 추가 기능으로 두고 하나씩 개발하는 것을 계획으로 잡았다.

 원본 게임의 설정까지 완전히 다 베낄 수는 없으니 쿠키가 아니라 김치를 만드는 게임으로 설정을 바꿨다.

 

2. 사전작업 - Yunuty 게임엔진의 개발

같은 솔루션에서 게임 엔진 프로젝트와 게임 클라이언트 프로젝트를 분리하여 작성했다. 왼쪽이 클라이언트, 오른쪽이 게임 엔진.

 나는 애초부터 게임 엔진을 밑바닥부터 만들어보고 싶다는 생각으로 게임 인재원이라는 기관에 들어왔기 때문에, 평소에도 유니티를 모방한 자체 게임엔진 개발을 진행하고 있었다. 그래서 이번 프로젝트를 시작하기 전에 먼저 팀원들에게 양해를 구해 이 게임엔진 위에 클라이언트 코드를 얹는 방식으로 개발을 진행하기로 했다.

상용엔진 유니티의 라이프 사이클
게임엔진 사이클을 정의하는 싱글톤 클래스 YunutyCycle의 선언부(좌)와 구현부(우), 객체의 구조만 잡기 위해 구현부는 비어있는 함수들도 많다.
게임 스레드가 돌리는 함수인 ThreadFunction의 구현부, while(true)로부터 그 성질을 알 수 있는 함수로, 프로세스가 끝날때까지 이 함수의 반복문 내부는 무한정 실행된다.

 먼저, 유니티처럼 엔진이 끊임없이 동작하게 만들고 싶다면 스레드가 끊임없이 동작하며 게임 오브젝트 객체들의 함수들을 순서대로 부를 수 있어야 했다. 한마디로 엔진의 라이프 사이클을 정의해야 했는데, 시간 여건상 Start, Update 정도만 구현하고 넘기기로 했다.

게임 씬과 게임오브젝트 클래스의 선언부

 게임 씬과 게임 오브젝트는 자식 게임 오브젝트들을 계층적으로 배치할 수 있는 구조로 만들었다.

컴포넌트 클래스의 코드, 게임 사이클의 특정 이벤트마다 실행할 함수들인 Start,Update,OnEnable 등의 함수들이 가상 함수로 정의되어 있다.
게임 오브젝트 클래스의 코드 중 AddComponent, GetComponent등의 함수로 컴포넌트를 추가하거나 레퍼런스를 가져올 수 있게 만들었다.

 그 다음 게임엔진에서 가장 핵심적인 코드라 할 수 있는 컴포넌트 코드를 만들었다. 컴포넌트는 게임 오브젝트에 붙일 수 있는 부품같은 존재로, 게임 사이클의 특정 주기마다 내부의 함수들이 호출된다. 클라이언트 코드 개발자는 컴포넌트 클래스를 상속받는 임의의 컴포넌트 객체를 만들고 각 게임 사이클마다 호출될 멤버 함수들을 재정의하는 것으로 클라이언트 로직을 구현할 수 있다.

콘솔 창 프로젝트에서만 쓰일 코드가 필터로 분리된 모습

 이번 프로젝트는 "게임 화면을 콘솔창에서만 출력해야 한다."는 특수 룰이 적용되어 있었다. 나중에 알게 된 사실이지만 게임화면의 경우 게임 엔진과 별도로 그래픽스 엔진을 개발해 화면 출력의 역할을 전담시키는 것이 일반적인 개발방식이라고 한다. 당시 그래픽스에 대한 개념이 전혀 없었던 나는 화면 출력과 관련된 코드를 따로 라이브러리로 뺄 생각까지는 하지 못했지만, 게임 엔진의 핵심적 동작과 이질적인 코드들을 따로 관리해야겠다는 생각은 하고 있었다. 따라서 나는 콘솔창에서 게임 화면이 출력되는 것을 염두에 두는 코드들은 언제든지 떼어버릴 수 있도록 따로 Console 필터에 몰아넣어 분류하였다.

카메라 클래스와 그를 상속받는 콘솔창 카메라

 카메라는 카메라의 위치선정, 메인 카메라의 지정 로직을 넘어 아예 렌더링 함수까지 카메라에 넣었다. 물론 콘솔창에다가 렌더링을 하는 코드는 바깥으로 빼고 싶었기 때문에 콘솔창용 카메라는 카메라의 파생 클래스로  따로 만들었다. 환경이 달라질때마다 렌더링 방법도 달라져야 했기에 베이스 카메라 클래스의 렌더 함수는 완전 가상함수로 선언했다.

사용자 입력을 처리하는 인풋 클래스와 그를 상속받는 콘솔창 인풋 클래스

 사용자 입력을 처리하는 클래스도 나는 추상클래스로 만들어 처리했다. 콘솔 창에서 사용자 입력을 받는 로직이 나중에 환경이 달라짐에 따라 얼마든지 바뀔 수 있다고 생각했기 때문이다. 지금은 게임 엔진을 안드로이드, 리눅스에서도 쓸 생각을 하지 않는다면야 굳이 이렇게까지 할 필요는 없었다는 생각이 든다. GetAsyncKeyState라는 함수가 윈도우 운영체제 위에서의 사용자 입력은 충분히 다 받아줄 수 있었기 때문이다.

 어쨌든 게임엔진 사이클, 게임 오브젝트 - 컴포넌트 구조, 사용자 입력 감지 기능, 콘솔창의 카메라 출력 기능과 콘솔창의 그래픽스 기능을 거의 다 개발한 상태에서 프로젝트 팀이 구성되었다.

 

3. 프로젝트의 시작

팀 구성  
팀장
클라이언트 근간 시스템, UI A
사운드, 이미지 등 리소스 탐색 B
외부 라이브러리 연동 C

외국어 이름은 모두 가명임

 

 A는 대학에서 경영을, B는 건축을 배웠으며 C는 러시아어를 전공했다. 이들은 모두 코딩을 게임 인재원에 들어와서 처음 배운 초심자들로, 코딩을 배운 지 한 달 반 정도 뒤에 이 프로젝트를 시작했으니 다들 C++은 고사하고 C언어의 개념에도 익숙지 않은 상태였다. 큰 문제는 아니었다. 상황이 주어지면 상황에 맞게 계획을 짜면 된다. 팀원들의 개발 역량을 많이 기대할 수 없었고 그들의 현재 상태를 파악하는 것도 힘든 상황이었지만, 작업을 적절한 단위로 쪼갠 후 가장 작은 일부터 시작해 점진적으로 큰 일을 맡기면서 자연스럽게 작은 책임으로부터 큰 책임으로 역할의 확장을 유도하면 되는 일이었다.

 프로젝트 시작후 2일에서 3일정도 엔진 개발을 마무리하는 시간을 가지면서 그동안 팀원들에게는 쿠키 클리커를 플레이해보고 아이디어를 구상해보도록 했다. 나의 작업 때문에 전체 인원의 작업에 병목이 생기는 게 싫긴 했지만, 작업의 규모와 영역을 체계적으로 잘라 분배하기 위해서는 어느 정도 프레임워크가 있는 것이 없는 것보단 나을 거라고 판단했다.

반복문을 활용해 텍스트박스를 그리는 함수, 몇몇 클래스의 경우 함수의 선언만 내가 하고 구현은 팀원들에게 맡겼다. 위 함수는 A가 작업했다.

 팀원들은 프로그래밍 실력을 따지기에 앞서 프로젝트 자체에 대단히 큰 부담감을 느끼고 있는 것 같았다. 아마 자신이 코드를 어설프게 짜면 팀플을 망치게 되지 않을까 그랬던 것 같았다. 때문에 팀원들에게 먼저 자기확신이 들 수 있는 작업경험을 느끼도록 유도하고, 그 동력을 살려 자신감의 스노우볼을 굴려야겠다는 생각을 했다. 나의 이런 시도에 가장 고맙게 호응해 준 친구가 A였다. 나는 A에게 함수 기능 중 특정 반복문의 중괄호 내부를 채워달라고 부탁했는데 그 작업을 성공하면서부터 코딩에 대한 자신감이 붙더니 나중에 가서는 함수 전체를 구현하질 않나, 내가 짠 예시 코드를 보고 화면들의 UI 구성을 다 해주지 않나, 나중에 가서는 반복되는 코드를 함수로 모듈화하는것까지 너끈하게 해내는 것이 여간 든든한 게 아니었다.

 C는 다른 의미에서 나를 든든하게 도와줬다. 나보다 한살 많은 형님이셨는데, 사운드출력 기능을 구현해야 하는 일이 생기자 "개발이 끝나기 전까지 사운드 라이브러리 FMOD를 공부해 오겠다."라는 말을 남기고 며칠 뒤 라이브러리 기능을 가져오고 사용법을 설명해 주시는 호방한 형님으로, 실로 뜨거운 술이 식기 전에 안량의 목을 취한 관우의 기상이 있는 분이셨다. 프로그래밍을 갓 시작하신 분 치고는 대단한 독립심과 자주성을 갖고 계셨으며, 목표달성에 필요하면 가리지 않고 공부하고 배우는 자세는 나도 배울점이 많았던 것 같다

 B는 이번 프로젝트에서 큰 성취를 이뤄내지는 못했다. 의기소침해있던 B에게 A와 비슷하게 접근해 가장 작은 단위의 작업을 먼저 맡겨 거기서부터 코딩에 대한 자신감을 심어주고 싶었지만, 내가 B에게 믿음을 주지 못했는지 그는 결국 한 줄의 코드도 짜지 못하고 프로그래밍 작업에서 완전히 소외되고 말았다. 리소스를 찾는 역할, 기획을 보조하는 역할을 주긴 했지만 결국 프로그래밍을 배우러 온 친구에게 잡일만 시킨 꼴이니, 마음 한켠에 항상 미안한 마음이 들었다.

커밋 메시지 목록과 커밋 비율

 어쨌든 팀의 응집력을 최대한 끈끈하게 유지하려 했던 나의 노력이 부족했을 수도 있고 세련되지 못했을 수도 있지만 어떻게든 팀은 굴러갔고, 게임의 윤곽이 서서히 드러나기 시작했다.

게임화면 중 생산시설 구매창

마지막으로 생산시설이나 업그레이드의 가격, 효율을 세심하게 세팅하는 것도 중요했다. 이 수치들을 아무렇게나 설정할 수는 없으니, 빠르게 해당 부분에 대해서만 기획문서를 작성했다.

 수치 조절까지 적절하게 끝내고 나니 상당히 재미있는 게임플레이가 탄생했다. 업그레이드들의 효율개선 수치가 각자 2배~256배까지 달랐기 때문에 업그레이드를 할 때마다 생산시설들의 효율이 극적으로 달라져 굉장히 역동적으로 생산 시설과 업그레이드를 구매하게 만드는 플레이를 유도했던 것 같다.

 

4. 소감

 처음으로 진행한 게임 개발 팀플이었는데, 팀 분위기도 기간 내내 괜찮았고, 결과물도 생각보다 잘 나와서 좋았다. 자동사냥게임 같은 느낌이 있어서 지금도 간간히 하는 게임이다.