Unity 검색

시간을 절약해주는 고급 에디터 스크립팅 팁 - 1부

2022년 10월 18일 테크놀로지 | 15 분 소요
A guide to advanced Editor scripting that will save you time | Hero
A guide to advanced Editor scripting that will save you time | Hero

대부분의 프로젝트에서 개발자는 반복적이고 오류가 발생하기 쉬운 작업을 자주 수행해야 하는데 특히 새로운 아트 에셋을 통합하는 일은 꽤 까다롭습니다. 예를 들어 캐릭터를 설정하려면 여러 에셋 레퍼런스를 드래그 앤 드롭하고, 체크박스를 선택하고, 버튼을 클릭해야 하는 경우가 많습니다. 예를 들어 모델의 릭(rig)을 Humanoid로 설정하거나, SDF 텍스처의 sRGB 기능을 비활성화하거나, 노멀 맵을 노멀 맵으로 설정하고 UI 텍스처를 스프라이트로 설정하는 등의 작업을 처리합니다. 다시 말해, 시간은 많이 걸리고 중요한 단계를 누락하기도 쉽습니다.

2부로 구성된 이 글에서는 새로운 프로젝트를 보다 원활하게 실행할 수 있도록 워크플로를 개선하는 데 도움이 되는 팁을 소개합니다. 더 자세하게 설명하기 위해 RTS 장르의 간단한 프로토타입을 만들었습니다. 이 프로토타입에서 플레이어 팀의 유닛은 적의 건물과 상대 유닛을 자동으로 공격합니다. 각 스크립팅 팁을 활용하여 텍스처와 모델 등 전체 프로세스를 하나씩 개선해 보겠습니다.

프로토타입의 모습은 아래 영상에서 확인할 수 있습니다.

초기 빌드

팁 1: 에셋 정리 및 자동화

에셋을 임포트할 때 개발자는 수많은 세세한 설정을 해야 합니다. 에셋이 사용되는 방식을 Unity에서 설정하지 않았으므로 에셋에 가장 적합한 설정이 무엇인지 역시 알 수 없기 때문입니다. 에셋 임포트 작업을 일부 자동화하려면, 이 문제를 먼저 해결해야 합니다.

에셋이 어떤 역할을 담당하고 다른 에셋과 어떤 관계를 가지는지 파악하는 가장 간단한 방법은 다음과 같이 구체적인 명명 규칙과 폴더 구조를 따르는 것입니다.

  • 명명 규칙: 에셋 이름 자체에 단어를 추가할 수 있습니다. 예를 들어 Shield_BC.png는 기본 컬러이고 Shield_N.png는 노멀 맵입니다.
  • 폴더 구조: 파일 형식(.fbx)이 같더라도 Knight/Animations/Walk.fbx는 애니메이션 파일이고 Knight/Models/Knight.fbx는 모델 파일입니다.

이러한 방식의 문제점은 한 방향으로만 유용하다는 점입니다. 에셋의 경로가 주어진 상황에서는 에셋의 역할이 무엇인지 파악할 수 있으나, 에셋의 역할만 주어진 상태에서는 에셋의 경로를 추론할 수 없습니다. 캐릭터에 사용되는 머티리얼 등의 에셋이 어떤 경로에 있는지 알 수 있다면 에셋의 일부 설정을 자동화할 때 매우 유용합니다. 경로를 쉽게 추론할 수 있도록 엄격한 명명 규칙을 사용해 이러한 문제를 해결할 수 있지만, 여전히 오류가 발생할 가능성이 존재합니다. 명명 규칙을 기억하더라도 오타가 발생할 수 있습니다.

이 문제는 레이블을 사용하여 해결할 수 있습니다. 에셋 경로를 파싱하고 경로에 따라 레이블을 할당하는 에디터 스크립트를 사용하면 됩니다. 레이블이 자동으로 지정되므로 에셋에 지정될 정확한 레이블을 확인할 수 있습니다. AssetDatabase.FindAssets를 사용하여 레이블별로 에셋을 찾을 수도 있습니다.

이 시퀀스를 자동화하려면 AssetPostprocessor라는 매우 편리한 클래스를 사용하면 됩니다. Unity에서 에셋을 임포트할 때 AssetPostprocessor는 다양한 메시지를 수신합니다. 수신하는 메시지 중 하나는 Unity가 에셋 임포트를 완료할 때마다 호출되는 OnPostprocessAllAssets 메서드입니다. 이 메서드를 통해 임포트한 에셋의 전체 경로를 알 수 있으므로 이를 활용하여 해당 경로를 처리할 수 있습니다. 경로를 처리하는 메서드는 다음과 같이 간단하게 작성할 수 있습니다.

private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, 
    string[] movedAssets, string[] movedFromAssetPaths)
{
    foreach (var asset in importedAssets)
        ProcessAssetLabels(asset);

    foreach (var asset in movedAssets)
        ProcessAssetLabels(asset);
}

프로토타입에서는 새로운 에셋과 위치를 옮긴 에셋을 모두 포함하도록 임포트한 에셋 목록에 집중하겠습니다. 경로가 바뀌면 레이블도 업데이트해야 합니다.

레이블을 만들려면 경로를 파싱하고 관련된 폴더와 이름의 접두사 및 접미사, 확장자를 찾아야 합니다. 레이블을 생성했으면 하나의 문자열로 합치고 레이블을 에셋에 설정합니다.

AssetDatabase.LoadAssetAtPath를 사용하여 에셋을 로드하고, AssetDatabase.SetLabels를 사용하여 레이블을 할당하면 됩니다.

var obj = AssetDatabase.LoadAssetAtPath<Object>(assetPath);
if (obj)
{
    if (labels.Count == 0)
    {
        AssetDatabase.ClearLabels(obj);
        return;
    }

    var oldLabels = AssetDatabase.GetLabels(obj);
    var labelsArray = new string[] { string.Join('-', labels) };
    if (HaveLabelsChanged(oldLabels, labelsArray))
    {
        AssetDatabase.SetLabels(obj, labelsArray);
    }
}

참고로 실제로 레이블이 변경된 경우에만 설정해야 한다는 점에 유념하세요. 레이블을 설정하면 에셋을 다시 임포트하게 되므로, 꼭 필요한 경우가 아니라면 다시 임포트되지 않도록 하는 편이 좋습니다.

레이블은 에셋을 처음 임포트할 때 설정되고 .meta 파일에 저장됩니다. 다시 말해 버전 관리에도 저장되며, 에셋의 이름이나 위치를 변경한 경우에만 에셋의 재임포트가 이루어집니다. 이 점을 기억한다면 재임포트는 문제가 되지 않습니다.

위의 단계를 완료하면 아래 그림과 같이 모든 에셋에 자동으로 레이블이 지정됩니다.

Screen capture of automatic labeling post-processing in the Unity Editor.
레이블이 지정된 머티리얼 예시

팁 2: 텍스처 설정 및 크기 결정

프로젝트에 텍스처를 임포트할 때 보통 각 텍스처의 설정을 조정해야 합니다. 일반 텍스처인지, 노멀 맵인지, 스프라이트인지, 선형인지 아니면 sRGB인지 설정해야 합니다. 에셋 임포터의 설정을 변경하고 싶다면 이번에도 AssetPostprocessor를 사용하면 됩니다.

이 경우에는 텍스처를 임포트하기 직전에 호출되는 OnPreprocessTexture 메시지를 사용하는 것이 좋습니다. 이 메시지를 사용하면 임포터의 설정을 변경할 수 있습니다.

모든 텍스처에 적절한 설정을 선택하려면 작업 중인 텍스처 유형을 확인해야 합니다. 첫 번째 단계에서 설정한 레이블이 중요한 이유는 바로 이것 때문입니다.

레이블 정보가 있다면 간단하게 TexturePreprocessor를 작성할 수 있습니다.

private void OnPreprocessTexture()
{
    var labels = AssetDatabase.GetLabels(assetImporter);

    // 我们只想影响资产
    if (labels.Length == 0 || !labels[0].Contains(AssetClassifier.ART_LABEL))
        return;

    // 获取导入器
    var textureImporter = assetImporter as TextureImporter;
    if (!textureImporter)
        return;

art 레이블(자체 텍스처)이 지정된 텍스처에만 이 메서드를 실행합니다. 그런 다음 텍스처 크기부터 시작해서 모든 것을 설정하도록 임포터에 대한 레퍼런스를 가져옵니다.

AssetPostprocessor에는 타겟 플랫폼을 정할 수 있는 컨텍스트 프로퍼티가 있습니다. 따라서 모바일용으로 텍스처의 해상도를 낮추는 등 플랫폼별로 설정을 변경할 수 있습니다.

// 设置纹理大小,比如android使用的纹理要小一些
if (context.selectedBuildTarget == BuildTarget.iOS || context.selectedBuildTarget == BuildTarget.Android)
    textureImporter.maxTextureSize = 256;
else
    textureImporter.maxTextureSize = 512;

이제 레이블을 확인하여 텍스처가 UI 텍스처인지 확인하고 텍스처 유형에 맞게 설정하면 됩니다.

// UI纹理是一个特例
if (labels[0].Contains(AssetClassifier.UI_LABEL))
{
    textureImporter.textureType = TextureImporterType.Sprite;
    textureImporter.sRGBTexture = true;
    return;
}

나머지 유형의 텍스처에 대해서는 값을 기본값으로 설정합니다. Albedo는 sRGB가 활성화된 유일한 텍스처라는 점을 알아 두는 것이 좋습니다.

// 我们所有的纹理都是标准纹理,但如果我们使用法线,可以在这里设置它
    textureImporter.textureType = TextureImporterType.Default;
    textureImporter.textureShape = TextureImporterShape.Texture2D;

    // 将它们设置为可读以便在编辑器中处理
    textureImporter.isReadable = true;

    // 只有 albedo 文理是 sRGB
    var texName = Path.GetFileNameWithoutExtension(assetPath);    
    textureImporter.sRGBTexture = labels[0].Contains(AssetClassifier.BASE_COLOR_LABEL)
}

위 스크립트를 사용하면 새로운 텍스처를 에디터로 드래그 앤 드롭했을 때 자동으로 텍스처에 알맞은 설정이 적용됩니다.

설정이 적용된 텍스처

팁 3: 텍스처 채널 패킹 활용

'채널 패킹(channel packing)'이란 서로 다른 채널을 사용해서 다양한 텍스처를 하나로 합치는 것입니다. 보편적으로 사용되며 다양한 이점을 가진 방식입니다. 예를 들어 Red 채널의 값은 메탈릭이고 Green 채널의 값은 채널의 평활도입니다.

그러나 모든 텍스처를 하나로 합치려면 아트 팀이 추가로 작업해야 합니다. 셰이더 변경 등 어떤 이유로 패킹을 변경해야 하는 경우, 아트 팀은 해당 셰이더에 사용된 모든 텍스처를 다시 작업해야 합니다.

이 부분을 개선하면 시간을 많이 절약할 수 있습니다. 채널 패킹에서 제가 선호하는 방법은 '원시(raw)' 텍스처를 설정하고 머티리얼에 사용할 채널 패킹된 텍스처를 생성하는 특수한 에셋 유형을 만드는 것입니다.

먼저 특정 확장자가 지정된 더미 파일을 만들고, 해당 에셋을 임포트할 때 모든 까다로운 작업을 수행할 스크립트된 임포터를 사용합니다. 작동 원리는 다음과 같습니다.

  • 임포터에서는 결합해야 하는 텍스처와 같은 파라미터를 사용할 수 있습니다.
  • 임포터에서 텍스처를 종속 관계로 설정하면 소스 텍스처 중 하나가 변경될 때마다 더미 에셋을 다시 임포트합니다. 그러면 생성된 텍스처를 변경된 소스 텍스처에 따라 다시 만들 수 있습니다.
  • 임포터에 버전을 사용할 수 있습니다. 텍스처의 패킹 방식을 변경해야 하는 경우, 임포터를 수정하고 버전을 변경하면 됩니다. 임포터 버전을 변경하면 프로젝트에서 패킹된 모든 텍스처가 강제로 다시 생성되고 모든 텍스처가 즉시 새로운 방식으로 패킹됩니다.
  • 임포터로 텍스처를 생성하면 생성된 에셋이 Library 폴더에만 있으므로 버전 관리를 더 간결하게 유지할 수 있다는 부수적인 이점이 있습니다.

이 방식을 구현하려면 생성된 텍스처를 보관하고 임포터의 결과로 사용될 스크립터블 오브젝트를 만듭니다. 예시에서는 이 클래스를 TexturePack이라 지정했습니다.

스크립터블 오브젝트를 만들었다면 먼저 임포터 클래스를 선언하고 ScriptedImporterAttribute를 추가하여 임포터와 연결된 버전 및 확장자를 정의하면 됩니다.

[ScriptedImporter(0, PathHelpers.TEXTURE_PACK_EXTENSION)]
public class TexturePackImporter : ScriptedImporter
{
}

임포터에서 사용하려는 필드를 선언합니다. 해당 필드는 MonoBehaviour나 스크립터블 오브젝트처럼 인스펙터에 표시됩니다.

public LazyLoadReference<Texture2D> albedo;
public LazyLoadReference<Texture2D> playerMap;
public LazyLoadReference<Texture2D> metallic;
public LazyLoadReference<Texture2D> smoothness;
The inspector for the importer.
인스펙터의 임포터 패널

파라미터를 설정했다면, 파라미터로 설정한 텍스처에서 새로운 텍스처를 생성합니다. 참고로, 이 작업을 위해 앞선 섹션에서 다룬 TexturePreprocessor에서 isReadableTrue로 설정해두었습니다.

이 프로토타입에는 두 개의 텍스처가 있습니다. 하나는 RGB 채널이 Albedo이고 Alpha에 플레이어 컬러 적용을 위한 마스크가 있는 Albedo 텍스처이고, 다른 하나는 Red 채널이 메탈릭이고 Green 채널이 평활도인 Mask 텍스처입니다.

이 글에서 다루는 범위를 조금 벗어나는 내용이지만, Albedo와 플레이어 마스크를 결합하는 방법을 예시로 살펴보겠습니다. 먼저 텍스처가 설정되어 있는지 확인하고, 텍스처가 설정되어 있다면 색상 데이터를 가져옵니다. 그런 다음 AssetImportContext.DependsOnArtifact를 사용하여 텍스처를 종속 관계로 설정합니다. 말씀드렸듯이 텍스처 중 하나라도 변경되면 오브젝트를 강제로 다시 계산합니다.

public Texture2D CombineAlbedoPlayer(AssetImportContext ctx)
{
    Color32[] albedoPixels = new Color32[0];
    bool albedoPresent = albedo.isSet;
    if (albedoPresent)
    {
        ctx.DependsOnArtifact(AssetDatabase.GetAssetPath(albedo.asset));
        albedoPixels = albedo.asset.GetPixels32();
    }

    Color32[] playerPixels = new Color32[0];
    bool playerPresent = playerMap.isSet;
    if (playerPresent)
    {
        ctx.DependsOnArtifact(AssetDatabase.GetAssetPath(playerMap.asset));
        playerPixels = playerMap.asset.GetPixels32();
    }

    if (!albedoPresent && !playerPresent)
        return null;

또한 새 텍스처를 만들어야 합니다. 새 텍스처를 만들려면 프리셋 제한을 따르도록 앞선 섹션에서 만든 TexturePreprocessor에서 텍스처 크기를 가져옵니다.

var size = TexturePreProcessor.GetMaxSizeForTarget(ctx.selectedBuildTarget);
var newTexture = new Texture2D(size, size, TextureFormat.RGBA32, true, false);
var pixels = new Color32[size * size];

이제 새 텍스처에 모든 데이터를 입력합니다. Jobs와 Burst를 사용하면 이 과정을 대규모로 최적화할 수 있습니다. 다만, 최적화 과정은 복잡하므로 여기에서는 다루지 않겠습니다. 여기에서는 다음과 같은 간단한 루프를 사용하겠습니다.

Color32 tmp = new Color32();
Color32 white = new Color32(255, 255, 255, 255);
for (int i = 0; i < pixels.Length; ++i)
{
    var color = albedoPresent ? albedoPixels[i] : white;
    var alpha = playerPresent ? playerPixels[i] : white;
    tmp.r = color.r;
    tmp.g = color.g;
    tmp.b = color.b;
    tmp.a = alpha.r;
    pixels[i] = tmp;
}

텍스처에서 이 데이터를 설정합니다.

newTexture.SetPixels32(pixels);
 // 将更改应用于mipmap
newTexture.Apply(true, false);
// 压缩纹理
newTexture.Compress(true);
// 设为不可读
newTexture.Apply(true, true);
newTexture.name = "AlbedoPlayer";
// 返回结果
return newTexture;

이제 매우 비슷한 방식으로 다른 텍스처 생성을 위한 메서드를 만들면 됩니다. 메서드를 생성했다면 임포터의 메인 바디를 만듭니다. 여기에서는 결과를 보관하고 텍스처를 생성하고 AssetImportContext를 통해 임포터의 결과를 설정하는 스크립터블 오브젝트만 생성합니다.

임포터를 작성할 때는 생성된 모든 에셋을 AssetImportContext.AddObjectToAsset을 사용하여 등록해야 에셋이 프로젝트 창에 표시됩니다. AssetImportContext.SetMainObject를 사용하여 메인 에셋을 선택합니다. 작성한 코드는 다음과 같습니다.

public override void OnImportAsset(AssetImportContext ctx)
{
    var result = ScriptableObject.CreateInstance<TexturePack>();

    result.albedoPlayer = CombineAlbedoPlayer(ctx);
    if (result.albedoPlayer)
        ctx.AddObjectToAsset("albedoPlayer", result.albedoPlayer);

    result.mask = CombineMask(ctx);
    if (result.mask)
        ctx.AddObjectToAsset("mask", result.mask);

    ctx.AddObjectToAsset("result", result);
    ctx.SetMainObject(result);
}

마지막으로 더미 에셋만 만들면 됩니다. 더미 에셋은 커스텀 에셋이기 때문에 CreateAssetMenu 속성을 사용할 수 없습니다. 반드시 직접 만들어야 합니다.

MenuItem 속성을 사용하여 에셋 생성 메뉴인 Assets/Create의 전체 경로를 지정합니다. 에셋을 생성하려면 ProjectWindowUtil.CreateAssetWithContent를 사용합니다. 사용자가 지정한 콘텐츠가 포함된 파일을 생성할 수 있으며, 사용자가 파일 이름을 입력할 수 있습니다. 다음과 같이 진행합니다.

[MenuItem("Assets/Create/Texture Pack", priority = 0)]
private static void CreateAsset()
{

마지막으로 채널 패킹된 텍스처를 만듭니다.

채널 패킹된 텍스처를 생성하는 과정

팁 4: 머티리얼에 커스텀 셰이더 사용

대부분의 프로젝트에는 커스텀 셰이더가 사용됩니다. 쓰러진 적이 사라지게 하는 디졸브 효과 등 효과를 추가하기 위해 커스텀 셰이더를 사용하기도 하며, 툰 셰이더 등 커스텀 아트 스타일을 구현하기 위해 셰이더를 사용하기도 합니다. Unity에서는 기본 셰이더로 새 머티리얼이 생성되므로, 커스텀 셰이더를 사용하려면 이를 변경해야 합니다.

이 프로토타입에는 유닛에 사용한 셰이더에 두 가지 기능이 추가되어 있습니다. 하나는 디졸브 효과이고, 다른 하나는 플레이어 컬러(동영상 프로토타입의 빨간색과 파란색)입니다. 프로젝트에 커스텀 셰이더를 구현하려는 경우, 모든 건물과 유닛에 적절한 셰이더를 사용하고 있는지 확인해야 합니다.

에셋이 특정 요구 사항(여기에서는 적절한 셰이더 사용)을 충족하는지 확인하려면 또 다른 유용한 클래스인 AssetModificationProcessor를 사용하면 됩니다. 특히 AssetModificationProcessor.OnWillSaveAssets을 사용하면 Unity가 디스크에 에셋을 쓰기 직전에 알림을 받을 수 있습니다. 이렇게 하면 에셋이 올바른지 확인하고 에셋을 저장하기 전에 수정할 수 있습니다.

또한 Unity가 에셋을 저장하지 않도록 할 수도 있으며, 발견한 문제점을 자동으로 수정할 수 없는 경우에 효과적인 방식입니다. 이를 구현하려면 OnWillSaveAssets 메서드를 만들어야 합니다.

private static string[] OnWillSaveAssets(string[] paths)
{
    foreach (string path in paths)
    {
        ProcessMaterial(path);
    }

    // 如果不想保存,移除列表的资产路径
    return paths;
}

에셋을 처리하려면, 에셋이 머티리얼인지, 올바른 레이블이 지정되어 있는지 확인해야 합니다. 아래와 같은 코드를 사용한다면 올바른 셰이더를 사용할 수 있습니다.

private static void ProcessMaterial(string path)
{
    var mat = AssetDatabase.LoadAssetAtPath<Material>(path);
    // 检查是否为材质
    if (!mat)
        return;

    // 检查是building还是unit
    var labels = AssetDatabase.GetLabels(mat);
    if (labels.Length == 0 || !(labels[0].Contains(AssetClassifier.UNIT_LABEL) 
        || labels[0].Contains(AssetClassifier.BUILDING_LABEL)))
        return;

    if (mat.shader.name != UNIT_SHADER_NAME)
    {
        mat.shader = Shader.Find(UNIT_SHADER_NAME);
    }
}

이 방법이 편리한 이유는 에셋을 생성할 때 이 코드도 호출되기 때문입니다. 즉, 새로운 머티리얼은 올바른 셰이더를 사용합니다.

실행 중인 스크립트

Unity 2022의 새로운 기능에는 머티리얼 배리언트도 있습니다. 머티리얼 배리언트는 특히 유닛의 머티리얼을 만들 때 유용합니다. 예를 들어 기본 머티리얼을 만든 다음 유닛에 따라 머티리얼의 관련 필드(예: 텍스처)는 오버라이드하고 나머지 프로퍼티는 상속할 수 있습니다. 이렇게 하면 안정적인 기본 머티리얼을 두고 필요에 따라 업데이트할 수 있습니다.

팁 5: 애니메이션 관리

애니메이션 임포트는 텍스처 임포트와 비슷합니다. 다양한 설정을 지정해야 하며, 그중 일부는 자동화가 가능합니다.

Unity는 기본적으로 모든 FBX(.fbx) 파일의 머티리얼을 임포트합니다. 애니메이션의 경우, 사용하려는 머티리얼이 프로젝트에 있거나 메시의 FBX에 있습니다. 프로젝트에서 머티리얼을 검색할 때마다 애니메이션 FBX의 추가 머티리얼이 표시되며, 이 경우 상당히 방해될 수 있으므로 비활성화하는 것이 좋습니다.

릭을 설정(HumanoidGeneric 중에 선택하고, 섬세하게 설정한 아바타를 사용하는 경우 할당하는 것까지)하려면 텍스처에 적용한 것과 같은 방식을 사용합니다. 하지만 애니메이션의 경우 AssetPostprocessor.OnPreprocessModel 메시지를 사용합니다. 이 메시지는 모든 FBX 파일에 대해 호출되므로, 애니메이션 FBX 파일과 모델 FBX 파일을 구분해야 합니다.

앞서 설정한 레이블 덕분에 이 과정은 비교적 간단합니다. 메서드는 텍스처에 사용한 것과 비슷하게 시작합니다.

private void OnPreprocessModel()
{
    // 我们只想影响动画
    var labels = AssetDatabase.GetLabels(assetImporter);
    if (labels.Length == 0 || !labels[0].Contains(AssetClassifier.ANIMATION_LABEL))
        return;

    // 获取导入器
    var modelImporter = assetImporter as ModelImporter;
    if (!modelImporter)
        return;

    // 我们需要动画
    modelImporter.importAnimation = true;
    // 我们不想要任何材质
    modelImporter.materialImportMode = ModelImporterMaterialImportMode.None;

이제 메시 FBX의 릭을 사용해야 하므로 해당 에셋을 찾아야 합니다. 에셋을 찾을 때 한 번 더 레이블을 사용합니다. 이 프로토타입의 경우 애니메이션에는 'animation'으로 끝나는 레이블이 지정되어 있고, 메시에는 'model'로 끝나는 레이블이 지정되어 있습니다. 코드를 조금만 수정하면 모델에 해당하는 레이블을 가져올 수 있습니다. 레이블을 가져왔다면 AssetDatabase.FindAssets에 'l: + 레이블 이름'을 사용하여 에셋을 찾습니다.

다른 에셋에 액세스할 때 한 가지 고려해야 할 점이 있습니다. 아직 아바타가 임포트되지 않았는데 임포트 과정에서 이 메서드가 호출되었을 수 있습니다. 이 경우 LoadAssetAtPath가 null을 반환하고 아바타를 설정할 수 없습니다. 이 문제를 해결하려면 아바타 경로에 종속성을 설정하면 됩니다. 아바타가 임포트되면 애니메이션이 임포트되고, 이후 아바타를 설정할 수 있습니다.

위에서 설명한 모든 내용을 코드로 작성하면 다음과 같습니다.

// 尝试获取avatar
var avatarLabel = labels[0].Replace(AssetClassifier.ANIMATION_LABEL, AssetClassifier.MODEL_LABEL);
var possibleModels = AssetDatabase.FindAssets("l:" + avatarLabel);
Avatar avatar = null;
if (possibleModels.Length > 0)
{
    var avatarPath = AssetDatabase.GUIDToAssetPath(possibleModels[0]);
    avatar = AssetDatabase.LoadAssetAtPath<Avatar>(avatarPath);

    if (!avatar)
        context.DependsOnArtifact(avatarPath);
}

modelImporter.animationType = ModelImporterAnimationType.Generic;
modelImporter.sourceAvatar = avatar;
modelImporter.avatarSetup = ModelImporterAvatarSetup.CopyFromOther;

이제 애니메이션을 올바른 폴더로 드래그할 수 있으며, 메시가 준비되면 각 아바타가 자동으로 설정됩니다. 하지만 애니메이션을 임포트할 때 사용할 수 있는 아바타가 없으면, 이후에는 프로젝트에서 자동으로 아바타를 선택할 수 없습니다. 대신 아바타를 만든 후 애니메이션을 수동으로 다시 임포트해야 합니다. 애니메이션이 있는 폴더를 오른쪽 클릭하고 Reimport를 선택하면 됩니다.

설명한 모든 과정은 아래 샘플 동영상에서 확인할 수 있습니다.

애니메이션 샘플 동영상

팁 6: FBX 임포터를 사용한 메시 설정

앞의 섹션에서와 같은 방식으로 사용할 모델을 설정할 수 있습니다. 이 경우 AssetPostrocessor.OnPreprocessModel을 사용하여 모델의 임포터 설정을 지정할 수 있습니다.

프로토타입에서는 직접 생성한 머티리얼을 사용하기 위해 임포터가 머티리얼을 생성하지 않도록 설정했고 레이블을 확인하여 모델이 유닛인지 건물인지 확인했습니다. 유닛에서 아바타를 생성하도록 설정되어 있지만, 건물은 애니메이션되지 않기 때문에 아바타를 생성하지 않도록 설정했습니다.

프로젝트에서 모델을 임포트할 때 머티리얼과 애니메이터, 추가하려는 다른 요소를 설정하는 것이 좋습니다. 그러면 임포터가 생성한 프리팹을 바로 사용할 수 있습니다.

이를 구현하려면 AssetPostprocessor.OnPostprocessModel 메서드를 사용하면 됩니다. 이 메서드는 모델이 완전히 임포트된 후에 호출됩니다. 또한 파라미터로 생성된 프리팹을 수신하므로 프리팹을 원하는 대로 수정할 수 있습니다.

프로토타입에서는 애니메이션의 아바타를 찾은 것과 같은 방법으로 일치하는 레이블을 찾아서 머티리얼과 애니메이션 컨트롤러를 찾았습니다. 프리팹의 렌더러와 애니메이터를 사용해서 일반적인 게임플레이처럼 머티리얼과 컨트롤러를 설정했습니다.

그런 다음 프로젝트에 모델을 넣으면 어느 씬에서나 모델을 사용할 수 있습니다. 아직 게임플레이 관련 컴포넌트는 설정하지 않았습니다. 이 부분은 2부에서 다룰 예정입니다.

모델을 프로젝트로 드래그 앤 드롭하면 어느 씬에서나 모델을 사용할 수 있습니다.

2부 소개

지금까지 소개해 드린 고급 스크립팅 팁은 게임에 바로 사용할 수 있습니다. 2부로 구성된 Tech from the Trenches의 다음 편에서는 게임 데이터 밸런싱 등과 관련된 팁을 다룰 예정이니 많이 기대해 주세요.

이번 포스팅의 내용에 관해 토론하고 싶거나, 포스팅을 읽은 후 떠오른 아이디어를 공유하고 싶다면, 스크립팅 포럼에 방문해 주세요. Twitter에서 @CaballolD로 직접 연락하셔도 됩니다.

2022년 10월 18일 테크놀로지 | 15 분 소요
관련 게시물