/** * 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.FadeInStartTime; for (var j = 0; j < playingMotionData.ParameterFadeInTimes.Length; j++) { if ((elapsedTime <= playingMotionData.FadeInTime) || ((0 <= playingMotionData.ParameterFadeInTimes[j]) && (elapsedTime <= playingMotionData.ParameterFadeInTimes[j])) || !_fadeStates[i].GetStateTransitionFinished()) { _isFading[i] = true; break; } } } var isFadingAllFinished = true; for (var i = 0; i < _fadeStates.Length; ++i) { if (_isFading[i]) { isFadingAllFinished = false; continue; } var playingMotions = _fadeStates[i].GetPlayingMotions(); for (var j = playingMotions.Count - 2; j >= 0; --j) { 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; // 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.FadeInStartTime; 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 || playingMotion.EndTime < 0.0f) ? 1.0f : CubismFadeMath.GetEasingSine((playingMotion.EndTime - time) / fadeOutTime); playingMotions[i] = playingMotion; var motionWeight = 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; } var value = fadeMotion.ParameterCurves[index].Evaluate(elapsedTime); if (DestinationParameters[j].IsRepeat()) { value = DestinationParameters[j].GetParameterRepeatValue(value); } else { value = DestinationParameters[j].GetParameterClampValue(value); } value = Evaluate( value, elapsedTime, endTime, fadeInWeight, fadeOutWeight, fadeMotion.ParameterFadeInTimes[index], fadeMotion.ParameterFadeOutTimes[index], motionWeight, DestinationParameters[j].Value); DestinationParameters[j].OverrideValue(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. return Evaluate( curve.Evaluate(elapsedTime), elapsedTime, endTime, fadeInTime, fadeOutTime, parameterFadeInTime, parameterFadeOutTime, motionWeight, currentValue); } /// /// Evaluate fade value. /// /// New value. /// 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( float value, float elapsedTime, float endTime, float fadeInTime, float fadeOutTime, float parameterFadeInTime, float parameterFadeOutTime, float motionWeight, float currentValue) { // Motion fade. if (parameterFadeInTime < 0.0f && parameterFadeOutTime < 0.0f) { return currentValue + (value - 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 || (endTime < 0.0f)) ? 1.0f : CubismFadeMath.GetEasingSine(endTime / parameterFadeOutTime); } var parameterWeight = fadeInWeight * fadeOutWeight; return currentValue + (value - 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 } }