“ECS면 무조건 빨라진다?” 게임 프로그래밍에 직접 써본 결과

profile_image
작성자 엔진구조실험가 하람
댓글 0건 조회 6회

화면에 적이 2,000마리쯤 등장하자 프레임 시간이 18ms를 넘기기 시작했습니다. 당시 저는 객체 지향 구조가 원인이라고 단정하고, 기존 코드를 ECS로 옮기면 성능 문제가 단숨에 해결될 것이라 기대했습니다. 하지만 약 6주 동안 직접 적용해 본 결과, ECS는 자동으로 게임을 빠르게 만드는 기술이 아니라 데이터를 다루는 방식을 바꾸는 설계에 더 가까웠습니다.

잘 맞는 구간에서는 업데이트 시간이 절반 이하로 줄었지만, 상호작용이 복잡한 기능에서는 코드량과 디버깅 시간이 오히려 늘었습니다. 이 글은 특정 엔진의 ECS 기능을 소개하기보다, 실제 게임 프로그래밍 프로젝트에 적용하면서 확인한 장점과 단점, 실패하기 쉬운 지점과 현실적인 사용 범위를 경험 중심으로 풀어냅니다.

ECS로 옮기기 전, 느린 원인부터 다시 측정했습니다

객체가 많다는 사실만으로는 근거가 부족했습니다

처음 만든 구조에서는 각 적 캐릭터가 위치, 체력, 애니메이션, 인공지능 상태를 하나의 클래스에 들고 있었습니다. 매 프레임 모든 객체의 Update 메서드를 호출했기 때문에 겉보기에는 전형적인 ECS 전환 대상처럼 보였습니다. 그러나 프로파일러를 붙여 보니 실제 병목은 객체 생성 자체보다 경로 탐색의 중복 호출과 캐시 효율이 낮은 데이터 순회에 있었습니다.

실험을 위해 동일한 전투 장면을 세 가지 조건으로 반복했습니다. 적 500마리, 2,000마리, 5,000마리를 각각 60초씩 실행하고 메인 스레드 시간, 할당량, 최악 프레임 시간을 기록했습니다. 평균 FPS만 보면 순간적인 끊김을 놓치기 쉬워서 1% 하위 프레임과 업데이트 시스템별 소요 시간도 함께 확인했습니다.

  • 500개 객체: 기존 구조도 목표 프레임을 안정적으로 유지했습니다.
  • 2,000개 객체: 이동과 시야 판정이 전체 CPU 시간의 약 46%를 차지했습니다.
  • 5,000개 객체: 메모리 접근과 반복 할당 때문에 최악 프레임이 급격히 늘었습니다.
  • 측정 조건: 같은 맵, 같은 입력 시드, 같은 카메라 위치를 유지했습니다.

이 과정에서 얻은 첫 번째 교훈은 간단합니다. ECS 도입 전에 “객체가 많다”가 아니라 어떤 데이터를 어떤 순서로 얼마나 자주 읽는가를 확인해야 합니다. 측정 없이 전체 구조를 교체하면, 비싼 리팩터링을 마친 뒤에도 진짜 병목인 경로 탐색이나 렌더링 제출 비용이 그대로 남을 수 있습니다.

사용 팁: 전환 여부를 결정하기 전에 대표 장면 세 개를 고르고, 평균값과 최악 프레임을 함께 저장해 두세요. 나중에 구조가 좋아졌는지 판단할 기준선이 됩니다.

첫 적용은 이동 시스템 하나로 제한했습니다

Position과 Velocity만 분리해 본 이유

처음부터 전투, 애니메이션, 인공지능을 전부 ECS로 바꾸지 않았습니다. 가장 단순하고 반복 횟수가 많은 이동 로직만 골라 Position, Velocity, MovementState 컴포넌트로 분리했습니다. 시스템은 위치와 속도 배열을 연속적으로 읽고 결과를 위치 배열에 쓰도록 구성했습니다. 이 정도 범위라면 기존 결과와 비교하기 쉽고, 문제가 생겨도 되돌리는 비용이 작았습니다.

2,000개 개체를 대상으로 한 테스트에서 이동 업데이트는 기존 평균 약 1.8ms에서 0.7~0.9ms 사이로 줄었습니다. 숫자 자체보다 인상적이었던 부분은 개체 수를 4,000개로 늘렸을 때 증가 폭이 비교적 일정했다는 점입니다. 반면 개체가 200개뿐인 메뉴 배경 장면에서는 차이를 체감하기 어려웠고, 시스템을 연결하는 코드만 추가되었습니다.

  1. 기존 이동 결과를 기록하는 회귀 테스트를 먼저 만들었습니다.
  2. 읽기 전용 데이터와 쓰기 데이터를 컴포넌트 수준에서 구분했습니다.
  3. 프레임 중간에 엔티티를 추가하거나 삭제하지 않도록 명령 버퍼를 사용했습니다.
  4. 기존 객체와 ECS 엔티티를 연결하는 얇은 변환 계층을 한시적으로 유지했습니다.

가장 유용했던 방법은 한 시스템을 끝까지 검증한 뒤 다음 시스템으로 이동하는 것이었습니다. 이동 결과가 기존 구현과 일치하는지 좌표 오차를 자동 비교했고, 10분 동안 반복 실행해 메모리 증가도 확인했습니다. 이 작은 성공 덕분에 팀원이 데이터 흐름을 이해할 시간을 확보했고, ECS가 맞지 않을 때 전체 프로젝트를 되돌려야 하는 위험도 줄었습니다.

전환 순서는 호출 빈도로 정했습니다

다음 후보는 단순한 충돌 후보군 생성과 수명 처리였습니다. 선택 기준은 “멋지게 ECS로 표현할 수 있는가”가 아니라 개체 수가 많고, 동일 연산이 반복되며, 데이터 의존성이 단순한가였습니다. 반대로 대화 연출이나 보스의 특수 패턴처럼 예외가 많은 기능은 기존 구조에 남겼습니다.

빨라진 구간과 느려진 구간은 명확히 갈렸습니다

반복 연산에는 강했고 예외 처리에는 까다로웠습니다

실제로 효과가 컸던 곳은 투사체 이동, 상태 지속 시간 감소, 단순 거리 판정처럼 같은 계산을 대량 데이터에 적용하는 부분이었습니다. 필요한 컴포넌트만 모아 순회하니 불필요한 필드를 읽지 않았고, 작업을 여러 묶음으로 나누기도 쉬웠습니다. 데이터 배치와 작업 스케줄링이 맞아떨어진 구간에서는 성능과 확장성이 동시에 개선되었습니다.

반면 보스 캐릭터 한 명이 연출 카메라, 대사, 페이즈 전환, 파괴 가능한 지형과 연쇄적으로 상호작용하는 로직은 만족스럽지 않았습니다. 기능 하나를 따라가려면 여러 시스템과 이벤트 버퍼를 오가야 했고, 실행 순서가 잘못되면 한 프레임 늦게 상태가 반영되었습니다. 객체 안에서 순차적으로 읽히던 코드가 데이터와 시스템으로 흩어지면서 인지 비용이 커진 것입니다.

적용 영역사용 결과추천 판단
대량 투사체 이동순회 비용과 할당 감소적극 추천
버프 지속 시간 처리데이터 구조가 단순해짐추천
복잡한 보스 연출시스템 간 추적 비용 증가부분 적용
메뉴와 설정 화면성능 이득이 거의 없음기존 구조 유지
  • 동일한 연산을 수백 개 이상 데이터에 반복한다면 좋은 후보입니다.
  • 개별 개체마다 예외가 많다면 시스템 분리가 오히려 복잡도를 높일 수 있습니다.
  • 프레임마다 구조가 자주 바뀌면 엔티티 생성·삭제 비용과 동기화 지점을 측정해야 합니다.
  • 렌더링 API 호출이 병목이라면 ECS만으로 해결되지 않을 가능성이 큽니다.

게임 기술 사례를 접할 때는 대규모 콘퍼런스 발표가 좋은 출발점이 됩니다. GDC의 성격과 배경을 설명한 지식백과를 참고하면 관련 발표가 어떤 산업적 맥락에서 공유되는지도 이해하기 쉽습니다. 다만 발표 속 대규모 성공 사례를 자신의 소규모 프로젝트에 그대로 옮기기보다 개체 수, 플랫폼, 팀 규모를 먼저 비교해야 합니다.

디버깅은 브레이크포인트보다 데이터 기록이 중요했습니다

사라진 엔티티를 추적하는 데 반나절을 썼습니다

ECS를 사용하며 가장 당황했던 문제는 체력이 남은 적이 간헐적으로 사라지는 버그였습니다. 삭제 시스템만 확인했을 때는 조건이 정상으로 보였지만, 원인은 이전 프레임에 예약된 명령 버퍼가 예상과 다른 순서로 실행된 것이었습니다. 일반적인 호출 스택만 따라가서는 “누가 언제 삭제를 요청했는지” 알아내기 어려웠습니다.

그 뒤부터 엔티티의 생성, 주요 컴포넌트 변경, 삭제 요청에 작은 이벤트 기록을 남겼습니다. 모든 데이터를 무조건 출력하면 로그가 폭발하므로 선택한 엔티티 ID와 특정 시스템만 필터링했습니다. 재현 시드와 프레임 번호까지 함께 저장하자 간헐적 문제도 몇 분 안에 같은 상태로 되돌릴 수 있었습니다.

  • 엔티티 수 변화: 시스템 실행 전후의 전체 개수를 기록합니다.
  • 구조 변경 출처: 생성·삭제를 요청한 시스템 이름을 남깁니다.
  • 실행 순서: 의존성이 있는 시스템의 시작과 종료 프레임을 표시합니다.
  • 선택 추적: 문제가 있는 ID의 컴포넌트 변경 이력만 별도로 봅니다.
  • 재현 정보: 난수 시드, 입력, 맵 버전까지 한 묶음으로 저장합니다.

에디터에서도 엔티티 하나를 선택하면 현재 컴포넌트 값뿐 아니라 최근 30프레임의 변경 이력을 볼 수 있게 만들었습니다. 개발에 이틀 정도 들었지만 이후 전투 상태 버그를 찾는 시간이 크게 줄었습니다. ECS 개발 생산성은 런타임 성능만큼 관찰 도구의 품질에 좌우된다는 사실을 체감한 지점입니다.

시스템 이름은 동작이 드러나게 지었습니다

처음에는 CombatSystem처럼 넓은 이름을 썼지만, 시간이 지나자 담당 범위를 알기 어려웠습니다. DamageRequestCollectSystem, ApplyDamageSystem, DeadEntityMarkSystem처럼 입력과 행동이 드러나는 이름으로 바꾸니 실행 순서를 읽기가 쉬워졌습니다. 이름이 길어지더라도 추상적인 단어 하나보다 디버깅 과정에서 훨씬 유용했습니다.

현장 팁: 엔티티 ID만 로그에 남기지 말고 생성 원형, 스폰 프레임, 마지막으로 값을 바꾼 시스템을 함께 기록하세요. ID가 재사용되는 구현에서는 특히 중요합니다.

협업에서는 컴포넌트보다 경계 합의가 먼저였습니다

기획 데이터와 런타임 데이터를 분리했습니다

팀 작업에서 가장 자주 충돌한 부분은 어떤 값을 컴포넌트에 넣을지였습니다. 기획자가 조정하는 기본 이동 속도와 런타임에서 버프가 반영된 최종 속도를 같은 필드로 취급하자, 누가 값을 덮어썼는지 알기 어려웠습니다. 이후 원본 설정 데이터, 런타임 상태, 한 프레임 동안만 존재하는 요청 데이터를 명확히 나눴습니다.

기획 직무가 다루는 범위와 협업 맥락은 기획자에 대한 지식백과 설명에서도 살펴볼 수 있습니다. 실제 제작에서는 직무 명칭보다 데이터의 소유권이 더 중요했습니다. 어떤 값은 편집 도구에서 바꾸고, 어떤 값은 프로그래머가 계산하며, 저장 파일에는 무엇을 남길지 문서 한 장으로 합의해 두는 편이 효과적이었습니다.

  1. Authoring 데이터: 디자이너가 에디터에서 입력하는 원본 수치입니다.
  2. Runtime 데이터: 플레이 중 시스템이 지속적으로 갱신하는 값입니다.
  3. Request 데이터: 공격 요청처럼 짧게 생성되고 소비되는 정보입니다.
  4. Presentation 데이터: 애니메이션과 효과 표현에 필요한 결과값입니다.

코드 리뷰에서는 컴포넌트를 추가한 이유와 수명, 읽는 시스템, 쓰는 시스템을 함께 적었습니다. 특히 여러 시스템이 같은 데이터를 쓰도록 허용하면 실행 순서가 숨은 규칙이 되기 쉬웠습니다. 가능하면 쓰기 담당을 하나로 제한하고, 다른 시스템은 요청 컴포넌트나 이벤트를 전달하도록 했습니다.

혼합 구조가 실제 제작에는 더 편했습니다

저희 프로젝트는 끝까지 순수 ECS를 고집하지 않았습니다. 대량 시뮬레이션은 ECS로 처리하고, UI와 카메라 연출, 고유한 보스 행동은 객체 기반 코드에 남겼습니다. 둘 사이에는 읽기 전용 스냅샷이나 명시적인 이벤트만 오가도록 제한했습니다. 처음에는 타협처럼 느껴졌지만 결과적으로 신규 기능을 빠르게 만들면서 성능이 필요한 구간만 최적화할 수 있었습니다.

  • 팀원이 데이터 지향 설계에 익숙하지 않다면 작은 기능부터 공동 리뷰합니다.
  • 컴포넌트 필드의 단위와 좌표계를 이름이나 주석에 명시합니다.
  • 시스템 실행 순서는 코드 밖 문서가 아니라 테스트로도 검증합니다.
  • 변환 계층은 임시인지 영구 경계인지 처음부터 구분합니다.

6주 적용 비용을 기능 단위 숫자로 환산했습니다

성능 이득보다 회수 기간을 계산했습니다

실제 투입 시간을 기록해 보니 첫 2주는 프로토타입과 측정, 다음 2주는 이동·투사체·상태 시스템 전환, 나머지 2주는 디버깅 도구와 회귀 테스트에 사용했습니다. 개발자 한 명이 전담하고 다른 한 명이 간헐적으로 리뷰했으므로 총투입량은 약 38인일이었습니다. 단순히 프레임 시간이 줄었다는 이유만으로 이 비용이 항상 정당화되는 것은 아닙니다.

프로젝트 기간이 두 달 남았고 현재 목표 플랫폼에서 60FPS가 안정적이라면, 대규모 전환은 추천하기 어렵습니다. 반대로 출시까지 1년 이상 남았고 콘텐츠 증가에 따라 개체 수가 세 배로 늘어날 예정이라면 초기 투자 가치가 생깁니다. 예산과 목표를 체계적으로 연결한다는 관점은 계획예산 제도의 개념 설명처럼 개발 외 분야의 접근에서도 힌트를 얻을 수 있습니다.

작업실제 소요권장 여유
병목 측정과 기준선 작성2~3일1일 추가
단일 시스템 시범 전환4~6일30% 버퍼
검증 테스트 구축3~5일복잡도에 따라 2일 추가
추적·시각화 도구 제작2~4일유지보수 월 0.5일
팀 교육과 코드 리뷰주당 2~3시간첫 달 집중 배정

개인 프로젝트라면 더 작은 숫자로 판단할 수 있습니다. 먼저 주말 8시간을 써서 가장 반복적인 시스템 하나만 옮기고, 업데이트 시간이 최소 20~30% 줄거나 처리 가능한 개체 수가 두 배 가까이 늘어나는지 확인해 보세요. 효과가 없다면 전체 전환을 멈추는 것도 성과입니다. 이미 비용이 들어갔다는 이유로 맞지 않는 구조를 확대하면 이후 기능 하나를 추가할 때마다 시간이 더 소모됩니다.

  • 8시간: 프로파일링과 최소 프로토타입에 쓸 개인 프로젝트 기준 시간입니다.
  • 3~5일: 팀 프로젝트에서 한 시스템을 검증할 현실적인 범위입니다.
  • 20% 이상: 복잡도 증가를 감수할지 논의해 볼 최소 성능 개선선으로 삼았습니다.
  • 30% 버퍼: 테스트, 도구, 문서화에 별도로 확보한 일정입니다.
  • 월 4시간: 추적 도구와 시스템 문서를 최신 상태로 유지하는 데 배정했습니다.

제가 다시 시작한다면 전체 엔진 구조를 바꾸겠다는 계획부터 세우지 않을 것입니다. 3일 측정, 5일 시범 적용, 2일 검증처럼 최대 10일의 실험 상한을 먼저 정하고, 목표 수치를 넘긴 시스템만 확장하겠습니다. ECS의 가치는 이름이나 유행이 아니라, 투입한 시간과 복잡도보다 실제 처리량·프레임 안정성·유지보수 이득이 큰 구간에서 드러납니다.

“ECS면 무조건 빨라진다?” 게임 프로그래밍에 직접 써본 결과

댓글목록

등록된 댓글이 없습니다.