/** * 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 } }