Eternal Survival 개발현황 공유
안녕하세요. 지난번에 이어서 Eternal Survival의 개발 현황을 공유하고자 포스팅합니다. 이미 개발이 어느정도 진행 된 상황이라 모든 개발 현황을 요약해서 공유하는건 어려울 것 같습니다. 특히, 새로운 캐릭터의 추가, 맵 디자인 등과 같은 디자인 측면의 변화들은 github 저장소에서 쉽게 확인할 수 있으니 생략하고자 합니다. 대신 오늘은 프로젝트의 첫 개발 일지로, 좋은 출발점이 되어준 ‘Undead Survivor’ 에셋의 스크립트들을 기반으로 지금까지 어떤 구조적 개선을 이루었는지 요약하여 공유하고자 합니다.
에셋은 훌륭한 프로토타이핑 도구이지만, 장기적인 프로젝트로 발전시키기 위해서는 결국 더욱 견고한 아키텍처가 필요합니다. 이번 포스트에서는 “어떻게 하면 더 많은 콘텐츠를 더 쉽게 추가할 수 있을까?”라는 고민에서 시작된 주요 개선점 세 가지를 중점적으로 다뤄보겠습니다.
1. 데이터 중심 설계로의 전환: 하드코딩과의 작별
원본 에셋의 스크립트들은 종종 캐릭터의 능력치나 무기 스펙이 코드 내에 직접 작성(하드코딩)되어 있었습니다. 이는 간단한 테스트에는 용이하지만, 캐릭터나 아이템이 수십 개로 늘어나는 순간 밸런싱과 유지보수가 매우 어려워지는 원인이
됩니다.
이를 해결하기 위해 저희는 ScriptableObject를 활용한 데이터 중심 설계를 도입했습니다.
- Before:
Character.cs스크립트 내에if (playerId == 0)과 같은 코드로 캐릭터별 능력치를 분기 처리. - After:
CharacterDataSO.cs: 캐릭터의 모든 정보(이름, 설명, 능력치 배율, 애니메이터 등)를 담는 데이터 컨테이너를 만들었습니다. 이제 새로운 캐릭터 추가는 코드 수정 없이, 이 에셋 파일을 하나 더 만드는 것으로 끝납니다.ItemData.cs: 모든 무기와 장비의 레벨별 스펙(데미지, 개수, 쿨타임 등)을 정의합니다. 기획자가 이 파일만 수정하면 즉시 게임 밸런싱에 반영할 수 있습니다.
이러한 변화는 단순히 코드를 정리하는 것을 넘어, 데이터와 로직을 명확히 분리하여 향후 라이브 서비스까지 염두에 둔 확장성의 초석을 다진, 가장 의미 있는 개선점이라고 생각합니다. Assets\Undead Survivor\Data 디렉토리에 다양한 ScriptableObject 구현을 확인하실 수 있습니다.
2. 오브젝트 풀링 기법 개선
‘Undead Survivor’ 에셋의 초기 PoolManager는 다음과 같은 코드를 사용했습니다.
// Before: "0"은 무엇을 의미하는가?
GameObject enemy = pool.Get(0);
GameObject bullet = pool.Get(1);이 pool.Get(0)과 같은 코드는 대표적인 매직 넘버(Magic Number)입니다. 숫자 0이 ‘소환할 적의 prefab’이라는 사실은 오직 코드를 작성한 사람과 PoolManager의 프리팹 배열 순서를 아는 사람만 알 수 있습니다. 만약 누군가 실수로 Inspector에서 프리팹 배열의 순서를 바꾸기라도 한다면, 게임은 말없이 오작동하기 시작할 것입니다.
해결책: PoolTagSelector를 활용한 직관적 관리
이 문제를 해결하기 위해, 풀링할 오브젝트의 종류를 문자열 태그로 관리하도록 PoolManager를 개선했습니다.
// 1. PoolManager는 string 태그를 키로 사용하는 Dictionary로 프리팹을 관리
[System.Serializable]
public class Pool
{
public string tag; // "Enemy", "Bullet", "Gem" 등의 명시적 태그
public GameObject prefab;
public int size;
}
private Dictionary<string, Queue<GameObject>> poolDictionary;
// 2. 사용할 때는 명시적인 태그 이름으로 호출
GameObject enemy = pool.Get("Enemy");
GameObject bullet = pool.Get("Bullet");더 나아가, 개발자가 태그를 직접 입력할 때 발생할 수 있는 오타나 실수를 방지하기 위해 PoolTagSelector 커스텀 어트리뷰트를 구현했습니다.
// 3. PoolTagSelector 어트리뷰트 사용 예시
public class Weapon : MonoBehaviour
{
[PoolTagSelector]
public string bulletTag; // Inspector에서 드롭다운으로 선택 가능
void Fire()
{
GameObject bullet = PoolManager.instance.Get(bulletTag);
}
}이 어트리뷰트를 사용하면 Unity Inspector에서 PoolManager에 등록된 모든 태그 목록이 드롭다운 메뉴로 표시되어, 개발자가 실수 없이 올바른 태그를 선택할 수 있습니다. 이제 코드는 누가 보더라도 명확하고, Inspector에서 프리팹 순서가 바뀌어도 전혀 영향을 받지 않으며, 오타로 인한 런타임 오류도 방지할 수 있습니다. 깃허브 저장소의 Assets\Undead Survivor\Script 경로에 위치한 ‘Poolmanager.cs’를 통해 실제 구현을 확인하실 수 있으니 참고 바랍니다.
3. 상호작용이 살아있는 월드 구축과 최적화
단순히 적을 피하고 공격하는 것을 넘어, 월드와의 상호작용을 통해 전략적인 재미를 더했습니다.
파괴 가능한 오브젝트(DestructibleObject)와 드랍 아이템(Consumable)
월드 곳곳에 배치된 상자나 항아리를 부수고 확률적으로 아이템을 얻을 수 있는 시스템을 구현했습니다. 이는 플레이어의 탐험 동기를 부여하고, 전투 외의 소소한 재미를 줍니다. 이 과정에서 특히 최적화에 신경써야 했습니다. 이 아이템들을 플레이어가 획득할 수 있게 하려면 Collider를 활용해 플레이어 캐릭터와 충돌하는지 확인해야 합니다. 그러나 시간이 지날수록 수많은 적들이 쌓이게 되는 뱀파이어 서바이벌 장르의 특성상, 이 수많은 적들과 적들이 드랍하는 아이템들까지 항상 활성화 돼있는 Collider를 가진다면 게임은 엄청나게 무거운 물리 연산을 감당하게 될 것입니다. 이를 해결하고자 모든 오브젝트를 같은 레이어에 두는 ‘Undead Survivor’ 에셋의 기존 설계에서 벗어나, 필요에 따라 각 오브젝트를 적절한 레이어에 배치하고 서로 상호작용이 없는 레이어들 간의 물리 연산은 유니티 Physics2D 설정을 활용해 제거하는 방식을 도입했습니다. 또한, Physics2D의 OverlapCircleAll을 활용해 플레이어의 흡수 반경 내부로 들어온 드랍 아이템들을 가볍게 탐지할 수 있도록 설계하였습니다. 깃허브 저장소의 Assets\Undead Survivor\Script 경로에 위치한 ‘ItemAbsorber.cs’, ‘Consumable.cs’를 통해 실제 구현을 확인하실 수 있으니 참고 바랍니다.
전략적 거점, 모닥불(Campfire)
위험을 감수하고 특정 지역에 머무르면 체력을 모두 회복할 수 있는 모닥불을 추가했습니다. 원작 게임인 이터널리턴에서 모닥불 근처에서 회복 아이템을 제작할 수 있다는 점을 새롭게 해석해 보았습니다. 이는 플레이어에게 “위험을 감수하고 회복할 것인가, 아니면 안전하게 도망칠 것인가”라는 전략적인 선택의 순간을 제공합니다. 화면에 표시되는 진행바를 통해서 플레이어는 체력 회복의 진행도를 알 수 있습니다.
낮/밤 사이클(DayNightController)
시간이 흐름에 따라 전역 조명이 어두워지고 플레이어의 시야가 제한되는 낮/밤 시스템을 도입했습니다. 원작 게임인 이터널리턴에서 낮과 밤에 따라 플레이어의 시야 범위가 바뀌는 점을 구현해 보았습니다. 밤이 되면 플레이어는 자신의 주변의 적들만 볼 수 있게 되어 긴장감이 고조되고, 이는 게임의 단조로움을 깨는 중요한 변수가 됩니다.
마치며
‘Undead Survivor’ 에셋은 저희 프로젝트의 훌륭한 디딤돌이 되어주었습니다. 그리고 저희는 그 위에 데이터 중심 설계, 최적화된 핵심 메커니즘, 깊이 있는 상호작용을 더하여 개성있는 색깔을 가진 게임으로 발전시켜 나가고 있습니다. 이제 이 탄탄한 기반 위에 더 다양한 무기, 개성 넘치는 적, 그리고 고유한 스킬들을 본격적으로 추가할 일만 남았습니다. 다음 개발일지에서 뵙겠습니다.