/**
* Copyright(c) Live2D Inc. All rights reserved.
*
* Use of this source code is governed by the Live2D Open Software license
* that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html.
*/
using Live2D.Cubism.Core;
using Live2D.Cubism.Framework.Motion;
using UnityEngine;
namespace Live2D.Cubism.Framework.MotionFade
{
///
/// Cubism fade controller.
///
[RequireComponent(typeof(Animator))]
public class CubismFadeController : MonoBehaviour, ICubismUpdatable
{
#region Variable
///
/// Cubism fade motion list.
///
[SerializeField]
public CubismFadeMotionList CubismFadeMotionList;
///
/// Parameters cache.
///
private CubismParameter[] DestinationParameters { get; set; }
///
/// Parts cache.
///
private CubismPart[] DestinationParts { get; set; }
///
/// Model has motion controller component.
///
private CubismMotionController _motionController;
///
/// Model has cubism update controller component.
///
[HideInInspector]
public bool HasUpdateController { get; set; }
///
/// Fade state machine behavior set in the animator.
///
private ICubismFadeState[] _fadeStates;
///
/// Model has animator component.
///
private Animator _animator;
///
/// Restore parameter value.
///
private CubismParameterStore _parameterStore;
///
/// Fading flags for each layer.
///
private bool[] _isFading;
#endregion
#region Function
///
/// Refreshes the controller. Call this method after adding and/or removing s.
///
public void Refresh()
{
_animator = GetComponent();
// Fail silently...
if (_animator == null)
{
return;
}
DestinationParameters = this.FindCubismModel().Parameters;
DestinationParts = this.FindCubismModel().Parts;
_motionController = GetComponent();
_parameterStore = GetComponent();
// Get cubism update controller.
HasUpdateController = (GetComponent() != null);
_fadeStates = (ICubismFadeState[])_animator.GetBehaviours();
if ((_fadeStates == null || _fadeStates.Length == 0) && _motionController != null)
{
_fadeStates = _motionController.GetFadeStates();
}
if (_fadeStates == null)
{
return;
}
_isFading = new bool[_fadeStates.Length];
}
///
/// Called by cubism update controller. Order to invoke OnLateUpdate.
///
public int ExecutionOrder
{
get { return CubismUpdateExecutionOrder.CubismFadeController; }
}
///
/// Called by cubism update controller. Needs to invoke OnLateUpdate on Editing.
///
public bool NeedsUpdateOnEditing
{
get { return false; }
}
///
/// Called by cubism update controller. Updates controller.
///
///
/// Make sure this method is called after any animations are evaluated.
///
public void OnLateUpdate()
{
// Fail silently.
if (!enabled || _fadeStates == null || _parameterStore == null
|| DestinationParameters == null || DestinationParts == null)
{
return;
}
var time = Time.time;
for (var i = 0; i < _fadeStates.Length; ++i)
{
_isFading[i] = false;
var playingMotions = _fadeStates[i].GetPlayingMotions();
if (playingMotions == null || playingMotions.Count <= 1)
{
continue;
}
var latestPlayingMotion = playingMotions[playingMotions.Count - 1];
var playingMotionData = latestPlayingMotion.Motion;
var elapsedTime = time - latestPlayingMotion.StartTime;
for (var j = 0; j < playingMotionData.ParameterFadeInTimes.Length; j++)
{
if ((elapsedTime <= playingMotionData.FadeInTime) ||
((0 <= playingMotionData.ParameterFadeInTimes[j]) &&
(elapsedTime <= playingMotionData.ParameterFadeInTimes[j])))
{
_isFading[i] = true;
break;
}
}
}
var isFadingAllFinished = true;
for (var i = 0; i < _fadeStates.Length; ++i)
{
var playingMotions = _fadeStates[i].GetPlayingMotions();
var playingMotionCount = playingMotions.Count - 1;
if (_isFading[i])
{
isFadingAllFinished = false;
continue;
}
for (var j = playingMotionCount; j >= 0; --j)
{
if (playingMotions.Count <= 1)
{
break;
}
var playingMotion = playingMotions[j];
if (time <= playingMotion.EndTime)
{
continue;
}
// If fade-in has been completed, delete the motion that has been played back.
_fadeStates[i].StopAnimation(j);
}
}
if (isFadingAllFinished)
{
return;
}
_parameterStore.RestoreParameters();
// Update sources and destinations.
for (var i = 0; i < _fadeStates.Length; ++i)
{
if (!_isFading[i])
{
continue;
}
UpdateFade(_fadeStates[i]);
}
}
///
/// Update motion fade.
///
/// Fade state observer.
private void UpdateFade(ICubismFadeState fadeState)
{
var playingMotions = fadeState.GetPlayingMotions();
if (playingMotions == null)
{
// Do not process if there is only one motion, if it does not switch.
return;
}
// Weight set for the layer being processed.
// (In the case of the layer located at the top, it is forced to 1.)
var layerWeight = fadeState.GetLayerWeight();
var time = Time.time;
// Set playing motions end time.
if ((playingMotions.Count > 0) && (playingMotions[playingMotions.Count - 1].Motion != null) && (playingMotions[playingMotions.Count - 1].IsLooping))
{
var motion = playingMotions[playingMotions.Count - 1];
var newEndTime = time + motion.Motion.FadeOutTime;
motion.EndTime = newEndTime;
while (true)
{
if ((motion.StartTime + motion.Motion.MotionLength) >= time)
{
break;
}
motion.StartTime += motion.Motion.MotionLength;
}
playingMotions[playingMotions.Count - 1] = motion;
}
// Calculate MotionFade.
for (var i = 0; i < playingMotions.Count; i++)
{
var playingMotion = playingMotions[i];
var fadeMotion = playingMotion.Motion;
if (fadeMotion == null)
{
continue;
}
var elapsedTime = time - playingMotion.StartTime;
var endTime = playingMotion.EndTime - elapsedTime;
var fadeInTime = fadeMotion.FadeInTime;
var fadeOutTime = fadeMotion.FadeOutTime;
var fadeInWeight = (fadeInTime <= 0.0f)
? 1.0f
: CubismFadeMath.GetEasingSine(elapsedTime / fadeInTime);
var fadeOutWeight = (fadeOutTime <= 0.0f)
? 1.0f
: CubismFadeMath.GetEasingSine((playingMotion.EndTime - Time.time) / fadeOutTime);
playingMotions[i] = playingMotion;
var motionWeight = (i == 0)
? (fadeInWeight * fadeOutWeight)
: (fadeInWeight * fadeOutWeight * layerWeight);
// Apply to parameter values
for (var j = 0; j < DestinationParameters.Length; ++j)
{
var index = -1;
for (var k = 0; k < fadeMotion.ParameterIds.Length; ++k)
{
if (fadeMotion.ParameterIds[k] != DestinationParameters[j].Id)
{
continue;
}
index = k;
break;
}
if (index < 0)
{
// There is not target ID curve in motion.
continue;
}
DestinationParameters[j].Value = Evaluate(
fadeMotion.ParameterCurves[index], elapsedTime, endTime,
fadeInWeight, fadeOutWeight,
fadeMotion.ParameterFadeInTimes[index], fadeMotion.ParameterFadeOutTimes[index],
motionWeight, DestinationParameters[j].Value);
}
// Apply to part opacities
for (var j = 0; j < DestinationParts.Length; ++j)
{
var index = -1;
for (var k = 0; k < fadeMotion.ParameterIds.Length; ++k)
{
if (fadeMotion.ParameterIds[k] != DestinationParts[j].Id)
{
continue;
}
index = k;
break;
}
if (index < 0)
{
// There is not target ID curve in motion.
continue;
}
DestinationParts[j].Opacity = Evaluate(
fadeMotion.ParameterCurves[index], elapsedTime, endTime,
fadeInWeight, fadeOutWeight,
fadeMotion.ParameterFadeInTimes[index], fadeMotion.ParameterFadeOutTimes[index],
motionWeight, DestinationParts[j].Opacity);
}
}
}
///
/// Evaluate fade curve.
///
/// Curves to be evaluated.
/// Elapsed Time.
/// Fading end time.
/// Fade in time.
/// Fade out time.
/// Fade in time parameter.
/// Fade out time parameter.
/// Motion weight.
/// Current value with weight applied.
public float Evaluate(
AnimationCurve curve, float elapsedTime, float endTime,
float fadeInTime, float fadeOutTime,
float parameterFadeInTime, float parameterFadeOutTime,
float motionWeight, float currentValue)
{
if (curve.length <= 0)
{
return currentValue;
}
// Motion fade.
if (parameterFadeInTime < 0.0f &&
parameterFadeOutTime < 0.0f)
{
return currentValue + (curve.Evaluate(elapsedTime) - currentValue) * motionWeight;
}
// Parameter fade.
float fadeInWeight, fadeOutWeight;
if (parameterFadeInTime < 0.0f)
{
fadeInWeight = fadeInTime;
}
else
{
fadeInWeight = (parameterFadeInTime < float.Epsilon)
? 1.0f
: CubismFadeMath.GetEasingSine(elapsedTime / parameterFadeInTime);
}
if (parameterFadeOutTime < 0.0f)
{
fadeOutWeight = fadeOutTime;
}
else
{
fadeOutWeight = (parameterFadeOutTime < float.Epsilon)
? 1.0f
: CubismFadeMath.GetEasingSine(endTime / parameterFadeOutTime);
}
var parameterWeight = fadeInWeight * fadeOutWeight;
return currentValue + (curve.Evaluate(elapsedTime) - currentValue) * parameterWeight;
}
#endregion
#region Unity Events Handling
///
/// Initializes instance.
///
private void OnEnable()
{
// Initialize cache.
Refresh();
}
///
/// Called by Unity.
///
private void LateUpdate()
{
if (!HasUpdateController)
{
OnLateUpdate();
}
}
#endregion
}
}