internal IEnumerable <CheckDupeResult> CalculateDuplicates(Dictionary <GUID, List <string> > implicitGuids, AddressableAssetsBuildContext aaContext) { //Get all guids that have more than one bundle referencing them IEnumerable <KeyValuePair <GUID, List <string> > > validGuids = from dupeGuid in implicitGuids where dupeGuid.Value.Distinct().Count() > 1 where IsValidPath(AssetDatabase.GUIDToAssetPath(dupeGuid.Key.ToString())) select dupeGuid; return (from guidToFile in validGuids from file in guidToFile.Value //Get the files that belong to those guids let fileToBundle = m_ExtractData.WriteData.FileToBundle[file] //Get the bundles that belong to those files let bundleToGroup = aaContext.bundleToAssetGroup[fileToBundle] //Get the asset groups that belong to those bundles let selectedGroup = aaContext.Settings.FindGroup(findGroup => findGroup != null && findGroup.Guid == bundleToGroup) select new CheckDupeResult { Group = selectedGroup, DuplicatedFile = file, AssetPath = AssetDatabase.GUIDToAssetPath(guidToFile.Key.ToString()), DuplicatedGroupGuid = guidToFile.Key }); }
private BuildLayout CreateBuildLayout() { LayoutLookupTables lookup = new LayoutLookupTables(); foreach (string bundleName in m_WriteData.FileToBundle.Values.Distinct()) { BuildLayout.Bundle bundle = new BuildLayout.Bundle(); bundle.Name = bundleName; string path = m_Parameters.GetOutputFilePathForIdentifier(bundle.Name); UnityEngine.BuildCompression compression = m_Parameters.GetCompressionForIdentifier(bundle.Name); bundle.FileSize = (ulong)new FileInfo(path).Length; bundle.Compression = compression.compression.ToString(); lookup.Bundles.Add(bundle.Name, bundle); } // create files foreach (KeyValuePair <string, string> fileBundle in m_WriteData.FileToBundle) { BuildLayout.Bundle bundle = lookup.Bundles[fileBundle.Value]; BuildLayout.File f = new BuildLayout.File(); f.Name = fileBundle.Key; WriteResult result = m_Results.WriteResults[f.Name]; foreach (ResourceFile rf in result.resourceFiles) { var sf = new BuildLayout.SubFile(); sf.IsSerializedFile = rf.serializedFile; sf.Name = rf.fileAlias; sf.Size = (ulong)new FileInfo(rf.fileName).Length; f.SubFiles.Add(sf); } bundle.Files.Add(f); lookup.Files.Add(f.Name, f); } // create assets foreach (KeyValuePair <GUID, List <string> > assetFile in m_WriteData.AssetToFiles) { BuildLayout.File file = lookup.Files[assetFile.Value[0]]; BuildLayout.ExplicitAsset a = new BuildLayout.ExplicitAsset(); a.Guid = assetFile.Key.ToString(); a.AssetPath = AssetDatabase.GUIDToAssetPath(a.Guid); file.Assets.Add(a); lookup.GuidToExplicitAsset.Add(a.Guid, a); } Dictionary <string, List <BuildLayout.DataFromOtherAsset> > guidToPulledInBuckets = new Dictionary <string, List <BuildLayout.DataFromOtherAsset> >(); foreach (BuildLayout.File file in lookup.Files.Values) { Dictionary <string, AssetBucket> buckets = new Dictionary <string, AssetBucket>(); WriteResult writeResult = m_Results.WriteResults[file.Name]; List <ObjectSerializedInfo> sceneObjects = new List <ObjectSerializedInfo>(); foreach (ObjectSerializedInfo info in writeResult.serializedObjects) { string sourceGuid = string.Empty; if (info.serializedObject.guid.Empty()) { if (info.serializedObject.filePath.Equals("temp:/assetbundle", StringComparison.OrdinalIgnoreCase)) { file.BundleObjectInfo = new BuildLayout.AssetBundleObjectInfo(); file.BundleObjectInfo.Size = info.header.size; continue; } else if (info.serializedObject.filePath.StartsWith("temp:/preloaddata", StringComparison.OrdinalIgnoreCase)) { file.PreloadInfoSize = (int)info.header.size; continue; } else if (info.serializedObject.filePath.StartsWith("temp:/", StringComparison.OrdinalIgnoreCase)) { sceneObjects.Add(info); continue; } else if (!string.IsNullOrEmpty(info.serializedObject.filePath)) { AssetBucket pathBucket = GetOrCreate(buckets, info.serializedObject.filePath.ToString()); pathBucket.isFilePathBucket = true; pathBucket.objs.Add(info); continue; } } AssetBucket bucket = GetOrCreate(buckets, info.serializedObject.guid.ToString()); bucket.objs.Add(info); } if (sceneObjects.Count > 0) { BuildLayout.ExplicitAsset sceneAsset = file.Assets.First(x => x.AssetPath.EndsWith(".unity")); AssetBucket bucket = GetOrCreate(buckets, sceneAsset.Guid); bucket.objs.AddRange(sceneObjects); } // Update buckets with a reference to their explicit asset file.Assets.ForEach(eAsset => { if (!buckets.TryGetValue(eAsset.Guid, out AssetBucket b)) { b = GetOrCreate(buckets, eAsset.Guid); // some assets might not pull in any objects } b.ExplictAsset = eAsset; }); // Create entries for buckets that are implicitly pulled in Dictionary <string, BuildLayout.DataFromOtherAsset> guidToOtherData = new Dictionary <string, BuildLayout.DataFromOtherAsset>(); foreach (AssetBucket bucket in buckets.Values.Where(x => x.ExplictAsset == null)) { string assetPath = bucket.isFilePathBucket ? bucket.guid : AssetDatabase.GUIDToAssetPath(bucket.guid); if (assetPath.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) || assetPath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) { file.MonoScriptCount++; file.MonoScriptSize += bucket.CalcObjectSize(); continue; } var otherData = new BuildLayout.DataFromOtherAsset(); otherData.AssetPath = assetPath; otherData.AssetGuid = bucket.guid; otherData.SerializedSize = bucket.CalcObjectSize(); otherData.StreamedSize = bucket.CalcStreamedSize(); otherData.ObjectCount = bucket.objs.Count; file.OtherAssets.Add(otherData); guidToOtherData[otherData.AssetGuid] = otherData; if (!guidToPulledInBuckets.TryGetValue(otherData.AssetGuid, out List <BuildLayout.DataFromOtherAsset> bucketList)) { bucketList = guidToPulledInBuckets[otherData.AssetGuid] = new List <BuildLayout.DataFromOtherAsset>(); } bucketList.Add(otherData); } // Add references foreach (BuildLayout.ExplicitAsset asset in file.Assets) { AssetBucket bucket = buckets[asset.Guid]; asset.SerializedSize = bucket.CalcObjectSize(); asset.StreamedSize = bucket.CalcStreamedSize(); IEnumerable <ObjectIdentifier> refs = null; if (m_DependencyData.AssetInfo.TryGetValue(new GUID(asset.Guid), out AssetLoadInfo info)) { refs = info.referencedObjects; } else { refs = m_DependencyData.SceneInfo[new GUID(asset.Guid)].referencedObjects; } foreach (string refGUID in refs.Select(x => x.guid.Empty() ? x.filePath : x.guid.ToString()).Distinct()) { if (guidToOtherData.TryGetValue(refGUID, out BuildLayout.DataFromOtherAsset dfoa)) { dfoa.ReferencingAssets.Add(asset); asset.InternalReferencedOtherAssets.Add(dfoa); } else if (buckets.TryGetValue(refGUID, out AssetBucket refBucket)) { asset.InternalReferencedExplicitAssets.Add(refBucket.ExplictAsset); } else if (lookup.GuidToExplicitAsset.TryGetValue(refGUID, out BuildLayout.ExplicitAsset refAsset)) { asset.ExternallyReferencedAssets.Add(refAsset); } } } } BuildLayout layout = new BuildLayout(); // This is the addressables section. Everything above could technically be moved to SBP. { AddressableAssetsBuildContext aaContext = (AddressableAssetsBuildContext)m_AaBuildContext; // Map from GUID to AddrssableAssetEntry Dictionary <string, AddressableAssetEntry> guidToEntry = aaContext.assetEntries.ToDictionary(x => x.guid, x => x); // create groups foreach (AddressableAssetGroup group in aaContext.Settings.groups) { var grp = new BuildLayout.Group(); grp.Name = group.Name; grp.Guid = group.Guid; foreach (AddressableAssetGroupSchema schema in group.Schemas) { var sd = new BuildLayout.SchemaData(); sd.Guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(schema)); sd.Type = schema.GetType().Name.ToString(); BundledAssetGroupSchema bSchema = schema as BundledAssetGroupSchema; if (bSchema != null) { sd.KvpDetails.Add(new Tuple <string, string>("PackingMode", bSchema.BundleMode.ToString())); sd.KvpDetails.Add(new Tuple <string, string>("Compression", bSchema.Compression.ToString())); } grp.Schemas.Add(sd); } lookup.GroupLookup.Add(group.Guid, grp); layout.Groups.Add(grp); } // go through all the bundles and put them in groups foreach (BuildLayout.Bundle b in lookup.Bundles.Values) { if (aaContext.bundleToImmediateBundleDependencies.TryGetValue(b.Name, out List <string> deps)) { b.Dependencies = deps.Select(x => lookup.Bundles[x]).Where(x => b != x).ToList(); } if (aaContext.bundleToExpandedBundleDependencies.TryGetValue(b.Name, out List <string> deps2)) { b.ExpandedDependencies = deps2.Select(x => lookup.Bundles[x]).Where(x => b != x).ToList(); } if (aaContext.bundleToAssetGroup.TryGetValue(b.Name, out string grpName)) { lookup.GroupLookup[grpName].Bundles.Add(b); } else { layout.BuiltInBundles.Add(b); } } // Apply the addressable name to the asset foreach (BuildLayout.ExplicitAsset a in BuildLayoutHelpers.EnumerateAssets(layout)) { if (guidToEntry.TryGetValue(a.Guid, out AddressableAssetEntry entry)) { a.AddressableName = entry.address; } } // The addressables build script can rename the bundles foreach (BuildLayout.Bundle b in BuildLayoutHelpers.EnumerateBundles(layout)) { if (m_BundleNameRemap.TryGetValue(b.Name, out string newName)) { b.Name = newName; } } } return(layout); }
string ProcessTextureScaler(TextureVariationSchema schema, AddressableAssetGroup assetGroup, AddressableAssetsBuildContext aaContext) { m_SourceGroupList.Add(assetGroup); var entries = new List <AddressableAssetEntry>(assetGroup.entries);//获取对应Group下面的所有条目 foreach (var entry in entries) { var entryPath = entry.AssetPath; if (AssetDatabase.GetMainAssetTypeAtPath(entryPath) == typeof(Texture2D)) { var fileName = Path.GetFileNameWithoutExtension(entryPath); if (string.IsNullOrEmpty(fileName)) { return("Failed to get file name for: " + entryPath); } if (!Directory.Exists("Assets/GeneratedTextures")) { Directory.CreateDirectory("Assets/GeneratedTextures"); } if (!Directory.Exists("Assets/GeneratedTextures/Texture")) { Directory.CreateDirectory("Assets/GeneratedTextures/Texture"); } var sourceTex = AssetDatabase.LoadAssetAtPath <Texture2D>(entryPath); var aiSource = AssetImporter.GetAtPath(entryPath) as TextureImporter; int maxDim = Math.Max(sourceTex.width, sourceTex.height); foreach (var pair in schema.Variations)//根据对应的策略开始处理 (现在是根据这一张图片分别设置不同的标签) { var newGroup = FindOrCopyGroup(assetGroup.Name + "_" + pair.label, assetGroup, aaContext.settings, schema); var newFile = entryPath.Replace(fileName, fileName + "_variationCopy_" + pair.label); newFile = newFile.Replace("Assets/", "Assets/GeneratedTextures/"); //生成新的对应条目原始文件fullName AssetDatabase.CopyAsset(entryPath, newFile); //创建对应新的文件 var aiDest = AssetImporter.GetAtPath(newFile) as TextureImporter; //转换成texture if (aiDest == null) { var message = "failed to get TextureImporter on new texture asset: " + newFile; return(message); } float scaleFactor = pair.textureScale; float desiredLimiter = maxDim * scaleFactor; aiDest.maxTextureSize = NearestMaxTextureSize(desiredLimiter); //生成里18*32*64*128*256等最近的尺寸 aiDest.isReadable = true; //可以通过脚本获取纹理信息 aiDest.SaveAndReimport(); //保存并导入(如果资产导入器是脏的,则保存资产导入器设置。) var newEntry = aaContext.settings.CreateOrMoveEntry(AssetDatabase.AssetPathToGUID(newFile), newGroup); newEntry.address = entry.address; //对应条目的名称或者地址 newEntry.SetLabel(pair.label, true); //设置为true将添加标签,设置为false将删除标签。 } entry.SetLabel(schema.BaselineLabel, true); } } return(string.Empty); }
protected override TResult DoBuild <TResult>(AddressablesDataBuilderInput builderInput, AddressableAssetsBuildContext aaContext) { // Build AssetBundles TResult result = base.DoBuild <TResult>(builderInput, aaContext); // Don't prepare content for asset packs if the build target isn't set to Android if (builderInput.Target != BuildTarget.Android) { Addressables.LogWarning("Build target is not set to Android. No custom asset pack config files will be created."); return(result); } var resetAssetPackSchemaData = !CustomAssetPackSettings.SettingsExists; var customAssetPackSettings = CustomAssetPackSettings.GetSettings(true); CreateCustomAssetPacks(aaContext.Settings, customAssetPackSettings, resetAssetPackSchemaData); return(result); }
void ProcessTextureScaler(TextureVariationSchema schema, AddressableAssetGroup assetGroup, AddressableAssetsBuildContext aaContext) { m_SourceGroupList.Add(assetGroup); var entries = new List <AddressableAssetEntry>(assetGroup.entries); foreach (var entry in entries) { foreach (var pair in schema.Variations) { entry.SetLabel(pair.label, true); } entry.SetLabel(schema.BaselineLabel, true); } }
string ProcessTextureScaler( TextureVariationSchema schema, AddressableAssetGroup assetGroup, AddressableAssetsBuildContext aaContext) { var entries = new List <AddressableAssetEntry>(assetGroup.entries); foreach (var entry in entries) { var entryPath = entry.AssetPath; if (AssetDatabase.GetMainAssetTypeAtPath(entryPath) == typeof(Texture2D)) { var fileName = Path.GetFileNameWithoutExtension(entryPath); if (string.IsNullOrEmpty(fileName)) { return("Failed to get file name for: " + entryPath); } if (!Directory.Exists("Assets/GeneratedTextures")) { Directory.CreateDirectory("Assets/GeneratedTextures"); } if (!Directory.Exists("Assets/GeneratedTextures/Texture")) { Directory.CreateDirectory("Assets/GeneratedTextures/Texture"); } var sourceTex = AssetDatabase.LoadAssetAtPath <Texture2D>(entryPath); var aiSource = AssetImporter.GetAtPath(entryPath) as TextureImporter; int maxDim = Math.Max(sourceTex.width, sourceTex.height); foreach (var pair in schema.Variations) { var newGroup = FindOrCopyGroup(assetGroup.Name + "_" + pair.label, assetGroup, aaContext.Settings, schema); var newFile = entryPath.Replace(fileName, fileName + "_variationCopy_" + pair.label); newFile = newFile.Replace("Assets/", "Assets/GeneratedTextures/"); AssetDatabase.CopyAsset(entryPath, newFile); var aiDest = AssetImporter.GetAtPath(newFile) as TextureImporter; if (aiDest == null) { var message = "failed to get TextureImporter on new texture asset: " + newFile; return(message); } float scaleFactor = pair.textureScale; float desiredLimiter = maxDim * scaleFactor; aiDest.maxTextureSize = NearestMaxTextureSize(desiredLimiter); aiDest.isReadable = true; aiDest.SaveAndReimport(); var newEntry = aaContext.Settings.CreateOrMoveEntry(AssetDatabase.AssetPathToGUID(newFile), newGroup); newEntry.address = entry.address; newEntry.SetLabel(pair.label, true); } entry.SetLabel(schema.BaselineLabel, true); } } if (!schema.IncludeSourceTextureInBuild) { assetGroup.GetSchema <BundledAssetGroupSchema>().IncludeInBuild = false; } m_SourceGroupList.Add(assetGroup); // need to reset labels for every texture variant group return(string.Empty); }
protected override TResult DoBuild <TResult>(AddressablesDataBuilderInput builderInput, AddressableAssetsBuildContext aaContext) { if (Directory.Exists(ETModel.PathHelper.RemoteBuildPath)) { FileHelper.CleanDirectory(ETModel.PathHelper.RemoteBuildPath); } AddressableAssetSettings settings = AssetDatabase.LoadAssetAtPath <AddressableAssetSettings>($"{AddressableAssetSettingsDefaultObject.kDefaultConfigFolder}/{AddressableAssetSettingsDefaultObject.kDefaultConfigAssetName}.asset"); //更新Group模板的发布路径和加载路径 UpdateGroupTemplateBuildAndLoadPath(settings, $"{settings.GroupTemplateFolder}/Packed Assets.asset"); //UpdateGroupBuildAndLoadPath(settings, $"{settings.GroupFolder}/Default Local Group.asset"); //清除旧有资源 CleanGroup(settings); List <AddressableAssetGroupSchema> schemas = AssetDatabase.LoadAssetAtPath <AddressableAssetGroupTemplate>($"{settings.GroupTemplateFolder}/Packed Assets.asset").SchemaObjects; string[] directories = Directory.GetDirectories($"{UnityEngine.Application.dataPath}/AddressableAssets/"); string buildPath = $"./{UnityEngine.AddressableAssets.Addressables.BuildPath}"; tempAddress = string.Empty; foreach (string folderPath in directories) { if (folderPath.Substring(folderPath.LastIndexOf('/') + 1).StartsWith("~")) { continue; } BuildGroup(folderPath, settings, schemas); } var result = base.DoBuild <TResult>(builderInput, aaContext); return(result); }
/// <summary> /// The processing of the bundled asset schema. This is where the bundle(s) for a given group are actually setup. /// </summary> /// <param name="schema">The BundledAssetGroupSchema to process</param> /// <param name="assetGroup">The group this schema was pulled from</param> /// <param name="aaContext">The general Addressables build builderInput</param> /// <returns>The error string, if any.</returns> protected virtual string ProcessBundledAssetSchema( BundledAssetGroupSchema schema, AddressableAssetGroup assetGroup, AddressableAssetsBuildContext aaContext) { if (schema == null || !schema.IncludeInBuild) { return(string.Empty); } var errorStr = ErrorCheckBundleSettings(schema, assetGroup, aaContext.settings); if (!string.IsNullOrEmpty(errorStr)) { return(errorStr); } var bundledProviderId = schema.GetBundleCachedProviderId(); var assetProviderId = schema.GetAssetCachedProviderId(); if (!m_CreatedProviderIds.Contains(bundledProviderId)) { m_CreatedProviderIds.Add(bundledProviderId); var bundleProviderType = schema.AssetBundleProviderType.Value; var bundleProviderData = ObjectInitializationData.CreateSerializedInitializationData(bundleProviderType, bundledProviderId); m_ResourceProviderData.Add(bundleProviderData); } if (!m_CreatedProviderIds.Contains(assetProviderId)) { m_CreatedProviderIds.Add(assetProviderId); var assetProviderType = schema.BundledAssetProviderType.Value; var assetProviderData = ObjectInitializationData.CreateSerializedInitializationData(assetProviderType, assetProviderId); m_ResourceProviderData.Add(assetProviderData); } var bundleInputDefs = new List <AssetBundleBuild>(); PrepGroupBundlePacking(assetGroup, bundleInputDefs, schema.BundleMode); for (int i = 0; i < bundleInputDefs.Count; i++) { string assetBundleName = bundleInputDefs[i].assetBundleName; if (aaContext.bundleToAssetGroup.ContainsKey(assetBundleName)) { int count = 1; var newName = assetBundleName; while (aaContext.bundleToAssetGroup.ContainsKey(newName) && count < 1000) { newName = assetBundleName.Replace(".bundle", string.Format("{0}.bundle", count++)); } assetBundleName = newName; } string hashedAssetBundleName = HashingMethods.Calculate(assetBundleName) + ".bundle"; m_OutputAssetBundleNames.Add(assetBundleName); m_AllBundleInputDefs.Add(new AssetBundleBuild { addressableNames = bundleInputDefs[i].addressableNames, assetNames = bundleInputDefs[i].assetNames, assetBundleName = hashedAssetBundleName, assetBundleVariant = bundleInputDefs[i].assetBundleVariant }); aaContext.bundleToAssetGroup.Add(hashedAssetBundleName, assetGroup.Guid); } return(string.Empty); }
protected override TResult DoBuild <TResult>(AddressablesDataBuilderInput builderInput, AddressableAssetsBuildContext aaContext) { foreach (var assetGroup in builderInput.AddressableSettings.groups) { var schema = assetGroup.GetSchema <BundledAssetGroupSchema>(); if (schema == null) { continue; } SerializedType _ty = schema.AssetBundleProviderType; if (_ty.Value != typeof(FirebaseStorageAssetBundleProvider)) { string _errMsg = $"Firebase Build 는 '{assetGroup.name}' Group의 AssetBundleProvider 를 FirebaseStorageAssetBundleProvider 로 바꿔줘야 합니다."; return(AddressableAssetBuildResult.CreateResult <TResult>(null, 0, _errMsg)); } } //원격빌드이므로 IsRemoteCatalog 값은 true builderInput.AddressableSettings.BuildRemoteCatalog = true; var result = base.DoBuild <TResult>(builderInput, aaContext); var settingsPath = Addressables.BuildPath + "/" + builderInput.RuntimeSettingsFilename; var data = JsonUtility.FromJson <ResourceManagerRuntimeData>(File.ReadAllText(settingsPath)); var remoteHash = data.CatalogLocations.Find(locationData => locationData.Keys[0] == "AddressablesMainContentCatalogRemoteHash"); if (remoteHash != null) { var newRemoteHash = new ResourceLocationData(remoteHash.Keys, remoteHash.InternalId, typeof(FirebaseStorageHashProvider), remoteHash.ResourceType, remoteHash.Dependencies); data.CatalogLocations.Remove(remoteHash); data.CatalogLocations.Add(newRemoteHash); } File.WriteAllText(settingsPath, JsonUtility.ToJson(data)); Debug.Log($"Player Version : {builderInput.PlayerVersion}"); return(result); }
/// <summary> /// The method that does the actual building after all the groups have been processed. /// </summary> /// <param name="builderInput">The generic builderInput of the</param> /// <param name="aaContext"></param> /// <typeparam name="TResult"></typeparam> /// <returns></returns> protected virtual TResult DoBuild <TResult>(AddressablesDataBuilderInput builderInput, AddressableAssetsBuildContext aaContext) where TResult : IDataBuilderResult { ExtractDataTask extractData = new ExtractDataTask(); var tempPath = Path.GetDirectoryName(Application.dataPath) + "/Library/com.unity.addressables/StreamingAssetsCopy/" + PlatformMappingService.GetPlatform() + "/addressables_content_state.bin"; string buildPath = ""; var playerBuildVersion = builderInput.PlayerVersion; if (m_AllBundleInputDefs.Count > 0) { if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) { return(AddressableAssetBuildResult.CreateResult <TResult>(null, 0, "Unsaved scenes")); } var buildTarget = builderInput.Target; var buildTargetGroup = builderInput.TargetGroup; var buildParams = new AddressableAssetsBundleBuildParameters( aaContext.settings, aaContext.bundleToAssetGroup, buildTarget, buildTargetGroup, aaContext.settings.buildSettings.bundleBuildPath); var builtinShaderBundleName = aaContext.settings.DefaultGroup.Guid + "_unitybuiltinshaders.bundle"; var buildTasks = RuntimeDataBuildTasks(builtinShaderBundleName); buildTasks.Add(extractData); string aaPath = aaContext.settings.AssetPath; IBundleBuildResults results; var exitCode = ContentPipeline.BuildAssetBundles(buildParams, new BundleBuildContent(m_AllBundleInputDefs), out results, buildTasks, aaContext); if (exitCode < ReturnCode.Success) { return(AddressableAssetBuildResult.CreateResult <TResult>(null, 0, "SBP Error" + exitCode)); } if (aaContext.settings == null && !string.IsNullOrEmpty(aaPath)) { aaContext.settings = AssetDatabase.LoadAssetAtPath <AddressableAssetSettings>(aaPath); } GenerateLocationListsTask.Run(aaContext, extractData.WriteData); foreach (var assetGroup in aaContext.settings.groups) { if (assetGroup == null) { continue; } List <string> buildBundles; if (aaContext.assetGroupToBundles.TryGetValue(assetGroup, out buildBundles)) { List <string> outputBundles = new List <string>(); for (int i = 0; i < buildBundles.Count; ++i) { var b = m_AllBundleInputDefs.FindIndex(inputDef => buildBundles[i].StartsWith(inputDef.assetBundleName)); outputBundles.Add(b >= 0 ? m_OutputAssetBundleNames[b] : buildBundles[i]); } var schema = assetGroup.GetSchema <BundledAssetGroupSchema>(); //schema.BuildPath.SetVariableByName( assetGroup.Settings, $"ExportedAddressables/{assetGroup.Name}/{EditorUserBuildSettings.activeBuildTarget}" ); //foreach ( var name in assetGroup.Settings.profileSettings.GetVariableNames() ) //{ // Debug.Log( $"name: {name}" ); //} //assetGroup.Settings.profileSettings.SetValue( assetGroup.Settings.activeProfileId, "BuildPath", $"ExportedAddressables/{assetGroup.Name}/{EditorUserBuildSettings.activeBuildTarget}" ); buildPath = schema.BuildPath.GetValue(assetGroup.Settings); Debug.Log($"buildPath: {buildPath}"); PostProcessBundles(assetGroup, buildBundles, outputBundles, results, extractData.WriteData, aaContext.runtimeData, aaContext.locations, builderInput.Registry); PostProcessCatalogEnteries(assetGroup, extractData.WriteData, aaContext.locations, builderInput.Registry); } } foreach (var r in results.WriteResults) { m_Linker.AddTypes(r.Value.includedTypes); } } //save catalog var contentCatalog = new ContentCatalogData(aaContext.locations, ResourceManagerRuntimeData.kCatalogAddress); contentCatalog.ResourceProviderData.AddRange(m_ResourceProviderData); foreach (var t in aaContext.providerTypes) { contentCatalog.ResourceProviderData.Add(ObjectInitializationData.CreateSerializedInitializationData(t)); } contentCatalog.InstanceProviderData = ObjectInitializationData.CreateSerializedInitializationData(instanceProviderType.Value); contentCatalog.SceneProviderData = ObjectInitializationData.CreateSerializedInitializationData(sceneProviderType.Value); CreateCatalog(aaContext.settings, contentCatalog, aaContext.runtimeData.CatalogLocations, playerBuildVersion, builderInput.RuntimeCatalogFilename, builderInput.Registry, buildPath); foreach (var pd in contentCatalog.ResourceProviderData) { m_Linker.AddTypes(pd.ObjectType.Value); m_Linker.AddTypes(pd.GetRuntimeTypes()); } m_Linker.AddTypes(contentCatalog.InstanceProviderData.ObjectType.Value); m_Linker.AddTypes(contentCatalog.InstanceProviderData.GetRuntimeTypes()); m_Linker.AddTypes(contentCatalog.SceneProviderData.ObjectType.Value); m_Linker.AddTypes(contentCatalog.SceneProviderData.GetRuntimeTypes()); foreach (var io in aaContext.settings.InitializationObjects) { var provider = io as IObjectInitializationDataProvider; if (provider != null) { var id = provider.CreateObjectInitializationData(); aaContext.runtimeData.InitializationObjects.Add(id); m_Linker.AddTypes(id.ObjectType.Value); m_Linker.AddTypes(id.GetRuntimeTypes()); } } aaContext.runtimeData.DisableCatalogUpdateOnStartup = aaContext.settings.DisableCatalogUpdateOnStartup; m_Linker.AddTypes(typeof(Addressables)); m_Linker.Save(buildPath + "/link.xml"); var settingsPath = buildPath + "/" + builderInput.RuntimeSettingsFilename; WriteFile(settingsPath, JsonUtility.ToJson(aaContext.runtimeData), builderInput.Registry); var opResult = AddressableAssetBuildResult.CreateResult <TResult>(settingsPath, aaContext.locations.Count); //save content update data if building for the player var allEntries = new List <AddressableAssetEntry>(); aaContext.settings.GetAllAssets(allEntries, false, g => g != null && g.HasSchema <ContentUpdateGroupSchema>() && g.GetSchema <ContentUpdateGroupSchema>().StaticContent); var remoteCatalogLoadPath = aaContext.settings.BuildRemoteCatalog ? aaContext.settings.RemoteCatalogLoadPath.GetValue(aaContext.settings) : string.Empty; if (extractData.BuildCache != null && ContentUpdateScript.SaveContentState(aaContext.locations, tempPath, allEntries, extractData.DependencyData, playerBuildVersion, remoteCatalogLoadPath)) { try { var contentStatePath = ContentUpdateScript.GetContentStateDataPath(false); File.Copy(tempPath, contentStatePath, true); builderInput.Registry.AddFile(contentStatePath); } catch (Exception e) { Debug.LogException(e); } } return(opResult); }
/// <summary> /// Called per group per schema to evaluate that schema. This can be an easy entry point for implementing the /// build aspects surrounding a custom schema. Note, you should not rely on schemas getting called in a specific /// order. /// </summary> /// <param name="schema">The schema to process</param> /// <param name="assetGroup">The group this schema was pulled from</param> /// <param name="aaContext">The general Addressables build builderInput</param> /// <returns></returns> protected virtual string ProcessGroupSchema(AddressableAssetGroupSchema schema, AddressableAssetGroup assetGroup, AddressableAssetsBuildContext aaContext) { var playerDataSchema = schema as PlayerDataGroupSchema; if (playerDataSchema != null) { return(ProcessPlayerDataSchema(playerDataSchema, assetGroup, aaContext)); } var bundledAssetSchema = schema as BundledAssetGroupSchema; if (bundledAssetSchema != null) { return(ProcessBundledAssetSchema(bundledAssetSchema, assetGroup, aaContext)); } return(string.Empty); }
List <AnalyzeResult> DoFakeBuild(AddressableAssetSettings settings) { m_ImplicitAssets = new HashSet <GUID>(); List <AnalyzeResult> emptyResult = new List <AnalyzeResult>(); emptyResult.Add(new AnalyzeResult(ruleName + " - No issues found")); var context = new AddressablesDataBuilderInput(settings); var timer = new Stopwatch(); timer.Start(); var aaSettings = context.AddressableSettings; //gather entries var locations = new List <ContentCatalogDataEntry>(); var allBundleInputDefs = new List <AssetBundleBuild>(); var bundleToAssetGroup = new Dictionary <string, string>(); var runtimeData = new ResourceManagerRuntimeData(); runtimeData.LogResourceManagerExceptions = aaSettings.buildSettings.LogResourceManagerExceptions; foreach (var assetGroup in aaSettings.groups) { var schema = assetGroup.GetSchema <BundledAssetGroupSchema>(); if (schema == null) { continue; } var bundleInputDefs = new List <AssetBundleBuild>(); BuildScriptPackedMode.PrepGroupBundlePacking(assetGroup, bundleInputDefs, locations, schema.BundleMode); for (int i = 0; i < bundleInputDefs.Count; i++) { if (bundleToAssetGroup.ContainsKey(bundleInputDefs[i].assetBundleName)) { var bid = bundleInputDefs[i]; int count = 1; var newName = bid.assetBundleName; while (bundleToAssetGroup.ContainsKey(newName) && count < 1000) { newName = bid.assetBundleName.Replace(".bundle", string.Format("{0}.bundle", count++)); } bundleInputDefs[i] = new AssetBundleBuild { assetBundleName = newName, addressableNames = bid.addressableNames, assetBundleVariant = bid.assetBundleVariant, assetNames = bid.assetNames }; } bundleToAssetGroup.Add(bundleInputDefs[i].assetBundleName, assetGroup.Guid); } allBundleInputDefs.AddRange(bundleInputDefs); } ExtractDataTask extractData = new ExtractDataTask(); if (allBundleInputDefs.Count > 0) { if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) { Debug.LogError("Cannot run Analyze with unsaved scenes"); return(emptyResult); } var buildTarget = context.Target; var buildTargetGroup = context.TargetGroup; var buildParams = new AddressableAssetsBundleBuildParameters(aaSettings, bundleToAssetGroup, buildTarget, buildTargetGroup, aaSettings.buildSettings.bundleBuildPath); var builtinShaderBundleName = aaSettings.DefaultGroup.Name.ToLower().Replace(" ", "").Replace('\\', '/').Replace("//", "/") + "_unitybuiltinshaders.bundle"; var buildTasks = RuntimeDataBuildTasks(builtinShaderBundleName); buildTasks.Add(extractData); var aaContext = new AddressableAssetsBuildContext { settings = aaSettings, runtimeData = runtimeData, bundleToAssetGroup = bundleToAssetGroup, locations = locations }; IBundleBuildResults buildResults; var exitCode = ContentPipeline.BuildAssetBundles(buildParams, new BundleBuildContent(allBundleInputDefs), out buildResults, buildTasks, aaContext); GenerateLocationListsTask.Run(aaContext, extractData.WriteData); if (exitCode < ReturnCode.Success) { Debug.LogError("Analyze build failed. " + exitCode); return(emptyResult); } HashSet <GUID> explicitGuids = new HashSet <GUID>(); foreach (var atf in extractData.WriteData.AssetToFiles) { explicitGuids.Add(atf.Key); } Dictionary <GUID, List <string> > implicitGuids = new Dictionary <GUID, List <string> >(); foreach (var fto in extractData.WriteData.FileToObjects) { foreach (ObjectIdentifier g in fto.Value) { if (!explicitGuids.Contains(g.guid)) { if (!implicitGuids.ContainsKey(g.guid)) { implicitGuids.Add(g.guid, new List <string>()); } implicitGuids[g.guid].Add(fto.Key); } } } //dictionary<group, dictionary<bundle, implicit assets >> Dictionary <string, Dictionary <string, List <string> > > allIssues = new Dictionary <string, Dictionary <string, List <string> > >(); foreach (var g in implicitGuids) { if (g.Value.Count > 1) //it's duplicated... { var path = AssetDatabase.GUIDToAssetPath(g.Key.ToString()); if (!AddressableAssetUtility.IsPathValidForEntry(path) || path.ToLower().Contains("/resources/") || path.ToLower().StartsWith("resources/")) { continue; } foreach (var file in g.Value) { var bun = extractData.WriteData.FileToBundle[file]; string groupGuid; if (aaContext.bundleToAssetGroup.TryGetValue(bun, out groupGuid)) { var group = aaSettings.FindGroup(grp => grp.Guid == groupGuid); if (group != null) { Dictionary <string, List <string> > groupData; if (!allIssues.TryGetValue(group.Name, out groupData)) { groupData = new Dictionary <string, List <string> >(); allIssues.Add(group.Name, groupData); } List <string> assets; if (!groupData.TryGetValue(bun, out assets)) { assets = new List <string>(); groupData.Add(bun, assets); } assets.Add(path); m_ImplicitAssets.Add(g.Key); } } } } } List <AnalyzeResult> result = new List <AnalyzeResult>(); foreach (var group in allIssues) { foreach (var bundle in group.Value) { foreach (var item in bundle.Value) { var issueName = ruleName + kDelimiter + group.Key + kDelimiter + bundle.Key + kDelimiter + item; result.Add(new AnalyzeResult(issueName, MessageType.Warning)); } } } if (result.Count > 0) { return(result); } } return(emptyResult); }
protected override TResult DoBuild <TResult>(AddressablesDataBuilderInput builderInput, AddressableAssetsBuildContext aaContext) { List <string> buildInABHashLines = new List <string>(); TResult opResult = base.DoBuild <TResult>(builderInput, aaContext); var groups = aaContext.Settings.groups; for (int i = 0; i < groups.Count; i++) { List <string> bundles; if (aaContext.assetGroupToBundles.TryGetValue(groups[i], out bundles)) { var locations = aaContext.locations; for (int j = 0; j < locations.Count; j++) { var d = locations[j].Data as AssetBundleRequestOptions; if (d != null) { for (int k = 0; k < bundles.Count; k++) { if (d.BundleName == bundles[k]) { if (d.BundleSize > 2097152 || d.BundleSize < 1572864)//大于2M和小于1.5M的 { // Logger.Log(string.Format("the size of group:{0} is {1},bundleName is :{2}", groups[i].name, GetFormatSizeString(d.BundleSize), bundles[k])); } else { //Logger.Log(string.Format("the size of group:{0} is {1},bundleName is :{2}", groups[i].name, GetFormatSizeString(d.BundleSize), bundles[k])); } if (IsBuildInAssetBundle(groups[i].name)) { buildInABHashLines.Add(d.Hash); } } } } } } } //写文件 string RuntimePath = UnityEngine.AddressableAssets.Addressables.RuntimePath; string BuildInABHashInfoFilePath = Path.Combine(System.Environment.CurrentDirectory, RuntimePath, "BuildInABHashFile.bytes"); // Logger.Log("BuildInABHashInfoFilePath = " + BuildInABHashInfoFilePath); if (File.Exists(BuildInABHashInfoFilePath)) { File.Delete(BuildInABHashInfoFilePath); } Debug.Log(buildInABHashLines); File.WriteAllLines(BuildInABHashInfoFilePath, buildInABHashLines); return(opResult); }
protected override string ProcessGroup(AddressableAssetGroup assetGroup, AddressableAssetsBuildContext aaContext) { bool include = Check(assetGroup); return(include ? base.ProcessGroup(assetGroup, aaContext) : string.Empty); }
public void AddHashToBundleNameTask_DoesNotChangeHash_WhenAssetsChangeOrder() { //Setup string path1 = $"{TempPath}/test1.prefab"; string path2 = $"{TempPath}/test2.prefab"; string path3 = $"{TempPath}/test3.prefab"; GUID guid1 = new GUID(CreateAsset(path1, "1")); GUID guid2 = new GUID(CreateAsset(path2, "2")); GUID guid3 = new GUID(CreateAsset(path3, "3")); List <GUID> list1 = new List <GUID>() { guid1, guid2, guid3 }; List <GUID> list2 = new List <GUID>() { guid2, guid1, guid3 }; AddressableAssetGroup group = m_Settings.CreateGroup("AddHashTestGroup", false, false, false, new List <AddressableAssetGroupSchema>()); m_Settings.CreateOrMoveEntry(guid1.ToString(), group); m_Settings.CreateOrMoveEntry(guid2.ToString(), group); m_Settings.CreateOrMoveEntry(guid3.ToString(), group); IDependencyData dependencyData = new BuildDependencyData() { AssetInfo = { { guid1, new AssetLoadInfo() { referencedObjects = ContentBuildInterface.GetPlayerObjectIdentifiersInAsset(guid1, EditorUserBuildSettings.activeBuildTarget).ToList() } }, { guid2, new AssetLoadInfo() { referencedObjects = ContentBuildInterface.GetPlayerObjectIdentifiersInAsset(guid2, EditorUserBuildSettings.activeBuildTarget).ToList() } }, { guid3, new AssetLoadInfo() { referencedObjects = ContentBuildInterface.GetPlayerObjectIdentifiersInAsset(guid3, EditorUserBuildSettings.activeBuildTarget).ToList() } } } }; AddressableAssetsBuildContext context = new AddressableAssetsBuildContext() { Settings = m_Settings }; AddHashToBundleNameTask addHashTask = new AddHashToBundleNameTask(); var field = typeof(AddHashToBundleNameTask).GetField("m_DependencyData", BindingFlags.Instance | BindingFlags.NonPublic); field.SetValue(addHashTask, dependencyData); //Test RawHash hash1 = addHashTask.GetAssetsHash(list1, context); RawHash hash2 = addHashTask.GetAssetsHash(list2, context); //Assert Assert.AreEqual(hash1, hash2); //Cleanup m_Settings.RemoveGroup(group); }
protected override TResult DoBuild <TResult>(AddressablesDataBuilderInput builderInput, AddressableAssetsBuildContext aaContext) { var buildResult = base.DoBuild <TResult>(builderInput, aaContext); if (aaContext.settings.BuildRemoteCatalog) { PatchSettingsFile(builderInput); } else { Debug.LogWarning("[TheGamedevGuru] PlayFab: Addressables Remote Catalog is not enabled, skipping patching of the settings file"); } return(buildResult); }
protected override TResult BuildDataImplementation <TResult>(AddressablesDataBuilderInput context) { TResult result = default(TResult); var timer = new Stopwatch(); timer.Start(); var aaSettings = context.AddressableSettings; var pathFormat = "{0}Library/com.unity.addressables/{1}_BuildScriptFastMode.json"; //create runtime data var aaContext = new AddressableAssetsBuildContext { Settings = aaSettings, runtimeData = new ResourceManagerRuntimeData(), bundleToAssetGroup = null, locations = new List <ContentCatalogDataEntry>() }; aaContext.runtimeData.BuildTarget = context.Target.ToString(); aaContext.runtimeData.LogResourceManagerExceptions = aaSettings.buildSettings.LogResourceManagerExceptions; aaContext.runtimeData.ProfileEvents = context.ProfilerEventsEnabled; aaContext.runtimeData.CatalogLocations.Add(new ResourceLocationData(new[] { ResourceManagerRuntimeData.kCatalogAddress }, string.Format(pathFormat, "file://{UnityEngine.Application.dataPath}/../", "catalog"), typeof(ContentCatalogProvider), typeof(ContentCatalogData))); var errorString = ProcessAllGroups(aaContext); if (!string.IsNullOrEmpty(errorString)) { result = AddressableAssetBuildResult.CreateResult <TResult>(null, 0, errorString); } if (result == null) { foreach (var io in aaSettings.InitializationObjects) { if (io is IObjectInitializationDataProvider) { aaContext.runtimeData.InitializationObjects.Add((io as IObjectInitializationDataProvider).CreateObjectInitializationData()); } } var settingsPath = string.Format(pathFormat, "", "settings"); WriteFile(settingsPath, JsonUtility.ToJson(aaContext.runtimeData), context.Registry); //save catalog var catalogData = new ContentCatalogData(aaContext.locations); if (m_legacy) { catalogData.ResourceProviderData.Add(ObjectInitializationData.CreateSerializedInitializationData(typeof(LegacyResourcesProvider))); } catalogData.ResourceProviderData.Add(ObjectInitializationData.CreateSerializedInitializationData <SyncAssetDatabaseProvider>()); catalogData.InstanceProviderData = ObjectInitializationData.CreateSerializedInitializationData(instanceProviderType.Value); catalogData.SceneProviderData = ObjectInitializationData.CreateSerializedInitializationData(sceneProviderType.Value); WriteFile(string.Format(pathFormat, "", "catalog"), JsonUtility.ToJson(catalogData), context.Registry); //inform runtime of the init data path var runtimeSettingsPath = string.Format(pathFormat, "file://{UnityEngine.Application.dataPath}/../", "settings"); PlayerPrefs.SetString(Addressables.kAddressablesRuntimeDataPath, runtimeSettingsPath); result = AddressableAssetBuildResult.CreateResult <TResult>(settingsPath, aaContext.locations.Count); } if (result != null) { result.Duration = timer.Elapsed.TotalSeconds; } return(result); }
string ProcessVariants(ProcessVariantAssetsSchema schema, AddressableAssetGroup group, AddressableAssetsBuildContext context) { var settings = context.Settings; var entries = new List <AddressableAssetEntry>(group.entries); foreach (var mainEntry in entries) { if (AssetDatabase.GetMainAssetTypeAtPath(mainEntry.AssetPath) != typeof(GameObject)) { continue; } GameObject mainAsset = AssetDatabase.LoadMainAssetAtPath(mainEntry.AssetPath) as GameObject; if (!schema.PreprocessCheck(mainAsset)) { continue; } string fileName = Path.GetFileNameWithoutExtension(mainEntry.AssetPath); string mainAssetPath = AssetDatabase.GUIDToAssetPath(mainEntry.guid); string groupDirectory = Path.Combine(defaultBaseDirectory, $"{group.Name}").Replace('\\', '/'); string newPrefabPath = groupDirectory + '/' + Path.GetFileName(mainEntry.AssetPath).Replace(fileName, $"{fileName}_variant"); Directory.CreateDirectory(groupDirectory); if (!AssetDatabase.CopyAsset(mainAssetPath, newPrefabPath)) { Debug.LogError("Failed to copy asset " + mainAssetPath); continue; } if (schema.deleteVariants) { variantsPrefabsToDelete.Add(newPrefabPath); } GameObject variant = AssetDatabase.LoadAssetAtPath <GameObject>(newPrefabPath); foreach (var script in schema.variantAssetsScripts) { script.ProcessVariantAsset(variant); } //Create the Variant Entry and set it's address and labels. var variantEntry = settings.CreateOrMoveEntry(AssetDatabase.AssetPathToGUID(newPrefabPath), mainEntry.parentGroup, false, false); variantEntry.address = mainEntry.address; foreach (string label in mainEntry.labels) { variantEntry.SetLabel(label, true, false, false); } variantEntriesToRemove.Add(AssetDatabase.AssetPathToGUID(newPrefabPath)); entriesToRestore.Add(new AssetEntry { address = mainEntry.address, assetGUID = mainEntry.guid, group = mainEntry.parentGroup, labels = mainEntry.labels }); settings.RemoveAssetEntry(AssetDatabase.AssetPathToGUID(mainEntry.AssetPath), false); } return(string.Empty); }
string ProcessVariants(PrefabTextureVariantSchema schema, AddressableAssetGroup group, AddressableAssetsBuildContext context) { var settings = context.Settings; Directory.CreateDirectory(m_BaseDirectory); var entries = new List <AddressableAssetEntry>(group.entries); foreach (var mainEntry in entries) { if (AssetDatabase.GetMainAssetTypeAtPath(mainEntry.AssetPath) != typeof(GameObject)) { continue; } string fileName = Path.GetFileNameWithoutExtension(mainEntry.AssetPath); string ext = Path.GetExtension(mainEntry.AssetPath); mainEntry.SetLabel(schema.DefaultLabel, true, true); string mainAssetPath = AssetDatabase.GUIDToAssetPath(mainEntry.guid); Hash128 assetHash = AssetDatabase.GetAssetDependencyHash(mainAssetPath); bool assetHashChanged = false; if (!m_AssetPathToHashCode.ContainsKey(mainAssetPath)) { m_AssetPathToHashCode.Add(mainAssetPath, assetHash); } else if (m_AssetPathToHashCode[mainAssetPath] != assetHash) { assetHashChanged = true; m_AssetPathToHashCode[mainAssetPath] = assetHash; } foreach (var variant in schema.Variants) { string groupDirectory = Path.Combine(m_BaseDirectory, $"{group.Name}-{Path.GetFileNameWithoutExtension(mainEntry.address)}").Replace('\\', '/'); m_DirectoriesInUse.Add(groupDirectory); var variantGroup = CreateTemporaryGroupCopy($"{group.Name}_VariantGroup_{variant.Label}", group.Schemas, settings); string baseVariantDirectory = Path.Combine(groupDirectory, variant.Label).Replace('\\', '/'); string newPrefabPath = mainAssetPath.Replace("Assets/", baseVariantDirectory + '/').Replace($"{fileName}{ext}", $"{fileName}_variant_{variant.Label}{ext}"); string variantDirectory = Path.GetDirectoryName(newPrefabPath).Replace('\\', '/'); m_DirectoriesInUse.Add(variantDirectory); Directory.CreateDirectory(variantDirectory); if (assetHashChanged || !File.Exists(newPrefabPath)) { if (!AssetDatabase.CopyAsset(mainAssetPath, newPrefabPath)) { return($"Copying asset from {mainAssetPath} to variant path {newPrefabPath} failed."); } } var dependencies = AssetDatabase.GetDependencies(newPrefabPath); foreach (var dependency in dependencies) { if (AssetDatabase.GetMainAssetTypeAtPath(dependency) == typeof(GameObject)) { var gameObject = AssetDatabase.LoadAssetAtPath <GameObject>(dependency); foreach (var childRender in gameObject.GetComponentsInChildren <MeshRenderer>()) { ConvertToVariant(childRender, variantDirectory, variant, assetHashChanged); } } } var entry = settings.CreateOrMoveEntry(AssetDatabase.AssetPathToGUID(newPrefabPath), variantGroup, false, false); entry.address = mainEntry.address; entry.SetLabel(variant.Label, true, true, false); } } if (!schema.IncludeSourcePrefabInBuild) { group.GetSchema <BundledAssetGroupSchema>().IncludeInBuild = false; m_SourceGroupList.Add(group); } return(string.Empty); }
internal IEnumerable <CheckDupeResult> CalculateDuplicates(Dictionary <GUID, List <string> > implicitGuids, AddressableAssetsBuildContext aaContext) { //Get all guids that have more than one bundle referencing them IEnumerable <KeyValuePair <GUID, List <string> > > validGuids = from dupeGuid in implicitGuids where dupeGuid.Value.Distinct().Count() > 1 where IsValidPath(AssetDatabase.GUIDToAssetPath(dupeGuid.Key.ToString())) select dupeGuid; // Key = bundle parents // Value = asset paths that share the same bundle parents duplicateAssetsAndParents.Clear(); foreach (var entry in validGuids) { string assetPath = AssetDatabase.GUIDToAssetPath(entry.Key.ToString()); List <string> assetParents = entry.Value; // Purge duplicate parents (assets inside a Scene can show multiple copies of the Scene AssetBundle as a parent) List <string> nonDupeParents = new List <string>(); foreach (var parent in assetParents) { if (nonDupeParents.Contains(parent)) { continue; } nonDupeParents.Add(parent); } assetParents = nonDupeParents; bool found = false; // Try to find assetParents in existing dictionary foreach (var bundleParentSetup in duplicateAssetsAndParents.Keys) { if (Enumerable.SequenceEqual(bundleParentSetup, assetParents)) { duplicateAssetsAndParents[bundleParentSetup].Add(assetPath); found = true; break; } } if (!found) { duplicateAssetsAndParents.Add(assetParents, new List <string>() { assetPath }); } } return (from guidToFile in validGuids from file in guidToFile.Value //Get the files that belong to those guids let fileToBundle = m_ExtractData.WriteData.FileToBundle[file] //Get the bundles that belong to those files let bundleToGroup = aaContext.bundleToAssetGroup[fileToBundle] //Get the asset groups that belong to those bundles let selectedGroup = aaContext.Settings.FindGroup(findGroup => findGroup != null && findGroup.Guid == bundleToGroup) select new CheckDupeResult { Group = selectedGroup, DuplicatedFile = file, AssetPath = AssetDatabase.GUIDToAssetPath(guidToFile.Key.ToString()), DuplicatedGroupGuid = guidToFile.Key }); }