콘솔기반에서 실시간 RPG를 만들며 다사다난했던 일주일을 회고한다.

https://github.com/leeseungjae97/TEXT_RPG_5

일주일간 정말 많은시간을 쏟았다.
콘솔 게임에 뭐 그렇게 까지 하냐라고 할 수 있지만
일단 재밌었고 짜둔 구조가 협업에 적합해 쌓이면 쌓는대로 컨텐츠가 만들어져서
후반에는 재미로 개발하며 새로운 기능을 추가했다

image-1780041632109

전쟁같은 흔적

image-1780043359272

발표까지 마치고 나니, 일주일 동안 정말 많은 것을 쏟아부은 프로젝트였다는 생각이 든다.

이번 프로젝트는 C++ 콘솔 환경에서 진행한 로그라이크 게임 프로젝트였고, 최종적으로는 타일 기반 이동, 보간 이동, Isometric View, 3D View, 몬스터 BFS 길찾기, 인벤토리/상점/상자 Cursor 시스템, 랭킹 저장 흐름까 포함한 형태로 완성했다.

콘솔 프로젝트였지만 단순히 텍스트만 출력하는 게임으로 끝내고 싶지는 않았다.
그래서 내부 로직은 타일 기반으로 단순하게 유지하면서도, 화면에서는 최대한 게임처럼 보이도록 보간과 시점 변환을 시도했다.

image-1780068430023


내가 담당한 부분

나는 프로젝트 전반의 엔진 구조기반 시스템을 주로 담당했다.

구현하면서 가장 신경 쓴 부분은 최대한 언리얼 엔진의 구조를 모방하는 것이었다.

대표적으로 모든 오브젝트가 매 프레임 갱신될 수 있도록 Tick(float DeltaTime) 형태를 사용했다.
이 구조를 사용하면 각 객체와 컴포넌트가 자신의 갱신 로직을 독립적으로 가질 수 있고, 프레임 기반 게임 루프를 더 명확하게 구성할 수 있었다.

또한 컴포넌트를 new로 직접 생성하지 않고, CreateDefaultComponent 함수를 통해 생성하도록 강제했다.
이 방식은 언리얼의 컴포넌트 생성 방식에서 영향을 받은 구조다.

직접 new를 허용하면 객체 소유권이 흩어지고, 생성 위치가 제각각이 되기 쉽다.
그래서 컴포넌트 생성 경로를 제한하고, 오브젝트가 자신의 컴포넌트를 관리하는 구조로 만들고 싶었다.

그리고 GWorld와 비슷한 역할을 하는 SceneManager를 두었다.
SceneManager에서는 SpawnObject를 통해 AObject 타입의 객체를 공통 방식으로 생성할 수 있게 했다.

플레이어, 몬스터, 투사체, NPC 같은 객체를 각각 따로 생성하는 것이 아니라,
공통된 오브젝트 생성 흐름을 타도록 만들었다.

여기에 Object Pooling도 추가했다.
오브젝트를 매번 생성하고 삭제하는 대신, 필요할 때 가져오고 사용이 끝나면 반납하는 구조로 만들었다.
덕분에 투사체나 몬스터처럼 반복적으로 생성되는 객체를 더 쉽게 관리할 수 있었다.

정리하면, 이번 프로젝트에서 내가 가장 집중한 것은
단순히 기능을 만드는 것이 아니라, 게임이 굴러갈 수 있는 기본 구조를 만드는 것이었다.

Unreal Engine 구조를 참고한 이유

이번 프로젝트는 C++ 콘솔 프로젝트였지만, 나는 최대한 실제 게임 엔진 구조에 가깝게 만들어보고 싶었다.

그 이유는 단순하다.
게임이 조금만 커져도 모든 로직을 한 파일, 한 클래스 안에 넣는 방식은 금방 한계가 오기 때문이다.

그래서 다음과 같은 구조를 의식했다.

  • GameInstance는 전체 게임 흐름과 상태를 관리
  • SceneManager는 월드 안의 오브젝트 생성과 관리 담당
  • AObject는 게임에 등장하는 기본 객체 역할
  • Component는 이동, 전투, 인벤토리, 장비, 이펙트 같은 기능 단위 역할
  • Tick(float DeltaTime)을 통해 프레임 기반 갱신 처리
  • SpawnObject와 Pooling을 통해 생성/반납 흐름 통일

이 구조를 만들면서 “내가 아는 엔진 구조를 작은 콘솔 프로젝트 안에서 어디까지 흉내 낼 수 있을까?”를 많이 고민했다.

물론 실제 언리얼 엔진과 비교하면 매우 단순한 구조지만,
그래도 이번 프로젝트 안에서는 충분히 의미 있는 시도였다고 생각한다.


Isometric View + 3D View 렌더

이번 프로젝트에서 가장 반응이 좋았던 부분은 Isometric View와 3D View를 같이 사용한 렌더링 실험이었다.

내부 로직은 타일 좌표를 기준으로 처리했다.
하지만 화면 출력에서는 타일 좌표를 Isometric 좌표로 변환해서 보여주었다.

또한 3D View에서는 Raycasting 방식으로 벽과 공간을 표현했다.
콘솔 환경이라 표현에 한계가 많았지만, 단순한 문자 출력만으로도 어느 정도 입체감과 연출을 만들 수 있었다.

개인적으로는 이 부분이 꽤 선방했다고 생각한다.

특히 발표 중에도 많은 사람들이 이 부분에서 반응을 보였다.
콘솔 프로젝트에서 이런 식으로 시점을 바꾸고, 3D처럼 보이게 만들 수 있다는 점이 꽤 인상적으로 보였던 것 같다.

이번 경험을 통해 느낀 것은,
그래픽 라이브러리나 엔진이 없어도 좌표 변환과 렌더링 방식만 잘 설계하면 충분히 재미있는 표현을 만들 수 있다는 점이다.


타일 기반 이동과 보간

이번 프로젝트의 이동은 기본적으로 타일 기반이다.

플레이어와 몬스터는 한 번에 한 칸씩 이동하고,
충돌 검사도 타일 단위로 처리한다.

하지만 화면에서 캐릭터가 한 칸씩 뚝뚝 끊겨 보이면 게임 느낌이 떨어진다.
그래서 PrevPositionNextPosition을 두고, 그 사이를 보간해서 출력했다.

이동 시간 동안 Ratio를 계산하고,
그 Ratio를 기준으로 현재 화면 위치를 계산했다.

중요한 부분은 논리 위치와 화면 위치를 분리했다는 점이다.

실제 판정에 사용되는 Position은 특정 시점에 커밋되고,
화면에서는 계속 부드럽게 이동하는 것처럼 보이게 만들었다.

이 구조 덕분에 타일 기반 게임의 단순함은 유지하면서도,
플레이 경험은 조금 더 자연스럽게 만들 수 있었다.


팀 프로젝트로서 느낀 점

같이 해준 팀원들이 정말 고생을 많이 했다.

나는 C++을 공부한 경험도 있고, C++로 게임 개발을 해본 경험도 있었다.
그래서 포인터, 클래스, 상속, 컴포넌트 구조, 게임 루프 같은 개념이 비교적 익숙했다.

하지만 팀원들은 C++을 접한 지 한 달이 조금 안 된 상태였다.
배열이 무엇인지도 아직 익숙하지 않고, 포인터 개념도 완전히 자리 잡지 않은 상태였다.

그런 상황에서 내가 만든 구조는 솔직히 쉬운 구조는 아니었다.

AObject, Component, Manager, SpawnObject, Object Pooling, Tick, DeltaTime 같은 개념은
게임 개발 경험이 있으면 자연스럽게 받아들일 수 있지만,
처음 C++을 배우는 입장에서는 매우 부담스러웠을 것 같다.

돌이켜보면 내가 팀원들에게 너무 많은 것을 요구한 것은 아닌가 하는 생각도 든다.

물론 결과물은 잘 나왔다고 생각하지만,
팀 프로젝트에서는 구조를 잘 만드는 것만큼
팀원들이 이해하고 따라올 수 있는 수준으로 설계하는 것도 중요하다는 것을 느꼈다.


아쉬웠던 점

가장 아쉬웠던 점은 시간이다.

일주일 동안 거의 매일 밤을 새우면서 작업했다.
하고 싶은 것은 많았고, 실제로 추가한 기능도 많았다.

하지만 시간이 부족하다 보니,
구조를 깔끔하게 정리하거나 주석을 충분히 달거나,
팀원들이 이해하기 쉽게 문서화하는 데까지는 많이 신경 쓰지 못했다.

또한 엔진 구조를 흉내 내는 데 집중하다 보니,
프로젝트 규모에 비해 구조가 다소 무거웠을 수도 있다.

작은 프로젝트에서는 단순한 구조가 더 효율적일 때도 있다.
하지만 이번에는 학습과 실험의 목적이 강했기 때문에,
조금 무겁더라도 엔진 구조를 흉내 내보는 방향을 선택했다.

다음에 비슷한 프로젝트를 한다면
초기 구조를 만들 때 팀원들의 이해도와 작업 속도를 더 고려해야겠다고 느꼈다.


잘했다고 생각하는 점

그래도 이번 프로젝트에서 잘했다고 생각하는 부분도 많다.

첫 번째는 전체 구조를 끝까지 유지했다는 점이다.
프로젝트가 급해지면 구조가 무너지기 쉬운데,
그래도 Manager와 Component 중심의 흐름을 최대한 유지하려고 했다.

두 번째는 콘솔 환경에서 시각적인 실험을 했다는 점이다.
Isometric View와 3D View는 구현 난이도에 비해 발표 효과가 좋았다.
특히 콘솔에서 3D처럼 보이는 연출을 시도한 것은 좋은 선택이었다.

세 번째는 타일 기반 로직과 보간 표현을 분리했다는 점이다.
이 구조 덕분에 판정은 단순하게 유지하면서도,
화면은 부드럽게 보여줄 수 있었다.

네 번째는 팀원들이 각자 맡은 기능을 끝까지 완성했다는 점이다.
각자의 숙련도 차이가 있었지만, 결국 하나의 게임으로 합쳐졌고 발표까지 마칠 수 있었다.


배운 점

이번 프로젝트를 통해 가장 크게 배운 것은
좋은 구조는 코드만을 위한 것이 아니라 팀을 위한 것이어야 한다는 점이다.

혼자 개발한다면 내가 이해하는 구조를 빠르게 만들면 된다.
하지만 팀 프로젝트에서는 다르다.

내가 아무리 좋은 구조라고 생각해도,
팀원들이 이해하지 못하면 그 구조는 협업에 부담이 된다.

그래서 다음에는 구조를 만들 때
기능의 확장성뿐만 아니라,
팀원들이 얼마나 쉽게 사용할 수 있는지도 같이 고려해야겠다고 느꼈다.

또 하나 배운 점은
짧은 시간 안에서도 핵심을 잘 잡으면 꽤 완성도 있는 결과물을 만들 수 있다는 점이다.

이번 프로젝트는 일주일이라는 짧은 시간 동안 진행됐지만,
타일 이동, 보간, BFS, 인벤토리, 상점, 상자, 스테이지, 랭킹 저장까지 많은 기능이 들어갔다.

물론 완벽하지는 않았지만,
짧은 시간 안에 모든 팀원이 정말 많은 것을 해냈다고 생각한다.


마무리

이번 DIABL5 프로젝트는 정말 힘들었지만, 그만큼 많이 배우고 하고싶은거 다한 프로젝트였다.

나는 전반적인 엔진 구조를 담당하면서
언리얼 엔진의 구조를 작은 C++ 콘솔 프로젝트 안에서 흉내 내보려고 했다.

Tick(float DeltaTime), CreateDefaultComponent, SceneManager, SpawnObject, Object Pooling 같은 구조를 직접 만들면서
게임 엔진의 기본 구조가 왜 필요한지 다시 체감할 수 있었다.

또한 Isometric View와 3D View를 통해
콘솔 환경에서도 충분히 재미있는 렌더링과 연출을 만들 수 있다는 것을 확인했다.

BFS 길찾기, Weapon 4종, Cursor 기반 아이템 선택, Worker 기반 랭킹 저장 흐름까지
짧은 시간 안에 많은 기능을 구현했고,
발표까지 무사히 마칠 수 있었다.

무엇보다 같이 밤새며 작업해준 팀원들에게 정말 고맙다.

각자 C++ 숙련도도 다르고 어려운 점도 많았지만,
끝까지 포기하지 않고 하나의 결과물을 만들어냈다는 점에서 의미 있는 프로젝트였다고 생각한다.

https://youtu.be/OfrNOXi3PGw