#if GAIA_2023
using Gaia;
#endif
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
#if UNITY_EDITOR
using UnityEditor;
#if PW_ADDRESSABLES
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Build;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.AddressableAssets.Settings.GroupSchemas;
#endif
#endif
using UnityEngine;
using UnityEngine.SceneManagement;
using System.IO;
namespace ProceduralWorlds.Addressables1
{
///
/// Contains static functions for handling Unity Addressables
///
public class PWAddressables
{
public static readonly string m_PWGroupNamePrefix = "PW_";
public static readonly string m_PWDefaultGroupName= "Default";
public static readonly string m_sharedAssetGroupPrefix = "TerrainShared-";
public static bool m_isUpdate = false;
private static List m_terrainAssetGroupAssociations;
public static List TerrainAssetGroupAssociations
{
get {
#if UNITY_EDITOR && PW_ADDRESSABLES
if (m_terrainAssetGroupAssociations == null)
{
string[] allConfigGUIDs = AssetDatabase.FindAssets("t:PWAddressablesConfig");
if (allConfigGUIDs.Length > 0)
{
PWAddressablesConfig config = (PWAddressablesConfig)AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(allConfigGUIDs[0]), typeof(PWAddressablesConfig));
if (config != null)
{
m_terrainAssetGroupAssociations = config.m_terrainAssetGroupAssociations;
}
}
}
return m_terrainAssetGroupAssociations;
#else
return null;
#endif
}
}
///
/// Returns true if the initial addressable settings have been created in this project.
///
///
public static bool DoSettingsExist()
{
#if UNITY_EDITOR && PW_ADDRESSABLES
return AddressableAssetSettingsDefaultObject.SettingsExists;
#else
return false;
#endif
}
///
/// Name of the PW Profile that we create our addressable config under
///
public static readonly string m_PWProfileName = "PW Profile";
///
/// Hardcoded list of asset paths that should never be part of the addressable config - sometimes the asset parsing can pull in undesireable assets or folders which can be blocked with this list
///
public static readonly List m_blockedAssets = new List()
{ "Resources/unity_builtin_extra"};
public static bool BuildRemoteCatalog
{
get
{
#if PW_ADDRESSABLES && UNITY_EDITOR
if (DoSettingsExist())
{
return AddressableAssetSettingsDefaultObject.Settings.BuildRemoteCatalog;
}
else
{
return false;
}
#else
return false;
#endif
}
set
{
#if PW_ADDRESSABLES && UNITY_EDITOR
if (DoSettingsExist())
{
AddressableAssetSettingsDefaultObject.Settings.BuildRemoteCatalog = value;
}
#endif
}
}
// public static void AddPathAndSubdirectoriesToGroup(string groupName, string rootPath)
// {
//#if PW_ADDRESSABLES && UNITY_EDITOR
// AddressableAssetGroup group = AddressableAssetSettingsDefaultObject.Settings.FindGroup(groupName);
// if(group==null)
// {
// group = CreateGroup(groupName);
// }
// AddPathAndSubdirectories(group, rootPath);
//#endif
// }
// ///
// /// Adds the asset files of a single directory and its subdirectory to the target group configuration for this content pack.
// /// Uses recursion to get the subdirs.
// ///
// /// The group to add the assets to
// /// The path to recursively traverse to search for assets
//#if PW_ADDRESSABLES && UNITY_EDITOR
// private static void AddPathAndSubdirectories(AddressableAssetGroup group, string path)
// {
// DirectoryInfo dirInfo = new DirectoryInfo(path);
// DirectoryInfo[] allSubDirectories = dirInfo.GetDirectories();
// FileInfo[] allFiles = dirInfo.GetFiles();
// foreach (FileInfo fileInfo in allFiles)
// {
// if (fileInfo.Extension != ".meta" && fileInfo.Extension != ".cs")
// {
// AddGroupEntryByPath(group, fileInfo.FullName, true, true);
// }
// }
// foreach (DirectoryInfo subDir in allSubDirectories)
// {
// AddPathAndSubdirectories(group, subDir.FullName);
// }
// }
//#endif
///
/// Creates the initial addressable settings / database in this project.
///
public static void CreateSettings()
{
#if UNITY_EDITOR && PW_ADDRESSABLES
if (!AddressableAssetSettingsDefaultObject.SettingsExists)
{
AddressableAssetSettingsDefaultObject.Settings = AddressableAssetSettingsDefaultObject.GetSettings(true);
}
#endif
}
#if UNITY_EDITOR && PW_ADDRESSABLES
///
/// Creates a new addressable asset group with the given name and default schemas
///
/// The name of the group
///
public static AddressableAssetGroup CreateGroup(string name)
{
CreateSettings();
AddressableAssetGroup addressableAssetGroup = AddressableAssetSettingsDefaultObject.Settings.FindGroup(name);
if (addressableAssetGroup == null)
{
addressableAssetGroup = AddressableAssetSettingsDefaultObject.Settings.CreateGroup(name, false, false, false, new List(), new Type[0]);
}
//Make sure we are using the default schemas with the default settings
if (addressableAssetGroup != null)
{
addressableAssetGroup.RemoveSchema(typeof(BundledAssetGroupSchema));
addressableAssetGroup.RemoveSchema(typeof(ContentUpdateGroupSchema));
addressableAssetGroup.AddSchema(typeof(BundledAssetGroupSchema));
addressableAssetGroup.AddSchema(typeof(ContentUpdateGroupSchema));
}
return addressableAssetGroup;
}
#endif
///
/// Adds a scene as a group in the addressable settings. This allows deploying updates for this scene without affecting other scenes.
///
/// The name of the scene
/// The path of the scene
public static void AddSceneAsGroup(string name, string path)
{
#if UNITY_EDITOR && PW_ADDRESSABLES
AddressableAssetGroup addressableAssetGroup = CreateGroup(m_PWGroupNamePrefix + name);
//Add the scene as first entry of the group
AddGroupEntryByPath(addressableAssetGroup, path, true, false);
#endif
}
///
/// Adds the "official" PW default group and sets it as default group for the addressable configuration
///
public static void AddPWDefaultGroupIfNotExists()
{
#if UNITY_EDITOR && PW_ADDRESSABLES
CreateSettings();
string defaultGroupName = m_PWGroupNamePrefix + m_PWDefaultGroupName;
AddressableAssetGroup defaultGroup = AddressableAssetSettingsDefaultObject.Settings.FindGroup(defaultGroupName);
if (defaultGroup == null)
{
defaultGroup = CreateGroup(m_PWGroupNamePrefix + m_PWDefaultGroupName);
AddressableAssetSettingsDefaultObject.Settings.DefaultGroup = defaultGroup;
}
#endif
}
#if PW_ADDRESSABLES && UNITY_EDITOR
///
/// Adds an entry for an addressable path in the given group
///
/// The group that we want to add the entry to
/// The path to the addressable asset (Starting at the "Assets" folder)
/// Whether the addressable asset is allowed to be put into another group if it already exists
/// Whether the dependencies of the asset should be added as their own explicit reference as well. If false, dependent assets may still be pulled as implicit reference since this would be standard behavior when adding assets as addressables.
private static void AddGroupEntryByPath(AddressableAssetGroup addressableAssetGroup, string path, bool allowGroupChange = false, bool addExplicitDependencies = false)
{
if (String.IsNullOrEmpty(path) || addressableAssetGroup == null)
{
return;
}
if (m_blockedAssets.Contains(path))
{
return;
}
if (path.EndsWith(".cs"))
{
return;
}
AddressableAssetEntry entry = addressableAssetGroup.Settings.FindAssetEntry(AssetDatabase.AssetPathToGUID(path));
//only really add the entry if it does not exist yet, or if it is in the wrong group
if (entry == null || (entry.parentGroup != addressableAssetGroup && allowGroupChange))
{
entry = addressableAssetGroup.Settings.CreateOrMoveEntry(AssetDatabase.AssetPathToGUID(path), addressableAssetGroup);
if (entry != null)
{
entry.address = path;
if (addExplicitDependencies)
{
//Get all dependencies and add them as well - otherwise those assets will potentially be added as duplicates by implicit references in other bundles
string[] dependencyPaths = AssetDatabase.GetDependencies(path);
foreach (string dependencyPath in dependencyPaths)
{
if (addressableAssetGroup.GetAssetEntry(AssetDatabase.AssetPathToGUID(dependencyPath)) == null)
{
AddGroupEntryByPath(addressableAssetGroup, dependencyPath, true);
}
}
}
}
}
}
///
/// Adds an entry for an addressable object in the given group
///
/// The group that we want to add the entry to
/// The object that we want to add as an addressable
private static void AddGroupEntryByObject(AddressableAssetGroup addressableAssetGroup, UnityEngine.Object addressableObject)
{
if (addressableObject == null || addressableAssetGroup == null)
{
return;
}
string path = AssetDatabase.GetAssetPath(addressableObject);
if (path == "" && addressableObject.GetType() == typeof(GameObject))
{
//if this is a game object, it might be a prefab instance, let's try to get the original prefab instead
GameObject prefab = PrefabUtility.GetCorrespondingObjectFromOriginalSource((GameObject)addressableObject);
if (prefab != null)
{
path = AssetDatabase.GetAssetPath(prefab);
}
}
AddGroupEntryByPath(addressableAssetGroup, path, true, true);
}
#endif
#if PW_ADDRESSABLES && GAIA_2023
///
/// Adds the reosurces of a list of Gaia spawners as addressables, with one addressable group per spawner being created each.
///
/// The list of spawners to create addressable groups for.
public static void AddSpawnersAsGroups(List spawners)
{
foreach (Gaia.Spawner spawner in spawners)
{
AddGaiaSpawnerAsGroup(spawner);
}
}
#endif
#if GAIA_2023
///
/// Adds all resources that are found in a Gaia spawner to an individual addressable group. The group is recreated from scratch each time to make sure it only contains the latest content.
///
/// The Gaia spawner we want to add
public static void AddGaiaSpawnerAsGroup(Gaia.Spawner spawner)
{
#if UNITY_EDITOR && PW_ADDRESSABLES
string groupName = "PW Spawner " + spawner.name;
CreateSettings();
AddressableAssetGroup spawnerGroup = AddressableAssetSettingsDefaultObject.Settings.FindGroup(groupName);
//Remove Group to create it from scratch
//This ensures all the current assets in spawner are up to date in the group, and unused assets are being removed
if (spawnerGroup != null)
{
AddressableAssetSettingsDefaultObject.Settings.RemoveGroup(spawnerGroup);
}
spawnerGroup = CreateGroup(groupName);
foreach (Gaia.SpawnRule sr in spawner.m_settings.m_spawnerRules)
{
switch (sr.m_resourceType)
{
case Gaia.GaiaConstants.SpawnerResourceType.TerrainTexture:
Gaia.ResourceProtoTexture resourceProtoTexture = spawner.m_settings.m_resources.m_texturePrototypes[sr.m_resourceIdx];
AddGroupEntryByObject(spawnerGroup, resourceProtoTexture.m_texture);
AddGroupEntryByObject(spawnerGroup, resourceProtoTexture.m_normal);
AddGroupEntryByObject(spawnerGroup, resourceProtoTexture.m_maskmap);
//Try to add the layer as well if it exists
if (!String.IsNullOrEmpty(resourceProtoTexture.m_LayerGUID))
{
AddGroupEntryByPath(spawnerGroup, AssetDatabase.GUIDToAssetPath(resourceProtoTexture.m_LayerGUID));
}
break;
case Gaia.GaiaConstants.SpawnerResourceType.TerrainDetail:
Gaia.ResourceProtoDetail resourceProtoTerrainDetail = spawner.m_settings.m_resources.m_detailPrototypes[sr.m_resourceIdx];
AddGroupEntryByObject(spawnerGroup, resourceProtoTerrainDetail.m_detailTexture);
AddGroupEntryByObject(spawnerGroup, resourceProtoTerrainDetail.m_detailProtoype);
break;
case Gaia.GaiaConstants.SpawnerResourceType.TerrainModifierStamp:
//nothing to do here, there isn't a real resource that needs to be in the bundle as an addressable
break;
case Gaia.GaiaConstants.SpawnerResourceType.TerrainTree:
Gaia.ResourceProtoTree resourceProtoTree = spawner.m_settings.m_resources.m_treePrototypes[sr.m_resourceIdx];
AddGroupEntryByObject(spawnerGroup, resourceProtoTree.m_desktopPrefab);
break;
case Gaia.GaiaConstants.SpawnerResourceType.GameObject:
Gaia.ResourceProtoGameObject resourceProtoGameObject = spawner.m_settings.m_resources.m_gameObjectPrototypes[sr.m_resourceIdx];
foreach (ResourceProtoGameObjectInstance instance in resourceProtoGameObject.m_instances)
{
AddGroupEntryByObject(spawnerGroup, instance.m_desktopPrefab);
}
break;
case Gaia.GaiaConstants.SpawnerResourceType.SpawnExtension:
//nothing to do here, if the spawn extension creates addressable content in any way that would need to be handled by the spawn extension
break;
case Gaia.GaiaConstants.SpawnerResourceType.Probe:
//nothing to do here, there isn't a real resource that needs to be in the bundle as an addressable
//The data of the spawned probes should be part of the per-terrain bundles
break;
case Gaia.GaiaConstants.SpawnerResourceType.StampDistribution:
//nothing to do here, there isn't a real resource that needs to be in the bundle as an addressable
break;
case Gaia.GaiaConstants.SpawnerResourceType.WorldBiomeMask:
//nothing to do here, there isn't a real resource that needs to be in the bundle as an addressable
break;
}
}
#endif
}
#endif
///
/// Gets or creates the main assets group
///
///
#if PW_ADDRESSABLES && UNITY_EDITOR
public static AddressableAssetGroup GetOrCreateSharedGroup(string sharedGroupName)
{
return CreateGroup(sharedGroupName);
}
#endif
///
/// Collect all assets on this terrain (textures, trees, terrain details) and all game objects in the children for the addressable configuration
///
///
public static void AddTerrainAssets(Terrain t, string sharedGroupName)
{
if (t == null || t.terrainData == null)
{
return;
}
#if UNITY_EDITOR && PW_ADDRESSABLES
AddressableAssetGroup sharedGroup = GetOrCreateSharedGroup(sharedGroupName);
AddressableAssetGroup sceneGroup = null;
string guid = AssetDatabase.AssetPathToGUID(t.gameObject.scene.path);
if (guid != null)
{
AddressableAssetEntry sceneEntry = AddressableAssetSettingsDefaultObject.Settings.FindAssetEntry(guid);
if (sceneEntry != null)
{
sceneGroup = sceneEntry.parentGroup;
}
}
if (sceneGroup != null)
{
//add the terrain data object itself to the scene group
AddGroupEntryByPath(sceneGroup, AssetDatabase.GetAssetPath(t.terrainData), true, false);
//add all dependencies as explicit entries to the main asset groups (terrain shaders, etc.)
AddGroupEntryByPath(sharedGroup, AssetDatabase.GetAssetPath(t.terrainData), false, true);
AddGroupEntryByPath(sharedGroup, AssetDatabase.GetAssetPath(t.materialTemplate), false, true);
#if UPPipeline
//URP Terrain detail shaders
AddGroupEntryByPath(sharedGroup, "Packages/com.unity.render-pipelines.universal/Shaders/Terrain/TerrainDetailLit.shader", false, true);
AddGroupEntryByPath(sharedGroup, "Packages/com.unity.render-pipelines.universal/Shaders/Terrain/WavingGrass.shader", false, true);
AddGroupEntryByPath(sharedGroup, "Packages/com.unity.render-pipelines.universal/Shaders/Terrain/WavingGrassBillboard.shader", false, true);
//URP Mesh Terrain / Impostor shaders
AddGroupEntryByPath(sharedGroup, "Packages/com.unity.render-pipelines.universal/Shaders/Lit.shader", false, true);
AddGroupEntryByPath(sharedGroup, "Packages/com.unity.render-pipelines.universal/Shaders/Utils/FallbackError.shader", false, true);
#endif
}
//Adding all terrain assets. Note that dependencies to those objects will automatically be added as well deeper in the "AddGroupEntryByObject" function.
//Terrain Layers
for (int i = 0; i < t.terrainData.terrainLayers.Length; i++)
{
TerrainLayer layer = t.terrainData.terrainLayers[i];
if (layer != null)
{
AddTerrainObjectToSharedGroups(sharedGroup, sceneGroup, t, layer);
}
//AddGroupEntryByObject(mainAssetGroup, layer);
}
//Trees
for (int i = 0; i < t.terrainData.treePrototypes.Length; i++)
{
TreePrototype treeProto = t.terrainData.treePrototypes[i];
AddTerrainObjectToSharedGroups(sharedGroup, sceneGroup, t, treeProto.prefab);
//AddGroupEntryByObject(mainAssetGroup, treeProto.prefab);
}
//Terrain Details
for (int i = 0; i < t.terrainData.detailPrototypes.Length; i++)
{
DetailPrototype detailPrototype = t.terrainData.detailPrototypes[i];
if (detailPrototype.usePrototypeMesh)
{
AddTerrainObjectToSharedGroups(sharedGroup, sceneGroup, t, detailPrototype.prototype);
//AddGroupEntryByObject(mainAssetGroup, detailPrototype.prototype);
}
else
{
AddTerrainObjectToSharedGroups(sharedGroup, sceneGroup, t, detailPrototype.prototypeTexture);
//AddGroupEntryByObject(mainAssetGroup, detailPrototype.prototypeTexture);
}
}
//Game Objects
AddAllGameObjectChildsToAssetGroup(sharedGroup, sceneGroup, t, t.gameObject);
#endif
}
#if UNITY_EDITOR && PW_ADDRESSABLES
private static void AddTerrainObjectToSharedGroups(AddressableAssetGroup mainSharedGroup, AddressableAssetGroup sceneGroup, Terrain terrain, UnityEngine.Object objectToAdd)
{
string path = AssetDatabase.GetAssetPath(objectToAdd);
if (string.IsNullOrEmpty(path) && objectToAdd.GetType() == typeof(GameObject))
{
//if this is a game object, it might be a prefab instance, let's try to get the original prefab instead
GameObject prefab = PrefabUtility.GetCorrespondingObjectFromOriginalSource((GameObject)objectToAdd);
if (prefab != null)
{
path = AssetDatabase.GetAssetPath(prefab);
}
}
if (!string.IsNullOrEmpty(path))
{
string guid = AssetDatabase.AssetPathToGUID(path);
if (!string.IsNullOrEmpty(guid))
{
//Look up the object first, is it part of any group already?
AddressableAssetEntry existingEntry = AddressableAssetSettingsDefaultObject.Settings.FindAssetEntry(guid);
if (m_isUpdate)
{
//Update case is relatively simple, if there is no entry yet, the asset goes in the main asset group. If an entry already exists
//we leave the asset in peace, for the update scenario it is more desireable to not disturb the already existing config over changing all the bundles
//which would create unneccessary downloads for the user in the end.
if (existingEntry == null)
{
AddGroupEntryByObject(mainSharedGroup, objectToAdd);
}
}
else
{
if (existingEntry == null)
{
//no entry yet? This needs to go in the terrain / scene specific group
AddGroupEntryByObject(sceneGroup, objectToAdd);
//if it does not exist already, create an entry for this terrain <-> group association
if (TerrainAssetGroupAssociations.Find(x => x.m_assetGroup == sceneGroup.name) == null)
{
TerrainAssetGroupAssociations.Add(new TerrainAssetGroupAssociation() { m_assetGroup = sceneGroup.name, m_terrainNames = new List() { terrain.name } });
}
}
else
{
//we do have an entry, what group is it in?
if (existingEntry.parentGroup == sceneGroup || existingEntry.parentGroup == mainSharedGroup)
{
//Already in the terrain specific or main group, nothing to do anymore.
return;
}
else
{
TerrainAssetGroupAssociation assoc = TerrainAssetGroupAssociations.Find(x => x.m_assetGroup == existingEntry.parentGroup.name);
if (assoc != null)
{
if (assoc.m_terrainNames.Contains(terrain.name))
{
//already in a shared asset bundle with other terrains, nothing to do here
return;
}
else
{
//Asset is in a bundle together with other terrains, but excluding the one we are processing at the moment. We need to put it in the bundle that is shared with those other terrains
//INCLUDING the one that we are processing - if that does not exist, we need to create it.
List targetTerrainNames = new List(assoc.m_terrainNames);
targetTerrainNames.Add(terrain.name);
//We do have a target group already if the number of associated terrains is equal the number of target terrains AND the number of intersecting terrains is equal as well.
TerrainAssetGroupAssociation targetAssoc = TerrainAssetGroupAssociations.Find(x => x.m_terrainNames.Count == targetTerrainNames.Count && x.m_terrainNames.Intersect(targetTerrainNames).Count() == targetTerrainNames.Count());
if (targetAssoc != null)
{
//such a group exists already, put asset in there
AddressableAssetGroup targetGroup = AddressableAssetSettingsDefaultObject.Settings.FindGroup(targetAssoc.m_assetGroup);
if (targetGroup != null)
{
AddGroupEntryByObject(targetGroup, objectToAdd);
}
}
else
{
//This group does not exist yet and needs to be created & tracked in the list
string newGroupName = m_PWGroupNamePrefix + m_sharedAssetGroupPrefix + TerrainAssetGroupAssociations.FindAll(x => x.m_assetGroup.StartsWith(m_PWGroupNamePrefix + m_sharedAssetGroupPrefix)).Count.ToString();
AddressableAssetGroup targetGroup = CreateGroup(newGroupName);
if (targetGroup != null)
{
AddGroupEntryByObject(targetGroup, objectToAdd);
TerrainAssetGroupAssociations.Add(new TerrainAssetGroupAssociation() { m_assetGroup = targetGroup.name, m_terrainNames = targetTerrainNames });
}
}
}
}
else
{
//not in the shared terrain asset groups either? We should keep it as it is then, must be a content pack asset or user defined.
return;
}
}
}
}
}
}
}
#endif
///
/// Adds the Game Object (if an asset) to the given addressable asset group, but will also recursively try to add all children as well.
///
/// The Asset Group we are adding to.
/// The Game Object which we are adding
#if UNITY_EDITOR && PW_ADDRESSABLES
private static void AddAllGameObjectChildsToAssetGroup(AddressableAssetGroup mainAssetGroup, AddressableAssetGroup sceneGroup, Terrain t, GameObject go)
{
AddTerrainObjectToSharedGroups(mainAssetGroup, sceneGroup, t, go);
//AddGroupEntryByObject(assetGroup, go);
for (int i = 0; i < go.transform.childCount; i++)
{
AddAllGameObjectChildsToAssetGroup(mainAssetGroup, sceneGroup, t, go.transform.GetChild(i).gameObject);
}
}
#endif
///
/// Starts the addressable build process
///
public static void StartNewBuild()
{
#if UNITY_EDITOR && PW_ADDRESSABLES && GAIA_2023
//Clear out target directory first, to not mix with older files from previous builds
string path = GaiaDirectories.CreatePathIfDoesNotExist(GetRemoteBuildPath());
DirectoryInfo di = new DirectoryInfo(path);
if (!di.Exists)
{
Debug.LogError("Error while starting the addressable build: Could not find or create the path " + path);
return;
}
var allFiles = di.GetFiles();
for (int i = allFiles.Length-1; i > 0; i--)
{
FileUtil.DeleteFileOrDirectory(allFiles[i].FullName);
}
AddressableAssetSettings.BuildPlayerContent();
#endif
}
///
/// Starts the addressable build process
/// The .bin file that contains the current content state
///
public static void UpdateExistingBuild(UnityEngine.Object binFile)
{
#if UNITY_EDITOR && PW_ADDRESSABLES
if (binFile == null)
{
return;
}
string contentStatePath = AssetDatabase.GetAssetPath(binFile);
if (string.IsNullOrEmpty(contentStatePath))
{
Debug.LogError($"Could not determine path for .bin file {binFile.name} when trying to build an addressable content update.");
return;
}
ContentUpdateScript.BuildContentUpdate(AddressableAssetSettingsDefaultObject.Settings, contentStatePath);
#endif
}
///
/// Get the path to the current content state file (.bin file)
///
///
public static string GetContentStateDataPath()
{
#if UNITY_EDITOR && PW_ADDRESSABLES
return ContentUpdateScript.GetContentStateDataPath(false);
#else
return "";
#endif
}
///
/// Sets the remote load path in the current profile
///
///
public static void SetServerURL(string m_addressableServerURL)
{
#if UNITY_EDITOR && PW_ADDRESSABLES
AddressableAssetSettingsDefaultObject.Settings.profileSettings.SetValue(AddressableAssetSettingsDefaultObject.Settings.activeProfileId, AddressableAssetSettings.kRemoteLoadPath, m_addressableServerURL);
#endif
}
///
/// Switches all asset groups to remote build and load path
///
public static void SwitchToRemote()
{
#if UNITY_EDITOR && PW_ADDRESSABLES
if (DoSettingsExist())
{
AddressableAssetSettingsDefaultObject.Settings.BuildRemoteCatalog = true;
SwitchBuildAndLoadPath(AddressableAssetSettings.kRemoteBuildPath, AddressableAssetSettings.kRemoteLoadPath);
}
#endif
}
///
/// Switches all asset groups to local build and load path
///
public static void SwitchToLocal()
{
#if UNITY_EDITOR && PW_ADDRESSABLES
if (DoSettingsExist())
{
AddressableAssetSettingsDefaultObject.Settings.BuildRemoteCatalog = false;
SwitchBuildAndLoadPath(AddressableAssetSettings.kLocalBuildPath, AddressableAssetSettings.kLocalLoadPath);
}
#endif
}
///
/// Switches the build and load path in the bundle asset schema to the given names
///
///
///
private static void SwitchBuildAndLoadPath(string buildPathSettingName, string loadPathSettingName)
{
#if UNITY_EDITOR && PW_ADDRESSABLES
//Master Settings
AddressableAssetSettingsDefaultObject.Settings.RemoteCatalogBuildPath.SetVariableByName(AddressableAssetSettingsDefaultObject.Settings, buildPathSettingName);
AddressableAssetSettingsDefaultObject.Settings.RemoteCatalogLoadPath.SetVariableByName(AddressableAssetSettingsDefaultObject.Settings, loadPathSettingName);
//Individual Groups
foreach (var group in AddressableAssetSettingsDefaultObject.Settings.groups)
{
if (group.name.StartsWith(m_PWGroupNamePrefix))
{
int index = group.FindSchema(typeof(BundledAssetGroupSchema));
if (index >= 0 && index < group.Schemas.Count)
{
BundledAssetGroupSchema schema = (BundledAssetGroupSchema)group.Schemas[index];
if (schema != null)
{
schema.BuildPath.SetVariableByName(group.Settings, buildPathSettingName);
schema.LoadPath.SetVariableByName(group.Settings, loadPathSettingName);
}
}
}
}
#endif
}
///
/// Gets the folder containing the built addressable bundles for the local build mode
///
/// Path to the folder containing the built addressable bundles for the local build mode
public static string GetLocalBuildPath()
{
#if UNITY_EDITOR && PW_ADDRESSABLES
return GetPath(AddressableAssetSettings.kLocalBuildPath);
#else
return "";
#endif
}
///
/// Gets the folder containing the built addressable bundles for the server build mode
///
/// Path to the folder containing the built addressable bundles for the server build mode
public static string GetRemoteBuildPath()
{
#if UNITY_EDITOR && PW_ADDRESSABLES
return GetPath(AddressableAssetSettings.kRemoteBuildPath);
#else
return "";
#endif
}
///
/// Gets one of the different Path variables from the current Addressable Profile, also evaluating dynamic variables like [BuildTarget]
///
///
///
public static string GetPath(string variableName)
{
#if UNITY_EDITOR && PW_ADDRESSABLES
string pathWithVariables = AddressableAssetSettingsDefaultObject.Settings.profileSettings.GetValueByName(AddressableAssetSettingsDefaultObject.Settings.activeProfileId, variableName);
return AddressableAssetSettingsDefaultObject.Settings.profileSettings.EvaluateString(AddressableAssetSettingsDefaultObject.Settings.activeProfileId, pathWithVariables);
#else
return "";
#endif
}
public static void OpenAddressableSettingsWindow()
{
#if UNITY_EDITOR && PW_ADDRESSABLES
//The editor window class for the addressable settings is a private class, so we need to open the window via calling its menu entry
EditorApplication.ExecuteMenuItem("Window/Asset Management/Addressables/Groups");
#endif
}
public static void CreatePWProfile()
{
#if UNITY_EDITOR && PW_ADDRESSABLES
CreateSettings();
var profileSettings = AddressableAssetSettingsDefaultObject.Settings.profileSettings;
List allProfileNames = profileSettings.GetAllProfileNames();
if (!allProfileNames.Contains(m_PWProfileName))
{
profileSettings.AddProfile(m_PWProfileName, null);
}
AddressableAssetSettingsDefaultObject.Settings.activeProfileId = profileSettings.GetProfileId(m_PWProfileName);
#endif
}
///
/// Adds all entries in this content pack configuration file to a group with the matching name and configures it accordingly.
///
///
public static void AddContentPackConfig(PWAddressableContentPackConfig config)
{
#if UNITY_EDITOR && PW_ADDRESSABLES
//Remove the group if it exists - we need to make sure group only contains the current entries of the config file
AddressableAssetGroup group = AddressableAssetSettingsDefaultObject.Settings.FindGroup(config.m_contentPackName);
if (group != null)
{
AddressableAssetSettingsDefaultObject.Settings.RemoveGroup(group);
}
group = CreateGroup(config.m_contentPackName);
//Apply the content pack configuration
//Check first if we have an entry for the package location in the Addressable Profile
CreatePWProfile();
var profileSettings = AddressableAssetSettingsDefaultObject.Settings.profileSettings;
string variableName = config.m_contentPackName + "LoadPath";
if (profileSettings.GetVariableNames().Find(x => x == variableName) == null)
{
profileSettings.CreateValue(variableName, config.m_URL);
}
else
{
profileSettings.SetValue(profileSettings.GetProfileId(m_PWProfileName), variableName, config.m_URL);
}
int index = group.FindSchema(typeof(BundledAssetGroupSchema));
if (index >= 0 && index < group.Schemas.Count)
{
BundledAssetGroupSchema schema = (BundledAssetGroupSchema)group.Schemas[index];
if (schema != null)
{
schema.BuildPath.SetVariableByName(group.Settings, AddressableAssetSettings.kRemoteBuildPath);
//schema.LoadPath.SetVariableByName(group.Settings, variableName);
schema.UseAssetBundleCrc = false;
schema.UseAssetBundleCrcForCachedBundles = false;
schema.BundleNaming = BundledAssetGroupSchema.BundleNamingStyle.FileNameHash;
var so = new SerializedObject(schema);
var prop = so.FindProperty("m_LoadPath");
var prop2 = prop.FindPropertyRelative("m_Id");
prop2.stringValue = config.m_URL;
so.ApplyModifiedProperties();
EditorUtility.SetDirty(schema);
}
}
//Add all asset entries to group
foreach (PWAddressableContentPackEntry entry in config.m_assetEntries)
{
AddGroupEntryByPath(group, entry.m_path, true, false);
}
#endif
}
///
/// Clears groups with the PW prefix that do not contain any assets in them - repeated creation of the configuration can bloat the config with empty groups
///
public static void ClearEmptyGroups()
{
#if UNITY_EDITOR && PW_ADDRESSABLES
for (int i = AddressableAssetSettingsDefaultObject.Settings.groups.Count - 1; i >= 0; i--)
{
if (AddressableAssetSettingsDefaultObject.Settings.groups[i].name.StartsWith(m_PWGroupNamePrefix) &&
AddressableAssetSettingsDefaultObject.Settings.groups[i].entries.Count<=0 &&
!AddressableAssetSettingsDefaultObject.Settings.groups[i].name.Contains(m_PWGroupNamePrefix+m_PWDefaultGroupName)) //Exception for the Default group - that one is allowed to remain if empty!
{
AddressableAssetSettingsDefaultObject.Settings.RemoveGroup(AddressableAssetSettingsDefaultObject.Settings.groups[i]);
}
}
#endif
}
//Removes all groups with the PW prefix
public static void RemoveAllPWGroups()
{
#if UNITY_EDITOR && PW_ADDRESSABLES
for (int i = AddressableAssetSettingsDefaultObject.Settings.groups.Count - 1; i >= 0; i--)
{
if (AddressableAssetSettingsDefaultObject.Settings.groups[i].name.StartsWith(m_PWGroupNamePrefix))
{
AddressableAssetSettingsDefaultObject.Settings.RemoveGroup(AddressableAssetSettingsDefaultObject.Settings.groups[i]);
}
}
#endif
}
///
/// Returns a special name for a shared group belonging to a master scene. This is the group where shared assets will be added to when the configuration is updated.
///
///
///
public static string CreateSharedGroupName(Scene masterScene)
{
return m_PWGroupNamePrefix + "-" + masterScene.name + "-" + string.Format("-{0:yyyyMMdd - HHmmss}", DateTime.Now) + "-Shared";
}
///
/// Returns true if a PW Addressable configuration has been created for this project at some point.
///
///
public static bool HasConfig()
{
#if UNITY_EDITOR && PW_ADDRESSABLES
if (!AddressableAssetSettingsDefaultObject.SettingsExists)
{
return false;
}
for (int i = AddressableAssetSettingsDefaultObject.Settings.groups.Count - 1; i >= 0; i--)
{
if (AddressableAssetSettingsDefaultObject.Settings.groups[i].name.StartsWith(m_PWGroupNamePrefix))
{
return true;
}
}
#endif
return false;
}
}
}