Initial Commit
This commit is contained in:
@@ -0,0 +1,378 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
public class AssetBundler
|
||||
{
|
||||
/// <summary>
|
||||
/// The types of assets to search for in checks.
|
||||
/// </summary>
|
||||
private static readonly string ASSET_SEARCH_QUERY = "t:prefab,t:textAsset,t:audioclip";
|
||||
|
||||
/// <summary>
|
||||
/// Temporary location for building AssetBundles.
|
||||
/// </summary>
|
||||
private static readonly string TEMP_BUILD_FOLDER = "Temp/AssetBundles";
|
||||
|
||||
/// <summary>
|
||||
/// Name of the output bundle file. This needs to match the bundle that you tag your assets with.
|
||||
/// </summary>
|
||||
private static readonly string BUNDLE_FILENAME = "mod.assets";
|
||||
|
||||
/// <summary>
|
||||
/// The output folder to place the completed bundle in.
|
||||
/// </summary>
|
||||
private static readonly string OUTPUT_FOLDER = "content";
|
||||
|
||||
/// <summary>
|
||||
/// The folders to not search for assets in.
|
||||
/// </summary>
|
||||
private static readonly string[] EXCLUDED_FOLDERS = new string[] { "Assets/Editor", "Packages" };
|
||||
|
||||
/// <summary>
|
||||
/// The build target of the asset bundle. Should either be StandaloneWindows or StandaloneOSX, depending on your platform.
|
||||
/// </summary>
|
||||
private BuildTarget Target = BuildTarget.StandaloneWindows;
|
||||
|
||||
/// <summary>
|
||||
/// Number of warnings encountered.
|
||||
/// </summary>
|
||||
private int NumWarnings;
|
||||
|
||||
/// <summary>
|
||||
/// Number of warnings encountered.
|
||||
/// </summary>
|
||||
private string GeneratedAssetBundleTag;
|
||||
|
||||
[MenuItem("PlateUp!/Build Asset Bundle _F6")]
|
||||
public static void BuildAssetBundle()
|
||||
{
|
||||
Debug.LogFormat("Creating \"{0}\" AssetBundle...", BUNDLE_FILENAME);
|
||||
|
||||
AssetBundler bundler = new AssetBundler();
|
||||
|
||||
if (Application.platform == RuntimePlatform.OSXEditor) bundler.Target = BuildTarget.StandaloneOSX;
|
||||
|
||||
// Randomly generate the resulting name of the asset bundle
|
||||
bundler.GenerateRandomAssetBundleTag();
|
||||
|
||||
bool success = false;
|
||||
try
|
||||
{
|
||||
// Check for assets
|
||||
bundler.WarnIfAssetsAreNotTagged();
|
||||
bundler.WarnIfZeroAssetsAreTagged();
|
||||
bundler.WarnIfMeshAssetsAreTagged();
|
||||
// bundler.WarnIfMaterialsAreTaggedOrIncluded();
|
||||
|
||||
// Delete the contents of OUTPUT_FOLDER
|
||||
bundler.CleanBuildFolder();
|
||||
|
||||
// Temporarily move the tagged assets to the temporary tag
|
||||
bundler.MoveAssetsToTemporaryAssetBundle();
|
||||
|
||||
// Lastly, create the asset bundle itself and copy it to the output folder
|
||||
bundler.CreateAssetBundle();
|
||||
|
||||
success = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogErrorFormat("Failed to build AssetBundle: {0}\n{1}", e.Message, e.StackTrace);
|
||||
}
|
||||
|
||||
// Return assets to the original asset bundle tag
|
||||
bundler.RestoreAssetBundleTags();
|
||||
AssetDatabase.RemoveUnusedAssetBundleNames();
|
||||
|
||||
if (success)
|
||||
{
|
||||
Debug.LogFormat("[{0}] Build complete with {1} warnings! Output: {2} (temporary ID: {3})", DateTime.Now.ToLocalTime(), bundler.NumWarnings, OUTPUT_FOLDER + "/" + BUNDLE_FILENAME, bundler.GeneratedAssetBundleTag);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate the random asset bundle tag to use when building the asset bundle.
|
||||
/// </summary>
|
||||
private void GenerateRandomAssetBundleTag()
|
||||
{
|
||||
System.Random rand = new System.Random();
|
||||
GeneratedAssetBundleTag = $"mod-{rand.Next(0, int.MaxValue)}.assets";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move assets tagged with BUNDLE_FILENAME to the temporary asset bundle
|
||||
/// </summary>
|
||||
private void MoveAssetsToTemporaryAssetBundle()
|
||||
{
|
||||
SubstituteAssetBundleTags(BUNDLE_FILENAME, GeneratedAssetBundleTag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move assets tagged with the temporary asset bundle back to BUNDLE_FILENAME
|
||||
/// </summary>
|
||||
private void RestoreAssetBundleTags()
|
||||
{
|
||||
SubstituteAssetBundleTags(GeneratedAssetBundleTag, BUNDLE_FILENAME);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find all assets tagged with a certain asset bundle tag and replace them with another tag
|
||||
/// </summary>
|
||||
/// <param name="from">The asset bundle tag to search for</param>
|
||||
/// <param name="to">The new asset bundle tag</param>
|
||||
private void SubstituteAssetBundleTags(string from, string to)
|
||||
{
|
||||
string[] assetGUIDs = AssetDatabase.FindAssets($"b:{from}");
|
||||
foreach (var assetGUID in assetGUIDs)
|
||||
{
|
||||
string path = AssetDatabase.GUIDToAssetPath(assetGUID);
|
||||
var importer = AssetImporter.GetAtPath(path);
|
||||
importer.assetBundleName = to;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete and recreate the OUTPUT_FOLDER to ensure a clean build.
|
||||
/// </summary>
|
||||
protected void CleanBuildFolder()
|
||||
{
|
||||
Debug.LogFormat("Cleaning {0}...", OUTPUT_FOLDER);
|
||||
|
||||
if (Directory.Exists(OUTPUT_FOLDER))
|
||||
{
|
||||
Directory.Delete(OUTPUT_FOLDER, true);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(OUTPUT_FOLDER);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build the AssetBundle itself and copy it to the OUTPUT_FOLDER.
|
||||
/// </summary>
|
||||
protected void CreateAssetBundle()
|
||||
{
|
||||
Debug.Log("Building AssetBundle...");
|
||||
|
||||
// Build all AssetBundles to the TEMP_BUILD_FOLDER
|
||||
if (!Directory.Exists(TEMP_BUILD_FOLDER))
|
||||
{
|
||||
Directory.CreateDirectory(TEMP_BUILD_FOLDER);
|
||||
}
|
||||
|
||||
#pragma warning disable 618
|
||||
// Build the asset bundle with the CollectDependencies flag. This is necessary or else ScriptableObjects will
|
||||
// not be accessible within the asset bundle. Unity has deprecated this flag claiming it is now always active,
|
||||
// but due to a bug we must still include it (and ignore the warning).
|
||||
BuildPipeline.BuildAssetBundles(
|
||||
TEMP_BUILD_FOLDER,
|
||||
BuildAssetBundleOptions.UncompressedAssetBundle | BuildAssetBundleOptions.CollectDependencies,
|
||||
Target);
|
||||
#pragma warning restore 618
|
||||
|
||||
// We are only interested in the BUNDLE_FILENAME bundle (and not any extra AssetBundle or the manifest files
|
||||
// that Unity makes), so just copy that to the final output folder
|
||||
string srcPath = Path.Combine(TEMP_BUILD_FOLDER, GeneratedAssetBundleTag);
|
||||
string destPath = Path.Combine(OUTPUT_FOLDER, BUNDLE_FILENAME);
|
||||
File.Copy(srcPath, destPath, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given path is a search path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to check.</param>
|
||||
/// <returns>true if the given path is a search path, otherwise false.</returns>
|
||||
protected static bool IsIncludedAssetPath(string path)
|
||||
{
|
||||
foreach (string excludedPath in EXCLUDED_FOLDERS)
|
||||
{
|
||||
if (path.StartsWith(excludedPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a warning for all potential assets that are not currently tagged to be in this AssetBundle.
|
||||
/// </summary>
|
||||
protected void WarnIfAssetsAreNotTagged()
|
||||
{
|
||||
string[] assetGUIDs = AssetDatabase.FindAssets(ASSET_SEARCH_QUERY);
|
||||
foreach (var assetGUID in assetGUIDs)
|
||||
{
|
||||
string path = AssetDatabase.GUIDToAssetPath(assetGUID);
|
||||
if (!IsIncludedAssetPath(path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var importer = AssetImporter.GetAtPath(path);
|
||||
if (!importer.assetBundleName.Equals(BUNDLE_FILENAME))
|
||||
{
|
||||
// Debug.LogWarningFormat("Asset \"{0}\" is not tagged with \"{1}\" and will not be included in the AssetBundle!", path, BUNDLE_FILENAME);
|
||||
// ++NumWarnings;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that there is at least one asset to be included in the asset bundle.
|
||||
/// </summary>
|
||||
protected void WarnIfZeroAssetsAreTagged()
|
||||
{
|
||||
string[] assetsInBundle = AssetDatabase.FindAssets($"{ASSET_SEARCH_QUERY},b:{BUNDLE_FILENAME}");
|
||||
if (assetsInBundle.Length == 0)
|
||||
{
|
||||
// throw new Exception(string.Format("No assets have been tagged for inclusion in the {0} AssetBundle.", BUNDLE_FILENAME));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Warn if there are any mesh assets tagged. If so, the user probably meant to tag a prefab instead.
|
||||
/// </summary>
|
||||
protected void WarnIfMeshAssetsAreTagged()
|
||||
{
|
||||
string[] assetGUIDs = AssetDatabase.FindAssets($"t:mesh,b:{BUNDLE_FILENAME}");
|
||||
foreach (var assetGUID in assetGUIDs)
|
||||
{
|
||||
string path = AssetDatabase.GUIDToAssetPath(assetGUID);
|
||||
if (!IsIncludedAssetPath(path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Debug.LogWarningFormat("Mesh asset \"{0}\" is tagged for inclusion in the {1} AssetBundle! This is likely a mistake. You should include a prefab instead.", path, BUNDLE_FILENAME);
|
||||
++NumWarnings;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Warn if there are any material assets tagged. If so, the user probably meant to tag a prefab instead.
|
||||
/// </summary>
|
||||
protected void WarnIfMaterialsAreTaggedOrIncluded()
|
||||
{
|
||||
// Check for directly tagged materials
|
||||
string[] assetGUIDs = AssetDatabase.FindAssets($"t:material,b:{BUNDLE_FILENAME}");
|
||||
foreach (var assetGUID in assetGUIDs)
|
||||
{
|
||||
string path = AssetDatabase.GUIDToAssetPath(assetGUID);
|
||||
if (!IsIncludedAssetPath(path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Debug.LogWarningFormat("Material asset \"{0}\" is tagged for inclusion in the {1} AssetBundle! This is likely a mistake. You should use generate materials using the vanilla shaders instead.", path, BUNDLE_FILENAME);
|
||||
++NumWarnings;
|
||||
}
|
||||
|
||||
// Check for materials assigned to prefabs
|
||||
assetGUIDs = AssetDatabase.FindAssets($"t:prefab,b:{BUNDLE_FILENAME}");
|
||||
foreach (var assetGUID in assetGUIDs)
|
||||
{
|
||||
string path = AssetDatabase.GUIDToAssetPath(assetGUID);
|
||||
if (!IsIncludedAssetPath(path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||||
MeshRenderer[] renderers = prefab.GetComponentsInChildren<MeshRenderer>();
|
||||
foreach (MeshRenderer renderer in renderers)
|
||||
{
|
||||
if (renderer.sharedMaterials.Any(m => m != null))
|
||||
{
|
||||
Debug.LogWarningFormat("Material found attached to bundle prefab in \"{0}\" at \"<root>/{1}\"! This is likely a mistake. To avoid log spam and texturing issues, you should remove these materials or set them to \"None\".", path, GetGameObjectPath(renderer.transform).Split(new char[] { '/' }, 3)[2]);
|
||||
++NumWarnings;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetGameObjectPath(Transform current)
|
||||
{
|
||||
if (current.parent == null)
|
||||
return "/" + current.name;
|
||||
return GetGameObjectPath(current.parent) + "/" + current.name;
|
||||
}
|
||||
|
||||
[MenuItem("PlateUp!/Preparation/[Deprecated] Strip Materials From Prefabs")]
|
||||
public static void RemoveAllPrefabMaterials()
|
||||
{
|
||||
if (!EditorUtility.DisplayDialog("Confirm", "Stripping materials from prefabs is an irreversible process. Perform at your own risk.", "Proceed", "Cancel"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string[] assetGUIDs = AssetDatabase.FindAssets($"t:prefab,b:{BUNDLE_FILENAME}");
|
||||
foreach (var assetGUID in assetGUIDs)
|
||||
{
|
||||
string path = AssetDatabase.GUIDToAssetPath(assetGUID);
|
||||
if (!IsIncludedAssetPath(path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||||
MeshRenderer[] renderers = prefab.GetComponentsInChildren<MeshRenderer>();
|
||||
foreach (MeshRenderer renderer in renderers)
|
||||
{
|
||||
if (renderer.sharedMaterials.Length > 0)
|
||||
{
|
||||
renderer.sharedMaterials = new Material[renderer.sharedMaterials.Length];
|
||||
Debug.LogFormat("Stripped materials from \"{0}\" at \"<root>/{1}\".", path, GetGameObjectPath(renderer.transform).Split(new char[] { '/' }, 3)[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Debug.LogFormat("[{0}] Done stripping materials.", DateTime.Now.ToLocalTime());
|
||||
}
|
||||
|
||||
|
||||
|
||||
[MenuItem("PlateUp!/Preparation/[Deprecated] Set Prefab Materials to Default")]
|
||||
public static void SetAllPrefabMaterialsToDefault()
|
||||
{
|
||||
if (!EditorUtility.DisplayDialog("Confirm", "Changing the materials of prefabs is an irreversible process. Perform at your own risk.", "Proceed", "Cancel"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string[] assetGUIDs = AssetDatabase.FindAssets($"t:prefab,b:{BUNDLE_FILENAME}");
|
||||
foreach (var assetGUID in assetGUIDs)
|
||||
{
|
||||
string path = AssetDatabase.GUIDToAssetPath(assetGUID);
|
||||
if (!IsIncludedAssetPath(path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||||
MeshRenderer[] renderers = prefab.GetComponentsInChildren<MeshRenderer>();
|
||||
Material defaultMaterial = AssetDatabase.GetBuiltinExtraResource<Material>("Default-Material.mat");
|
||||
|
||||
foreach (MeshRenderer renderer in renderers)
|
||||
{
|
||||
if (renderer.sharedMaterials.Length > 0)
|
||||
{
|
||||
var newMaterials = new Material[renderer.sharedMaterials.Length];
|
||||
|
||||
for (int i = 0; i < newMaterials.Length; i++)
|
||||
{
|
||||
newMaterials[i] = defaultMaterial;
|
||||
}
|
||||
|
||||
renderer.sharedMaterials = newMaterials;
|
||||
|
||||
Debug.LogFormat("Set materials from \"{0}\" at \"<root>/{1}\".", path, GetGameObjectPath(renderer.transform).Split(new char[] { '/' }, 3)[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Debug.LogFormat("[{0}] Done setting materials.", DateTime.Now.ToLocalTime());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user