Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
60d6fe6
Add sequence ease support
legoritma Mar 18, 2025
b48161b
Add sequence test
legoritma Mar 26, 2025
4e7ceed
Bugfix: Endless exception loop when exiting prefab mode
emptybraces Apr 23, 2025
2e21d81
Prevent duplicate event registration
emptybraces Apr 23, 2025
20a6c3f
Feature: color change for sprite SubMeshUI
emptybraces Jun 25, 2025
1cdd9e3
Fix: "Duplicate Array Element" in the context menu of LitMotionAnimat…
emptybraces Jun 25, 2025
15016d8
Fix: Color doesn't reset after pressing the Stop button
emptybraces Jun 25, 2025
651d216
Add feature solo-playing
emptybraces Jun 25, 2025
d713b92
Changed the timing of GroupID calculation to Awake
emptybraces Jun 26, 2025
43df1ba
bugfix
emptybraces Jun 26, 2025
c1ef320
ignore .meta file
Rotanticu Jul 16, 2025
365cd45
fix error
Rotanticu Jul 16, 2025
dd1ee21
Declaration of fork target
Rotanticu Jul 16, 2025
c2f44d6
Merge branch 'pr/210'
Rotanticu Jul 16, 2025
3711619
Merge branch 'pr/215'
Rotanticu Jul 16, 2025
8ea3089
Merge branch 'pr/223'
Rotanticu Jul 16, 2025
59a7f1a
Merge branch 'pr/232'
Rotanticu Jul 16, 2025
2846372
Merge branch 'pr/233'
Rotanticu Jul 16, 2025
9043270
Merge branch 'pr/234'
Rotanticu Jul 16, 2025
9093b86
Add animation spec base classes and interfaces
Rotanticu Jul 17, 2025
f614063
IAnimationSpec and AnimationVector
Rotanticu Jul 17, 2025
9682924
第一次修改 放弃
Rotanticu Sep 18, 2025
0466a96
弹簧缓冲器代码
Rotanticu Sep 18, 2025
31c7192
Update DamperUtility.cs
Rotanticu Sep 18, 2025
570c747
经过优化和测试的Damper代码
Rotanticu Sep 19, 2025
d5b1ea8
改个更通用的名字 统一使用double类型
Rotanticu Sep 20, 2025
46ab302
新增Spring动画
Rotanticu Sep 20, 2025
0a0cd14
增加动画中动态修改结束值功能,增加多维弹簧功能
Rotanticu Sep 21, 2025
fbcf242
floatX的多维版本阻尼器函数要改成ref才能通过burst编译
Rotanticu Sep 23, 2025
b6e44f6
删除不需要的函数
Rotanticu Sep 23, 2025
6ad07ba
修改刚度默认值
Rotanticu Sep 23, 2025
bc7f335
改了下时间限制阻尼器,简化参数输入,收敛时间更接近给出的时间
Rotanticu Sep 23, 2025
bb2d678
参数都改成引用传递,可以直接使用option的值
Rotanticu Sep 23, 2025
153736c
省略精度容差参数
Rotanticu Sep 24, 2025
7f63441
增加阈值判断
Rotanticu Sep 24, 2025
975a447
完善对Delay、Loops、Compeleted相关功能的支持
Rotanticu Sep 26, 2025
b21770a
删掉未使用代码,注释更改为中文
Rotanticu Sep 27, 2025
b1998fa
增加测试脚本和场景文件
Rotanticu Sep 27, 2025
0525cb3
Update SpringUtilityTest.unity
Rotanticu Sep 27, 2025
db29a00
临时更新一版文档
Rotanticu Sep 27, 2025
e350230
Merge branch 'SecendAttempt'
Rotanticu Sep 27, 2025
e2d4230
程序集错误设置
Rotanticu Sep 27, 2025
0c9178f
Merge branch 'main' of https://github.com/Rotanticu/LitMotion
Rotanticu Sep 27, 2025
a18301a
还原错误的合并
Rotanticu Sep 27, 2025
8aab115
测试脚本执行顺序错误
Rotanticu Sep 27, 2025
5dafd58
修复测试脚本的一些显示错误
Rotanticu Sep 28, 2025
79b1319
修正测试脚本显示问题
Rotanticu Sep 28, 2025
2611b61
Update SpringUtility.cs
Rotanticu Sep 29, 2025
0d45bf5
Merge branch 'main' into main
Rotanticu Dec 20, 2025
fa2c3cf
revert gitignore
Rotanticu Dec 28, 2025
563529f
delete api md
Rotanticu Dec 28, 2025
83e9424
delete readme md
Rotanticu Dec 28, 2025
d2e8860
revert readme
Rotanticu Dec 28, 2025
b764cc8
delete document
Rotanticu Dec 28, 2025
3ea6db1
delete spring.test
Rotanticu Dec 28, 2025
2b81649
revert gitignore
Rotanticu Dec 28, 2025
d35bcf7
delete spring test scene
Rotanticu Dec 28, 2025
f6feea6
revert LitMotion.Animation/Editor/LitMotionAnimationEditor.cs
Rotanticu Dec 28, 2025
1251462
revert TextMeshProComponents.cs
Rotanticu Dec 28, 2025
1d7ecc9
revert LitMotion.Animation/Runtime/LitMotionAnimation.cs
Rotanticu Dec 28, 2025
70b2a45
Revert "Add sequence ease support"
Rotanticu Dec 28, 2025
96b6d19
When calculating deltaTime, multiply by the playback speed first.
Rotanticu Dec 28, 2025
33ebad7
Translation annotations in English
Rotanticu Dec 28, 2025
c6a413d
revert SequenceTest.cs
Rotanticu Dec 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
*.DS_Store

# Project for Asset Store
src/LitMotion.AssetStore/*
src/LitMotion.AssetStore/*
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ IEnumerator Start()
var direction = i % 2 == 0 ? 1 : -1;
yield return LMotion.Create(-5f * direction, 5f * direction, 2f)
.WithEase(Ease.InOutSine)
.BindToPositionX(targets[i])
.ToYieldInteraction();
.BindToPositionX(targets[i]);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using Unity.Jobs;
using UnityEngine;
using Unity.Mathematics;
using LitMotion;
using LitMotion.Adapters;

[assembly: RegisterGenericJobType(typeof(MotionUpdateJob<float, SpringOptions, FloatSpringMotionAdapter>))]
[assembly: RegisterGenericJobType(typeof(MotionUpdateJob<Vector2, SpringOptions, Vector2SpringMotionAdapter>))]
[assembly: RegisterGenericJobType(typeof(MotionUpdateJob<Vector3, SpringOptions, Vector3SpringMotionAdapter>))]
[assembly: RegisterGenericJobType(typeof(MotionUpdateJob<Vector4, SpringOptions, Vector4SpringMotionAdapter>))]

namespace LitMotion.Adapters
{
public readonly struct FloatSpringMotionAdapter : IMotionAdapter<float, SpringOptions>
{
public float Evaluate(ref float startValue, ref float endValue, ref SpringOptions options, in MotionEvaluationContext context)
{
options.TargetValue.x = endValue;
SpringUtility.SpringElastic(
(float)context.DeltaTime,
ref options.CurrentValue.x,
ref options.CurrentVelocity.x,
options.TargetValue.x,
options.TargetVelocity.x,
options.DampingRatio,
options.Stiffness
);
return options.CurrentValue.x;
}

public bool IsCompleted(ref float startValue, ref float endValue, ref SpringOptions options)
{
return SpringUtility.Approximately(options.CurrentValue.x, options.TargetValue.x);
}

public bool IsDurationBased => false;
}

/// <summary>
/// Spring motion adapter for Vector2 using float4 SpringElastic method.
/// </summary>
public readonly struct Vector2SpringMotionAdapter : IMotionAdapter<Vector2, SpringOptions>
{
public Vector2 Evaluate(ref Vector2 startValue, ref Vector2 endValue, ref SpringOptions options, in MotionEvaluationContext context)
{
float deltaTime = (float)context.DeltaTime;
options.TargetValue.xy = endValue;
SpringUtility.SpringElastic(
deltaTime,
ref options.CurrentValue,
ref options.CurrentVelocity,
options.TargetValue,
options.TargetVelocity,
options.DampingRatio,
options.Stiffness
);
return options.CurrentValue.xy;
}

public bool IsCompleted(ref Vector2 startValue, ref Vector2 endValue, ref SpringOptions options)
{
return SpringUtility.Approximately(options.CurrentValue, options.TargetValue);
}

public bool IsDurationBased => false;
}

/// <summary>
/// Spring motion adapter for Vector3 using float4 SpringElastic method.
/// </summary>
public readonly struct Vector3SpringMotionAdapter : IMotionAdapter<Vector3, SpringOptions>
{
public Vector3 Evaluate(ref Vector3 startValue, ref Vector3 endValue, ref SpringOptions options, in MotionEvaluationContext context)
{
float deltaTime = (float)context.DeltaTime;
options.TargetValue.xyz = endValue;
SpringUtility.SpringElastic(
deltaTime,
ref options.CurrentValue,
ref options.CurrentVelocity,
options.TargetValue,
options.TargetVelocity,
options.DampingRatio,
options.Stiffness
);
return options.CurrentValue.xyz;
}

public bool IsCompleted(ref Vector3 startValue, ref Vector3 endValue, ref SpringOptions options)
{
return SpringUtility.Approximately(options.CurrentValue, options.TargetValue);
}

public bool IsDurationBased => false;
}

/// <summary>
/// Spring motion adapter for Vector4 using float4 SpringElastic method.
/// </summary>
public readonly struct Vector4SpringMotionAdapter : IMotionAdapter<Vector4, SpringOptions>
{
public Vector4 Evaluate(ref Vector4 startValue, ref Vector4 endValue, ref SpringOptions options, in MotionEvaluationContext context)
{
float deltaTime = (float)context.DeltaTime;
options.TargetValue = endValue;
SpringUtility.SpringElastic(
deltaTime,
ref options.CurrentValue,
ref options.CurrentVelocity,
options.TargetValue,
options.TargetVelocity,
options.DampingRatio,
options.Stiffness
);

return options.CurrentValue;
}

public bool IsCompleted(ref Vector4 startValue, ref Vector4 endValue, ref SpringOptions options)
{
return SpringUtility.Approximately(options.CurrentValue, options.TargetValue);
}

public bool IsDurationBased => false;
}
}
7 changes: 7 additions & 0 deletions src/LitMotion/Assets/LitMotion/Runtime/IMotionAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,12 @@ public interface IMotionAdapter<TValue, TOptions>
/// <param name="context">Animation context</param>
/// <returns>Current value</returns>
TValue Evaluate(ref TValue startValue, ref TValue endValue, ref TOptions options, in MotionEvaluationContext context);

bool IsCompleted(ref TValue startValue, ref TValue endValue, ref TOptions options)
{
return false;
}

bool IsDurationBased => true;
}
}
5 changes: 4 additions & 1 deletion src/LitMotion/Assets/LitMotion/Runtime/IMotionOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@ namespace LitMotion
/// <summary>
/// Implement this interface to define special options that can be applied to motion.
/// </summary>
public interface IMotionOptions { }
public interface IMotionOptions
{
void Restart() { }
}
}
139 changes: 120 additions & 19 deletions src/LitMotion/Assets/LitMotion/Runtime/Internal/MotionData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@ public readonly double TotalDuration

public MotionState State;
public MotionParameters Parameters;
public double CurrentLoopStartTime;

public readonly double TimeSinceStart => State.Time - Parameters.Delay;

public void Update(double time, out float progress)
public void UpdateDurationBasedState(double time, out float progress)
{
State.PrevCompletedLoops = State.CompletedLoops;
State.PrevStatus = State.Status;
Expand Down Expand Up @@ -172,6 +173,41 @@ public void Update(double time, out float progress)
}
}

public void UpdateIterationState(double time, double deltaTime,bool isOnceCompleted)
{
State.PrevCompletedLoops = State.CompletedLoops;
State.PrevStatus = State.Status;
State.Time = time;
bool isDelayed;
if(time == 0 || (isOnceCompleted && State.CompletedLoops <= Parameters.Loops - 1))
{
CurrentLoopStartTime = time;
}
if (Parameters.DelayType == DelayType.FirstLoop)
isDelayed = TimeSinceStart < 0f;
else
{
isDelayed = State.Time - CurrentLoopStartTime - Parameters.Delay < 0f;
}

if (isOnceCompleted)
{
State.CompletedLoops++;
}
if (isOnceCompleted && Parameters.Loops >= 0 && State.CompletedLoops > Parameters.Loops - 1)
{
State.Status = MotionStatus.Completed;
}
else if (isDelayed || State.Time < 0)
{
State.Status = MotionStatus.Delayed;
}
else
{
State.Status = MotionStatus.Playing;
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Complete(out float progress)
{
Expand Down Expand Up @@ -224,34 +260,99 @@ internal struct MotionData<TValue, TOptions>
public TValue EndValue;
public TOptions Options;

public void Update<TAdapter>(double time, out TValue result)
public void Update<TAdapter>(double time, double deltaTime, out TValue result)
where TAdapter : unmanaged, IMotionAdapter<TValue, TOptions>
{
Core.Update(time, out var progress);

result = default(TAdapter).Evaluate(ref StartValue, ref EndValue, ref Options, new MotionEvaluationContext()
bool isDurationBased = default(TAdapter).IsDurationBased;
if (isDurationBased)
{
Progress = progress,
Time = time,
});
Core.UpdateDurationBasedState(time, out var progress);
result = default(TAdapter).Evaluate(ref StartValue, ref EndValue, ref Options, new MotionEvaluationContext()
{
Progress = progress,
Time = time,
DeltaTime = deltaTime,
});
}
else
{
bool isOnceCompleted = false;
if (time <= 0 || Core.State.Status == MotionStatus.Scheduled)
Core.UpdateIterationState(time, deltaTime, false);

if (Core.State.Status == MotionStatus.Delayed)
result = StartValue;
else if (Core.State.Status == MotionStatus.Completed)
result = EndValue;
else
{
if (Core.Parameters.LoopType == LoopType.Restart)
{
result = default(TAdapter).Evaluate(ref StartValue, ref EndValue, ref Options, new MotionEvaluationContext()
{
Time = time,
DeltaTime = deltaTime,
});
isOnceCompleted = default(TAdapter).IsCompleted(ref StartValue, ref EndValue, ref Options);
if (isOnceCompleted && Core.State.Status == MotionStatus.Playing)
Options.Restart();
}
else if (Core.Parameters.LoopType == LoopType.Flip || Core.Parameters.LoopType == LoopType.Yoyo)
{
var isOdd = Core.State.CompletedLoops % 2 == 1;
result = isOdd
? default(TAdapter).Evaluate(ref EndValue, ref StartValue, ref Options, new MotionEvaluationContext()
{
Time = time,
DeltaTime = deltaTime,
})
: default(TAdapter).Evaluate(ref StartValue, ref EndValue, ref Options, new MotionEvaluationContext()
{
Time = time,
DeltaTime = deltaTime,
});
isOnceCompleted = isOdd
? default(TAdapter).IsCompleted(ref EndValue, ref StartValue, ref Options)
: default(TAdapter).IsCompleted(ref StartValue, ref EndValue, ref Options);
}
else
{

result = default(TAdapter).Evaluate(ref StartValue, ref EndValue, ref Options, new MotionEvaluationContext()
{
Time = time,
DeltaTime = deltaTime,
});
isOnceCompleted = default(TAdapter).IsCompleted(ref StartValue, ref EndValue, ref Options);
}
}
Core.UpdateIterationState(time, deltaTime, isOnceCompleted);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Complete<TAdapter>(out TValue result)
where TAdapter : unmanaged, IMotionAdapter<TValue, TOptions>
{
bool isDurationBased = default(TAdapter).IsDurationBased;
Core.Complete(out var progress);

result = default(TAdapter).Evaluate(
ref StartValue,
ref EndValue,
ref Options,
new()
{
Progress = progress,
Time = Core.State.Time,
}
);
if (isDurationBased)
{
result = default(TAdapter).Evaluate(
ref StartValue,
ref EndValue,
ref Options,
new()
{
Progress = progress,
Time = Core.State.Time,
}
);
}
else
{
result = EndValue;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ public static ref MotionData GetDataRef(MotionHandle handle, bool checkIsInSeque
CheckTypeId(handle);
return ref list[handle.StorageId].GetDataRef(handle, checkIsInSequence);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref MotionData<TValue, TOptions> GetTypeDataRef<TValue, TOptions>(MotionHandle handle, bool checkIsInSequence = true)
where TValue : unmanaged
where TOptions : unmanaged, IMotionOptions
{
CheckTypeId(handle);
return ref list[handle.StorageId].GetTypeDataRef<TValue, TOptions>(handle, checkIsInSequence);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref ManagedMotionData GetManagedDataRef(MotionHandle handle, bool checkIsInSequence = true)
Expand Down
13 changes: 12 additions & 1 deletion src/LitMotion/Assets/LitMotion/Runtime/Internal/MotionStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ internal interface IMotionStorage
void Complete(MotionHandle handle, bool checkIsInSequence = true);
void SetTime(MotionHandle handle, double time, bool checkIsInSequence = true);
ref MotionData GetDataRef(MotionHandle handle, bool checkIsInSequence = true);
ref MotionData<ValueType, OptionsType> GetTypeDataRef<ValueType, OptionsType>(MotionHandle handle, bool checkIsInSequence = true)
where ValueType : unmanaged
where OptionsType : unmanaged, IMotionOptions;
ref ManagedMotionData GetManagedDataRef(MotionHandle handle, bool checkIsInSequence = true);
void AddToSequence(MotionHandle handle, out double motionDuration);
MotionDebugInfo GetDebugInfo(MotionHandle handle);
Expand Down Expand Up @@ -387,7 +390,7 @@ public unsafe void SetTime(MotionHandle handle, double time, bool checkIsInSeque

if (checkIsInSequence && state.IsInSequence) Error.MotionIsInSequence();

dataPtr->Update<TAdapter>(time, out var result);
dataPtr->Update<TAdapter>(time, time - state.Time, out var result);

var status = state.Status;
ref var managedData = ref managedDataArray[denseIndex];
Expand Down Expand Up @@ -454,6 +457,14 @@ public ref MotionData GetDataRef(MotionHandle handle, bool checkIsInSequence = t
return ref UnsafeUtility.As<MotionData<TValue, TOptions>, MotionData>(ref unmanagedDataArray[slot.DenseIndex]);
}

public ref MotionData<ValueType, OptionsType> GetTypeDataRef<ValueType, OptionsType>(MotionHandle handle, bool checkIsInSequence = true)
where ValueType : unmanaged
where OptionsType : unmanaged, IMotionOptions
{
ref var slot = ref GetSlotWithVarify(handle, checkIsInSequence);
return ref UnsafeUtility.As<MotionData<TValue, TOptions>, MotionData<ValueType, OptionsType>>(ref unmanagedDataArray[slot.DenseIndex]);
}

public MotionDebugInfo GetDebugInfo(MotionHandle handle)
{
ref var slot = ref GetSlotWithVarify(handle, false);
Expand Down
Loading