Unity 검색

프리셋을 이용한 워크플로 개선, 디자인 검증 및 오류 방지

2019년 10월 11일 테크놀로지 | 10 분 소요
공유

Is this article helpful for you?

Thank you for your feedback!

프리셋을 이용하면 컴포넌트, 임포터, 관리자 등 Unity의 모든 요소를 코딩 없이도 커스터마이즈할 수 있습니다. 프리셋은 개발팀의 규모와 상관없이 반복 작업을 간소화해주고 디자인 검증을 원활하게 하며, 표준화와 프로젝트 템플릿 구성 등을 도와줍니다. 이번 블로그 포스팅은 프리셋의 기본 사항을 비롯한 다양한 기능, 유용한 팁과 고급 사용 사례를 다룹니다.

프리셋이란?

기본적으로 프리셋은 특정 컴포넌트, 임포터나 관리자 등 Unity 오브젝트를 확장하는 모든 요소의 기본값을 오버라이드할 수 있는 에셋입니다. 프리셋을 이용하면 프로젝트의 리지드바디 기본 질량을 1이 아닌 10으로 설정하고 중력을 비활성화하는 등 자유로운 조정이 가능합니다.

텍스처, FBX 파일, 그리고 MonoBehaviour 컴포넌트인 광원, 카메라, 리지드바디  등 계층 구조에서 임포트되거나 인스턴스화되는 요소에 프리셋을 사용할 수 있습니다.

프리셋은 제작 과정을 원활하게 해주는 에디터 기능이며, 런타임 기능이 아닙니다. 프리셋은 바이너리와 함께 제공되지 않으므로, GameObject.AddComponent<RigidBody>()에는 같은 기본값이 적용되지 않습니다. 단, Editor-time ObjectFactory API는 프리셋을 지원하므로 ObjectFactory.AddComponent<RigidBody>()는 프로젝트 프리셋이 준수된다는 가정하에 작동합니다.

프리셋을 만들려면 같은 유형의 기존 임포터나 컴포넌트로 이동하여 인스펙터에서 변경을 수행한 다음, 오른쪽 상단의 프리셋 아이콘을 클릭하기만 하면 됩니다. 그 후에 창이 하나 열리면 'Save Current To...'를 선택하여 에셋을 저장할 수 있습니다.

프리셋으로 신속하게 작업하기 위한 팁

프로젝트에 프리셋을 적용할 수 있는 방법 몇 가지를 소개합니다.

프리셋 아이콘 클릭

인스펙터에서 프리셋 아이콘을 클릭하면 동일한 유형의 컴포넌트나 파일에 대한 프리셋을 선택할 수 있습니다.

인스펙터로 드래그 앤 드롭

컴포넌트의 값을 변경하려면 프로젝트(Project) 창에서 인스펙터의 해당 컴포넌트로 프리셋을 드래그 앤 드롭하면 됩니다.

프리셋을 인스펙터로 드래그하여 게임 오브젝트에 새로운 컴포넌트를 추가할 수도 있습니다.

계층 구조로 드래그 앤 드롭

프리셋을 사용하여 계층 구조에 새로운 오브젝트를 생성할 수도 있습니다. 프로젝트 창에서 계층 구조로 프리셋을 직접 드래그하면 해당 프리셋에 연결된 컴포넌트를 포함하는 새로운 게임 오브젝트를 빠르게 생성할 수 있습니다.

이 워크플로는 프리팹으로 작업할 때와 비슷하지만, 프리셋과 프리팹을 혼동하지 마세요! 프리셋은 해당 프리셋에서 생성된 오브젝트와 어떤 방식으로도 연결되어 있지 않습니다.

여러 개의 프리셋을 한꺼번에 드래그 앤 드롭하면 모든 컴포넌트가 포함된 게임 오브젝트를 만들 수도 있습니다.

프리셋 관리하기

프리셋은 Edit → Project Settings에 있는 UI인 Preset Manager로 관리합니다.

기본 프리셋을 추가하려면 Preset Manager를 열고, 'Add Default Preset'를 클릭한 다음 원하는 기본 프리셋 유형을 선택합니다. 마지막으로 'None (프리셋)' 옆의 원 모양 아이콘을 클릭하고 프리셋 선택(Select Preset) 팝업 창에서 동일한 유형의 프리셋 에셋을 선택합니다.

다음은 이미 여러 컴포넌트가 추가된 Preset Manager입니다. 포스팅의 앞부분에서 만든 RigidBody 10 프리셋이 기본값으로 적용되어 있습니다.

이 설정을 적용하면 앞으로 생성될 모든 리지드바디의 질량이 10으로 기본 설정되며, 중력은 기본적으로 비활성화됩니다.

그뿐만 아니라 Preset Manager에서는 유형당 2가지 이상의 기본 프리셋을 설정할 수 있습니다. Unity 2019.3버전의 새로운 기능인 확장된 프리셋을 이용하면 여러 가지 '기본 프리셋'을 지정하고 적용된 기본 프리셋을 이름으로 구별할 수 있습니다. 위 이미지의 Camera를 보면, 빈 필터가 Camera Gameplay 프리셋을 사용한다는 사실을 확인할 수 있습니다. 즉, 모든 카메라는 기본으로 이 프리셋을 사용합니다. 하지만 여기서는 UI 카메라에서 Camera UI 프리셋과 함께 'UI' 필터를 사용하도록 설정했습니다. 이 설정에서는 게임 오브젝트 이름에 'UI'가 포함된 카메라(예: 'UICamera', 'Camera UI', 'HUDUICamera' 등)를 만드는 경우 Camera UI 프리셋이 기본 프리셋보다 우선적으로 사용됩니다.

Preset Manager에서 다음 사항에 유의하세요.

  • 문자열 검색 시 대소문자를 구분하지 않으므로, 예를 들어 'GuideCamera'와 같은 이름에 'UI' 스트링이 포함되어 있는 경우 의도치 않은 결과를 가져올 수 있습니다.
  • 오브젝트가 하나의 유형에 대해 여러 개의 기본 프리셋을 설정한 경우, 목록에서 마지막으로 설정된 프리셋이 적용됩니다.
  • 컴포넌트에서 'Reset'을 선택하면 언제든지 기본값으로 되돌릴 수 있습니다.
  • 임포터의 경우 게임 오브젝트 이름이 아닌 파일 이름이 사용될 필터를 결정합니다.

관리자에 프리셋 적용하기

프리셋을 사용하면 팀이 동시에 중요한 아트 및 디자인에 관련된 작업을 진행할 수 있습니다. 예를 들어, 광원 색상이나 리지드바디의 중량, 노멀 맵 임포트 기준 등을 정할 수 있습니다. 관리자에 프리셋을 적용하면 이런 작업을 프로젝트 단위로 구현할 수도 있습니다. 그 예로 Physics Manager와 Tags and Layers Manager를 살펴보겠습니다.

이 예에서는 레이어 간 특정한 물리 상호작용이 발생하도록 여러 가지 설정을 변경했습니다. 예를 들어 Enemies는 다른 Enemies와 상호작용하지 않으며, Allies와 Allies의 Bullets는 다른 Allies와 상호작용하지 않도록 설정했습니다. 이러한 설정은 후속 프로젝트나 관련 프로젝트에서 계속 사용될 수 있으며, 프리셋을 이용하면 설정이 저장되어 프로젝트 간 공유가 쉬워집니다.

아래의 애니메이션에서 두 개의 관리자에 설정을 저장한 후 별개의 프로젝트에 적용한 방법을 볼 수 있습니다. 이 기능은 사내에서 새로운 프로젝트를 독자적으로 진행할 때도 유용합니다.

API 지원

대부분의 경우 위의 비주얼 인터페이스로 충분하지만, 예를 들어 임포트 파이프라인을 제작하거나 툴링을 빌드하는 경우 프리셋을 직접 프로그래밍하여 사용하는 게 더 편리합니다. 따라서 유니티는 작업의 편의와 생산성을 제고하는 Presets API를 제공합니다.

다음 예는 모두 Github에서 확인하실 수 있습니다.

간단한 예부터 살펴보겠습니다. 광원 1개가 포함된 여러 프로퍼티를 씬의 모든 광원에 적용하는 툴을 추가해 봅시다.

[MenuItem("CONTEXT/Light/Replicate Color in Scene")]
public static void ApplyAllLights(MenuCommand command)
{
    // Get our current selected light
    var referenceLight = command.context as Light;
    if (referenceLight != null)
    {
        // Create a Preset out of it
        var lightPreset = new Preset(referenceLight);
        // Find all Light components in the scene of our reference light
        var allLights = referenceLight.gameObject.scene.GetRootGameObjects()
            .SelectMany(r => r.GetComponentsInChildren<Light>(true));
        // Choose which serialized property we want to apply to everyone
        var propertyToApply = new[] { "m_Color" };
        // Apply the Preset with only the selected property to all the Lights
        foreach (var light in allLights)
        {
            lightPreset.ApplyTo(light, propertyToApply);
        }
    }
}

이 코드는 레퍼런스 오브젝트와 광원으로 신속하게 프리셋을 만듭니다(var lightPreset = new Preset(referenceLight)). 그런 다음 referenceLight의 m_Color를 사용하여 이 값을 씬의 모든 광원에 적용합니다.

새로운 툴이 Unity에서 작동되는 모습입니다.

이는 부분 프리셋의 프로그래밍 예시로 코드가 전체 프리셋 프로퍼티 세트의 하위 세트만을 가져와 적용합니다. 유니티는 이러한 기능을 UI에서 곧 제공할 예정입니다.

두 번째 예시에서는 프로젝트 폴더에서 같은 유형의 모든 에셋에 단일 프리셋을 적용할 수 있는 방법을 알아보겠습니다.

[MenuItem("CONTEXT/Material/Replicate Material Color in Folder")]
public static void ApplyAllMaterialsInFolder(MenuCommand command)
{
    // Get our current selected Material
    var referenceMaterial = command.context as Material;
    if (referenceMaterial != null)
    {
        var assetPath = AssetDatabase.GetAssetPath(referenceMaterial);
        if (!string.IsNullOrEmpty(assetPath))
        {
            // Create a Preset out of it
            var materialPreset = new Preset(referenceMaterial);
            // Find all Material assets in the same folder
            var assetFolder = Path.GetDirectoryName(assetPath);
            var allMaterials = AssetDatabase.FindAssets("t:Material", new[] { assetFolder })
                .Select(AssetDatabase.GUIDToAssetPath)
                .Select(AssetDatabase.LoadAssetAtPath<Material>);
            // Select the first color entry; in the standard shader this entry is _Color
            var propertyToApply = new[] { "m_SavedProperties.m_Colors.Array.data[0]" };
            // Apply the Preset with only the selected property to all the Materials
            foreach (var material in allMaterials)
            {
                materialPreset.ApplyTo(material, propertyToApply);
            }
        }
    }
}

앞에서와 마찬가지로 여기에서도 레퍼런스 오브젝트를 이용합니다. 이번에는 선택한 머티리얼(var referenceMaterial = command.context as Material)을 사용하여 신속하게 프리셋을 만듭니다(var materialPreset = new Preset(referenceMaterial)). 그런 다음 이 머티리얼이 있는 폴더를 찾아 같은 폴더에 있는 모든 머티리얼에 컬러 프로퍼티를 적용합니다.

이제 에디터에서 결과물을 확인해 봅시다.

마지막으로 좀 더 복잡한 예시를 살펴보겠습니다. 이 스니핏(코드 조각)에서는 광원 기본 프리셋의 여부에 따라 선택된 광원을 프리셋으로 만들거나 기본 프리셋을 업데이트하여 선택된 광원에 맞도록 변경합니다. 자세한 내용은 아래의 코드 주석을 참고해 주세요.

public static void UpdateOrAddLightDefaults(MenuCommand command)
{
    // Get our current selected light
    var referenceLight = command.context as Light;
    // Get the list of default Presets that apply to that light
    var defaults = Preset.GetDefaultPresetsForObject(referenceLight);


    if (defaults.Length == 0)
    {
        // We don't have a default yet, let's create one!
        var defaultLight = new Preset(referenceLight);
        // We're kind people, so let's nicely ask the user where to save this default
        var path = EditorUtility.SaveFilePanelInProject("Create Default Light preset",
                "Light", "preset", "Select a folder to save the new default");
        // If the selected path already contains a Preset, its instanceID would be changed by a plain replace
        // We use this trick to replace the values only so an Object referencing the existing asset will not break
        var existingAsset = AssetDatabase.LoadAssetAtPath<Preset>(path);
        if (existingAsset != null)
        {
            EditorUtility.CopySerialized(defaultLight, existingAsset);
            defaultLight = existingAsset;
        }
        else
        {
            AssetDatabase.CreateAsset(defaultLight, path);
        }
        // Load the existing default list
        // We don't want to lose any configuration that may point specific GameObject because of the filters
        var existingDefault = Preset.GetDefaultPresetsForType(defaultLight.GetPresetType()).ToList();
        // Insert the new one at the beginning of the list with no filter
        // so it applies to any Light that doesn't have a default
        existingDefault.Insert(0, new DefaultPreset("", defaultLight));
        // Set the new list as default for Lights.
        Preset.SetDefaultPresetsForType(defaultLight.GetPresetType(), existingDefault.ToArray());
    }
    else
    {
        // We want to update the values only to the last default
        // because maybe other Presets apply to other objects first
        // and we don't want to change them
        var lastPreset = defaults.Last();
        lastPreset.UpdateProperties(referenceLight);
    }
}

다음은 이 코드가 Unity에서 작동하는 모습을 보여주는 애니메이션입니다. 첫 번째 패스 스루에서는 새로운 프리셋이 생성되지만, 두 번째 패스에서는 기존 프리셋을 업데이트합니다.

다음 단계: 자유롭게 프리셋 활용

부분 프리셋, 필터링 기능 개선, 폴더에 프리셋 적용 등 여러 가지 기능을 향후 추가 및 개선할 예정입니다. 프리셋은 개발자가 원하는 대로 Unity를 활용하도록 해줍니다. 프리셋을 사용해보시고 많은 의견을 공유해 주시기 바랍니다. 또한 이 기능을 활용하여 생산성을 높일 수 있는 개선 방안이 있다면 댓글을 남겨주세요.

2019년 10월 11일 테크놀로지 | 10 분 소요

Is this article helpful for you?

Thank you for your feedback!