diff --git a/src/LitMotion/Assets/LitMotion/Runtime/DelayType.cs b/src/LitMotion/Assets/LitMotion/Runtime/DelayType.cs new file mode 100644 index 00000000..1e97dfd8 --- /dev/null +++ b/src/LitMotion/Assets/LitMotion/Runtime/DelayType.cs @@ -0,0 +1,17 @@ +namespace LitMotion +{ + /// + /// Specifies the behavior of WithDelay. + /// + public enum DelayType : byte + { + /// + /// Delay when starting playback + /// + FirstLoop = 0, + /// + /// Delay every loop + /// + EveryLoop = 1, + } +} \ No newline at end of file diff --git a/src/LitMotion/Assets/LitMotion/Runtime/DelayType.cs.meta b/src/LitMotion/Assets/LitMotion/Runtime/DelayType.cs.meta new file mode 100644 index 00000000..67791698 --- /dev/null +++ b/src/LitMotion/Assets/LitMotion/Runtime/DelayType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ede4abfbaa71b475ab0e832d38c4495a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/LitMotion/Assets/LitMotion/Runtime/Internal/MotionData.cs b/src/LitMotion/Assets/LitMotion/Runtime/Internal/MotionData.cs index c13d71ce..d07a41b4 100644 --- a/src/LitMotion/Assets/LitMotion/Runtime/Internal/MotionData.cs +++ b/src/LitMotion/Assets/LitMotion/Runtime/Internal/MotionData.cs @@ -21,6 +21,7 @@ public struct MotionData public MotionTimeKind TimeKind; public float Delay; public int Loops; + public DelayType DelayType; public LoopType LoopType; public TValue StartValue; diff --git a/src/LitMotion/Assets/LitMotion/Runtime/MotionBuilder.cs b/src/LitMotion/Assets/LitMotion/Runtime/MotionBuilder.cs index 32118ab6..870f2b18 100644 --- a/src/LitMotion/Assets/LitMotion/Runtime/MotionBuilder.cs +++ b/src/LitMotion/Assets/LitMotion/Runtime/MotionBuilder.cs @@ -28,6 +28,7 @@ public static void Return(MotionBuilderBuffer buffer) buffer.Duration = default; buffer.Ease = default; buffer.Delay = default; + buffer.DelayType = default; buffer.Loops = 1; buffer.LoopType = default; buffer.StartValue = default; @@ -50,6 +51,7 @@ public static void Return(MotionBuilderBuffer buffer) public float Duration; public Ease Ease; public float Delay; + public DelayType DelayType; public int Loops = 1; public LoopType LoopType; @@ -111,6 +113,21 @@ public readonly MotionBuilder WithDelay(float delay) return this; } + /// + /// Specify the delay time when the motion starts. + /// + /// Delay time (seconds) + /// Delay type + /// This builder to allow chaining multiple method calls. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly MotionBuilder WithDelay(float delay, DelayType delayType) + { + CheckBuffer(); + buffer.Delay = delay; + buffer.DelayType = delayType; + return this; + } + /// /// Specify the number of times the motion is repeated. If specified as less than 0, the motion will continue to play until manually completed or canceled. /// @@ -344,6 +361,7 @@ internal MotionData BuildMotionData() Duration = buffer.Duration, Ease = buffer.Ease, Delay = buffer.Delay, + DelayType = buffer.DelayType, Loops = buffer.Loops, LoopType = buffer.LoopType, Status = MotionStatus.Scheduled, diff --git a/src/LitMotion/Assets/LitMotion/Runtime/MotionUpdateJob.cs b/src/LitMotion/Assets/LitMotion/Runtime/MotionUpdateJob.cs index 8cb1276e..df7d0a23 100644 --- a/src/LitMotion/Assets/LitMotion/Runtime/MotionUpdateJob.cs +++ b/src/LitMotion/Assets/LitMotion/Runtime/MotionUpdateJob.cs @@ -42,42 +42,77 @@ public void Execute([AssumeRange(0, int.MaxValue)] int index) }; var motionTime = currentTime - ptr->StartTime; - var time = motionTime - ptr->Delay; double t; bool isCompleted; + bool isDelayed; int completedLoops; int clampedCompletedLoops; if (Hint.Unlikely(ptr->Duration <= 0f)) { - isCompleted = time > 0f; - if (isCompleted) + if (ptr->DelayType == DelayType.FirstLoop || ptr->Delay == 0f) { - t = 1f; - completedLoops = ptr->Loops; + var time = motionTime - ptr->Delay; + isCompleted = ptr->Loops >= 0 && time > 0f; + if (isCompleted) + { + t = 1f; + completedLoops = ptr->Loops; + } + else + { + t = 0f; + completedLoops = time < 0f ? -1 : 0; + } + clampedCompletedLoops = ptr->Loops < 0 ? math.max(0, completedLoops) : math.clamp(completedLoops, 0, ptr->Loops); + isDelayed = time < 0; } else { - t = 0f; - completedLoops = time < 0f ? -1 : 0; + completedLoops = (int)math.floor(motionTime / ptr->Delay); + clampedCompletedLoops = ptr->Loops < 0 ? math.max(0, completedLoops) : math.clamp(completedLoops, 0, ptr->Loops); + isCompleted = ptr->Loops >= 0 && clampedCompletedLoops > ptr->Loops - 1; + isDelayed = !isCompleted; + t = isCompleted ? 1f : 0f; } - clampedCompletedLoops = ptr->Loops < 0 ? math.max(0, completedLoops) : math.clamp(completedLoops, 0, ptr->Loops); } else { - completedLoops = (int)math.floor(time / ptr->Duration); - clampedCompletedLoops = ptr->Loops < 0 ? math.max(0, completedLoops) : math.clamp(completedLoops, 0, ptr->Loops); - isCompleted = ptr->Loops >= 0 && clampedCompletedLoops > ptr->Loops - 1; - - if (isCompleted) + if (ptr->DelayType == DelayType.FirstLoop) { - t = 1f; + var time = motionTime - ptr->Delay; + completedLoops = (int)math.floor(time / ptr->Duration); + clampedCompletedLoops = ptr->Loops < 0 ? math.max(0, completedLoops) : math.clamp(completedLoops, 0, ptr->Loops); + isCompleted = ptr->Loops >= 0 && clampedCompletedLoops > ptr->Loops - 1; + isDelayed = time < 0f; + + if (isCompleted) + { + t = 1f; + } + else + { + var currentLoopTime = time - ptr->Duration * clampedCompletedLoops; + t = math.clamp(currentLoopTime / ptr->Duration, 0f, 1f); + } } else { - var currentLoopTime = time - ptr->Duration * clampedCompletedLoops; - t = math.clamp(currentLoopTime / ptr->Duration, 0f, 1f); + var currentLoopTime = math.fmod(motionTime, ptr->Duration + ptr->Delay) - ptr->Delay; + completedLoops = (int)math.floor(motionTime / (ptr->Duration + ptr->Delay)); + clampedCompletedLoops = ptr->Loops < 0 ? math.max(0, completedLoops) : math.clamp(completedLoops, 0, ptr->Loops); + isCompleted = ptr->Loops >= 0 && clampedCompletedLoops > ptr->Loops - 1; + isDelayed = currentLoopTime < 0; + + if (isCompleted) + { + t = 1f; + } + else + { + t = math.clamp(currentLoopTime / ptr->Duration, 0f, 1f); + } } } @@ -97,11 +132,15 @@ public void Execute([AssumeRange(0, int.MaxValue)] int index) break; } - if (ptr->Loops > 0 && time >= ptr->Duration * ptr->Loops) + var totalDuration = ptr->DelayType == DelayType.FirstLoop + ? ptr->Delay + ptr->Duration * ptr->Loops + : (ptr->Delay + ptr->Duration) * ptr->Loops; + + if (ptr->Loops > 0 && motionTime >= totalDuration) { ptr->Status = MotionStatus.Completed; } - else if (motionTime < ptr->Delay) + else if (isDelayed) { ptr->Status = MotionStatus.Delayed; } diff --git a/src/LitMotion/Assets/LitMotion/Tests/Runtime/DelayTest.cs b/src/LitMotion/Assets/LitMotion/Tests/Runtime/DelayTest.cs index 11e201f1..7fef9f4c 100644 --- a/src/LitMotion/Assets/LitMotion/Tests/Runtime/DelayTest.cs +++ b/src/LitMotion/Assets/LitMotion/Tests/Runtime/DelayTest.cs @@ -10,23 +10,47 @@ public class DelayTest [UnityTest] public IEnumerator Test_Delay() { - var t = Time.time; + var t = Time.timeAsDouble; yield return LMotion.Create(0f, 1f, 0.5f) .WithDelay(0.5f) - .RunWithoutBinding() + .BindToUnityLogger() .ToYieldInteraction(); - Assert.IsTrue(Time.time - t >= 1f); + Assert.That(Time.timeAsDouble - t, Is.GreaterThan(0.95).And.LessThan(1.1)); } [UnityTest] public IEnumerator Test_Delay_WithZeroDuration() { - var t = Time.time; + var t = Time.timeAsDouble; yield return LMotion.Create(0f, 1f, 0f) .WithDelay(1f) - .RunWithoutBinding() + .BindToUnityLogger() .ToYieldInteraction(); - Assert.IsTrue(Time.time - t >= 1f); + Assert.That(Time.timeAsDouble - t, Is.GreaterThan(0.95).And.LessThan(1.1)); + } + + [UnityTest] + public IEnumerator Test_Delay_EveryLoop() + { + var t = Time.timeAsDouble; + yield return LMotion.Create(0f, 1f, 0.5f) + .WithLoops(2) + .WithDelay(0.5f, DelayType.EveryLoop) + .BindToUnityLogger() + .ToYieldInteraction(); + Assert.That(Time.timeAsDouble - t, Is.GreaterThan(1.95).And.LessThan(2.1)); + } + + [UnityTest] + public IEnumerator Test_Delay_EveryLoop_WithZeroDuration() + { + var t = Time.timeAsDouble; + yield return LMotion.Create(0f, 1f, 0f) + .WithLoops(3) + .WithDelay(0.5f, DelayType.EveryLoop) + .BindToUnityLogger() + .ToYieldInteraction(); + Assert.That(Time.timeAsDouble - t, Is.GreaterThan(1.45).And.LessThan(1.6)); } } } \ No newline at end of file