using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.AddressableAssets.Settings.GroupSchemas;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.SmartFormat;
using Object = UnityEngine.Object;
namespace UnityEditor.Localization.Addressables
{
///
/// Provides support for placing assets into different s.
///
///
/// This example places all English assets into a local group and all other languages into a remote group which could then be downloaded after the game is released.
///
///
[Serializable]
public class GroupResolver
{
[Serializable]
class LocaleGroupPair
{
public LocaleIdentifier localeIdentifier;
public AddressableAssetGroup group;
}
[SerializeField] string m_SharedGroupName = "Localization-Assets-Shared";
[SerializeField] AddressableAssetGroup m_SharedGroup;
[SerializeField] string m_LocaleGroupNamePattern = "Localization-Assets-{LocaleName}";
[SerializeField] List m_LocaleGroups = new List();
[SerializeField] bool m_MarkEntriesReadOnly = true;
///
/// The name to use when creating a new group.
///
public string SharedGroupName { get => m_SharedGroupName; set => m_SharedGroupName = value; }
///
/// The group to use for shared assets. If null then a new group will be created using .
///
public AddressableAssetGroup SharedGroup { get => m_SharedGroup; set => m_SharedGroup = value; }
///
/// The name to use when generating a new for a .
///
public string LocaleGroupNamePattern { get => m_LocaleGroupNamePattern; set => m_LocaleGroupNamePattern = value; }
///
/// Should new Entries be marked as read only? This will prevent editing them in the Addressables window.
///
public bool MarkEntriesReadOnly { get => m_MarkEntriesReadOnly; set => m_MarkEntriesReadOnly = value; }
///
/// Creates a new default instance of .
///
public GroupResolver() {}
///
/// Creates a new instance which will store all assets into a single group.
///
/// The name to use when creating a new group.
public GroupResolver(string groupName)
{
m_SharedGroupName = groupName;
m_LocaleGroupNamePattern = groupName;
}
///
/// Creates a new instance which will store all assets into a single group;
///
/// The group to use.
public GroupResolver(AddressableAssetGroup group)
{
m_SharedGroup = group;
m_SharedGroupName = group.Name;
m_LocaleGroupNamePattern = group.Name;
}
///
/// Creates an instance using custom group names for each Locale.
///
/// The name to use when creating a new Group for a selected . The name is formatted using where the argument passed will be the . The default is "Localization-Assets-{Code}"."
/// The name of the group to use when an asset is shared by multiple Locales that do not all use the same group.
public GroupResolver(string localeGroupNamePattern, string sharedGroupName)
{
m_SharedGroupName = sharedGroupName;
m_LocaleGroupNamePattern = localeGroupNamePattern;
}
///
/// Add a group for the . If a Group is already assigned it will be replaced with the new group.
///
/// The Locale Id to use for the selected group.
/// The group to add for the selected Locale Id, must not be .
public void AddLocaleGroup(LocaleIdentifier identifier, AddressableAssetGroup group)
{
if (group == null)
throw new ArgumentNullException(nameof(group));
var expectedGroupPair = m_LocaleGroups.FirstOrDefault(g => g.localeIdentifier == identifier);
if (expectedGroupPair == null)
{
expectedGroupPair = new LocaleGroupPair { localeIdentifier = identifier };
m_LocaleGroups.Add(expectedGroupPair);
}
expectedGroupPair.group = group;
}
///
/// Removes the group for the chosen Locale Id.
///
/// The Locale Id to be removed.
/// Returns if an item was removed.
public bool RemoveLocaleGroup(LocaleIdentifier identifier) => m_LocaleGroups.RemoveAll(g => g.localeIdentifier == identifier) > 0;
///
/// Returns the active group assigned to the or if one has not been assigned.
///
/// The Locale Id to search for.
///
public AddressableAssetGroup GetLocaleGroup(LocaleIdentifier identifier) => m_LocaleGroups.FirstOrDefault(g => g.localeIdentifier == identifier)?.group;
///
/// Add an asset to an .
/// The asset will be moved into a group which either matches or .
///
/// The asset to be added to a group.
/// List of locales that depend on this asset or null if the asset is used by all.
/// The Addressables setting that will be used if a new group should be added.
/// Should an Undo record be created?
/// The asset entry for the added asset.
public virtual AddressableAssetEntry AddToGroup(Object asset, IList locales, AddressableAssetSettings aaSettings, bool createUndo)
{
var group = SharedGroup ?? GetGroup(locales, asset, aaSettings, createUndo);
var guid = GetAssetGuid(asset);
var assetEntry = aaSettings.FindAssetEntry(guid);
if (assetEntry == null)
{
if (createUndo)
Undo.RecordObjects(new Object[] { aaSettings, group }, "Add to group");
assetEntry = aaSettings.CreateOrMoveEntry(guid, group, MarkEntriesReadOnly);
}
else
{
// TODO: We may want to provide an option to leave assets that are in unknown groups here. We would need to figure out a way to know what is a known group and what is not.
if (createUndo)
Undo.RecordObjects(new Object[] { aaSettings, group, assetEntry.parentGroup }, "Add to group");
aaSettings.MoveEntry(assetEntry, group, MarkEntriesReadOnly);
}
return assetEntry;
}
///
/// Returns the name of the group that the asset will be moved to when calling .
///
///
///
///
///
public virtual string GetExpectedGroupName(IList locales, Object asset, AddressableAssetSettings aaSettings)
{
if (locales == null || locales.Count == 0)
return GetExpectedSharedGroupName(locales, asset, aaSettings);
Locale locale;
if (asset is Locale l && l.Identifier == locales[0])
locale = l;
else
locale = LocalizationEditorSettings.GetLocale(locales[0].Code) ?? Locale.CreateLocale(locales[0]);
var expectedGroupPair = m_LocaleGroups.FirstOrDefault(g => g.localeIdentifier == locales[0]);
var expectedGroupName = expectedGroupPair?.group != null ? expectedGroupPair.group.Name : Smart.Format(LocaleGroupNamePattern, locale, asset);
for (var i = 1; i < locales.Count; ++i)
{
var groupPair = m_LocaleGroups.FirstOrDefault(g => g.localeIdentifier == locales[i]);
locale = LocalizationEditorSettings.GetLocale(locales[i]);
var groupName = groupPair?.group != null ? groupPair.group.Name : Smart.Format(LocaleGroupNamePattern, locale, asset);
if (expectedGroupName != groupName)
{
// Use shared group
return GetExpectedSharedGroupName(locales, asset, aaSettings);
}
}
return expectedGroupName;
}
///
/// Returns the name that the Shared group is expected to have.
///
///
///
///
///
protected virtual string GetExpectedSharedGroupName(IList locales, Object asset, AddressableAssetSettings aaSettings)
{
return SharedGroup != null ? SharedGroup.Name : SharedGroupName;
}
///
/// Returns the Addressable group for the asset.
///
/// The locales that depend on the asset.
/// The asset that is to be added to an Addressable group.
/// The Addressable asset settings.
/// Should an Undo record be created if changes are made?
///
protected virtual AddressableAssetGroup GetGroup(IList locales, Object asset, AddressableAssetSettings aaSettings, bool createUndo)
{
var groupName = GetExpectedGroupName(locales, asset, aaSettings);
return FindOrCreateGroup(groupName, aaSettings, createUndo);
}
AddressableAssetGroup FindOrCreateGroup(string name, AddressableAssetSettings aaSettings, bool createUndo) => aaSettings.FindGroup(name) ?? CreateNewGroup(name, MarkEntriesReadOnly, aaSettings, createUndo);
static AddressableAssetGroup CreateNewGroup(string name, bool readOnly, AddressableAssetSettings aaSettings, bool createUndo)
{
if (createUndo)
Undo.RecordObject(aaSettings, "Create group");
var group = aaSettings.CreateGroup(name, false, readOnly, true, null, typeof(BundledAssetGroupSchema), typeof(ContentUpdateGroupSchema));
var schema = group.GetSchema();
// Don't use hash as it creates very long file names that can cause issues on Windows.
schema.BundleNaming = BundledAssetGroupSchema.BundleNamingStyle.NoHash;
if (createUndo)
Undo.RegisterCreatedObjectUndo(group, "Create group");
return group;
}
AddressableAssetEntry GetAssetEntry(Object asset, AddressableAssetSettings aaSettings) => aaSettings.FindAssetEntry(GetAssetGuid(asset));
static string GetAssetGuid(Object asset)
{
if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(asset, out var guid, out long _))
return guid;
Debug.LogError("Failed to extract the asset Guid for " + asset.name, asset);
return null;
}
}
}