#if UNITY_EDITOR using System; using System.Collections.Generic; using System.IO; using System.Text; using UnityEditor; using UnityEditor.AddressableAssets; using UnityEditor.AddressableAssets.Settings; using UnityEditor.AddressableAssets.Settings.GroupSchemas; using UnityEngine; /// /// Configura os arquivos de Assets/Jogos como Addressables remotos sem mover nenhum arquivo. /// Compatível com Unity 2019.3 e Addressables 1.1.x. /// public static class ConfigurarJogosAddressables { private const string JogosRoot = "Assets/Jogos"; private const string GroupPrefix = "RemoteJogo_"; private const string LabelPrefix = "jogo_"; [MenuItem("Tools/Zoe/Configurar Assets-Jogos como Addressables")] public static void Configurar() { if (EditorApplication.isPlayingOrWillChangePlaymode) { Debug.LogError("Saia do Play Mode antes de configurar os Addressables."); return; } if (!AssetDatabase.IsValidFolder(JogosRoot)) { Debug.LogError("A pasta não foi encontrada: " + JogosRoot); return; } AddressableAssetSettings settings = AddressableAssetSettingsDefaultObject.GetSettings(true); if (settings == null) { Debug.LogError("Não foi possível criar ou carregar AddressableAssetSettings."); return; } Dictionary groups = new Dictionary(); string[] guids = AssetDatabase.FindAssets(string.Empty, new[] { JogosRoot }); int configured = 0; int ignored = 0; int errors = 0; try { AssetDatabase.StartAssetEditing(); for (int i = 0; i < guids.Length; i++) { string guid = guids[i]; string path = AssetDatabase.GUIDToAssetPath(guid).Replace('\\', '/'); EditorUtility.DisplayProgressBar( "Configurando Assets/Jogos", path, guids.Length == 0 ? 1f : (float)i / guids.Length ); if (ShouldIgnore(path)) { ignored++; continue; } string gameKey = GetTopLevelFolder(path); string safeGameKey = Sanitize(gameKey); string groupName = GroupPrefix + safeGameKey; string labelName = LabelPrefix + safeGameKey.ToLowerInvariant(); AddressableAssetGroup group; if (!groups.TryGetValue(groupName, out group)) { group = GetOrCreateRemoteGroup(settings, groupName); groups.Add(groupName, group); settings.AddLabel(labelName); } try { AddressableAssetEntry entry = settings.CreateOrMoveEntry(guid, group, false, false); if (entry == null) { errors++; Debug.LogError("Falha ao criar entrada Addressable: " + path); continue; } entry.address = BuildAddress(path); entry.SetLabel(labelName, true, true); configured++; } catch (Exception ex) { errors++; Debug.LogError("Erro em " + path + ": " + ex.Message); } } } finally { AssetDatabase.StopAssetEditing(); EditorUtility.ClearProgressBar(); } settings.BuildRemoteCatalog = true; settings.RemoteCatalogBuildPath.SetVariableByName( settings, AddressableAssetSettings.kRemoteBuildPath ); settings.RemoteCatalogLoadPath.SetVariableByName( settings, AddressableAssetSettings.kRemoteLoadPath ); EditorUtility.SetDirty(settings); foreach (AddressableAssetGroup group in groups.Values) EditorUtility.SetDirty(group); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate); string result = "Configuração concluída." + "\n\nGrupos criados/atualizados: " + groups.Count + "\nAssets configurados: " + configured + "\nArquivos ignorados: " + ignored + "\nErros: " + errors + "\n\nNenhum arquivo foi movido de Assets/Jogos."; Debug.Log(result.Replace("\n", " | ")); EditorUtility.DisplayDialog("Addressables - Assets/Jogos", result, "OK"); } [MenuItem("Tools/Zoe/Validar Assets-Jogos Addressables")] public static void Validar() { AddressableAssetSettings settings = AddressableAssetSettingsDefaultObject.Settings; if (settings == null) { Debug.LogError("AddressableAssetSettings não encontrado."); return; } int groups = 0; int entries = 0; int withoutLabel = 0; StringBuilder details = new StringBuilder(); foreach (AddressableAssetGroup group in settings.groups) { if (group == null || !group.Name.StartsWith(GroupPrefix)) continue; groups++; int groupEntries = 0; foreach (AddressableAssetEntry entry in group.entries) { if (entry == null) continue; entries++; groupEntries++; bool hasGameLabel = false; foreach (string label in entry.labels) { if (label.StartsWith(LabelPrefix)) { hasGameLabel = true; break; } } if (!hasGameLabel) withoutLabel++; } details.AppendLine(group.Name + ": " + groupEntries + " assets"); } string message = "Grupos: " + groups + "\nEntradas: " + entries + "\nSem label jogo_: " + withoutLabel + "\n\n" + details; Debug.Log(message.Replace("\n", " | ")); EditorUtility.DisplayDialog("Validação Assets/Jogos", message, "OK"); } private static AddressableAssetGroup GetOrCreateRemoteGroup( AddressableAssetSettings settings, string groupName) { AddressableAssetGroup group = settings.FindGroup(groupName); if (group == null) { group = settings.CreateGroup( groupName, false, false, false, null, typeof(BundledAssetGroupSchema), typeof(ContentUpdateGroupSchema) ); } BundledAssetGroupSchema bundled = group.GetSchema(); if (bundled == null) bundled = group.AddSchema(); bundled.BuildPath.SetVariableByName( settings, AddressableAssetSettings.kRemoteBuildPath ); bundled.LoadPath.SetVariableByName( settings, AddressableAssetSettings.kRemoteLoadPath ); bundled.BundleMode = BundledAssetGroupSchema.BundlePackingMode.PackTogether; ContentUpdateGroupSchema update = group.GetSchema(); if (update == null) group.AddSchema(); EditorUtility.SetDirty(bundled); return group; } private static bool ShouldIgnore(string path) { if (string.IsNullOrEmpty(path)) return true; if (AssetDatabase.IsValidFolder(path)) return true; Type type = AssetDatabase.GetMainAssetTypeAtPath(path); if (type == typeof(MonoScript)) return true; string extension = Path.GetExtension(path).ToLowerInvariant(); return extension == ".cs" || extension == ".dll" || extension == ".asmdef" || extension == ".asmref" || extension == ".meta"; } private static string GetTopLevelFolder(string path) { string relative = path.Substring(JogosRoot.Length).TrimStart('/'); int slash = relative.IndexOf('/'); if (slash <= 0) return "Comum"; return relative.Substring(0, slash); } private static string BuildAddress(string path) { string relative = path.Substring(JogosRoot.Length).TrimStart('/'); string extension = Path.GetExtension(relative); if (!string.IsNullOrEmpty(extension)) relative = relative.Substring(0, relative.Length - extension.Length); return "Jogos/" + relative.Replace('\\', '/'); } private static string Sanitize(string value) { if (string.IsNullOrEmpty(value)) return "Comum"; StringBuilder result = new StringBuilder(value.Length); for (int i = 0; i < value.Length; i++) { char c = value[i]; result.Append(char.IsLetterOrDigit(c) || c == '_' || c == '-' ? c : '_'); } return result.ToString(); } } #endif