#if UNITY_EDITOR using UnityEngine; using System.Collections.Generic; using System.IO; using VRC.SDK3.Editor; using VRC.SDK3.Avatars.ScriptableObjects; using UnityEditor.Animations; using System; using VRC.SDKBase; using nadena.dev.ndmf; using gay.lilyy.PresetGenerator.Editor; using AnimatorAsCode.V1; using VRC.SDK3.Avatars.Components; using AnimatorAsCode.V1.ModularAvatar; using UnityEditor; using AnimatorAsCode.V1.VRC; using Mono.Cecil.Cil; using NUnit.Framework; [assembly: ExportsPlugin(typeof(ParameterPresetPlugin))] namespace gay.lilyy.PresetGenerator.Editor { public class ParameterPresetPlugin : Plugin { public override string QualifiedName => "gay.lilyy.presetgenerator"; public override string DisplayName => "Preset Generator"; private const string SystemName = "PresetGenerator"; private const bool UseWriteDefaults = true; protected override void Configure() { InPhase(BuildPhase.Generating).Run($"Generate {DisplayName}", Generate); } private void Generate(BuildContext ctx) { // Find all components of type ExampleToggle in this avatar. var presets = ctx.AvatarRootTransform.GetComponents(); if (presets.Length == 0) return; // If there are none in the avatar, skip this entirely. // Initialize Animator As Code. var aac = AacV1.Create(new AacConfiguration { SystemName = SystemName, AnimatorRoot = ctx.AvatarRootTransform, DefaultValueRoot = ctx.AvatarRootTransform, AssetKey = GUID.Generate().ToString(), AssetContainer = ctx.AssetContainer, ContainerMode = AacConfiguration.Container.OnlyWhenPersistenceRequired, // (For AAC 1.2.0 and above) The next line is recommended starting from NDMF 1.6.0. // If you use a lower version of NDMF or if you don't use it, remove that line. AssetContainerProvider = new NDMFContainerProvider(ctx), // States will be created with Write Defaults set to ON or OFF based on whether UseWriteDefaults is true or false. DefaultsProvider = new AacDefaultsProvider(UseWriteDefaults) }); // Create a new animator controller. // This will be merged with the rest of the playable layer at the end of this function. var ctrl = aac.NewAnimatorController(); var layer = ctrl.NewLayer("Presets"); var presetParam = layer.IntParameter("Preset"); Dictionary> intParams = new(); Dictionary> floatParams = new(); Dictionary> boolParams = new(); var emptyClip = aac.NewClip(); var idle = layer.NewState("Idle").WithAnimation(emptyClip); var presetIndex = 0; foreach (var preset in presets) { presetIndex++; var state = layer.NewState("Preset " + preset.Name) .WithAnimation(emptyClip); foreach (var param in preset.Parameters) { if (!param.shouldChange) continue; switch (param.valueType) { case VRCExpressionParameters.ValueType.Int: { if (!intParams.ContainsKey(param.name)) { intParams[param.name] = layer.IntParameter(param.name); } var layerParam = intParams[param.name]; state.Drives(layerParam, (int)param.setTo); break; } case VRCExpressionParameters.ValueType.Float: { if (!floatParams.ContainsKey(param.name)) { floatParams[param.name] = layer.FloatParameter(param.name); } var layerParam = floatParams[param.name]; state.Drives(layerParam, param.setTo); break; } case VRCExpressionParameters.ValueType.Bool: { if (!boolParams.ContainsKey(param.name)) { boolParams[param.name] = layer.BoolParameter(param.name); } var layerParam = boolParams[param.name]; state.Drives(layerParam, param.setTo > 0.5f); break; } } } idle.TransitionsTo(state).When(presetParam.IsEqualTo(presetIndex)); state.TransitionsTo(idle).When(presetParam.IsEqualTo(0)); } // Create a new object in the scene. We will add Modular Avatar components inside it. var modularAvatar = MaAc.Create(new GameObject(SystemName) { transform = { parent = ctx.AvatarRootTransform } }); // By creating a Modular Avatar Merge Animator component, // our animator controller will be added to the avatar's FX layer. modularAvatar.NewMergeAnimator(ctrl.AnimatorController, VRCAvatarDescriptor.AnimLayerType.FX); } } internal class NDMFContainerProvider : IAacAssetContainerProvider { private readonly BuildContext _ctx; public NDMFContainerProvider(BuildContext ctx) => _ctx = ctx; public void SaveAsPersistenceRequired(UnityEngine.Object objectToAdd) => _ctx.AssetSaver.SaveAsset(objectToAdd); public void SaveAsRegular(UnityEngine.Object objectToAdd) { } // Let NDMF crawl our assets when it finishes public void ClearPreviousAssets() { } // ClearPreviousAssets is never used in non-destructive contexts } } #endif