diff --git a/src/AtomAnimations/Animations/AtomAnimation.cs b/src/AtomAnimations/Animations/AtomAnimation.cs index 0e638a26..be58fccb 100644 --- a/src/AtomAnimations/Animations/AtomAnimation.cs +++ b/src/AtomAnimations/Animations/AtomAnimation.cs @@ -19,6 +19,7 @@ public class IsPlayingEvent : UnityEvent { } private static readonly Regex _lastDigitsRegex = new Regex(@"^(?.+)(?[0-9]+)$", RegexOptions.Compiled); public const string RandomizeAnimationName = "(Randomize)"; + public const string SlaveAnimationName = "(Slave)"; public const string RandomizeGroupSuffix = "/*"; public UnityEvent onAnimationSettingsChanged = new UnityEvent(); @@ -176,7 +177,7 @@ public AtomAnimationClip CreateClip([NotNull] string animationLayer, [NotNull] s if (animationLayer == null) throw new ArgumentNullException(nameof(animationLayer)); if (animationName == null) throw new ArgumentNullException(nameof(animationName)); - if (clips.Any(c => c.animationName == animationName)) throw new InvalidOperationException($"Animation '{animationName}' already exists"); + if (clips.Any(c => c.animationLayer == animationLayer && c.animationName == animationName)) throw new InvalidOperationException($"Animation '{animationLayer}::{animationName}' already exists"); var clip = new AtomAnimationClip(animationName, animationLayer); if (position == -1) AddClip(clip); @@ -212,7 +213,8 @@ public string GetNewAnimationName(AtomAnimationClip source) for (var i = animationNameInt + 1; i < 999; i++) { var animationName = animationNameBeforeInt + i; - if (clips.All(c => c.animationName != animationName)) return animationName; + if (index.ByLayer(source.animationLayer).All(c => c.animationName != animationName)) + return animationName; } return Guid.NewGuid().ToString(); } @@ -308,9 +310,13 @@ public void PlayClip(AtomAnimationClip clip, bool sequencing, bool allowPreserve return; if (previousMain.loop && previousMain.preserveLoops && clip.loop && clip.preserveLoops) + { ScheduleNextAnimation(previousMain, clip, previousMain.animationLength - clip.blendInDuration / 2f - previousMain.clipTime); + } else + { ScheduleNextAnimation(previousMain, clip, 0); + } } else { @@ -323,8 +329,11 @@ public void PlayClip(AtomAnimationClip clip, bool sequencing, bool allowPreserve clip.animationPattern.SetBoolParamValue("loopOnce", false); clip.animationPattern.ResetAndPlay(); } + if (sequencing && clip.nextAnimationName != null) + { AssignNextAnimation(clip); + } onIsPlayingChanged.Invoke(clip); } @@ -584,6 +593,9 @@ private void AssignNextAnimation(AtomAnimationClip source) if (source.nextAnimationTime <= 0) return; + if (source.nextAnimationName == SlaveAnimationName) + return; + AtomAnimationClip next; string group; @@ -626,8 +638,18 @@ private void ScheduleNextAnimation(AtomAnimationClip source, AtomAnimationClip n if (source.preserveLoops && next.preserveLoops) { if (source.nextAnimationTimeRandomize > 0f) - nextTime = Random.Range(nextTime - source.animationLength * 0.49f, nextTime + source.nextAnimationTimeRandomize.RoundToNearest(source.animationLength) + source.animationLength * 0.49f); - nextTime = nextTime.RoundToNearest(source.animationLength) - next.blendInDuration / 2f - source.clipTime; + { + nextTime += Random + .Range(source.animationLength * -0.49f, source.nextAnimationTimeRandomize.RoundToNearest(source.animationLength) + source.animationLength * 0.49f) + .RoundToNearest(source.animationLength); + } + else + { + + nextTime = nextTime.RoundToNearest(source.animationLength); + } + nextTime = nextTime - next.blendInDuration / 2f + (source.animationLength - source.clipTime); + //SuperController.LogMessage($"T{Time.time:0.000}s: clip: {source.clipTime}, next: {nextTime:0.000}s"); } else if (source.nextAnimationTimeRandomize > 0f) { @@ -656,6 +678,33 @@ private void ScheduleNextAnimation(AtomAnimationClip source, AtomAnimationClip n source.playbackScheduledFadeOutAtRemaining = float.NaN; } } + SyncSiblingClips(source, next); + } + + private void SyncSiblingClips(AtomAnimationClip source, AtomAnimationClip next) + { + AtomAnimationClip siblingSourceClip = null; + AtomAnimationClip siblingNextClip = null; + + var layers = index.ByLayer(); + for (var i = 0; i < layers.Count; i++) + { + var layer = layers[i]; + if (layer[0].animationLayer == source.animationLayer) continue; + for (var j = 0; j < layer.Count; j++) + { + var clip = layer[j]; + if (clip.animationName == source.animationName) siblingSourceClip = clip; + else if (clip.animationName == next.animationName) siblingNextClip = clip; + if (siblingSourceClip != null && siblingNextClip != null) break; + } + if (siblingSourceClip != null && siblingNextClip != null) break; + } + + if (siblingSourceClip == null || siblingNextClip == null) return; + + siblingSourceClip.playbackScheduledNextAnimationName = siblingNextClip.animationName; + siblingSourceClip.playbackScheduledNextTimeLeft = source.playbackScheduledNextTimeLeft; } #endregion diff --git a/src/AtomAnimations/Utils/FloatExtensions.cs b/src/AtomAnimations/Utils/FloatExtensions.cs index f7db3ed0..f95f5109 100644 --- a/src/AtomAnimations/Utils/FloatExtensions.cs +++ b/src/AtomAnimations/Utils/FloatExtensions.cs @@ -50,10 +50,10 @@ public static float ExponentialScale(this float value, float midValue, float max return a + b * Mathf.Exp(c * value); } - public static float RoundToNearest(this float value, float multiple) + public static float RoundToNearest(this float value, float modulo) { - var half = multiple / 2; - return value + half - (value + half) % multiple; + var half = modulo / 2; + return value + half - (value + half) % modulo; } } } diff --git a/src/UI/Screens/AddAnimationScreen.cs b/src/UI/Screens/AddAnimationScreen.cs index b892b3b1..e33c36df 100644 --- a/src/UI/Screens/AddAnimationScreen.cs +++ b/src/UI/Screens/AddAnimationScreen.cs @@ -33,12 +33,8 @@ public override void Init(IAtomPlugin plugin, object arg) prefabFactory.CreateHeader("Add animations", 1); - prefabFactory.CreateSpacer(); - prefabFactory.CreateHeader("Add animations", 2); - InitCreateAnimationUI(); - prefabFactory.CreateSpacer(); prefabFactory.CreateHeader("Add options", 2); InitCreateAnimationOptionsUI(); @@ -82,6 +78,9 @@ private void InitCreateAnimationUI() private void InitCreateAnimationOptionsUI() { + _createName = new JSONStorableString("New animation name", ""); + prefabFactory.CreateTextInput(_createName); + _createPosition = new JSONStorableStringChooser( "Add at position", new List { _positionFirst, _positionPrevious, _positionNext, _positionLast }, @@ -89,10 +88,7 @@ private void InitCreateAnimationOptionsUI() "Add at position"); prefabFactory.CreatePopup(_createPosition, false, true); - _createName = new JSONStorableString("Name", ""); - prefabFactory.CreateTextInput(_createName); - - _createInOtherAtoms = new JSONStorableBool("Create in other atoms", _previousCreateInOtherAtoms, val => _previousCreateInOtherAtoms = val); + _createInOtherAtoms = new JSONStorableBool("Auto create in other atoms", _previousCreateInOtherAtoms, val => _previousCreateInOtherAtoms = val); prefabFactory.CreateToggle(_createInOtherAtoms); } diff --git a/src/UI/Screens/SequencingScreen.cs b/src/UI/Screens/SequencingScreen.cs index 181f3693..9d5d7dc1 100644 --- a/src/UI/Screens/SequencingScreen.cs +++ b/src/UI/Screens/SequencingScreen.cs @@ -146,8 +146,7 @@ private void InitRandomizeLengthUI() private void ChangeRandomizeLength(float val) { - current.nextAnimationTimeRandomize = current.preserveLoops ? val.RoundToNearest(current.animationLength) : val.Snap(animationEditContext.snap); - _randomizeRangeJSON.valNoCallback = current.nextAnimationTimeRandomize; + SyncPlayNext(); } private void InitPreviewUI() @@ -365,13 +364,19 @@ private void SyncPlayNext() RoundNextTimeToNearestLoop(); var nextTime = _nextAnimationTimeJSON.val; var nextName = _nextAnimationJSON.val; + var randomizeTime = current.preserveLoops ? _randomizeRangeJSON.val.RoundToNearest(current.animationLength) : _randomizeRangeJSON.val.Snap(animationEditContext.snap); - if (nextName == _noNextAnimation) + if (nextName == "(Slave)") + { + // Do nothing, but this shouldn't be "set" + } + else if (nextName == _noNextAnimation) { foreach (var clip in animation.GetClips(current.animationName)) { clip.nextAnimationName = null; clip.nextAnimationTime = 0f; + clip.nextAnimationTimeRandomize = 0f; } } else @@ -392,11 +397,13 @@ private void SyncPlayNext() if (!NextExists(clip, nextName)) continue; - clip.nextAnimationName = _nextAnimationJSON.val; + clip.nextAnimationName = clip == current ? _nextAnimationJSON.val : AtomAnimation.SlaveAnimationName; clip.nextAnimationTime = nextTime; + clip.nextAnimationTimeRandomize = randomizeTime; } _nextAnimationTimeJSON.valNoCallback = nextTime; + _randomizeRangeJSON.valNoCallback = randomizeTime; } RefreshTransitionUI();