diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/AssetBundleConverter.cs b/asset-bundle-converter/Assets/AssetBundleConverter/AssetBundleConverter.cs index 13f5bf60..0839e667 100644 --- a/asset-bundle-converter/Assets/AssetBundleConverter/AssetBundleConverter.cs +++ b/asset-bundle-converter/Assets/AssetBundleConverter/AssetBundleConverter.cs @@ -12,6 +12,7 @@ using GLTFast; using System.Diagnostics; using UnityEditor; +using UnityEditor.Animations; using UnityEngine; using UnityEngine.Networking; using UnityEngine.Profiling; @@ -24,6 +25,12 @@ namespace DCL.ABConverter { public class AssetBundleConverter { + public struct ConversionParams + { + public IReadOnlyList rawContents; + public string entityType; + } + private struct GltfImportSettings { public string url; @@ -99,6 +106,8 @@ public enum Step private bool isExitForced = false; private IABLogger log => env.logger; + private Dictionary downloadedData = new(); + private string entityType; public AssetBundleConverter(Environment env, ClientSettings settings) { @@ -119,10 +128,12 @@ public AssetBundleConverter(Environment env, ClientSettings settings) /// /// Entry point of the AssetBundleConverter /// - /// + /// /// - public async Task ConvertAsync(IReadOnlyList rawContents) + public async Task ConvertAsync(ConversionParams conversionParams) { + var rawContents = conversionParams.rawContents; + entityType = conversionParams.entityType; startupAllocated = Profiler.GetTotalAllocatedMemoryLong() / 100000.0; startupReserved = Profiler.GetTotalReservedMemoryLong() / 100000.0; @@ -327,10 +338,10 @@ private async Task ProcessAllGltfs() embedExtractTextureTime.Start(); + string directory = Path.GetDirectoryName(relativePath); + if (textures.Count > 0) { - string directory = Path.GetDirectoryName(relativePath); - textures = ExtractEmbedTexturesFromGltf(textures, directory); } @@ -340,6 +351,11 @@ private async Task ProcessAllGltfs() ExtractEmbedMaterialsFromGltf(textures, gltf, gltfImport, gltfUrl); embedExtractMaterialTime.Stop(); + if (animationMethod == AnimationMethod.Mecanim) + { + CreateAnimatorController(gltfImport, directory); + } + log.Verbose($"Importing {relativePath}"); configureGltftime.Start(); @@ -419,8 +435,51 @@ private async Task ProcessAllGltfs() return false; } - private AnimationMethod GetAnimationMethod() => - settings.buildTarget is BuildTarget.StandaloneWindows64 or BuildTarget.StandaloneOSX ? AnimationMethod.Mecanim : AnimationMethod.Legacy; + private void CreateAnimatorController(IGltfImport gltfImport, string directory) + { + var animatorRoot = $"{directory}/Animator/"; + + if (!env.directory.Exists(animatorRoot)) + env.directory.CreateDirectory(animatorRoot); + + var filePath = $"{animatorRoot}animatorController.controller"; + var controller = AnimatorController.CreateAnimatorControllerAtPath(filePath); + var clips = gltfImport.GetClips(); + + var rootStateMachine = controller.layers[0].stateMachine; + + foreach (AnimationClip animationClip in clips) + { + // copy the animation asset so we dont use the same references that will get disposed + var newCopy = Object.Instantiate(animationClip); + newCopy.name = animationClip.name; + + // setup the loop + var animationSettings = AnimationUtility.GetAnimationClipSettings(newCopy); + animationSettings.loopTime = true; + AnimationUtility.SetAnimationClipSettings(newCopy, animationSettings); + + // embed clip into the animatorController + AssetDatabase.AddObjectToAsset(newCopy, controller); + AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(newCopy)); + + // configure the animator + string animationClipName = newCopy.name; + controller.AddParameter(animationClipName, AnimatorControllerParameterType.Trigger); + var state = controller.AddMotion(newCopy, 0); + var anyStateTransition = rootStateMachine.AddAnyStateTransition(state); + anyStateTransition.AddCondition(AnimatorConditionMode.If, 0, animationClipName); + } + + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + } + + private AnimationMethod GetAnimationMethod() + { + if (!entityType.ToLower().Contains("emote")) return AnimationMethod.Legacy; + return settings.buildTarget is BuildTarget.StandaloneWindows64 or BuildTarget.StandaloneOSX ? AnimationMethod.Mecanim : AnimationMethod.Legacy; + } private void ExtractEmbedMaterialsFromGltf(List textures, GltfImportSettings gltf, IGltfImport gltfImport, string gltfUrl) { @@ -813,7 +872,6 @@ private void PopulateLowercaseMappings(IReadOnlyListAn array containing all the assets to be dumped. /// true if succeeded /// - private Dictionary downloadedData = new(); private async Task DownloadAssets(IReadOnlyList rawContents) { diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/Editor/CustomGltfImporter.cs b/asset-bundle-converter/Assets/AssetBundleConverter/Editor/CustomGltfImporter.cs index de61b1b5..fd32e868 100644 --- a/asset-bundle-converter/Assets/AssetBundleConverter/Editor/CustomGltfImporter.cs +++ b/asset-bundle-converter/Assets/AssetBundleConverter/Editor/CustomGltfImporter.cs @@ -7,6 +7,7 @@ using GLTFast.Editor; using System.Text.RegularExpressions; using UnityEditor; +using UnityEditor.Animations; using UnityEditor.AssetImporters; using UnityEngine; using Object = UnityEngine.Object; @@ -79,7 +80,7 @@ public override void OnImportAsset(AssetImportContext ctx) { base.OnImportAsset(ctx); - SwapAnimatorPostProcess(ctx); + ReferenceAnimatorController(ctx); } catch (Exception e) { @@ -89,30 +90,30 @@ public override void OnImportAsset(AssetImportContext ctx) } } - // In explorer alpha we are using non legacy animations and since we cannot create an Animator graph, we just add the clips to an Animation component so we can fetch them easily - // we dont even need to check the current platform target since animators only are created by desktop targets - private void SwapAnimatorPostProcess(AssetImportContext ctx) + private void ReferenceAnimatorController(AssetImportContext ctx) { GameObject gameObject = ctx.mainObject as GameObject; - if (gameObject != null) { Animator animator = gameObject.GetComponent(); + var folderName = $"{Path.GetDirectoryName(ctx.assetPath)}/Animator/"; + string filePath = folderName + "animatorController.controller"; + AnimatorController animationController = AssetDatabase.LoadAssetAtPath(filePath); + animator.runtimeAnimatorController = animationController; + } + } - if (animator != null) - { - DestroyImmediate(animator); - var clips = m_Gltf.GetAnimationClips(); - var animation = gameObject.AddComponent(); + // When creating Animators we embed the clips into the animator controller, so we prevent duplicating the clips here + protected override void CreateAnimationClips(AssetImportContext ctx) + { + GameObject gameObject = ctx.mainObject as GameObject; - foreach (AnimationClip animationClip in clips) - { - // we trick the Animation component believing that we are truly adding a legacy animation - animationClip.legacy = true; - animation.AddClip(animationClip, animationClip.name); - animationClip.legacy = false; - } - } + if (gameObject != null) + { + Animator animator = gameObject.GetComponent(); + + if (animator == null) + base.CreateAnimationClips(ctx); } } @@ -196,9 +197,11 @@ private List ReplaceMaterials(string folderName, Renderer[] renderers) Material validDefaultMaterial = GetExtractedMaterial(folderName, m_Gltf.defaultMaterial.name); materials.Add(validDefaultMaterial); ReplaceReferences(renderers, validDefaultMaterial); + foreach (Renderer renderer in renderers) { var sharedMaterials = renderer.sharedMaterials; + for (var i = 0; i < sharedMaterials.Length; i++) if (sharedMaterials[i] == null) sharedMaterials[i] = validDefaultMaterial; diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/SceneClient.cs b/asset-bundle-converter/Assets/AssetBundleConverter/SceneClient.cs index 123aca1d..80bb2789 100644 --- a/asset-bundle-converter/Assets/AssetBundleConverter/SceneClient.cs +++ b/asset-bundle-converter/Assets/AssetBundleConverter/SceneClient.cs @@ -280,13 +280,15 @@ private static void ParseCommonSettings(string[] commandLineArgs, ClientSettings var apiResponse = await Utils.GetEntityMappingsAsync(settings.targetHash, settings, env.webRequest); if (apiResponse == null) return GetUnexpectedResult(); var mappings = apiResponse.SelectMany(m => m.content); - return await ConvertEntitiesToAssetBundles(mappings.ToArray(), settings); + string entityType = apiResponse[0].type; + return await ConvertEntitiesToAssetBundles(mappings.ToArray(), entityType, settings); } public static async Task ConvertEmptyScenesByMapping(ClientSettings settings) { EnsureEnvironment(settings.BuildPipelineType); - return await ConvertEntitiesToAssetBundles(await Utils.GetEmptyScenesMappingAsync(settings.targetHash, settings, env.webRequest), settings); + ContentServerUtils.MappingPair[] emptyScenesMapping = await Utils.GetEmptyScenesMappingAsync(settings.targetHash, settings, env.webRequest); + return await ConvertEntitiesToAssetBundles(emptyScenesMapping, "scene", settings); } /// @@ -302,7 +304,8 @@ private static void ParseCommonSettings(string[] commandLineArgs, ClientSettings var apiResponse = await Utils.GetEntityMappings(settings.targetPointer.Value, settings, env.webRequest); if (apiResponse == null) return GetUnexpectedResult(); var mappings = apiResponse.SelectMany(m => m.content); - return await ConvertEntitiesToAssetBundles(mappings.ToArray(), settings); + var entityType = apiResponse[0].type; + return await ConvertEntitiesToAssetBundles(mappings.ToArray(), entityType, settings); } private static AssetBundleConverter.State GetUnexpectedResult() => @@ -315,7 +318,7 @@ private static AssetBundleConverter.State GetUnexpectedResult() => settings.isWearable = true; var mappings = await WearablesClient.GetCollectionMappingsAsync(settings.targetHash, ContentServerUtils.ApiTLD.ORG, env.webRequest); - return await ConvertEntitiesToAssetBundles(mappings, settings); + return await ConvertEntitiesToAssetBundles(mappings, "wearable", settings); } /// @@ -324,7 +327,7 @@ private static AssetBundleConverter.State GetUnexpectedResult() => /// The cid list for the scenes to gather from the catalyst's content server /// Any conversion settings object, if its null, a new one will be created /// A state context object useful for tracking the conversion progress - private static async Task ConvertEntitiesToAssetBundles(IReadOnlyList mappingPairs, ClientSettings settings) + private static async Task ConvertEntitiesToAssetBundles(IReadOnlyList mappingPairs, string entityType, ClientSettings settings) { if (mappingPairs == null || mappingPairs.Count == 0) { @@ -339,7 +342,11 @@ private static AssetBundleConverter.State GetUnexpectedResult() => EnsureEnvironment(settings.BuildPipelineType); var core = new AssetBundleConverter(env, settings); - await core.ConvertAsync(mappingPairs); + await core.ConvertAsync(new AssetBundleConverter.ConversionParams + { + rawContents = mappingPairs, + entityType = entityType, + }); return core.CurrentState; } } diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/Tests/AssetBundleConverterShould.cs b/asset-bundle-converter/Assets/AssetBundleConverter/Tests/AssetBundleConverterShould.cs index 44350b9d..049dc408 100644 --- a/asset-bundle-converter/Assets/AssetBundleConverter/Tests/AssetBundleConverterShould.cs +++ b/asset-bundle-converter/Assets/AssetBundleConverter/Tests/AssetBundleConverterShould.cs @@ -85,7 +85,7 @@ private void ThrowIfExitCodeIsNotZero(int exitCode) [Test] public async Task LoadVisualSceneOnStart() { - await converter.ConvertAsync(new List()); + await converter.ConvertAsync(new DCL.ABConverter.AssetBundleConverter.ConversionParams()); await editor.Received().LoadVisualTestSceneAsync(); } @@ -93,7 +93,7 @@ public async Task LoadVisualSceneOnStart() [Test] public async Task InitializeDirectories() { - await converter.ConvertAsync(new List()); + await converter.ConvertAsync(new DCL.ABConverter.AssetBundleConverter.ConversionParams()); directory.Received(1).InitializeDirectory(Arg.Is(EXAMPLE_AB_PATH), Arg.Any()); } @@ -112,7 +112,7 @@ public async Task TextureAssetIsProcessed() var manifest = Substitute.For(); buildPipeline.BuildAssetBundles(Arg.Any(), Arg.Any(), Arg.Any()).Returns(manifest); - await converter.ConvertAsync(new List { exampleAsset }); + await converter.ConvertAsync(new DCL.ABConverter.AssetBundleConverter.ConversionParams()); // Ensure that web request is done webRequest.Received().Get(Arg.Is(exampleBaseURL)); @@ -159,7 +159,7 @@ public async Task GltfAssetIsProcessed() gltf.TextureCount.Returns(0); gltf.MaterialCount.Returns(0); - await converter.ConvertAsync(new List { exampleAsset }); + await converter.ConvertAsync(new DCL.ABConverter.AssetBundleConverter.ConversionParams()); // Ensure that web request is done webRequest.Received().Get(Arg.Is(exampleBaseURL)); @@ -206,7 +206,7 @@ public async Task TextureIsExtractedFromGltf() exampleTexture.name = textureName; gltf.GetTexture(0).Returns(exampleTexture); - await converter.ConvertAsync(new List { exampleAsset }); + await converter.ConvertAsync(new DCL.ABConverter.AssetBundleConverter.ConversionParams()); var texturePath = $"{DOWNLOAD_FOLDER}{hash}{separator}Textures{separator}{textureName}.png"; @@ -250,7 +250,7 @@ public async Task MaterialIsExtractedFromGltf() // Ensure that a when the material asset is created, the new instance of the material is a copy assetDatabase.When(ad => ad.CreateAsset(Arg.Any(), Arg.Any())).Do(c => Assert.AreNotEqual(c.Arg(), material)); - await converter.ConvertAsync(new List { exampleAsset }); + await converter.ConvertAsync(new DCL.ABConverter.AssetBundleConverter.ConversionParams()); // Ensure that a material asset is created and the texture is set for this material assetDatabase.Received(1).CreateAsset(Arg.Is(m => m.GetTexture("_BaseMap")), Arg.Any()); diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Implementations/Default/GltfImportWrapper.cs b/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Implementations/Default/GltfImportWrapper.cs index 13d6f276..fd42eab7 100644 --- a/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Implementations/Default/GltfImportWrapper.cs +++ b/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Implementations/Default/GltfImportWrapper.cs @@ -3,6 +3,7 @@ using GLTFast.Logging; using GLTFast.Materials; using System; +using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; @@ -38,6 +39,9 @@ public Texture2D GetTexture(int index) => public Material GetMaterial(int index) => importer.GetMaterial(index); + public IReadOnlyList GetClips() => + importer.GetAnimationClips(); + public void Dispose() { importer.Dispose(); diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Interfaces/IGltfImport.cs b/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Interfaces/IGltfImport.cs index ad5d3467..078cbfb4 100644 --- a/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Interfaces/IGltfImport.cs +++ b/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Interfaces/IGltfImport.cs @@ -1,5 +1,6 @@ using GLTFast; using GLTFast.Logging; +using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; @@ -19,6 +20,8 @@ public interface IGltfImport Material GetMaterial(int index); + IReadOnlyList GetClips(); + void Dispose(); Material defaultMaterial { get; }