Unity 검색

AssetPostprocessor 및 Blender를 사용해 Breachers의 디자인 반복 작업을 개선한 방법

2023년 4월 18일 게임 | 17 분 소요
Rapid design iteration in Breachers using AssetPostprocessor and Blender | Hero image
Rapid design iteration in Breachers using AssetPostprocessor and Blender | Hero image
공유

Is this article helpful for you?

Thank you for your feedback!

Triangle Factory는 빠르게 성장하는 벨기에의 게임 회사로, Hyper Dash 및 최신 게임인 Breachers와 같은 고품질 멀티플레이어 VR 타이틀의 제작에 Unity를 사용하고 있습니다. Triangle Factory는 Cinemachine, Unity Profiler, 게임 서버 호스팅, Matchmaker, 음성 채팅(Vivox), Friends 와 같은 툴을 활용하여 플레이어에게 몰입도 높은 경험을 선사합니다.

이 블로그에서는 리드 레벨 디자이너/테크 아티스트인 젤 사도네스와 리드 디벨로퍼인 피터 방토르가 Blender-Unity 파이프라인과 VR 전술 FPS 타이틀인 Breachers를 구현한 방법을 소개합니다.

저희는 10년 넘게 Unity를 대표 엔진이자 개발 환경으로 사용해 왔으며, 환경 모델링과 디자인을 위해 수년 동안 많은 워크플로를 거쳤습니다. 여기에는 신속한 프로토타이핑을 위해 자주 사용하고 있는 ProBuilder와 같은 엔진 내 모델링 툴을 활용하고 다른 모델링 패키지에서 만든 프리팹으로 씬을 구성하는 것도 포함됩니다. 하지만 현재 프로젝트의 경우 Blender에서 레벨을 모델링하고 구성한 다음 Unity의 AssetPostprocessor를 사용하여 Unity 프로젝트에 통합하는 워크플로를 채택했습니다.

이번에는 이러한 워크플로를 개발하게 된 과정을 설명하고, 이 워크플로가 게임에 필요한 신속한 디자인 반복 작업을 어떻게 지원하는지 소개합니다.

이 콘텐츠는 Targeting Cookies 카테고리를 수락해야만 동영상을 시청할 수 있도록 허용하는 타사 제공업체에서 호스팅합니다. 이러한 제공업체의 비디오를 보려면 쿠키 환경 설정에서 Targeting Cookies 카테고리를 수락하시기 바랍니다.

적합한 워크플로 찾기

2021년에는 빠르게 진행되는 5대5 아레나 슈팅 게임인 Hyper Dash라는 첫 번째 대형 VR 타이틀을 출시했습니다. 2019년에 게임 개발을 시작했을 때는 아마 많은 분들이 알고 계실 기본 Blender-Unity 워크플로를 사용했습니다. Blender에서 지오메트리를 모델링하고 에셋을 FBX 파일로 익스포트한 다음 수동으로 Unity에 통합하는 것이었습니다. 그런데 수동 통합에는 다음과 같이 여러 단계가 필요했습니다.

  • 씬에 무기 픽업, 스폰 문, 캡처 포인트 등의 동적 오브젝트 설정
  • 플레이어가 특정 지역에서 걷거나 순간이동하지 못하도록 콜라이더 배치
  • 봇이 올바르게 작동하도록 보이지 않는 가이드 설정
  • 기타
Hyper Dash (2021)
Hyper Dash(2021)

이 프로세스는 소규모 프로젝트에서는 잘 작동할 수 있지만, 프로젝트가 확장되고 발전함에 따라 금세 번거로워집니다. 그래서 새로운 타이틀 개발을 계획하기 시작했을 때 워크플로를 대폭 개선해야 한다는 것을 깨달았습니다.

프로토타입을 사용하여 문제점 파악

Breachers는 복잡한 레벨 레이아웃, 섬세한 게임플레이 메카닉, 더 풍부한 기술 시스템, 최신 세대의 스탠드얼론 VR 하드웨어를 타게팅하는 높은 수준의 그래픽 완성도를 갖춘 경쟁식 슈팅 게임입니다. 복잡성 측면에서도 Hyper Dash보다 훨씬 개선되어, 워크플로에 미치는 영향을 금방 느낄 수 있었습니다.

Breachers: Skyscraper rappel in 4K

프로토타이핑 단계에서는 창문 바리케이드와 같은 동적 오브젝트의 경우 여전히 프리팹에 크게 의존했습니다. 창문 바리케이드는 창틀 안쪽에 설치하여 실내와 실외 사이의 시야를 차단해서 경기 워밍업 중에 양 팀이 서로를 보지 못하도록 하는 오브제입니다.

프로토타입을 테스트하는 동안 게임플레이를 개선하기 위해 계속 여러 창을 사용해야 했는데, Blender에서 지오메트리를 변경하고 Unity로 다시 익스포트한 다음 바리케이드 오브젝트를 변경 사항에 맞게 수동으로 옮기는 식이었습니다. Unity 씬(Scene) 뷰를 돌아다니면서 이런 종류의 문제를 수동으로 확인하고 수정하는 데 많은 시간을 보냈습니다. 그럼에도 불구하고 플레이 테스트를 하는 동안 게임 플레이 중에 간과한 부분을 여러 번 발견했습니다.

Figure 2: Breachers image provided by Triangle Factory (Prototyping the Hideout level)
은신처 레벨의 프로토타이핑
Figure 3.5: Breachers image provided by Triangle Factory (The final version of Hideout)
은신처의 최종 버전

물론 이 워크플로로는 내부적으로 플레이 테스트를 진행하면서 맵 디자인 반복 작업을 빠르게 진행할 수 없었습니다. 커뮤니티의 피드백을 받기 위해 하나의 맵을 무료로 공개할 계획이었던 오픈 알파 버전에서도 마찬가지였습니다. 저희는 이러한 피드백을 기대했지만, 수정 사항을 맵에 적용하는 데 필요한 수작업은 예상하지 못했습니다.

프리팹 기반 디자인 워크플로에서 발생할 수 있는 또 다른 단점은 성능입니다. 저희는 게임에 주로 모바일 스탠드얼론 VR 헤드셋을 타게팅합니다. 비주얼을 최대한 끌어올려야 하기 때문에 워크플로에서 마지막 한 방울까지 성능을 짜내야 하죠.

프리팹으로 레벨을 구성하는 것은 모델링 프로그램에서 방수 메시를 만드는 것보다 효율성이 떨어질 수 있습니다. 두 개의 모듈식 벽 조각을 함께 스냅하면 항상 그 사이에 병합되지 않은 지오메트리 루프가 있습니다. 프리팹을 사용하면 오브젝트 아래쪽에 있거나 벽에 배치되어 보이지 않지만 중요한 라이트맵 공간을 차지하는 지오메트리를 씬에 많이 배치하기 쉽습니다. 전체 레벨에서 봤을 때 이처럼 작은 비효율성이라도 성능 낭비와 비주얼 저하로 이어질 수 있습니다.

Figure 3: Breachers image provided by Triangle Factory (Breakable windows in a sandbox level during the prototype)
프로토타이핑 중 샌드박스 레벨의 깨지기 쉬운 창

마지막으로 언급하고 싶은 프리팹의 문제점은 오브젝트 이름 변경과 같이 Blender의 소스 모델에 별 문제가 없어 보이는 변경 사항을 적용했을 때 문제가 발생하기 쉽다는 점입니다. 게임이나 레벨이 발전함에 따라 에셋을 재구성하고 개선되거나 더 일관성 있는 이름을 부여해야 하는 경우가 많습니다. 그러나 Blender에서 오브젝트의 이름을 변경하고 다시 익스포트하면 Unity에서 오브젝트에 대한 오버라이드 및 추가가 경고 없이 쉽게 중단되어 회귀(regression)가 발생할 수 있습니다.

아래의 간단한 예제에서는 창살이 달린 환기구 프리팹이 있고 여기에서 연기가 나오게 하려고 합니다. 메시를 Unity로 임포트한 후 아티스트는 연기 파티클 시스템을 자식 오브젝트로 추가하고 프리팹에 표면 유형 컴포넌트를 추가하여 금속 오브젝트라는 것을 표시했습니다.

Blender에서 메시의 이름을 변경하면 어떻게 되는지 확인해 보세요.

업데이트된 이름으로 메시를 다시 임포트할 때 Unity는 더 이상 이전 메시의 이름을 찾을 수 없으므로 모델 프리팹에서 오브젝트를 제거합니다. 이렇게 제거된 오브젝트의 자식은 프리팹의 루트로 옮겨지고 기존 스크립트는 제거되므로 다시 수동으로 정리하는 작업을 방지할 수 있습니다.

프로덕션 파이프라인에 대한 명확한 목표 설정

Breachers의 프로토타이핑 단계가 마무리되고 2022년 초에 본격적인 프로덕션 모드로 전환할 준비를 하면서, 아트 팀과 개발 팀이 함께 모여 이러한 문제를 어떻게 해결할 수 있을지 조사했습니다. 저희는 Breachers에 필요한 신속하고 유연한 반복 작업을 지원할 수 있는 이상적인 에셋 파이프라인에 대해 명확한 목표를 정의했습니다.

  • 레벨 지오메트리의 모든 생성 및 수정은 Blender에서 이루어져야 합니다.
  • WYSIWYG: 디자이너가 Blender에서 생성한 결과물이 Unity에서 생성한 결과물과 최대한 일치해야 합니다.
  • Blender에서 무언가를 업데이트하면 변경 사항을 Unity로 임포트할 때 수동으로 작업할 필요 없이 자동으로 임포트되어야 합니다.
Figure 4: Breachers image provided by Triangle Factory

Blender와 Unity의 원활한 연계

위에서 언급했듯이, 저희의 주요 목표는 Blender에서 게임을 정확하게 시각화하여 최종 결과물이 Unity에서 어떻게 보일지뿐만 아니라 게임플레이 메카닉이 어떻게 설정되는지를 적절히 반영하는 것이었습니다. Breachers의 게임플레이는 레벨의 레이아웃뿐만 아니라 동적 오브젝트(예: 뚫을 수 있는 벽)와 보이지 않는 요소(예: 사운드 볼륨 및 콜라이더)에 따라서도 달라집니다. 저희는 이 모든 정보를 디자인 단계에서 확인하고 Unity로 정확하게 전달할 수 있기를 원합니다.

Figure 5: Breachers image provided by Triangle Factory [Part of our Skyscraper level in Blender (static level geometry and props only)]
Blender의 초고층 빌딩 레벨 일부(정적 레벨 지오메트리 및 프랍만 해당)
Figure 6: Breachers image provided by Triangle Factory (The same scene with dynamic objects added)
동적 오브젝트가 추가된 동일한 씬
Figure 7: Breachers image provided by Triangle Factory (The same scene imported into Unity, before light baking)
라이트 베이킹 전, Unity로 임포트한 동일한 씬
Figure 8: Breachers image provided by Triangle Factory (The final scene in Unity)
Unity에서의 최종 씬

커스텀 프로퍼티와 Unity의 AssetPostprocessor

커스텀 프로퍼티는 워크플로에 매우 중요하며, 저희는 Blender에서 이러한 프로퍼티를 오브젝트에 할당합니다. 그런 다음 에셋을 Unity로 임포트할 때 이를 읽고 커스텀 로직을 실행할 수 있도록 FBX 포맷으로 Unity에 전달합니다.

Figure 9: Breachers image provided by Triangle Factory (An example of custom properties assigned to an object in Blender)
Blender에서 오브젝트에 할당된 커스텀 프로퍼티의 예시

이를 통해 안정성은 물론 뛰어난 유연성을 확보할 수 있습니다. 이러한 프로퍼티는 파이프라인 전체에 걸쳐 오브젝트에 연결된 상태로 유지되므로 레벨의 오브젝트를 원하는 만큼 재구성하고 이름을 변경할 수 있습니다. 오브젝트가 깨지거나 동기화되지 않을까 걱정하지 않아도 됩니다.

Unity에는 에셋을 임포트하는 동안 에셋을 수정할 수 있는 강력한 클래스인 AssetPostprocessor가 있습니다. 저희는 임포트할 때 AssetPostprocessor를 사용해서 커스텀 프로퍼티를 파싱하고 작업을 수행합니다.

사용 사례

프리팹 링크

PrefabLink라는 커스텀 프로퍼티가 있는데, 이 프로퍼티는 임포트한 모델의 트랜스폼을 유지하면서 Blender에서 임포트한 오브젝트를 Unity 프로젝트에 이미 있는 프리팹으로 대체해야 한다고 알립니다. 이를 통해 이러한 동적 오브젝트를 Unity로 임포트한 후에도 프리팹의 장점을 유지하면서 Blender에 배치할 수 있습니다. 위 Blender 씬의 창문 바리케이드가 좋은 예시입니다.

Figure 10: Breachers image provided by Triangle Factory

표면 유형

표면 정의는 Breachers에서 매우 중요합니다. 금속 계단을 걷는 것은 콘크리트 바닥을 걷는 것과 다른 느낌이며, 총알이 나무를 관통하는 것은 강철을 관통하는 것과 많은 차이가 있습니다. 그리고 각 표면 유형에는 고유한 효과가 있습니다. Unity에서 각 프랍을 검토하고 올바른 표면 유형으로 태그를 지정하는 작업은 시간이 많이 걸리기 때문에, 저희는 디자인 시 Blender에서 지오메트리 콜라이더에 커스텀 프로퍼티를 설정하여 이 문제를 해결합니다.

정적 플래그

최적화를 위한 또 다른 중요한 설정은 Unity의 정적 플래그입니다. 이를 올바르게 설정하면 가시성 컬링, 라이트 베이킹 및 배칭과 같은 작업에 큰 영향을 미칠 수 있습니다. Blender에서 커스텀 프로퍼티를 사용하면 재사용 가능한 프랍을 포함하여 레벨의 모든 부분에 정적 플래그를 설정할 수 있으며, 해당 정보가 레벨 전체에 걸쳐 Unity로 전달되도록 할 수 있습니다.

콜라이더

마지막으로 저희가 콜라이더를 어떻게 설정했는지 알려 드리겠습니다. Unity는 모델 에셋 이름에 _LOD0, _LOD1 등의 접미사를 붙이면 모델의 디테일 수준(LOD) 배리언트를 자동으로 감지하는 간단하지만 효과적인 시스템을 갖추고 있습니다. 저희는 여기서 아이디어를 얻어 비슷한 콜라이더 시스템을 만들었습니다. 지오메트리 이름에 _BoxCollider 또는 _NoCollision을 붙이는 것만으로도 Blender의 메시를 Unity의 콜라이더로 대체할 수 있습니다.

Figure 11: Breachers image provided by Triangle Factory (Observe the _BoxCollider and _NoCollision names in Blender)
Blender에서 이름에 _BoxCollider 및 _NoCollision을 추가합니다.
Figure 12: Breachers image provided by Triangle Factory (Object marked as _BoxCollider gets translated to an actual BoxCollider in Unity)
_BoxCollider로 표시된 오브젝트는 Unity에서 실제 BoxCollider로 변환됩니다.

코드 예제

구체적인 예로, 커스텀 프로퍼티를 읽고 임포트된 각 오브젝트에 적합한 정적 플래그를 할당하는 LevelSetupPostprocessor의 스니핏을 살펴보겠습니다.

public class LevelSetupPostprocessor : AssetPostprocessor
{
  // カスタムプロパティを使用している各オブジェクトの Dictionary
  private readonly Dictionary<string, (string[], object[])> _userPropertyMap = new ();

  // サポートされているすべてのカスタムプロパティ
  private static readonly string[] SupportedPropNames = new []
  {
      "Surface",
      "Layer",
      "PrefabLink",
      "Collision",
      "StaticFlags",
      "LightmapScale",
      "LightMeshPreset"
  };

  // AssetPostprocessor の Unity イベント
  // モデルの各オブジェクトに対して呼び出される
  private void OnPostprocessGameObjectWithUserProperties(GameObject go, string[] propNames, object[] values)
  {
  // 対象のカスタムプロパティが含まれているか確認し、Dictionary に追加する
  if (SupportedPropNames.Select(x => x.ToLowerInvariant()).Intersect(propNames.Select(x => x.ToLowerInvariant())).Any())
      {
          _userPropertyMap.Add(go.name, (propNames, values));
      }
  }

  // AssetPostprocessor の Unity イベント
  private void OnPostprocessModel(GameObject model)
  {
      // 見つかった各カスタムプロパティに対して
      // Model Prefab Variant 内の対応する Gameobject を取得し
      // 適切なロジックを適用する
      for(int i = _userPropertyMap.Count -1; i >= 0; i--)
      {
          var kvp = _userPropertyMap.ElementAt(i);
          GameObject go = FindGameObjectInHierarchy(model, kvp.Key);	// モデルの子要素を名前で検索
          string[] propNames = kvp.Value.Item1;
          object[] values = kvp.Value.Item2;
          for(int j = 0; j < propNames.Length; j++)
          {
              object value = values[j];
              switch (propNames[j])
              {
                  case "staticflags":
                      HandleStaticFlags(go, value);
                      break;
                  // ...
              }
          }
      }
  }

  // Blender のカスタムプロパティに基づいてオブジェクトに StaticFlags を適用
  private void HandleStaticFlags(GameObject go, object value)
  {
      string[] staticFlags = value.ToString().Split(',');
      StaticEditorFlags activeFlags = 0;
      for(int i = 0; i < staticFlags.Length; ++i)
      {
          string flag = staticFlags[i].ToLower().Trim();
          switch (flag)
          {
              case "batching static":
                  activeFlags |= StaticEditorFlags.BatchingStatic;
                  break;
              // ...
              default:
                  LogWarning($"Unknown static flag {flag} detected when importing {go.name}", go);
                  break;
          }
      }
      GameObjectUtility.SetStaticEditorFlags(go, activeFlags);
  }
}

Unity와 더 원활하게 작동하도록 Blender 커스터마이징

이 모든 작업이 원활하게 이루어질 수 있도록 Blender 쪽에서도 몇 가지 작업을 해야 했습니다.

커스텀 프로퍼티는 Blender의 UI에 약간 숨겨져 있어 아티스트가 매번 커스텀 프로퍼티를 수동으로 입력해야 한다는 점에서 사용자 경험이 좋지는 않습니다. 수동 텍스트 입력에 의존하면 오류가 발생하기 쉬우며, 처음부터 Blender에서 설정했을 경우에 얻을 수 있는 많은 이점이 사라지게 됩니다. 프리팹 기반 워크플로에서 Blender로 전환하면서, 원하는 오브젝트를 찾아 보고 선택할 수 있는 멋진 오브젝트 라이브러리와 같은 프리팹의 장점도 활용하기가 어려워졌습니다. 다행히도 Blender는 Unity와 마찬가지로 매우 유연하고 쉽게 확장할 수 있습니다.

Blender 에셋 라이브러리

프리팹 구성 문제에 대한 해답은 에셋 라이브러리가 포함된 Blender 3.2에서 나왔습니다. 이 시스템은 Unity의 프리팹 시스템과 비슷하게 작동하는 면이 있습니다. 별도의 파일에 에셋을 생성한 다음 Blender 씬으로 임포트할 수 있으며, 에셋 파일의 변경 사항은 Blender 씬에 자동으로 반영됩니다. 또한 커스텀 프로퍼티 또는 콜라이더가 Blender에서 이 에셋의 각 인스턴스에 올바르게 적용되도록 합니다.

Figure 13: Breachers image provided by Triangle Factory (Part of our props library in Blender)
블렌더 내 프랍 라이브러리의 일부
Figure 14: Breachers image provided by Triangle Factory (All of the dynamic object types that use the PrefabLink custom property)
PrefabLink 커스텀 프로퍼티를 사용하는 모든 동적 오브젝트 유형

커스텀 Blender 애드온

Blender의 경우, 커스텀 프로퍼티를 보다 명확한 사용자 인터페이스에서 설정할 수 있도록 자체 애드온을 개발했습니다. 이렇게 하면 각 프로퍼티를 수동으로 입력하는 대신 관련 Blender 오브젝트를 선택하고 버튼을 누르기만 하면 커스텀 프로퍼티를 간편하게 설정할 수 있습니다.

Bundle Exporter 애드온은 클릭 한 번으로 모든 FBX 파일을 익스포트하는 데 사용하는 오픈 소스 애드온입니다. 저희는 이 애드온을 커스텀 프로퍼티와 함께 사용할 수도 있도록 수정하고 요구 사항에 맞게 더 빠르게 익스포트할 수 있도록 UI를 업데이트했습니다.

Figure 15: Breachers image provided by Triangle Factory

결론

Breachers의 레벨 디자인 워크플로를 설정하는 데 처음에는 많은 시간을 투자해야 했지만, 이 프로젝트에 적합한 선택이었다고 생각합니다. 꽤 재미있기도 했고요.

초기 블록아웃부터 알파 테스트, 몇 달 뒤의 최종 출시까지 오랫동안 게임을 개발해 왔기 때문에 레벨에서의 반복 작업은 빠르고 수월했습니다. 디자이너와 아티스트의 오버헤드와 바쁜 업무를 없애는 동시에, 이전에는 개발자가 필요했던 업무를 이들에게 이관할 수 있었습니다.

Unity와 Blender가 이렇게 원활하게 통합할 수 있다는 사실에 깊은 인상을 받았으며, 이러한 통합이 Breachers를 전 세계와 공유할 수 있는 자랑스러운 게임으로 만드는 데 결정적인 역할을 했다고 확신합니다. 

지금까지 읽어 주셔서 감사 드리며, 게임을 즐겨 주세요!

4K still from Triangle Factory's VR FPS Breachers

Triangle Factory의 Breachers는 현재 이용 가능합니다. Made with Unity 개발자들의 더 많은 블로그 게시글은 여기에서 확인하세요.

2023년 4월 18일 게임 | 17 분 소요

Is this article helpful for you?

Thank you for your feedback!

관련 게시물