[수정] Unity 6000.2.9f1에서 Tilde(~)가 Android 빌드에서는 제외되지만, iOS 빌드에서는 제외되지 않는 버그를 확인해서 그냥 Import 되지 않는 다른곳(프로젝트 내 Assets 외부의 _Backup 폴더)으로 이동(Move) 시키도록 소스를 변경함.(2025-10-30)
----
[수정] Unity 6.2 에서 Dot(.)이 숨겨지긴 하지만 빌드에서 제외되지 않는 버그가 확인돼서 Dot(.)과 Tilde(~) 둘다 붙이도록 수정함(Tilde(~)는 빌드 제외 되는 것 확인함) [2025-08-28]
※ 아래 링크에서 설명을 보면 StreamingAssets 폴더 하위에서 dot(.) 정책이 변경 된 것 같다.
[Hidden assets] https://docs.unity3d.com/Manual/SpecialFolders.html
- Files and folders which start with ., except for those under StreamingAssets where this pattern is not ignored.
----
[수정] Rename 방식으로 변경 [2024-07-04]
----
 
[빌드 시, Builtin AssetBundles에서 해당 Platform 에셋번들만 빌드 되도록 하기 위한 PreProcessor 추가]
기본적으로 에셋번들은 플랫폼 별로 모두 가지고 있는데 해당 플랫폼에 맞는 에셋번들만 빌드 되도록 하기 위해서
빌드 전(Preprocessor) 해당 안되는 AssetBundle을 Hidden 폴더(예: .Android / .iOS)로 Rename 했다가, 빌드 후(Postprocessor) 되돌려 놓음.
빌드 전(Preprocessor) 해당 안되는 AssetBundle을 Hidden 폴더(예: .Android~ / .iOS~)로 Rename 했다가, 빌드 후(Postprocessor) 되돌려 놓음.
빌드 전(Preprocessor) 해당 안되는 AssetBundle을 _Backup 폴더로 Move 했다가, 빌드 후(Postprocessor) 되돌려 놓음.
 
using System;
using UnityEngine;
using UnityEditor;
using UnityEditor.Build;
using System.IO;
using System.Collections.Generic;
#if UNITY_2018_1_OR_NEWER
using UnityEditor.Build.Reporting;
#endif
using UnityEditor.Callbacks;
namespace blueasa
{
    /// <summary>
    /// 빌드 시, Built-in AssetBundle에서 해당 Platform AssetBundle만 빌드 되도록 하기 위한 Preprocessor
    /// 빌드 전(Preprocessor) 해당 안되는 AssetBundle을 Hidden 폴더(예: .Android~ / .iOS~)로 Rename 했다가, 빌드 후(Postprocessor) 되돌려 놓음
    /// [Hidden(./~) 참조] https://docs.unity3d.com/Manual/SpecialFolders.html
    /// [수정] Unity6 이상에서 dot(.)이 안먹혀서, tilde(~)도 함께 붙이도록 함(예: .Android~ / .iOS~)
    /// </summary>
#if UNITY_2018_1_OR_NEWER
    public class BuildPreprocessor_BuiltinResourceInStreamingAssets : IPreprocessBuildWithReport, IPostprocessBuildWithReport
#else
    public class BuildPreprocessor_BuiltinResourceInStreamingAssets : IPreprocessBuild, IPostprocessBuild
#endif
    {
        private static readonly string m_strDataPath = Application.dataPath;
        private static readonly string m_strStreamingAssetsPath = Application.streamingAssetsPath;
        private static readonly string m_strAssets = "Assets";
        private static readonly string m_strBackup = "_Backup";
        private static readonly string m_strStreamingAssets = "StreamingAssets";
        private static readonly string m_strRaw = "Raw";
        private static readonly string m_strDot_Raw_Tilde = ".Raw~";
        private static readonly string m_strAndroid = "Android";
        private static readonly string m_strDot_Android_Tilde = ".Android~";
        private static readonly string m_striOS = "iOS";
        private static readonly string m_strDot_iOS_Tilde = ".iOS~";
        private static readonly string m_strDotMeta = ".meta";
        private static readonly string m_strAssetBundles = "AssetBundles";
        private static readonly string m_strStreamingAssetsPath_Builtin_Raw = string.Format("{0}/{1}/{2}/", m_strAssets, m_strStreamingAssets, m_strRaw);
        private static readonly string m_strBackupPath_Builtin_Raw = string.Format("{0}/{1}/", m_strBackup, m_strRaw);
        private static readonly string m_strStreamingAssetsPath_Builtin_Android = string.Format("{0}/{1}/{2}/{3}/", m_strAssets, m_strStreamingAssets, m_strAssetBundles, m_strAndroid);
        private static readonly string m_strBackupPath_Builtin_Android = string.Format("{0}/{1}/", m_strBackup, m_strAndroid);
        private static readonly string m_strStreamingAssetsPath_Builtin_iOS = string.Format("{0}/{1}/{2}/{3}/", m_strAssets, m_strStreamingAssets, m_strAssetBundles, m_striOS);
        private static readonly string m_strBackupPath_Builtin_iOS = string.Format("{0}/{1}/", m_strBackup, m_striOS);
        /// 
        private static readonly string m_strStreamingAssetsPath_Builtin_Raw_FullPath = string.Format("{0}/{1}", m_strStreamingAssetsPath, m_strRaw);
        private static readonly string m_strStreamingAssetsPath_Builtin_Dot_Raw_Tilde_FullPath = string.Format("{0}/{1}", m_strStreamingAssetsPath, m_strDot_Raw_Tilde);
        private static readonly string m_strAssetBundlesPath_Builtin_FullPath = string.Format("{0}/{1}", m_strStreamingAssetsPath, m_strAssetBundles);
        private static readonly string m_strAssetBundlesPath_Builtin_Android_FullPath = string.Format("{0}/{1}", m_strAssetBundlesPath_Builtin_FullPath, m_strAndroid);
        private static readonly string m_strAssetBundlesPath_Builtin_Dot_Android_Tilde_FullPath = string.Format("{0}/{1}", m_strAssetBundlesPath_Builtin_FullPath, m_strDot_Android_Tilde);
        private static readonly string m_strAssetBundlesPath_Builtin_iOS_FullPath = string.Format("{0}/{1}", m_strAssetBundlesPath_Builtin_FullPath, m_striOS);
        private static readonly string m_strAssetBundlesPath_Builtin_Dot_iOS_Tilde_FullPath = string.Format("{0}/{1}", m_strAssetBundlesPath_Builtin_FullPath, m_strDot_iOS_Tilde);
        public int callbackOrder { get { return 0; } }
        private static void Refresh()
        {
            System.Threading.Thread.Sleep(100);
            AssetDatabase.Refresh();
            System.Threading.Thread.Sleep(100);
        }
#if UNITY_2018_1_OR_NEWER
        public void OnPreprocessBuild(BuildReport report)
#else
        public void OnPreprocessBuild(BuildTarget target, string path)
#endif
        {
            Debug.LogWarning($"[OnPreprocessBuild] {this}");
            // [빌드 전] 사용되지 않는 폴더 임시 제외
            Move_StreamingAssetsUnusedFolders();
            //Rename_StreamingAssetsUnusedFolders();
            // Start listening for errors when build starts
            Application.logMessageReceived += OnBuildError;
        }
        // CALLED DURING BUILD TO CHECK FOR ERRORS
        private void OnBuildError(string condition, string stacktrace, LogType type)
        {
            Debug.LogWarning($"[OnBuildError] {condition} {stacktrace} {type}");
            if (type == LogType.Error)
            {
                // FAILED TO BUILD, STOP LISTENING FOR ERRORS
                Application.logMessageReceived -= OnBuildError;
                // 빌드 에러 시에도 이동된 폴더 되돌리기
                Restore_StreamingAssetsUnusedFolders();
                //Restore_Rename_StreamingAssetsUnusedFolders();
            }
        }
#if UNITY_2018_1_OR_NEWER
        public void OnPostprocessBuild(BuildReport report)
#else
        public void OnPostprocessBuild(BuildTarget target, string path)
#endif
        {
            Debug.LogWarning($"[OnPostprocessBuild] {this}");
            // [빌드 후] 제외 됐던 다른 폴더 되돌리기
            Restore_StreamingAssetsUnusedFolders();
            //Restore_Rename_StreamingAssetsUnusedFolders();
            // IF BUILD FINISHED AND SUCCEEDED, STOP LOOKING FOR ERRORS
            Application.logMessageReceived -= OnBuildError;
        }
        //////////////////////////////////////////////////////////////////
        #region Move
        public static void Move_StreamingAssetsUnusedFolders()
        {
            Debug.LogWarning($"[Move_StreamingAssetsUnusedFolders]");
            // [빌드 전] 다른 플랫폼 AssetBundle 폴더 임시 제외
            switch (GuitarGirl.ClientSettings.ResourceLoadType)
            {
                case eResourceLoadType.Local_Resource:
                    {
                        // AssetBundles 폴더 모두 제거
                        Move_StreamingAssetsUnusedFolder_iOS();
                        Move_StreamingAssetsUnusedFolder_Android();
                    }
                    break;
                case eResourceLoadType.Local_AssetBundle:
                case eResourceLoadType.Server_AssetBundle:
                    {
                        // Raw / 해당 안되는 플랫폼 폴더 제거
                        Move_StreamingAssetsUnusedFolder_Raw();
#if UNITY_ANDROID
                        Move_StreamingAssetsUnusedFolder_iOS();
#elif UNITY_IOS
                        Move_StreamingAssetsUnusedFolder_Android();
#endif
                    }
                    break;
            }
            Refresh();
        }
        public static void Move_StreamingAssetsUnusedFolder_Raw()
        {
            // [Move]
            if (MoveFolder(m_strStreamingAssetsPath_Builtin_Raw, m_strBackupPath_Builtin_Raw))
            {
                // 빈(Empty) 폴더 생성을 방지하기 위해서 폴더의 meta 파일도 함께 삭제
                if (true == File.Exists(m_strStreamingAssetsPath_Builtin_Raw_FullPath + m_strDotMeta))
                {
                    File.Delete(m_strStreamingAssetsPath_Builtin_Raw_FullPath + m_strDotMeta);
                }
                AssetDatabase.Refresh();
            }
            //if (true == Directory.Exists(m_strStreamingAssetsPath_Builtin_Raw_FullPath)
            //    && false == Directory.Exists(m_strStreamingAssetsPath_Builtin_Dot_Raw_Tilde_FullPath))
            //{
            //    Debug.LogWarning("[OnPreprocessBuild] Move 'Raw' Folder to '.Raw'");
            //    Directory.Move(m_strStreamingAssetsPath_Builtin_Raw_FullPath, m_strStreamingAssetsPath_Builtin_Dot_Raw_Tilde_FullPath);
            //    // 빈(Empty) 폴더 생성을 방지하기 위해서 폴더의 meta 파일도 함께 삭제
            //    if (true == File.Exists(m_strStreamingAssetsPath_Builtin_Raw_FullPath + m_strDotMeta))
            //    {
            //        File.Delete(m_strStreamingAssetsPath_Builtin_Raw_FullPath + m_strDotMeta);
            //    }
            //}
        }
        public static void Move_StreamingAssetsUnusedFolder_iOS()
        {
            // [Move]
            if (MoveFolder(m_strStreamingAssetsPath_Builtin_iOS, m_strBackupPath_Builtin_iOS))
            {
                // 빈(Empty) 폴더 생성을 방지하기 위해서 폴더의 meta 파일도 함께 삭제
                if (true == File.Exists(m_strAssetBundlesPath_Builtin_iOS_FullPath + m_strDotMeta))
                {
                    File.Delete(m_strAssetBundlesPath_Builtin_iOS_FullPath + m_strDotMeta);
                }
                AssetDatabase.Refresh();
            }
            //if (true == Directory.Exists(m_strAssetBundlesPath_Builtin_iOS_FullPath)
            //    && false == Directory.Exists(m_strAssetBundlesPath_Builtin_Dot_iOS_Tilde_FullPath))
            //{
            //    Debug.LogWarning("[OnPreprocessBuild] Move 'iOS' Folder to '.iOS~'");
            //    Directory.Move(m_strAssetBundlesPath_Builtin_iOS_FullPath, m_strAssetBundlesPath_Builtin_Dot_iOS_Tilde_FullPath);
            //    // 빈(Empty) 폴더 생성을 방지하기 위해서 폴더의 meta 파일도 함께 삭제
            //    if (true == File.Exists(m_strAssetBundlesPath_Builtin_iOS_FullPath + m_strDotMeta))
            //    {
            //        File.Delete(m_strAssetBundlesPath_Builtin_iOS_FullPath + m_strDotMeta);
            //    }
            //}
        }
        public static void Move_StreamingAssetsUnusedFolder_Android()
        {
            // [Move]
            if (MoveFolder(m_strStreamingAssetsPath_Builtin_Android, m_strBackupPath_Builtin_Android))
            {
                // 빈(Empty) 폴더 생성을 방지하기 위해서 폴더의 meta 파일도 함께 삭제
                if (true == File.Exists(m_strAssetBundlesPath_Builtin_Android_FullPath + m_strDotMeta))
                {
                    File.Delete(m_strAssetBundlesPath_Builtin_Android_FullPath + m_strDotMeta);
                }
                AssetDatabase.Refresh();
            }
            //if (true == Directory.Exists(m_strAssetBundlesPath_Builtin_Android_FullPath)
            //    && false == Directory.Exists(m_strAssetBundlesPath_Builtin_Dot_Android_Tilde_FullPath))
            //{
            //    Debug.LogWarning("[OnPreprocessBuild] Move 'Android' Folder to '.Android~'");
            //    Directory.Move(m_strAssetBundlesPath_Builtin_Android_FullPath, m_strAssetBundlesPath_Builtin_Dot_Android_Tilde_FullPath);
            //    // 빈(Empty) 폴더 생성을 방지하기 위해서 폴더의 meta 파일도 함께 삭제
            //    if (true == File.Exists(m_strAssetBundlesPath_Builtin_Android_FullPath + m_strDotMeta))
            //    {
            //        File.Delete(m_strAssetBundlesPath_Builtin_Android_FullPath + m_strDotMeta);
            //    }
            //}
        }
        /// <summary>
        /// 숨겨진 폴더 되돌리기
        /// Move : 이동시켜 놨던게 있다면 다 되돌리기
        /// Rename : 옮겨진게(./~찍힌게) 있다면 다 되돌리기
        /// </summary>
        public static void Restore_StreamingAssetsUnusedFolders()
        {
            Debug.LogWarning($"[Restore_StreamingAssetsUnusedFolders]");
            // [Move] 이동 시켰던 폴더 되돌리기
            Restore_Move_StreamingAssetsUnusedFolders();
            // [Rename] 제외 됐던(./~) 폴더 되돌리기
            //Restore_Rename_StreamingAssetsUnusedFolders();
        }
        public static void Restore_Move_StreamingAssetsUnusedFolders()
        {
            // [Restore] 이동 시켰던 폴더 되돌리기
            bool bExcute = false;
            // [Move] Raw
            if (MoveFolder(m_strBackupPath_Builtin_Raw, m_strStreamingAssetsPath_Builtin_Raw))
            {
                bExcute = true;
            }
            // [Move] iOS
            if (MoveFolder(m_strBackupPath_Builtin_iOS, m_strStreamingAssetsPath_Builtin_iOS))
            {
                bExcute = true;
            }
            // [Move] Android
            if (MoveFolder(m_strBackupPath_Builtin_Android, m_strStreamingAssetsPath_Builtin_Android))
            {
                bExcute = true;
            }
            if (true == bExcute)
            {
                Refresh();
            }
        }
        private static bool MoveFolder(string _strPath_Src, string _strPath_Dest)
        {
            AssetDatabase.Refresh();
            bool isSuccess = false;
            if (System.IO.Directory.Exists(_strPath_Src))
            {
                System.IO.DirectoryInfo di = new System.IO.DirectoryInfo(_strPath_Src);
                var files = di.GetFiles();
                if (files != null && files.Length > 0)
                {
                    if (System.IO.Directory.Exists(_strPath_Dest))
                    {
                        UnityEditor.FileUtil.DeleteFileOrDirectory(_strPath_Dest);
                    }
                    else
                    {
                        string[] arrKeys = _strPath_Dest.Split(new string[] { "/" }, System.StringSplitOptions.RemoveEmptyEntries);
                        string strPath = "";
                        for (int i = 0; i < arrKeys.Length - 1; i++)
                        {
                            strPath += arrKeys[i] + "/";
                            if (!System.IO.Directory.Exists(strPath))
                            {
                                Directory.CreateDirectory(strPath);
                            }
                        }
                    }
                    AssetDatabase.Refresh();
                    UnityEditor.FileUtil.MoveFileOrDirectory(_strPath_Src, _strPath_Dest);
                    //FileUtil.ReplaceDirectory(_sPath, _sNewPath);
                    isSuccess = true;
                }
                else
                {
                    Debug.LogWarning("Files Empty! Can't move files - " + _strPath_Src);
                }
            }
            else
            {
                Debug.LogWarning(_strPath_Src + " Not Exist");
            }
            return isSuccess;
        }
        #endregion
        //////////////////////////////////////////////////////////////////
        #region Rename
        public static void Rename_StreamingAssetsUnusedFolders()
        {
            Debug.LogWarning($"[Rename_StreamingAssetsUnusedFolders]");
            // [빌드 전] 다른 플랫폼 AssetBundle 폴더 임시 제외
            switch (GuitarGirl.ClientSettings.ResourceLoadType)
            {
                case eResourceLoadType.Local_Resource:
                    {
                        // AssetBundles 폴더 모두 제거
                        Rename_StreamingAssetsUnusedFolder_iOS();
                        Rename_StreamingAssetsUnusedFolder_Android();
                    }
                    break;
                case eResourceLoadType.Local_AssetBundle:
                case eResourceLoadType.Server_AssetBundle:
                    {
                        // Raw / 해당 안되는 플랫폼 폴더 제거
                        Rename_StreamingAssetsUnusedFolder_Raw();
#if UNITY_ANDROID
                        Rename_StreamingAssetsUnusedFolder_iOS();
#elif UNITY_IOS
                        Rename_StreamingAssetsUnusedFolder_Android();
#endif
                    }
                    break;
            }
            Refresh();
        }
        public static void Rename_StreamingAssetsUnusedFolder_Raw()
        {
            // [Rename]
            if (true == Directory.Exists(m_strStreamingAssetsPath_Builtin_Raw_FullPath)
                && false == Directory.Exists(m_strStreamingAssetsPath_Builtin_Dot_Raw_Tilde_FullPath))
            {
                Debug.LogWarning("[OnPreprocessBuild] Rename 'Raw' Folder to '.Raw'");
                Directory.Move(m_strStreamingAssetsPath_Builtin_Raw_FullPath, m_strStreamingAssetsPath_Builtin_Dot_Raw_Tilde_FullPath);
                // 빈(Empty) 폴더 생성을 방지하기 위해서 폴더의 meta 파일도 함께 삭제
                if (true == File.Exists(m_strStreamingAssetsPath_Builtin_Raw_FullPath + m_strDotMeta))
                {
                    File.Delete(m_strStreamingAssetsPath_Builtin_Raw_FullPath + m_strDotMeta);
                }
            }
        }
        public static void Rename_StreamingAssetsUnusedFolder_iOS()
        {
            // [Rename]
            if (true == Directory.Exists(m_strAssetBundlesPath_Builtin_iOS_FullPath)
                && false == Directory.Exists(m_strAssetBundlesPath_Builtin_Dot_iOS_Tilde_FullPath))
            {
                Debug.LogWarning("[OnPreprocessBuild] Rename 'iOS' Folder to '.iOS~'");
                Directory.Move(m_strAssetBundlesPath_Builtin_iOS_FullPath, m_strAssetBundlesPath_Builtin_Dot_iOS_Tilde_FullPath);
                // 빈(Empty) 폴더 생성을 방지하기 위해서 폴더의 meta 파일도 함께 삭제
                if (true == File.Exists(m_strAssetBundlesPath_Builtin_iOS_FullPath + m_strDotMeta))
                {
                    File.Delete(m_strAssetBundlesPath_Builtin_iOS_FullPath + m_strDotMeta);
                }
            }
        }
        public static void Rename_StreamingAssetsUnusedFolder_Android()
        {
            // [Rename]
            if (true == Directory.Exists(m_strAssetBundlesPath_Builtin_Android_FullPath)
                && false == Directory.Exists(m_strAssetBundlesPath_Builtin_Dot_Android_Tilde_FullPath))
            {
                Debug.LogWarning("[OnPreprocessBuild] Rename 'Android' Folder to '.Android~'");
                Directory.Move(m_strAssetBundlesPath_Builtin_Android_FullPath, m_strAssetBundlesPath_Builtin_Dot_Android_Tilde_FullPath);
                // 빈(Empty) 폴더 생성을 방지하기 위해서 폴더의 meta 파일도 함께 삭제
                if (true == File.Exists(m_strAssetBundlesPath_Builtin_Android_FullPath + m_strDotMeta))
                {
                    File.Delete(m_strAssetBundlesPath_Builtin_Android_FullPath + m_strDotMeta);
                }
            }
        }
        /// <summary>
        /// 숨겨진 폴더 되돌리기
        /// (옮겨진게(.찍힌게) 있다면 다 되돌리기)
        /// </summary>
        public static void Restore_Rename_StreamingAssetsUnusedFolders()
        {
            Debug.LogWarning($"[Restore_Rename_StreamingAssetsUnusedFolders]");
            // [Restore] 제외 됐던(.) 폴더 되돌리기
            bool bExcute = false;
            // [Rename] Raw
            if (true == Directory.Exists(m_strStreamingAssetsPath_Builtin_Dot_Raw_Tilde_FullPath)
                && false == Directory.Exists(m_strStreamingAssetsPath_Builtin_Raw_FullPath))
            {
                Debug.LogWarning("[OnPostprocessBuild] Rename '.Raw~' Folder to 'Raw'");
                Directory.Move(m_strStreamingAssetsPath_Builtin_Dot_Raw_Tilde_FullPath, m_strStreamingAssetsPath_Builtin_Raw_FullPath);
                bExcute = true;
            }
            // [Rename] iOS
            if (true == Directory.Exists(m_strAssetBundlesPath_Builtin_Dot_iOS_Tilde_FullPath)
                && false == Directory.Exists(m_strAssetBundlesPath_Builtin_iOS_FullPath))
            {
                Debug.LogWarning("[OnPostprocessBuild] Rename '.iOS~' Folder to 'iOS'");
                Directory.Move(m_strAssetBundlesPath_Builtin_Dot_iOS_Tilde_FullPath, m_strAssetBundlesPath_Builtin_iOS_FullPath);
                bExcute = true;
            }
            // [Rename] Android
            if (true == Directory.Exists(m_strAssetBundlesPath_Builtin_Dot_Android_Tilde_FullPath)
                && false == Directory.Exists(m_strAssetBundlesPath_Builtin_Android_FullPath))
            {
                Debug.LogWarning("[OnPostprocessBuild] Rename '.Android~' Folder to 'Android'");
                Directory.Move(m_strAssetBundlesPath_Builtin_Dot_Android_Tilde_FullPath, m_strAssetBundlesPath_Builtin_Android_FullPath);
                bExcute = true;
            }
            if (true == bExcute)
            {
                Refresh();
            }
        }
        #endregion
    }
}