Unity 워크플로 확장: 중간 규모에서 대규모 프로젝트의 교훈

이 블로그 게시물은 메가캣 스튜디오에서 일련의 게시물 중 첫 번째로, Unity 전문 지식과 실제 상업용 게임 개발 과제에 대한 솔루션을 공유하는 곳입니다. 유용한 팁을 얻으시길 바랍니다!
멋진 아이디어가 있고 코드는 타자를 치는 속도만큼 빠르게 진행됩니다. 각 커밋마다 새로운 기능이 구체화됩니다. 하지만 아이디어를 형성하는 속도가 빠를수록 곧 큰 버그 덩어리를 마주하게 될 수 있습니다.
메가캣 스튜디오에서는 모든 프로젝트를 열정으로 시작하므로, 빠르고 자유롭게 플레이하고 가능한 한 빨리 일을 끝내는 매력을 이해합니다. 프로토타입의 경우 이러한 접근 방식은 좋으며, 실제로 권장합니다! 현명한 개발자는 반복 속도를 우선시할 때와 안정성을 우선시할 때를 알고 있습니다. 프로토타입 단계를 종료하면 "빠르고 자유로운" 접근 방식이 오히려 불이익이 되기 때문입니다.
우리는 이 전환을 여러 번 살아남았습니다. 메가캣 스튜디오그리고 모든 프로젝트에서 우리는 새로운 것을 배웁니다. 최근 프로젝트를 성공적으로 마무리하면서 얻은 몇 가지 교훈을 공유하고자 합니다. 백야드 야구, 실행 준비 완료.
레슨 1: 규모에 맞는 구조

스케일링 문제는 대부분 나쁜 코드 때문이 아니라, 계획되지 않은 아키텍처 때문에 더 자주 발생합니다. 개발자나 아티스트가 10초 안에 에셋을 찾을 수 없다면 워크플로를 변경해야 합니다. 확장을 위해 프로젝트를 설정하는 몇 가지 팁은 다음과 같습니다.
- 유형 및 목적별로 정리하기: 유형, 그 다음 용도별로 그룹화합니다. 유형에는 아트, 코드 및 오디오와 같은 범주가 포함됩니다. 목적은 그들이 사용되는 용도입니다. 직관적인 구성은 온보딩의 마찰을 줄입니다. 새로운 아티스트는 "캐릭터 대기" 스프라이트가 어디에 속하는지 정확히 알아야 하며 질문하지 않아도 됩니다.
- 씬을 단순하게 유지: 우리는 "메가씬"을 버리고 대신 저장 데이터와 중요한 시스템을 보유한 메인 씬과 플레이어가 경기에 있는지에 따라 가산적으로 로드되는 타이틀 화면과 야구 다이아몬드 씬과 같은 더 작은 씬을 선택했습니다. 마찬가지로, 우리는 자체 포함된 기능에 프리팹을 사용하므로 변경 사항이 해당 프리팹을 포함하는 씬 파일에 직렬화될 가능성이 적습니다. 이를 통해 아티스트는 디자이너가 동일한 "레벨"에서 게임플레이를 미세 조정하는 동안 환경 작업을 할 수 있으며 파일 충돌이 발생하지 않습니다(나중에 자세히 설명).
- Addressables 시스템 설정: 전통적인 리소스 폴더 대신 우리는 다음을 사용합니다. Addressables 시스템 필요할 때만 에셋을 로드하여 메모리 사용량을 줄입니다. 또한, Addressable 키를 사용하여 에셋을 로드하는 것이 리소스 폴더의 특정 위치로의 파일 경로를 통해 로드하는 것보다 더 명확하고 오류 발생 가능성이 적습니다.
어려운 부분은 이러한 모범 사례를 이해하는 것이 아닙니다. 처음부터 그들에게 전념하고 수년이 지난 후에도 그 규율을 유지하는 것입니다.
개발 중 어느 단계에 있는지, 그리고 그 단계에서 무엇을 우선시하는지 알아야 합니다.
"프로토타입 단계에서는 코드가 거의 없기 때문에 모듈화하기 전에 먼저 기능을 구현하는 것이 좋습니다."라고 Unity 개발자 Paolo Roxas는 말합니다. 백야드 야구. "프로젝트가 어떻게 진행되는지 보고 싶어요. 복잡하게 만들기 전에요."

2과: Unity와 함께 일하자, Unity에 반대하지 말자

"컴포넌트, ScriptableObjects, 또는 커스텀 클래스와 같이 집중적이고 간결하며 자체 포함된 빌딩 블록을 만드는 것보다 더 강력한 것은 없습니다."라고 Mega Cat Studios의 엔지니어링 책임자인 David Chávez Armenteros는 말합니다. Unity는 핵심에 모듈성을 채택합니다. 유연성을 유지하기 위해 우리는 세 가지 클래식 원칙을 따릅니다.
1. 단일 책임: 각 스크립트 또는 클래스는 하나의 잘 정의된 역할을 가져야 합니다.
2. 결합/결합도가 낮음: 시스템은 직접 참조가 아닌 인터페이스나 이벤트를 통해 상호 작용해야 하며, 적절한 경우에만 상호 작용해야 합니다. 주기적이거나 복잡한 아키텍처를 방지하기 위해 코드에 시스템을 구현하기 전에 시스템 간의 종속성을 그래프로 그리는 것이 좋습니다.
3. 플러그 앤 플레이: "신" 스크립트를 작성하는 대신 작고 집중된 단위를 결합하여 복잡한 동작을 만드세요.
레슨 3: 제한을 사용하여 자유로워지세요

어셈블리 정의 (AsmDefs)는 코드를 그룹화하는 C# 구문입니다. 그들이 광고한 장점은 컴파일 시간 단축이지만, 그들의 비밀 슈퍼 파워는 모듈화를 강제하는 것입니다.
리드 개발자이자 스파게티 코드 혐오자인 니코 고든지는 이를 맹세합니다.
"In 백야드 야구입력 레이어와 게임플레이 레이어는 다른 DLL에 있습니다. 게임플레이는 입력 세부 사항에 완전히 무관심합니다."
이는 각 종속성을 계산된 결정으로 만듦으로써 엔지니어를 스스로 구제합니다. 게임패드 처리부터 네트워크 코드에 이르기까지 전체 입력 시스템을 다시 작성해야 한다면 플레이어 물리학이나 AI 동작을 망칠 위험 없이 다시 작성할 수 있습니다. 더 가능성 있는 것은, 한 엔지니어가 코드베이스의 단일 도메인에서 작업할 수 있게 하고, 다른 시스템에 대한 변경 사항이 우발적으로 연쇄적으로 발생하지 않도록 하며, 개발자가 기능 구현 및 버그 수정을 위해 유지 관리해야 하는 코드 양을 줄여줍니다.
4과: 더 똑똑하게 테스트하되, 더 열심히 하지 마세요.
프로젝트가 성장함에 따라 "도미노 효과"가 발생할 수 있습니다: 여기서 작은 변경 사항이 저기서 무언가를 망가뜨립니다. 좋은 아키텍처는 큰 도움이 되지만 만능 해결책은 아닙니다.
게임은 기능 변경 사항이 수동 테스트를 위해 존경받는 품질 보증 팀의 손에 들어가기 전에 일련의 자동화된 단위 테스트를 통해 철저히 분석됩니다.
니코는 "테스트는 요구 사항 목록으로 작동합니다."라고 말합니다. "그들은 예상되는 것을 설명하고 주요 사용 사례를 제공합니다."
캐릭터가 있을 때 백야드 야구 패스트볼을 던지거나, 지지대를 도루하거나, 홈런을 치는 등의 행위가 있을 때, 우리는 공이 투구에 맞는 속도로 날아가게 하거나, 주자의 타이밍이 도루 메커닉과 일치하도록 하거나, 수비수가 히트에 올바르게 반응하는 등 특정 게임플레이 결과를 달성하고자 합니다. 캐릭터는 지면과 정확하게 충돌해야 하며, 공은 적절한 속도로 움직여야 하고, 와인딩 업, 스윙 또는 베이스 사이를 스프린트하는 행동을 추적하는 플레이어 컨트롤러 플래그와 같은 더 세부적인 시스템은 작동해야 합니다.
Pablo Sanchez의 파워를 조정하거나, 더 중요하게는 배트 스윙을 제어하는 공유 코드를 조정할 때, 접촉 타이밍부터 공의 궤적에 이르기까지 모든 상호 작용이 게임 전체에서 일관되게 동작하도록 해야 합니다.
종종 예상치 못한 것이 고장 나곤 하는데, 이것이 바로 테스트가 매우 중요한 이유입니다.
이 시스템을 워크플로에 통합함으로써, 특정 요구 사항이 깨지는 순간을 즉시 인식할 수 있게 되어, 바늘을 찾는 것만큼이나 시간이 많이 걸리는 테스트 및 문제 해결 세션을 줄일 수 있습니다.
Unity의 Test Runner 은 시스템이 모듈화되어 있을 때 가장 효과적으로 작동하며, 이는 Assembly 정의를 사용하는 또 다른 이유입니다.
5과: 에셋을 준비합니다.

"실수는 인간적이지만, 용서는 신의 영역이다."
하지만 인간의 실수를 방지하기 위한 시스템을 설정하는 것은 전적으로 전설적입니다.
수 시간의 코딩과 디버깅 끝에, 몇몇 피곤한 개발자가 몇 번의 잘못된 클릭을 하거나 실수로 리포지토리에 임시로만 적용될 예정이었던 변경 사항을 푸시할 수밖에 없습니다. 잘못은 용서할 수 있지만(가끔 피곤한 나로서는 이렇게 말합니다), 가져오기 설정이 잘못 구성된 에셋은 큰 결과를 초래할 수 있습니다. 그리고 많은 개발자들이 강력한 컴퓨터에서 작업하기 때문에, 경기장 씬에서 캐릭터, 애니메이션 및 효과가 하위 하드웨어에서 속도 저하 또는 불안정성을 일으키기 시작할 때까지 성능 문제가 눈에 띄지 않을 수도 있습니다.
이러한 위험을 완화하려면 에셋에 대한 액세스 권한을 제한할 수 있지만, 게임 콘텐츠를 조정해야 하는 여러 가지 이유로 인해 병목 현상이 발생합니다.
- 카메라 설정에 맞게 모델이 너무 큽니다.
- 이 오디오 클립의 볼륨이 낮으므로 특정 효과가 필요합니다.
- 셰이더가 변경된 지금은 모든 텍스처를 조정해야 합니다.
게임과 같은 경우 백야드 야구시각적 정체성이 중요한 경우, 모델과 VFX는 출시 전 디자인(look and feel)을 완벽하게 맞추기 위해 수백 번의 미세 조정을 거칩니다.
"콘텐츠 다양성을 갖는다는 것은 다양한 에셋 간의 미묘하지만 중요한 차이를 다루는 것을 의미한다는 사실은 어떤 기술 사양도 피할 수 없습니다."라고 니코는 말합니다.
자동화도 여기에 도움이 됩니다.
- AssetPostprocessor: 프로젝트 표준을 강제하는 맞춤형 임포트 로직을 작성합니다.
- OnValidate: 빌드 전에 항상 실행되는 OnValidate 메서드를 사용하여 에디터에서 참조 누락을 보고합니다.
마지막으로, 자동화에 대한 이 모든 이야기가 더 빠른 간단한 수동 수정 작업에서 당신을 혼란스럽게 하지 않도록 하십시오.
데이비드는 "수동으로 수행하는 데 10분이 걸리는 작업을 자동화하는 데 10일을 소비하지 마십시오."라고 경고합니다.
레슨 6: 인간 요소(협업) 마스터하기

버전 관리 Git과 같은 시스템은 매일 수십 명의 개발자로부터 수백 가지의 변경 사항을 조정할 때 가장 먼저 떠오르는 것들 중 하나입니다. 데이비드는 메가캣의 모든 프로젝트에 대해 다음과 같은 검증된 방법론을 옹호합니다.
- 작은 원자적 변경 사항: 여러 시스템에 한 번에 영향을 미치는 "대규모 커밋"을 피하세요. 개별 기능 브랜치 작업을 안정화하고 검토될 때까지 격리합니다. 필요에 따라 체리 피킹 및 기타 Git 마법을 더 쉽게 하기 위해 잘 문서화된 버전 기록을 위해 개별 커밋에 개별 변경 사항을 유지합니다.
- 메인에서의 일일 병합: 최종 병합의 크기와 복잡도를 줄이고 대규모 충돌을 방지하는 데 도움이 되므로 기능, 부서 및 장기적인 브랜치를 메인 브랜치와 최신 상태로 유지합니다.
- 병합 요청 검토: 이것은 버그를 잡고, 프로젝트 표준을 시행하며, 전체 시스템과의 일관성을 보장하는 품질 보증의 첫 번째 단계입니다.
데이비드는 "대규모 Unity 프로젝트에서는 코드 검토가 단순한 형식에 불과하지 않습니다."라고 조언합니다. "그것은 충돌 방지와 전반적인 프로젝트 품질의 핵심 부분입니다."
코드를 검토하는 사람들이 구현 중인 영역에 대한 전문 지식을 가지고 있으며 코딩 모범 사례를 알고 있으므로 정확성과 유지 보수성을 정확하게 평가할 수 있도록 하십시오.
Unity에서 버전 관리를 가능한 원활하게 하기 위한 구체적인 유용한 팁이 있습니다. 씬과 프리팹은 프로젝트의 기반이 되므로 CPU 성능뿐만 아니라 개발자 협업을 위해서도 최적화해야 합니다.
우리는 항상 큰 씬이나 단일체 프리팹보다 작고 가산적이며 중첩된 컴포넌트를 선호합니다. 이렇게 하면 개발자들이 충돌 없이 병렬로 작업할 수 있습니다.
씬과 프리팹의 병합 충돌은 개발자가 데이터를 쉽게 읽을 수 없기 때문에 해결하기 가장 어렵기 때문에 이는 중요합니다. 이 과정을 원활하게 하기 위해 이러한 파일을 바이너리가 아닌 텍스트로 직렬화하고 YAML 파일의 Git 구성에서 자동 병합 기능을 사용 가능하게 합니다. 이렇게 하면 Git이 병합 충돌을 스스로 해결할 가능성이 높아지고 개발자의 시간이 새로운 기능을 만드는 중요한 작업에 사용될 수 있습니다.
하지만 그럼에도 불구하고:
"충돌을 방지하는 것이 일반적으로 충돌을 해결하려는 것보다 더 나은 전략입니다."라고 Nico는 말합니다.
명확한 에셋 소유권은 이를 위해 많은 도움이 될 수 있습니다.
"특정 씬이나 프리팹을 수정할 수 있는 사람을 정의하세요."라고 David는 말합니다. "그런 다음 팀 구성원은 에셋을 직접 편집하는 대신 소유권 밖에서 변경을 요청합니다."
Nico는 이와 유사한 절차를 "신호등 시스템"이라고 설명합니다. 이것은 기본적으로 개발자가 에셋을 변경할 때 로그를 기록하는 스프레드시트로, 이를 통해 에셋을 효과적으로 "잠글" 수 있습니다. 다른 개발자가 해당 파일에 변경을 하려면 해당 파일을 잠근 개발자가 변경 사항을 리포지토리에 푸시하고 "잠금을 해제"할 때까지 기다려야 합니다.
항상 팀에 가장 적합한 절차를 찾으세요.
미래를 위한 구축

메가캣 스튜디오에서는 Unity 프로젝트를 확장하는 것은 "더 열심히 코딩"하는 것보다 아키텍처 규율을 지키는 것이 더 중요하다는 것을 배웠습니다. Unity의 컴포넌트 기반 특성을 존중하고, 어셈블리 정의로 경계를 강제하며, 미래를 염두에 두고 에셋을 구성함으로써 프로젝트가 실행되기 전에 기술 부채로 붕괴되지 않도록 프로토타입 단계의 창의적인 흐름을 유지합니다.
이러한 교훈이 중요하지만, 완벽한 코드베이스는 없다는 것을 기억하세요. 소프트웨어 개발은 권장되는 프로그래밍 패턴과 실용적인 고려 사항이 매일 충돌하는 격렬한 전투와 같습니다. 이러한 원칙 중 하나를 따르는 것이 유리한 절충안을 얻지 못하고 개발을 지연시킨다면, 교과서적인 권장 사항이 아닌 자신의 팀의 구체적인 사항에 더 민감해야 할 때라는 신호입니다. 결국 모든 프로젝트, 팀 및 사람은 다릅니다.
우리는 메가캣 스튜디오에서 매일 그 균형을 맞추려고 노력합니다. 새로운 프로젝트가 시작될 때마다, 우리는 게임 라이브러리 우리는 계속 성장하고 있으며, 더 나은 Unity 개발자이자 더 나은 협력자가 되기를 바랍니다.
