스크립터블 오브젝트로 더 나은 씬 워크플로 달성하기

Unity에서 여러 씬을 관리하는 것은 어려운 작업이며, 이 워크플로를 개선하는 것은 게임의 성능과 팀의 생산성 모두에 중요합니다. 여기에서는 대규모 프로젝트에 맞게 확장할 수 있는 방식으로 씬 워크플로를 설정하는 몇 가지 팁을 공유합니다.
대부분의 게임에는 여러 레벨이 포함되며, 레벨에는 하나 이상의 씬이 포함되는 경우가 많습니다. 씬의 크기가 상대적으로 작은 게임에서는 프리팹을 사용하여 씬을 여러 섹션으로 나눌 수 있습니다. 하지만 게임 중에 활성화하거나 인스턴스화하려면 이러한 모든 프리팹을 참조해야 합니다. 즉, 게임이 커지고 이러한 레퍼런스가 메모리 공간을 더 많이 차지할수록 씬을 사용하는 것이 더 효율적이 됩니다.
레벨을 하나 또는 여러 개의 Unity 씬으로 세분화할 수 있습니다. 이 모든 것을 관리할 수 있는 최적의 방법을 찾는 것이 핵심입니다. 에디터에서 여러 씬을 열고 런타임에 멀티 씬 편집을 사용하여 여러 씬을 열 수 있습니다. 또한 레벨을 여러 개의 씬으로 분할하면 Git, SVN, Unity 콜라보레이트 등과 같은 협업 툴에서 병합 충돌을 방지할 수 있어 팀워크가 더 쉬워진다는 장점이 있습니다.
아래 동영상에서는 게임 로직과 레벨의 여러 부분을 여러 개의 개별 Unity 씬으로 분할하여 레벨을 보다 효율적으로 로드하는 방법을 보여줍니다. 그런 다음 이러한 씬을 로드할 때 애디티브 씬 로딩 모드를 사용하여 게임 로직과 함께 필요한 부분을 로드하고 언로드하며, 이는 지속적입니다. 프리팹은 씬의 '앵커' 역할을 하는데, 모든 씬이 레벨의 일부를 나타내며 개별적으로 편집할 수 있기 때문에 팀 작업 시 유연성이 뛰어납니다.
편집 모드에 있는 동안에도 이러한 씬을 로드하고 언제든지 플레이를 누르면 레벨 디자인을 만들 때 모두 함께 시각화할 수 있습니다.
이러한 씬을 로드하는 두 가지 방법을 보여드립니다. 첫 번째는 거리 기반이며, 오픈 월드와 같이 내부가 아닌 레벨에 적합합니다. 이 기술은 로딩 및 언로딩 과정을 숨기기 위한 일부 시각 효과(예: 안개)에도 유용합니다.
두 번째 기법은 트리거를 사용하여 로드할 씬을 확인하므로 인테리어 작업 시 더 효율적입니다.
이제 모든 것이 레벨 내부에서 관리되므로 레벨 위에 레이어를 추가하여 레벨을 더 잘 관리할 수 있습니다.
각 레벨의 다양한 장면은 물론 게임플레이가 진행되는 동안 모든 레벨을 추적하고 싶습니다. 이를 수행하는 한 가지 가능한 방법은 모노비헤이비어 스크립트에서 정적 변수와 싱글톤 패턴을 사용하는 것이지만, 이 솔루션에는 몇 가지 문제가 있습니다. 싱글톤 패턴을 사용하면 시스템 간에 견고한 연결이 가능하므로 엄밀히 말해 모듈식이 아닙니다. 시스템은 분리되어 존재할 수 없으며 항상 서로 의존하게 됩니다.
또 다른 문제는 정적 변수의 사용과 관련이 있습니다. 인스펙터에서 볼 수 없기 때문에 코드를 변경하여 설정해야 하므로 아티스트나 레벨 디자이너가 게임을 쉽게 테스트하기 어렵습니다. 서로 다른 씬 간에 데이터를 공유해야 하는 경우 DontDestroyOnLoad와 결합된 정적 변수를 사용하지만, 후자는 가능하면 피해야 합니다.
다양한 씬에 대한 정보를 저장하려면 주로 데이터를 저장하는 데 사용되는 직렬화 가능한 클래스인 스크립터블 오브젝트를 사용할 수 있습니다. 게임 오브젝트에 첨부된 컴포넌트로 사용되는 모노비헤이비어 스크립트와 달리 스크립터블 오브젝트는 게임 오브젝트에 첨부되지 않으므로 전체 프로젝트의 여러 씬 간에 공유할 수 있습니다.
이 구조를 레벨뿐만 아니라 게임 내 메뉴 장면에도 사용할 수 있기를 원합니다. 이렇게 하려면 레벨과 메뉴 간의 다양한 공통 프로퍼티를 포함하는 GameScene 클래스를 생성합니다.
알 수 없는 블록 유형 "codeBlock"인 경우 `serializers.types` 프롭에서 해당 블록에 대한 직렬화기를 지정하세요.
이 클래스는 MonoBehaviour가 아닌 ScriptableObject에서 상속된다는 점에 유의하세요. 게임에 필요한 만큼의 프로퍼티를 추가할 수 있습니다. 이 단계가 끝나면 방금 만든 GameScene 클래스를 상속하는 Level 및 Menu 클래스를 만들 수 있으며, 이 클래스도 스크립터블 오브젝트입니다.
알 수 없는 블록 유형 "codeBlock"인 경우 `serializers.types` 프롭에서 해당 블록에 대한 직렬화기를 지정하세요.
상단에 CreateAssetMenu 어트리뷰트를 추가하면 Unity의 에셋 메뉴에서 새 레벨을 생성할 수 있습니다. 메뉴 클래스에 대해서도 동일한 작업을 수행할 수 있습니다. 인스펙터에서 메뉴 유형을 선택할 수 있도록 열거형을 포함할 수도 있습니다.
알 수 없는 블록 유형 "codeBlock"인 경우 `serializers.types` 프롭에서 해당 블록에 대한 직렬화기를 지정하세요.
이제 레벨과 메뉴를 만들 수 있으므로 쉽게 참조할 수 있도록 레벨과 메뉴를 나열하는 데이터베이스를 추가해 보겠습니다. 인덱스를 추가하여 플레이어의 현재 레벨을 추적할 수도 있습니다. 그런 다음 새 게임을 로드하고(이 경우 첫 번째 레벨이 로드됨), 현재 레벨을 재생하고, 다음 레벨로 이동하는 메서드를 추가할 수 있습니다. 이 세 가지 메서드 간에는 인덱스만 변경되므로 인덱스로 레벨을 로드하는 메서드를 생성하여 여러 번 사용할 수 있습니다.
알 수 없는 블록 유형 "codeBlock"인 경우 `serializers.types` 프롭에서 해당 블록에 대한 직렬화기를 지정하세요.
메뉴에 대한 메서드도 있으며, 이전에 만든 열거 형을 사용하여 원하는 특정 메뉴를 로드할 수 있습니다. 열거 형의 순서와 메뉴 목록의 순서가 동일한지 확인하기만 하면 됩니다.
이제 프로젝트 창에서 우클릭하여 에셋 메뉴에서 레벨, 메뉴 또는 데이터베이스 스크립터블 오브젝트를 생성할 수 있습니다.

거기에서 필요한 레벨과 메뉴를 계속 추가하고 설정을 조정한 다음 장면 데이터베이스에 추가하기만 하면 됩니다. 아래 예는 Level1, MainMenu 및 Scenes 데이터가 어떻게 보이는지 보여줍니다.

이제 그 메서드를 호출할 차례입니다. 이 예제에서 플레이어가 레벨의 끝에 도달하면 표시되는 사용자 인터페이스(UI)의 다음 레벨 버튼은 다음 레벨 메서드를 호출합니다. 메서드를 버튼에 연결하려면 버튼 컴포넌트의 On Click 이벤트의 더하기 버튼을 클릭하여 새 이벤트를 추가한 다음, 아래와 같이 Scenes Data ScriptableObject를 개체 필드에 끌어다 놓고 ScenesData에서 NextLevel 메서드를 선택합니다.

이제 레벨을 재생하거나 메인 메뉴로 이동하는 등 다른 버튼에 대해서도 동일한 과정을 거칠 수 있습니다. 다른 스크립트에서 스크립터블 오브젝트를 참조하여 배경 음악용 오디오 클립이나 포스트 프로세싱 프로파일과 같은 다양한 프로퍼티에 액세스하고 레벨에서 사용할 수도 있습니다.
- 로딩/언로딩 최소화
비디오에 표시된 ScenePartLoader 스크립트를 보면 플레이어가 계속해서 콜라이더에 여러 번 들어오고 나가면서 씬의 반복적인 로딩과 언로딩을 트리거할 수 있음을 알 수 있습니다. 이를 방지하려면 스크립트에서 씬의 로딩 및 언로딩 메서드를 호출하기 전에 코루틴을 추가하고 플레이어가 트리거를 벗어나면 코루틴을 중지할 수 있습니다.
- 명명 규칙
또 다른 일반적인 팁은 프로젝트에서 견고한 이름 지정 규칙을 사용하는 것입니다. 팀은 스크립트, 씬, 머티리얼 등 프로젝트의 다양한 유형의 에셋에 이름을 지정하는 방법에 대해 미리 합의해야 합니다. 이렇게 하면 본인뿐만 아니라 팀원들도 프로젝트를 더 쉽게 작업하고 유지 관리할 수 있습니다. 이는 항상 좋은 생각이지만, 이 특별한 경우에는 스크립터블 오브젝트를 사용한 씬 관리가 중요합니다. 이 예제에서는 씬 이름을 기반으로 하는 간단한 접근 방식을 사용했지만 씬 이름에 덜 의존하는 다양한 솔루션이 있습니다. 특정 컨텍스트에서 Unity 씬의 이름을 변경하면 게임의 다른 부분에서는 해당 씬이 로드되지 않으므로 문자열 기반 접근 방식은 피해야 합니다.
- 맞춤형 툴링
게임 전체에서 이름 종속성을 피하는 한 가지 방법은 씬을 오브젝트 유형으로 참조하도록 스크립트를 설정하는 것입니다. 이렇게 하면 인스펙터에서 씬 에셋을 끌어다 놓은 다음 스크립트에서 안전하게 이름을 가져올 수 있습니다. 하지만 에디터 클래스이므로 런타임에 AssetDatabase 클래스에 액세스할 수 없으므로 에디터에서 작동하고 인적 오류를 방지하며 런타임에도 작동하는 솔루션을 위해서는 두 가지 데이터를 결합해야 합니다. 직렬화 시 씬 에셋에서 문자열 경로를 추출하여 런타임에 사용할 수 있도록 저장할 수 있는 오브젝트를 구현하는 방법에 대한 예제는 ISerializationCallbackReceiver 인터페이스를 참조하세요.
또한 커스텀 인스펙터를 생성하면 해당 메뉴를 통해 수동으로 씬을 추가하고 동기화 상태를 유지해야 하는 대신 버튼을 사용하여 빌드 설정에 씬을 빠르게 추가할 수 있습니다.
이러한 유형의 툴의 예로 개발자 JohannesMP가 구현한 훌륭한 오픈 소스 구현을 확인해 보세요(공식 Unity 리소스는 아님).
이 게시물은 스크립터블 오브젝트가 프리팹과 결합된 여러 씬으로 작업할 때 워크플로를 향상시킬 수 있는 한 가지 방법을 보여줍니다. 게임마다 씬을 관리하는 방식이 크게 다르기 때문에 모든 게임 구조에 적합한 단일 솔루션은 없습니다. 프로젝트의 조직에 맞게 사용자 지정 도구를 구현하는 것은 매우 합리적입니다.
이 정보가 프로젝트에 도움이 되거나 자신만의 씬 관리 도구를 만드는 데 영감을 줄 수 있기를 바랍니다.
궁금한 점이 있으면 댓글로 알려주세요. 게임 내 씬을 관리하는 데 어떤 방법을 사용하시는지 알려주세요. 향후 블로그 게시물에서 다루었으면 하는 다른 사용 사례도 자유롭게 제안해 주세요.
