Search Unity

How Do I Add a Custom Build Script?

Discussion in 'Addressables' started by ohthepain, Nov 13, 2021.

  1. ohthepain

    ohthepain

    Joined:
    May 31, 2017
    Posts:
    100
    I am working through the Addressables Example project, Variants example. I can't figure out how to add the custom build script `BuildScriptInherited.cs` to my project. It won't let me add it the script to Build and Play Mode Scripts in the Addressables Settings. The file browser greys out the filename when I try to add it.

    In the example project the script has a little blue icon with red curly brackets but I don't know what that icon means. Some sort of script prefab?

    I check the docs for my version 1.19.11 but it just says
    See the [URL='https://github.com/Unity-Technologies/Addressables-Sample/tree/master/Advanced/Addressable%20Variants']Addressable variants project[/URL] in the [URL='https://github.com/Unity-Technologies/Addressables-Sample']Addressables-Sample[/URL] repository for an example.


    Code (CSharp):
    1.  
    2. using System;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using System.IO;
    6. using System.Linq;
    7. using UnityEditor;
    8. using UnityEditor.AddressableAssets.Build;
    9. using UnityEditor.AddressableAssets.Build.DataBuilders;
    10. using UnityEditor.AddressableAssets.Settings;
    11. using UnityEditor.AddressableAssets.Settings.GroupSchemas;
    12. using UnityEditor.VersionControl;
    13. using UnityEngine;
    14. using UnityEngine.ResourceManagement.Util;
    15. using UnityEngine.Serialization;
    16.  
    17. [CreateAssetMenu(fileName = "BuildScriptInherited.asset", menuName = "Addressables/Custom Build/Packed Variations")]
    18. public class BuildScriptInherited : BuildScriptPackedMode, ISerializationCallbackReceiver
    19. {
    20.     public override string Name
    21.     {
    22.         get { return "Packed Variations"; }
    23.     }
    24.  
    25.     Dictionary<string, Hash128> m_AssetPathToHashCode = new Dictionary<string, Hash128>();
    26.     HashSet<string> m_DirectoriesInUse = new HashSet<string>();
    27.     string m_BaseDirectory = "Assets/AddressablesGenerated";
    28.  
    29.     protected override TResult BuildDataImplementation<TResult>(AddressablesDataBuilderInput context)
    30.     {
    31.         var result = base.BuildDataImplementation<TResult>(context);
    32.  
    33.         AddressableAssetSettings settings = context.AddressableSettings;
    34.         DoCleanup(settings);
    35.         return result;
    36.     }
    37.  
    38.     protected override string ProcessGroup(AddressableAssetGroup assetGroup, AddressableAssetsBuildContext aaContext)
    39.     {
    40.         if (assetGroup.HasSchema<BundledAssetGroupSchema>() && assetGroup.GetSchema<BundledAssetGroupSchema>().IncludeInBuild)
    41.         {
    42.             if (assetGroup.HasSchema<TextureVariationSchema>())
    43.             {
    44.                 var errorString = ProcessTextureScaler(assetGroup.GetSchema<TextureVariationSchema>(), assetGroup, aaContext);
    45.                 if (!string.IsNullOrEmpty(errorString))
    46.                     return errorString;
    47.             }
    48.  
    49.             if (assetGroup.HasSchema<PrefabTextureVariantSchema>())
    50.             {
    51.                 var errorString = ProcessVariants(assetGroup.GetSchema<PrefabTextureVariantSchema>(), assetGroup, aaContext);
    52.                 if (!string.IsNullOrEmpty(errorString))
    53.                     return errorString;
    54.             }
    55.         }
    56.         return base.ProcessGroup(assetGroup, aaContext);
    57.     }
    58.  
    59.     List<AddressableAssetGroup> m_SourceGroupList = new List<AddressableAssetGroup>();
    60.     Dictionary<string, AddressableAssetGroup> m_GeneratedGroups = new Dictionary<string, AddressableAssetGroup>();
    61.  
    62.  
    63.     AddressableAssetGroup FindOrCopyGroup(string groupName, AddressableAssetGroup baseGroup, AddressableAssetSettings settings, TextureVariationSchema schema)
    64.     {
    65.         AddressableAssetGroup result;
    66.         if (!m_GeneratedGroups.TryGetValue(groupName, out result))
    67.         {
    68.             List<AddressableAssetGroupSchema> schemas = new List<AddressableAssetGroupSchema>(baseGroup.Schemas);
    69.             schemas.Remove(schema);
    70.             result = settings.CreateGroup(groupName, false, false, false, schemas);
    71.             m_GeneratedGroups.Add(groupName, result);
    72.         }
    73.  
    74.         return result;
    75.     }
    76.  
    77.     string ProcessVariants(PrefabTextureVariantSchema schema,
    78.         AddressableAssetGroup group,
    79.         AddressableAssetsBuildContext context)
    80.     {
    81.         var settings = context.Settings;
    82.         Directory.CreateDirectory(m_BaseDirectory);
    83.  
    84.         var entries = new List<AddressableAssetEntry>(group.entries);
    85.         foreach (var mainEntry in entries)
    86.         {
    87.             if (AssetDatabase.GetMainAssetTypeAtPath(mainEntry.AssetPath) != typeof(GameObject))
    88.                 continue;
    89.  
    90.             string fileName = Path.GetFileNameWithoutExtension(mainEntry.AssetPath);
    91.             string ext = Path.GetExtension(mainEntry.AssetPath);
    92.             mainEntry.SetLabel(schema.DefaultLabel, true, true);
    93.          
    94.             string mainAssetPath = AssetDatabase.GUIDToAssetPath(mainEntry.guid);
    95.             Hash128 assetHash = AssetDatabase.GetAssetDependencyHash(mainAssetPath);
    96.  
    97.             bool assetHashChanged = false;
    98.             if (!m_AssetPathToHashCode.ContainsKey(mainAssetPath))
    99.                 m_AssetPathToHashCode.Add(mainAssetPath, assetHash);
    100.             else if (m_AssetPathToHashCode[mainAssetPath] != assetHash)
    101.             {
    102.                 assetHashChanged = true;
    103.                 m_AssetPathToHashCode[mainAssetPath] = assetHash;
    104.             }
    105.  
    106.             foreach (var variant in schema.Variants)
    107.             {
    108.                 string groupDirectory = Path.Combine(m_BaseDirectory, $"{group.Name}-{Path.GetFileNameWithoutExtension(mainEntry.address)}").Replace('\\', '/');
    109.                 m_DirectoriesInUse.Add(groupDirectory);
    110.                 var variantGroup = CreateTemporaryGroupCopy($"{group.Name}_VariantGroup_{variant.Label}", group.Schemas, settings);
    111.  
    112.                 string baseVariantDirectory = Path.Combine(groupDirectory, variant.Label).Replace('\\', '/');
    113.                 string newPrefabPath = mainAssetPath.Replace("Assets/", baseVariantDirectory + '/').Replace($"{fileName}{ext}", $"{fileName}_variant_{variant.Label}{ext}");
    114.  
    115.                 string variantDirectory = Path.GetDirectoryName(newPrefabPath).Replace('\\', '/');
    116.                 m_DirectoriesInUse.Add(variantDirectory);
    117.                 Directory.CreateDirectory(variantDirectory);
    118.  
    119.                 if (assetHashChanged || !File.Exists(newPrefabPath))
    120.                 {
    121.                     if (!AssetDatabase.CopyAsset(mainAssetPath, newPrefabPath))
    122.                         return $"Copying asset from {mainAssetPath} to variant path {newPrefabPath} failed.";
    123.                 }
    124.  
    125.                 var dependencies = AssetDatabase.GetDependencies(newPrefabPath);
    126.                 foreach (var dependency in dependencies)
    127.                 {
    128.                     if (AssetDatabase.GetMainAssetTypeAtPath(dependency) == typeof(GameObject))
    129.                     {
    130.                         var gameObject = AssetDatabase.LoadAssetAtPath<GameObject>(dependency);
    131.                         foreach (var childRender in gameObject.GetComponentsInChildren<MeshRenderer>())
    132.                             ConvertToVariant(childRender, variantDirectory, variant, assetHashChanged);
    133.                     }
    134.                 }
    135.  
    136.                 var entry = settings.CreateOrMoveEntry(AssetDatabase.AssetPathToGUID(newPrefabPath), variantGroup, false, false);
    137.                 entry.address = mainEntry.address;
    138.                 entry.SetLabel(variant.Label, true, true, false);
    139.             }
    140.         }
    141.  
    142.         if (!schema.IncludeSourcePrefabInBuild)
    143.         {
    144.             group.GetSchema<BundledAssetGroupSchema>().IncludeInBuild = false;
    145.             m_SourceGroupList.Add(group);
    146.         }
    147.  
    148.         return string.Empty;
    149.     }
    150.  
    151.     void ConvertToVariant(MeshRenderer meshRenderer, string variantDirectory, PrefabTextureVariantSchema.VariantLabelPair variant, bool assetHashChanged)
    152.     {
    153.         if (meshRenderer != null && meshRenderer.sharedMaterial != null)
    154.         {
    155.             var mat = CreateOrGetVariantMaterial(meshRenderer.sharedMaterial, variantDirectory, variant.Label, assetHashChanged);
    156.             var texture = CreateOrGetVariantTexture(meshRenderer.sharedMaterial.mainTexture,
    157.                 variantDirectory, variant.Label, variant.TextureScale, assetHashChanged);
    158.  
    159.             mat.mainTexture = texture;
    160.             meshRenderer.sharedMaterial = mat;
    161.             AssetDatabase.SaveAssets();
    162.         }
    163.     }
    164.  
    165.     AddressableAssetGroup CreateTemporaryGroupCopy(string groupName, List<AddressableAssetGroupSchema> schemas, AddressableAssetSettings settings)
    166.     {
    167.         var variantGroup = settings.CreateGroup(groupName, false, false, false, schemas);
    168.         if(variantGroup.HasSchema<PrefabTextureVariantSchema>())
    169.             variantGroup.RemoveSchema<PrefabTextureVariantSchema>();
    170.         if (!m_GeneratedGroups.ContainsKey(variantGroup.Name))
    171.             m_GeneratedGroups.Add(variantGroup.Name, variantGroup);
    172.         return variantGroup;
    173.     }
    174.  
    175.     Material CreateOrGetVariantMaterial(Material baseMaterial, string variantDirectory, string label, bool assetHashChanged)
    176.     {
    177.         string assetPath = AssetDatabase.GetAssetPath(baseMaterial);
    178.         if(assetPath.StartsWith(variantDirectory))
    179.             return AssetDatabase.LoadAssetAtPath<Material>(assetPath);
    180.  
    181.         string matFileName = Path.GetFileNameWithoutExtension(assetPath);
    182.         string path = assetPath.Replace("Assets/", variantDirectory + '/').Replace(matFileName, $"{matFileName}_{label}");
    183.         if (assetHashChanged || !File.Exists(path))
    184.         {
    185.             Directory.CreateDirectory(Path.GetDirectoryName(path));
    186.             AssetDatabase.CopyAsset(assetPath, path);
    187.         }
    188.  
    189.         var mat = AssetDatabase.LoadAssetAtPath<Material>(path);
    190.         m_DirectoriesInUse.Add(Path.GetDirectoryName(path).Replace('\\', '/'));
    191.         return mat;
    192.     }
    193.  
    194.     Texture2D CreateOrGetVariantTexture(Texture baseTexture, string variantDirectory, string label, float scale, bool assetHashChanged)
    195.     {
    196.         string assetPath = AssetDatabase.GetAssetPath(baseTexture);
    197.         if (assetPath.StartsWith(variantDirectory))
    198.             return AssetDatabase.LoadAssetAtPath<Texture2D>(assetPath);
    199.  
    200.         string textureFileName = Path.GetFileNameWithoutExtension(assetPath);
    201.         string path = assetPath.Replace("Assets/", variantDirectory + '/').Replace(textureFileName, $"{textureFileName}_{label}");
    202.  
    203.         if (assetHashChanged || !File.Exists(path))
    204.         {
    205.             Directory.CreateDirectory(Path.GetDirectoryName(path));
    206.             AssetDatabase.CopyAsset(assetPath, path);
    207.  
    208.             var srcTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(assetPath);
    209.             int maxDim = Math.Max(srcTexture.width, srcTexture.height);
    210.             var aiDest = AssetImporter.GetAtPath(path) as TextureImporter;
    211.             if (aiDest == null)
    212.                 return null;
    213.  
    214.             float scaleFactor = scale;
    215.             float desiredLimiter = maxDim * scaleFactor;
    216.             aiDest.maxTextureSize = NearestMaxTextureSize(desiredLimiter);
    217.             aiDest.isReadable = true;
    218.             aiDest.SaveAndReimport();
    219.         }
    220.         var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
    221.         m_DirectoriesInUse.Add(Path.GetDirectoryName(path).Replace('\\', '/'));
    222.         return texture;
    223.  
    224.     }
    225.  
    226.     string ProcessTextureScaler(
    227.         TextureVariationSchema schema,
    228.         AddressableAssetGroup assetGroup,
    229.         AddressableAssetsBuildContext aaContext)
    230.     {
    231.         var entries = new List<AddressableAssetEntry>(assetGroup.entries);
    232.         foreach (var entry in entries)
    233.         {
    234.             var entryPath = entry.AssetPath;
    235.             if (AssetDatabase.GetMainAssetTypeAtPath(entryPath) == typeof(Texture2D))
    236.             {
    237.                 var fileName = Path.GetFileNameWithoutExtension(entryPath);
    238.                 if(string.IsNullOrEmpty(fileName))
    239.                     return "Failed to get file name for: " + entryPath;
    240.                 if (!Directory.Exists("Assets/GeneratedTextures"))
    241.                     Directory.CreateDirectory("Assets/GeneratedTextures");
    242.                 if (!Directory.Exists("Assets/GeneratedTextures/Texture"))
    243.                     Directory.CreateDirectory("Assets/GeneratedTextures/Texture");
    244.              
    245.                 var sourceTex = AssetDatabase.LoadAssetAtPath<Texture2D>(entryPath);
    246.                 var aiSource = AssetImporter.GetAtPath(entryPath) as TextureImporter;
    247.                 int maxDim = Math.Max(sourceTex.width, sourceTex.height);
    248.  
    249.                 foreach (var pair in schema.Variations)
    250.                 {
    251.                     var newGroup = FindOrCopyGroup(assetGroup.Name + "_" + pair.label, assetGroup, aaContext.Settings, schema);
    252.                     var newFile = entryPath.Replace(fileName, fileName+"_variationCopy_" + pair.label);
    253.                     newFile = newFile.Replace("Assets/", "Assets/GeneratedTextures/");
    254.                  
    255.                     AssetDatabase.CopyAsset(entryPath, newFile);
    256.              
    257.                     var aiDest = AssetImporter.GetAtPath(newFile) as TextureImporter;
    258.                     if (aiDest == null)
    259.                     {
    260.                         var message = "failed to get TextureImporter on new texture asset: " + newFile;
    261.                         return message;
    262.                     }
    263.                  
    264.                     float scaleFactor = pair.textureScale;
    265.  
    266.                     float desiredLimiter = maxDim * scaleFactor;
    267.                     aiDest.maxTextureSize = NearestMaxTextureSize(desiredLimiter);
    268.  
    269.                     aiDest.isReadable = true;
    270.  
    271.                     aiDest.SaveAndReimport();
    272.                     var newEntry = aaContext.Settings.CreateOrMoveEntry(AssetDatabase.AssetPathToGUID(newFile), newGroup);
    273.                     newEntry.address = entry.address;
    274.                     newEntry.SetLabel(pair.label, true);
    275.                 }
    276.                 entry.SetLabel(schema.BaselineLabel, true);
    277.             }
    278.         }
    279.  
    280.         if (!schema.IncludeSourceTextureInBuild)
    281.             assetGroup.GetSchema<BundledAssetGroupSchema>().IncludeInBuild = false;
    282.         m_SourceGroupList.Add(assetGroup); // need to reset labels for every texture variant group
    283.  
    284.         return string.Empty;
    285.     }
    286.  
    287.     static int NearestMaxTextureSize(float desiredLimiter)
    288.     {
    289.         float lastDiff = Math.Abs(desiredLimiter);
    290.         int lastPow = 32;
    291.         for (int i = 0; i < 9; i++)
    292.         {
    293.  
    294.             int powOfTwo = lastPow << 1;
    295.             float newDiff = Math.Abs(desiredLimiter - powOfTwo);
    296.             if (newDiff > lastDiff)
    297.                 return lastPow;
    298.  
    299.             lastPow = powOfTwo;
    300.             lastDiff = newDiff;
    301.  
    302.         }
    303.  
    304.         return 8192;
    305.     }
    306.  
    307.     void DoCleanup(AddressableAssetSettings settings)
    308.     {
    309.         List<string> directories = new List<string>();
    310.         foreach (var group in m_GeneratedGroups.Values)
    311.         {
    312.             if (group.HasSchema<TextureVariationSchema>())
    313.             {
    314.                 List<AddressableAssetEntry> entries = new List<AddressableAssetEntry>(group.entries);
    315.                 foreach (var entry in entries)
    316.                 {
    317.                     var path = entry.AssetPath;
    318.                     AssetDatabase.DeleteAsset(path);
    319.                 }
    320.             }
    321.  
    322.             settings.RemoveGroup(group);
    323.             if (Directory.Exists(m_BaseDirectory) && group.HasSchema<PrefabTextureVariantSchema>())
    324.             {
    325.                 foreach (var directory in Directory.EnumerateDirectories(m_BaseDirectory, "*", SearchOption.AllDirectories))
    326.                 {
    327.                     string formattedDirectory = directory.Replace('\\', '/');
    328.                     if (m_DirectoriesInUse.Contains(formattedDirectory))
    329.                         continue;
    330.                     Directory.Delete(formattedDirectory, true);
    331.                 }
    332.  
    333.             }
    334.  
    335.         }
    336.  
    337.         m_DirectoriesInUse.Clear();
    338.         m_GeneratedGroups.Clear();
    339.  
    340.         foreach (AddressableAssetGroup group in m_SourceGroupList)
    341.         {
    342.             group.GetSchema<BundledAssetGroupSchema>().IncludeInBuild = true;
    343.  
    344.             var schema = group.GetSchema<TextureVariationSchema>();
    345.             if (schema == null)
    346.                 continue;
    347.  
    348.             foreach (var entry in group.entries)
    349.             {
    350.                 entry.labels.Remove(schema.BaselineLabel);
    351.             }
    352.         }
    353.      
    354.         m_SourceGroupList.Clear();
    355.     }
    356.  
    357.     [SerializeField]
    358.     List<string> m_AssetPathToHashKey = new List<string>();
    359.     [SerializeField]
    360.     List<Hash128> m_AssetPathToHashValue = new List<Hash128>();
    361.  
    362.     public void OnBeforeSerialize()
    363.     {
    364.         foreach (var key in m_AssetPathToHashCode.Keys)
    365.         {
    366.             m_AssetPathToHashKey.Add(key);
    367.             m_AssetPathToHashValue.Add(m_AssetPathToHashCode[key]);
    368.         }
    369.     }
    370.  
    371.     public void OnAfterDeserialize()
    372.     {
    373.         for (int i = 0; i < Math.Min(m_AssetPathToHashKey.Count, m_AssetPathToHashValue.Count); i++)
    374.         {
    375.             if(!m_AssetPathToHashCode.ContainsKey(m_AssetPathToHashKey[i]))
    376.                 m_AssetPathToHashCode.Add(m_AssetPathToHashKey[i], m_AssetPathToHashValue[i]);
    377.         }
    378.     }
    379. }
    380.  
     
  2. ohthepain

    ohthepain

    Joined:
    May 31, 2017
    Posts:
    100
    Maybe somebody from Unity can answer this? Gotta be a simple question.
     
  3. ohthepain

    ohthepain

    Joined:
    May 31, 2017
    Posts:
    100
    Bump!

    I searched through the documentation again and came up with nothing.
     
  4. ohthepain

    ohthepain

    Joined:
    May 31, 2017
    Posts:
    100
  5. ohthepain

    ohthepain

    Joined:
    May 31, 2017
    Posts:
    100
  6. ohthepain

    ohthepain

    Joined:
    May 31, 2017
    Posts:
    100
  7. Uzike-Remona

    Uzike-Remona

    Joined:
    May 25, 2018
    Posts:
    2
  8. pillakirsten

    pillakirsten

    Unity Technologies

    Joined:
    May 22, 2019
    Posts:
    346
    @ohthepain @Uzike-Remona Hi apologies for not seeing this sooner.

    The trick is that you'd need to create the ScriptableObject .asset file. In this example, you can use Assets > Create > Addressables > Custom Build > Packed Variations menu option to create the BuildScriptInherited.asset. Then add that file to the list of Build and Play Mode Scripts in the AddressableAssetSettings.

    I can see how that's not immediately clear in the documentation. Will create a ticket to make sure this is emphasized in the docs!