feat: delay GameObjects turning OFF by one frame, when they control ShapeChangers (#934)

Closes: #918
This commit is contained in:
bd_ 2024-07-28 20:22:32 -07:00 committed by GitHub
parent 0bc64eb755
commit d8e01234f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 87 additions and 2 deletions

View File

@ -299,12 +299,12 @@ namespace nadena.dev.modular_avatar.animation
_pathToClip = new Dictionary<string, HashSet<ClipHolder>>();
foreach (var clip in _clips)
{
recordPaths(clip);
RecordPaths(clip);
}
}
}
private void recordPaths(ClipHolder holder)
private void RecordPaths(ClipHolder holder)
{
var clip = holder.GetCurrentClipUnsafe() as AnimationClip;

View File

@ -84,6 +84,8 @@ namespace nadena.dev.modular_avatar.animation
}
}
public IEnumerable<(EditorCurveBinding, string)> BoundReadableProperties => _readableProperty.BoundProperties;
// HACK: This is a temporary crutch until we rework the entire animator services system
public void AddPropertyDefinition(AnimatorControllerParameter paramDef)
{

View File

@ -0,0 +1,75 @@
using System.Linq;
using nadena.dev.ndmf;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
namespace nadena.dev.modular_avatar.animation
{
/// <summary>
/// This pass delays turning GameObjects OFF by one frame when those objects control a ReadableProperty. This
/// ensures that we don't expose hidden meshes when removing articles of clothing, for example.
/// </summary>
internal class GameObjectDelayDisablePass : Pass<GameObjectDelayDisablePass>
{
protected override void Execute(BuildContext context)
{
var asc = context.Extension<AnimationServicesContext>();
if (!asc.BoundReadableProperties.Any()) return;
var fx = (AnimatorController)context.AvatarDescriptor.baseAnimationLayers
.FirstOrDefault(l => l.type == VRCAvatarDescriptor.AnimLayerType.FX).animatorController;
if (fx == null) return;
var blendTree = new BlendTree();
blendTree.blendType = BlendTreeType.Direct;
blendTree.useAutomaticThresholds = false;
blendTree.children = asc.BoundReadableProperties.Select(GenerateDelayChild).ToArray();
var asm = new AnimatorStateMachine();
var state = new AnimatorState();
state.name = "DelayDisable";
state.motion = blendTree;
state.writeDefaultValues = true;
asm.defaultState = state;
asm.states = new[]
{
new ChildAnimatorState
{
state = state,
position = Vector3.zero
}
};
fx.layers = fx.layers.Append(new AnimatorControllerLayer
{
name = "DelayDisable",
stateMachine = asm,
defaultWeight = 1,
blendingMode = AnimatorLayerBlendingMode.Override
}).ToArray();
}
private ChildMotion GenerateDelayChild((EditorCurveBinding, string) binding)
{
var ecb = binding.Item1;
var prop = binding.Item2;
var motion = new AnimationClip();
var curve = new AnimationCurve();
curve.AddKey(0, 1);
AnimationUtility.SetEditorCurve(motion, ecb, curve);
return new ChildMotion
{
motion = motion,
directBlendParameter = prop,
timeScale = 1
};
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9b3eb561f76b459fbfbcf29fc4484261
timeCreated: 1722222066

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using nadena.dev.ndmf;
using UnityEditor;
using UnityEditor.Animations;
@ -23,6 +24,9 @@ namespace nadena.dev.modular_avatar.animation
_asc = asc;
}
public IEnumerable<(EditorCurveBinding, string)> BoundProperties =>
_alreadyBound.Select(kv => (kv.Key, kv.Value));
/// <summary>
/// Creates an animator parameter which tracks the effective value of a property on a component. This only
/// tracks FX layer properties.

View File

@ -69,6 +69,7 @@ namespace nadena.dev.modular_avatar.core.editor.plugin
#if MA_VRCSDK3_AVATARS
seq.Run(BlendshapeSyncAnimationPluginPass.Instance);
#endif
seq.Run(GameObjectDelayDisablePass.Instance);
});
#if MA_VRCSDK3_AVATARS
seq.Run(PhysbonesBlockerPluginPass.Instance);