[수정] 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
}
}