using System; namespace Cthangover.Live2D.Cubism.Framework { /// /// Abstract base for all Live2D Cubism motions (animations and expressions). /// Provides fade-in/fade-out timing, looping, weight blending, and event /// callbacks shared across and /// . /// /// Motions are managed by , which /// calls on start, then /// and /// every frame. /// public abstract class ACubismMotion { /// /// Callback invoked when the motion finishes (non-looping only). /// The finished motion is passed as the argument. /// public delegate void FinishedMotionCallback(ACubismMotion motion); /// /// Callback invoked when the motion begins playback. /// Called from right after /// the entry's times are configured. /// public delegate void BeganMotionCallback(ACubismMotion motion); protected float _fadeInSeconds = -1f; protected float _fadeOutSeconds = -1f; protected float _weight = 1f; protected float _offsetSeconds; protected bool _isLoop; protected bool _isLoopFadeIn = true; protected bool _previousLoopState; /// /// Duration of the fade-in transition in seconds. /// A negative value means the default (1 second) will be used /// by and . /// public float FadeInSeconds { get => _fadeInSeconds; set => _fadeInSeconds = value; } /// /// Duration of the fade-out transition in seconds. /// A negative value means the default (1 second) will be used. /// public float FadeOutSeconds { get => _fadeOutSeconds; set => _fadeOutSeconds = value; } /// /// Overall motion weight (0..1). Multiplied with the fade-in/fade-out /// factors in to produce the final blend weight. /// public float Weight { get => _weight; set => _weight = value; } /// /// Time offset in seconds applied to the motion's start time. /// Allows starting playback from an arbitrary point. /// public float OffsetSeconds { get => _offsetSeconds; set => _offsetSeconds = value; } /// /// Whether the motion loops. When enabled, /// wraps timeOffset back to the beginning after the duration elapses. /// public bool IsLoop { get => _isLoop; set => _isLoop = value; } /// /// When true and looping, the motion performs a fade-in at each loop /// restart. When false, the fade-in is skipped on subsequent loops. /// public bool IsLoopFadeIn { get => _isLoopFadeIn; set => _isLoopFadeIn = value; } /// /// Invoked when the motion finishes (non-looping only). /// Automatically called by /// when timeOffset exceeds duration. /// public FinishedMotionCallback OnFinishedMotion { get; set; } /// /// Invoked when the motion begins. Fires at the start of /// after the entry is initialized. /// public BeganMotionCallback OnBeganMotion { get; set; } /// /// Returns the total motion duration in seconds. /// Override in to return the motion3.json /// duration, or -1 for looping motions. /// public virtual float GetDuration() => -1f; /// /// Returns the loop cycle duration in seconds. /// Override in to return the full duration /// even when looping is active. /// public virtual float GetLoopDuration() => -1f; /// /// Initializes a queue entry with start/end times based on current /// , , /// and . Fires . /// public void SetupMotionQueueEntry(CubismMotionQueueEntry entry, float userTimeSeconds) { var fadeIn = _fadeInSeconds >= 0f ? _fadeInSeconds : 1f; entry.StartTime = userTimeSeconds - _offsetSeconds; entry.FadeInStartTime = userTimeSeconds; var duration = GetDuration(); UpdateEndTime(entry, duration); OnBeganMotion?.Invoke(this); } /// /// Computes the final frame weight = x fadeIn x fadeOut. /// Fade curves use the sine easing function from . /// public float UpdateFadeWeight(CubismMotionQueueEntry entry, float userTimeSeconds) { var fadeIn = GetFadeIn(entry, userTimeSeconds); var fadeOut = GetFadeOut(entry, userTimeSeconds); return _weight * fadeIn * fadeOut; } private float GetFadeIn(CubismMotionQueueEntry entry, float userTimeSeconds) { var fadeInTime = _fadeInSeconds >= 0f ? _fadeInSeconds : 1f; if (fadeInTime <= 0f) return 1f; var t = (userTimeSeconds - entry.FadeInStartTime) / fadeInTime; return CubismMath.GetEasingSine(t); } private float GetFadeOut(CubismMotionQueueEntry entry, float userTimeSeconds) { var fadeOutTime = _fadeOutSeconds >= 0f ? _fadeOutSeconds : 1f; if (fadeOutTime <= 0f) return 1f; var t = (entry.EndTime - userTimeSeconds) / fadeOutTime; return CubismMath.GetEasingSine(t); } /// /// Sets the queue entry's EndTime based on loop state and duration. /// Looping motions get EndTime = -1 (infinite). /// protected void UpdateEndTime(CubismMotionQueueEntry entry, float duration) { entry.EndTime = _isLoop ? -1f : entry.StartTime + duration; } /// /// Recalculates EndTime when changes mid-playback. /// Called by every frame /// to detect loop state transitions. /// protected void AdjustEndTime(CubismMotionQueueEntry entry, float duration) { if (_isLoop != _previousLoopState) { _previousLoopState = _isLoop; UpdateEndTime(entry, duration); } } /// /// Applies motion parameter values to the model for the current frame. /// Called every frame by . /// /// The native Cubism model to modify. /// Elapsed user time in seconds. /// Blend weight already multiplied by fade factors. /// The queue entry containing start/end timing. public abstract void DoUpdateParameters( CubismNativeModel model, float userTimeSeconds, float weight, CubismMotionQueueEntry entry); } }