/** * 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.Framework.MotionFade; using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Animations; using UnityEngine.Playables; namespace Live2D.Cubism.Framework.Motion { /// /// Cubism motion layer. /// public class CubismMotionLayer : ICubismFadeState { #region Action /// /// Action animation end handler. /// public Action AnimationEndHandler; #endregion #region Variable /// /// Playable output. /// public AnimationMixerPlayable PlayableOutput { get; private set; } /// /// Playable output. /// private PlayableGraph _playableGraph; /// /// Cubism playing motions. /// private List _playingMotions; /// /// Cubism playing motions. /// private CubismMotionState _motionState; /// /// List of cubism fade motion. /// private CubismFadeMotionList _cubismFadeMotionList; /// /// Layer index. /// private int _layerIndex; /// /// Layer weight. /// private float _layerWeight; /// /// Animation is finished. /// private bool _isFinished; /// /// Is finished. /// /// True if the animation is finished, false otherwise. public bool IsFinished { get { return _isFinished; } } #endregion #region Fade State Interface /// /// Get cubism playing motion list. /// /// Cubism playing motion list. public List GetPlayingMotions() { return _playingMotions; } /// /// Is default state. /// /// State is default; otherwise. public bool IsDefaultState() { return false; } /// /// Get layer weight. /// /// Layer weight. public float GetLayerWeight() { return _layerWeight; } /// /// Get state transition finished. /// /// State transition is finished; otherwise. public bool GetStateTransitionFinished() { return true; } /// /// Set state transition finished. /// /// State is finished. public void SetStateTransitionFinished(bool isFinished) {} /// /// Stop animation. /// /// Playing motion index. public void StopAnimation(int index) { // Remove from playing motion list. _playingMotions.RemoveAt(index); } /// /// Stop animation. /// public void StopAnimationClip() { // Remove from motion state list. if (_motionState == null) { return; } _playableGraph.Disconnect(_motionState.ClipMixer, 0); _motionState = null; _isFinished = true; StopAllAnimation(); } #endregion #region Function /// /// Initialize motion layer. /// /// . /// . /// . public static CubismMotionLayer CreateCubismMotionLayer(PlayableGraph playableGraph, CubismFadeMotionList fadeMotionList, int layerIndex, float layerWeight = 1.0f) { var ret = new CubismMotionLayer(); ret._playableGraph = playableGraph; ret._cubismFadeMotionList = fadeMotionList; ret._layerIndex = layerIndex; ret._layerWeight = layerWeight; ret._isFinished = true; ret._motionState = null; ret._playingMotions = new List(); ret.PlayableOutput = AnimationMixerPlayable.Create(playableGraph, 1); return ret; } /// /// Create fade playing motion. /// /// Animator clip. /// Animation speed. private CubismFadePlayingMotion CreateFadePlayingMotion(AnimationClip clip, bool isLooping, float speed = 1.0f) { var ret = new CubismFadePlayingMotion(); var isNotFound = true; var instanceId = -1; var events = clip.events; for(var i = 0; i < events.Length; ++i) { if(events[i].functionName != "InstanceId") { continue; } instanceId = events[i].intParameter; } for (int i = 0; i < _cubismFadeMotionList.MotionInstanceIds.Length; i++) { if(_cubismFadeMotionList.MotionInstanceIds[i] != instanceId) { continue; } isNotFound = false; ret.Speed = speed; ret.StartTime = Time.time; ret.FadeInStartTime = Time.time; ret.Motion = _cubismFadeMotionList.CubismFadeMotionObjects[i]; ret.EndTime = (ret.Motion.MotionLength <= 0) ? -1 : ret.StartTime + ret.Motion.MotionLength / speed; ret.IsLooping = isLooping; ret.Weight = 0.0f; break; } if(isNotFound) { Debug.LogError("CubismMotionController : Not found motion from CubismFadeMotionList."); } return ret; } /// /// Play animation. /// /// Animation clip. /// Animation is loop. /// Animation speed. public void PlayAnimation(AnimationClip clip, bool isLoop = true, float speed = 1.0f) { if (_motionState != null) { _playableGraph.Disconnect(_motionState.ClipMixer, 0); } // Create cubism motion state. _motionState = CubismMotionState.CreateCubismMotionState(_playableGraph, clip, isLoop, speed); #if UNITY_2018_2_OR_NEWER PlayableOutput.DisconnectInput(0); #else PlayableOutput.GetGraph().Disconnect(PlayableOutput, 0); #endif PlayableOutput.ConnectInput(0, _motionState.ClipMixer, 0); PlayableOutput.SetInputWeight(0, 1.0f); // Set last motion end time and fade in start time; if ((_playingMotions.Count > 0) && (_playingMotions[_playingMotions.Count - 1].Motion != null)) { var motion = _playingMotions[_playingMotions.Count - 1]; var time = Time.time; var newEndTime = time + motion.Motion.FadeOutTime; if (newEndTime < 0.0f || newEndTime < motion.EndTime) { motion.EndTime = newEndTime; } while (motion.IsLooping) { if ((motion.StartTime + motion.Motion.MotionLength) >= time) { break; } motion.StartTime += motion.Motion.MotionLength; } _playingMotions[_playingMotions.Count - 1] = motion; } // Create fade playing motion. var playingMotion = CreateFadePlayingMotion(clip, isLoop, speed); _playingMotions.Add(playingMotion); _isFinished = false; } /// /// Stop all animation. /// public void StopAllAnimation() { for(var i = _playingMotions.Count - 1; i >= 0; --i) { StopAnimation(i); } } /// /// Set layer weight. /// /// Layer weight. public void SetLayerWeight(float weight) { _layerWeight = weight; } /// /// Set state speed. /// /// index of playing motion list. /// Animation speed. public void SetStateSpeed(int index, float speed) { // Fail silently... if(index < 0) { return; } var playingMotionData = _playingMotions[index]; playingMotionData.Speed = speed; playingMotionData.EndTime = (playingMotionData.EndTime - Time.time) / speed; _playingMotions[index] = playingMotionData; _motionState.ClipMixer.SetSpeed(speed); _motionState.ClipPlayable.SetDuration(_motionState.Clip.length / speed - 0.0001f); } /// /// Set state is loop. /// /// index of playing motion list. /// Animation is loop. public void SetStateIsLoop(int index, bool isLoop) { // Fail silently... if(index < 0) { return; } if(isLoop) { _motionState.ClipPlayable.SetDuration(double.MaxValue); } else { _motionState.ClipPlayable.SetDuration(_motionState.Clip.length - 0.0001f); } } #endregion public void Update() { // Fail silently... if (AnimationEndHandler == null || _playingMotions.Count != 1 || _isFinished || _motionState.ClipPlayable.GetDuration() == double.MaxValue || Time.time <= _playingMotions[0].EndTime) { return; } _isFinished = true; var instanceId = -1; var events = _motionState.Clip.events; for (var i = 0; i < events.Length; ++i) { if (events[i].functionName != "InstanceId") { continue; } instanceId = events[i].intParameter; } AnimationEndHandler(_layerIndex, instanceId); } } }