Unity Version: 2020.3.13f1

TextMeshPro Version: 3.0.6

 

AssetBundle에 TMP_FontAsset을 포함하여 사용할 때 AssetBundle이 간헐적으로 수정되어 발생했던 문제.

 

최초 설치 시 필요한 리소스를 제외하고 나머지를 AssetBundle로 관리하여 업데이트를 진행할 수 있게 개발하던 중 AssetBundle이 업데이트가 필요한지, 동일한 파일인지 체크하기 위해 파일의 Hash 값을 비교하도록 구현하였는데 Font와 TMP_FontAsset를 포함한 AssetBundle이 간헐적으로 Hash 값이 변경되는 현상이 발생.

 

사용되는 Font의 개수가 여러 개여서 몇 MB 단위인 AssetBundle 이 간헐적으로 변경되어 업데이트되는 상황이 발생하면 안 되기 때문에 문제를 파악하기 위해 Font와 TMP_FontAsset 중 원인이 어느쪽인지 확인 진행.

 

확인 방법은 Font 와 TMP_FontAsset 을 별개의 AssetBundle로 만들고 테스트를 진행하여 Hash 값이 변하는 원인이 어느쪽인지 확인.

 

결과적으로 TMP_FontAsset이 원인이었으며 변경되는 내용은 TMP_FontAsset의 'Atlas Population Mode' 가 'Dynamic' 이어서 해당 TMP_FontAsset이 쓰이는 TMP 컴포넌트에서 텍스트가 사용될 때마다 TMP Atlas에 텍스트가 추가되어 변경되는 것이었다. 

 

해결 방법은 개발 중 TMP_FontAsset의 Atlas가 동적으로 변경되어도 AssetBundle을 만들 때는 항상 Atlas가 동일한 상태가 되도록 만들면 해결될 문제였다.

 

 

아래의 스크립트를 이용해 Editor 상에서 Play & Edit 상태에 진입 시, AssetBundle & 프로그램 빌드 시 TMP_FontAsset 중 Dynamic 으로 설정되어 있는 것들의 Atlas를 초기화하도록 처리하여 항상 동일한 상태가 되도록 만들었다.

 

적용 시 주의점

1. 초기화 시 텍스트 Atlas 정보가 사라져서 씬의 TMP 들이 일시적으로 깨져보일 수 있음 ( TMP 오브젝트를 재활성화 하거나 씬에 다시 진입 시 해결 )

2. AssetBundle, 프로그램 빌드 등의 Editor 처리가 되면 Canvas의 Rebuild가 일어나면서 'OnRebuildRequested' 호출로 TMP 갱신이 됨. 이로 인한 처리를 갱신하기 위해 'PostProcess' , 'CompilationPipeline'  Callback을 사용. 

 

스크립트

#if UNITY_EDITOR
    public class PostProcess
    {
        [PostProcessBuild(1)]
        static void PostProcessBuild(BuildTarget buildTarget, string path)
        {
            EditorApplication.delayCall += () =>
            {
                EditorApplication.delayCall += () =>
                {
                    EditorPlayModeState.ClearTMPFontAssetTable();
                };
            };
        }

        [PostProcessScene]
        static void PostProcessScene()
        {
            if (!EditorApplication.isPlaying)
            {
                EditorApplication.delayCall += () =>
                {
                    EditorPlayModeState.ClearTMPFontAssetTable();
                };
            }
        }
    }

    [InitializeOnLoad]
    [DefaultExecutionOrder(-100)]
    public static class EditorPlayModeState
    {
        static EditorPlayModeState()
        {
            EditorApplication.playModeStateChanged += ClearTMPFontAssetTable;

            CompilationPipeline.compilationStarted += (object) =>
            {
                ClearTMPFontAssetTable();
            };

            CompilationPipeline.compilationFinished += (object) =>
            {
                EditorApplication.delayCall += () =>
                {
                    ClearTMPFontAssetTable();
                };
            };
        }

        private static void ClearTMPFontAssetTable(PlayModeStateChange playModeStateChange)
        {
            switch (playModeStateChange)
            {
                case PlayModeStateChange.EnteredPlayMode:
                case PlayModeStateChange.EnteredEditMode:

                    string[] tmpFontAssetGUIDs = AssetDatabase.FindAssets("t:" + typeof(TMP_FontAsset));

                    foreach (string guid in tmpFontAssetGUIDs)
                    {
                        TMP_FontAsset fontAsset = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid), typeof(TMP_FontAsset)) as TMP_FontAsset;
                        if (fontAsset.atlasPopulationMode == AtlasPopulationMode.Dynamic)
                        {
                            fontAsset.ClearFontAssetData();
                            EditorUtility.SetDirty(fontAsset);
                        }
                    }
                    break;
            }

            if (playModeStateChange == PlayModeStateChange.EnteredEditMode)
            {
                EditorApplication.playModeStateChanged -= ClearTMPFontAssetTable;
            }

            AssetDatabase.Refresh();
        }

        public static void ClearTMPFontAssetTable()
        {
            ClearTMPFontAssetTable(PlayModeStateChange.EnteredEditMode);
        }
    }
#endif
Posted by Heon_Dev
,