live2D (sdk가 6.5 미지원으로 sprite로 대응)

This commit is contained in:
2026-08-08 22:13:54 +09:00
parent 9ce6930772
commit 8a4a001fe0
4278 changed files with 287426 additions and 18 deletions

View File

@@ -0,0 +1,447 @@
/**
* 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
{
/// <summary>
/// Cubism fade controller.
/// </summary>
[RequireComponent(typeof(Animator))]
public class CubismFadeController : MonoBehaviour, ICubismUpdatable
{
#region Variable
/// <summary>
/// Cubism fade motion list.
/// </summary>
[SerializeField]
public CubismFadeMotionList CubismFadeMotionList;
/// <summary>
/// Parameters cache.
/// </summary>
private CubismParameter[] DestinationParameters { get; set; }
/// <summary>
/// Parts cache.
/// </summary>
private CubismPart[] DestinationParts { get; set; }
/// <summary>
/// Model has motion controller component.
/// </summary>
private CubismMotionController _motionController;
/// <summary>
/// Model has cubism update controller component.
/// </summary>
[HideInInspector]
public bool HasUpdateController { get; set; }
/// <summary>
/// Fade state machine behavior set in the animator.
/// </summary>
private ICubismFadeState[] _fadeStates;
/// <summary>
/// Model has animator component.
/// </summary>
private Animator _animator;
/// <summary>
/// Restore parameter value.
/// </summary>
private CubismParameterStore _parameterStore;
/// <summary>
/// Fading flags for each layer.
/// </summary>
private bool[] _isFading;
#endregion
#region Function
/// <summary>
/// Refreshes the controller. Call this method after adding and/or removing <see cref="CubismFadeParameter"/>s.
/// </summary>
public void Refresh()
{
_animator = GetComponent<Animator>();
// Fail silently...
if (_animator == null)
{
return;
}
DestinationParameters = this.FindCubismModel().Parameters;
DestinationParts = this.FindCubismModel().Parts;
_motionController = GetComponent<CubismMotionController>();
_parameterStore = GetComponent<CubismParameterStore>();
// Get cubism update controller.
HasUpdateController = (GetComponent<CubismUpdateController>() != null);
_fadeStates = (ICubismFadeState[])_animator.GetBehaviours<CubismFadeStateObserver>();
if ((_fadeStates == null || _fadeStates.Length == 0) && _motionController != null)
{
_fadeStates = _motionController.GetFadeStates();
}
if (_fadeStates == null)
{
return;
}
_isFading = new bool[_fadeStates.Length];
}
/// <summary>
/// Called by cubism update controller. Order to invoke OnLateUpdate.
/// </summary>
public int ExecutionOrder
{
get { return CubismUpdateExecutionOrder.CubismFadeController; }
}
/// <summary>
/// Called by cubism update controller. Needs to invoke OnLateUpdate on Editing.
/// </summary>
public bool NeedsUpdateOnEditing
{
get { return false; }
}
/// <summary>
/// Called by cubism update controller. Updates controller.
/// </summary>
/// <remarks>
/// Make sure this method is called after any animations are evaluated.
/// </remarks>
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]);
}
}
/// <summary>
/// Update motion fade.
/// </summary>
/// <param name="fadeState">Fade state observer.</param>
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);
}
}
}
/// <summary>
/// Evaluate fade curve.
/// </summary>
/// <param name="curve">Curves to be evaluated.</param>
/// <param name="elapsedTime">Elapsed Time.</param>
/// <param name="endTime">Fading end time.</param>
/// <param name="fadeInTime">Fade in time.</param>
/// <param name="fadeOutTime">Fade out time.</param>
/// <param name="parameterFadeInTime">Fade in time parameter.</param>
/// <param name="parameterFadeOutTime">Fade out time parameter.</param>
/// <param name="motionWeight">Motion weight.</param>
/// <param name="currentValue">Current value with weight applied.</param>
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);
}
/// <summary>
/// Evaluate fade value.
/// </summary>
/// <param name="value">New value.</param>
/// <param name="elapsedTime">Elapsed Time.</param>
/// <param name="endTime">Fading end time.</param>
/// <param name="fadeInTime">Fade in time.</param>
/// <param name="fadeOutTime">Fade out time.</param>
/// <param name="parameterFadeInTime">Fade in time parameter.</param>
/// <param name="parameterFadeOutTime">Fade out time parameter.</param>
/// <param name="motionWeight">Motion weight.</param>
/// <param name="currentValue">Current value with weight applied.</param>
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
/// <summary>
/// Initializes instance.
/// </summary>
private void OnEnable()
{
// Initialize cache.
Refresh();
}
/// <summary>
/// Called by Unity.
/// </summary>
private void LateUpdate()
{
if (!HasUpdateController)
{
OnLateUpdate();
}
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0cd9e6647bfff7542acb0200ab74bdd1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
/**
* 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.
*/
namespace Live2D.Cubism.Framework.MotionFade
{
public enum CubismFadeCurveType
{
/// <summary>
/// Parameter.
/// </summary>
Parameter,
/// <summary>
/// Part opacity.
/// </summary>
Part
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 47af690fba051ca428c5e5ee69068b28
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,29 @@
/**
* 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 System;
namespace Live2D.Cubism.Framework.MotionFade
{
public static class CubismFadeMath
{
/// <summary>
/// Calculate the easing processed signaure.
/// </summary>
/// <param name="value">Value to be subjected to easing.</param>
/// <returns>Eased sign value.</returns>
public static float GetEasingSine(float value)
{
if (value < 0.0f) return 0.0f;
if (value > 1.0f) return 1.0f;
return (float)(0.5f - 0.5f * Math.Cos(value * (float)Math.PI));
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b4508bdfe290c2a44858dbe185724e76
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,201 @@
/**
* 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 UnityEngine;
using Live2D.Cubism.Framework.Json;
namespace Live2D.Cubism.Framework.MotionFade
{
public class CubismFadeMotionData : ScriptableObject
{
/// <summary>
/// Name of motion.
/// </summary>
[SerializeField]
public string MotionName;
/// <summary>
/// Store time to fade in from .model3.json.
/// NOTE: It is used to save `FadeInTime` from .model3.json. Please use <see cref="FadeInTime"/> instead of using it directly.
/// </summary>
[HideInInspector, SerializeField]
public float ModelFadeInTime = -1.0f;
/// <summary>
/// Store time to fade out from .model3.json.
/// NOTE: It is used to save `FadeOutTime` from .model3.json. Please use <see cref="FadeOutTime"/> instead of using it directly.
/// </summary>
[HideInInspector, SerializeField]
public float ModelFadeOutTime = -1.0f;
/// <summary>
/// Time to fade in.
/// </summary>
[SerializeField]
public float FadeInTime;
/// <summary>
/// Time to fade out.
/// </summary>
[SerializeField]
public float FadeOutTime;
/// <summary>
/// Parameter ids.
/// </summary>
[SerializeField]
public string[] ParameterIds;
/// <summary>
/// Parameter curves.
/// </summary>
[SerializeField]
public AnimationCurve[] ParameterCurves;
/// <summary>
/// Fade in time parameters.
/// </summary>
[SerializeField]
public float[] ParameterFadeInTimes;
/// <summary>
/// Fade out time parameters.
/// </summary>
[SerializeField]
public float[] ParameterFadeOutTimes;
/// <summary>
/// Motion length.
/// </summary>
[SerializeField]
public float MotionLength;
/// <summary>
/// Create CubismFadeMotionData from CubismMotion3Json.
/// </summary>
/// <param name="motion3Json">Motion3json as the creator.</param>
/// <param name="motionName">Motion name of interest.</param>
/// <param name="motionLength">Length of target motion.</param>
/// <param name="shouldImportAsOriginalWorkflow">Whether the original work flow or not.</param>
/// <param name="isCallFromModelJson">Whether it is a call from the model json.</param>
/// <param name="model3Json">.model3.json to retrieve the fade time.</param>
/// <returns>Fade data created based on motion3json.</returns>
public static CubismFadeMotionData CreateInstance(
CubismMotion3Json motion3Json, string motionName, float motionLength,
bool shouldImportAsOriginalWorkflow = false, bool isCallFromModelJson = false, CubismModel3Json model3Json = null)
{
var fadeMotion = CreateInstance<CubismFadeMotionData>();
var curveCount = motion3Json.Curves.Length;
fadeMotion.ParameterIds = new string[curveCount];
fadeMotion.ParameterFadeInTimes = new float[curveCount];
fadeMotion.ParameterFadeOutTimes = new float[curveCount];
fadeMotion.ParameterCurves = new AnimationCurve[curveCount];
return CreateInstance(fadeMotion, motion3Json, motionName, motionLength, shouldImportAsOriginalWorkflow, isCallFromModelJson, model3Json);
}
/// <summary>
/// Put motion3json's fade information back into fade motion data.
/// </summary>
/// <param name="fadeMotion">Instance containing fade information.</param>
/// <param name="motion3Json">Target motion3json.</param>
/// <param name="motionName">Motion name of interest.</param>
/// <param name="motionLength">Motion length.</param>
/// <param name="shouldImportAsOriginalWorkflow">Whether the original work flow or not.</param>
/// <param name="isCallFormModelJson">Whether it is a call from the model json.</param>
/// <param name="model3Json">.model3.json to retrieve the fade time.</param>
/// <returns>Fade data created based on fademotiondata.</returns>
public static CubismFadeMotionData CreateInstance(
CubismFadeMotionData fadeMotion, CubismMotion3Json motion3Json, string motionName, float motionLength,
bool shouldImportAsOriginalWorkflow = false, bool isCallFormModelJson = false, CubismModel3Json model3Json = null)
{
if (model3Json != null)
{
GetFadeDataFromModel3Json(model3Json, fadeMotion);
}
if (motion3Json == null)
{
return fadeMotion;
}
fadeMotion.MotionName = motionName;
fadeMotion.MotionLength = motionLength;
if (fadeMotion.ModelFadeInTime < 0.0f)
{
fadeMotion.FadeInTime = (motion3Json.Meta.FadeInTime < 0.0f) ? 1.0f : motion3Json.Meta.FadeInTime;
}
else
{
fadeMotion.FadeInTime = fadeMotion.ModelFadeInTime;
}
if (fadeMotion.ModelFadeOutTime < 0.0f)
{
fadeMotion.FadeOutTime = (motion3Json.Meta.FadeOutTime < 0.0f) ? 1.0f : motion3Json.Meta.FadeOutTime;
}
else
{
fadeMotion.FadeOutTime = fadeMotion.ModelFadeOutTime;
}
for (var i = 0; i < motion3Json.Curves.Length; ++i)
{
var curve = motion3Json.Curves[i];
// In original workflow mode, skip add part opacity curve when call not from model3.json.
if (curve.Target == "PartOpacity" && shouldImportAsOriginalWorkflow && !isCallFormModelJson)
{
continue;
}
fadeMotion.ParameterIds[i] = curve.Id;
fadeMotion.ParameterFadeInTimes[i] = (curve.FadeInTime < 0.0f) ? -1.0f : curve.FadeInTime;
fadeMotion.ParameterFadeOutTimes[i] = (curve.FadeOutTime < 0.0f) ? -1.0f : curve.FadeOutTime;
fadeMotion.ParameterCurves[i] = new AnimationCurve(CubismMotion3Json.ConvertCurveSegmentsToKeyframes(curve.Segments));
}
return fadeMotion;
}
private static void GetFadeDataFromModel3Json(CubismModel3Json modelJson, CubismFadeMotionData fadeMotion)
{
var motions = modelJson.FileReferences.Motions.Motions;
for (var groupIndex = 0; groupIndex < motions?.Length; groupIndex++)
{
if (motions[groupIndex] == null)
{
continue;
}
for (var motionIndex = 0; motionIndex < motions[groupIndex]?.Length; motionIndex++)
{
var motion = motions[groupIndex][motionIndex];
// Set FadeInTime.
if (!(motion.FadeInTime < 0.0f))
{
fadeMotion.ModelFadeInTime = motion.FadeInTime;
fadeMotion.FadeInTime = fadeMotion.ModelFadeInTime;
}
// Set FadeOutTime.
if (!(motion.FadeOutTime < 0.0f))
{
fadeMotion.ModelFadeOutTime = motion.FadeOutTime;
fadeMotion.FadeInTime = fadeMotion.ModelFadeInTime;
}
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8f1ee1adc36a8a04a8d7c42ac5d6bc27
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,29 @@
/**
* 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 UnityEngine;
namespace Live2D.Cubism.Framework.MotionFade
{
[CreateAssetMenu(menuName = "Live2D Cubism/Fade Motion List")]
public class CubismFadeMotionList : ScriptableObject
{
/// <summary>
/// Cubism fade motion instance ids.
/// </summary>
[SerializeField]
public int[] MotionInstanceIds;
/// <summary>
/// Cubism fade motion objects.
/// </summary>
[SerializeField]
public CubismFadeMotionData[] CubismFadeMotionObjects;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 403ae2dd693bb1d4b924f6b8d206b053
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,71 @@
/**
* 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 System;
using UnityEngine;
namespace Live2D.Cubism.Framework.MotionFade
{
public struct CubismFadePlayingMotion
{
/// <summary>
/// Animation clip start time.
/// </summary>
[SerializeField]
public float StartTime;
/// <summary>
/// Animation clip end time.
/// </summary>
[SerializeField]
public float EndTime;
/// <summary>
/// Cubism fade in start time.
/// </summary>
[SerializeField]
public float FadeInStartTime;
/// <summary>
/// Animation playing speed.
/// </summary>
[SerializeField, Range(0.0f, float.MaxValue)]
public float Speed;
/// <summary>
/// Cubism fade motion data.
/// </summary>
[SerializeField]
public CubismFadeMotionData Motion;
/// <summary>
/// Is animation loop.
/// </summary>
[SerializeField]
public bool IsLooping;
/// <summary>
/// Motion weight.
/// </summary>
[NonSerialized]
public float Weight;
/// <summary>
/// Clip event <see cref="CubismFadeMotionData"/> InstanceId.
/// </summary>
[NonSerialized]
public int? InstanceId;
/// <summary>
/// Is animation end event invoked.
/// </summary>
[NonSerialized]
public bool IsAnimationEndEventInvoked;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 71820e64bc297764c8a2d11a31cff80f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,249 @@
/**
* 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 System.Collections.Generic;
using UnityEngine;
using UnityEngine.Animations;
namespace Live2D.Cubism.Framework.MotionFade
{
public class CubismFadeStateObserver : StateMachineBehaviour, ICubismFadeState
{
#region variable
/// <summary>
/// Cubism fade motion list.
/// </summary>
private CubismFadeMotionList _cubismFadeMotionList;
/// <summary>
/// Cubism playing motion list.
/// </summary>
private List<CubismFadePlayingMotion> _playingMotions;
/// <summary>
/// State that attached this is default.
/// </summary>
private bool _isDefaulState;
/// <summary>
/// Layer index that attached this.
/// </summary>
private int _layerIndex;
/// <summary>
/// Weight of layer that attached this.
/// </summary>
private float _layerWeight;
/// <summary>
/// State that attached this is transition finished.
/// </summary>
private bool _isStateTransitionFinished;
#endregion
#region Fade State Interface
/// <summary>
/// Get cubism playing motion list.
/// </summary>
/// <returns>Cubism playing motion list.</returns>
public List<CubismFadePlayingMotion> GetPlayingMotions()
{
return _playingMotions;
}
/// <summary>
/// Is default state.
/// </summary>
/// <returns><see langword="true"/> State is default; <see langword="false"/> otherwise.</returns>
public bool IsDefaultState()
{
return _isDefaulState;
}
/// <summary>
/// Get layer weight.
/// </summary>
/// <returns>Layer weight.</returns>
public float GetLayerWeight()
{
return _layerWeight;
}
/// <summary>
/// Get state transition finished.
/// </summary>
/// <returns><see langword="true"/> State transition is finished; <see langword="false"/> otherwise.</returns>
public bool GetStateTransitionFinished()
{
return _isStateTransitionFinished;
}
/// <summary>
/// Set state transition finished.
/// </summary>
/// <param name="isFinished">State is finished.</param>
public void SetStateTransitionFinished(bool isFinished)
{
_isStateTransitionFinished = isFinished;
}
/// <summary>
/// Stop animation.
/// </summary>
/// <param name="index">Playing motion index.</param>
public void StopAnimation(int index)
{
_playingMotions.RemoveAt(index);
}
#endregion
#region Unity Event Handling
/// <summary>
/// Called by Unity.
/// </summary>
private void OnEnable()
{
_isStateTransitionFinished = false;
if (_playingMotions == null)
{
_playingMotions = new List<CubismFadePlayingMotion>();
}
}
/// <summary>
/// Called by Unity.
/// </summary>
/// <param name="animator">Animator.</param>
/// <param name="stateInfo">Animator state info.</param>
/// <param name="layerIndex">Index of the layer.</param>
/// <param name="controller">Animation controller playable.</param>
public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex, AnimatorControllerPlayable controller)
{
var fadeController = animator.gameObject.GetComponent<CubismFadeController>();
// Fail silently...
if (fadeController == null)
{
return;
}
_cubismFadeMotionList = fadeController.CubismFadeMotionList;
_isStateTransitionFinished = false;
_layerIndex = layerIndex;
_layerWeight = (_layerIndex == 0)
? 1.0f
: animator.GetLayerWeight(_layerIndex);
var animatorClipInfo = controller.GetNextAnimatorClipInfo(layerIndex);
_isDefaulState = (animatorClipInfo.Length == 0);
if (_isDefaulState)
{
// Get the motion of Default State only for the first time.
animatorClipInfo = controller.GetCurrentAnimatorClipInfo(layerIndex);
}
// Set playing motions end 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 (motion.EndTime < 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;
}
for (var i = 0; i < animatorClipInfo.Length; ++i)
{
CubismFadePlayingMotion playingMotion;
var instanceId = -1;
var events = animatorClipInfo[i].clip.events;
for(var k = 0; k < events.Length; ++k)
{
if(events[k].functionName != "InstanceId")
{
continue;
}
instanceId = events[k].intParameter;
break;
}
var motionIndex = -1;
for (var j = 0; j < _cubismFadeMotionList.MotionInstanceIds.Length; ++j)
{
if (_cubismFadeMotionList.MotionInstanceIds[j] != instanceId)
{
continue;
}
motionIndex = j;
break;
}
playingMotion.Motion = (motionIndex == -1)
? null
: _cubismFadeMotionList.CubismFadeMotionObjects[motionIndex];
playingMotion.Speed = 1.0f;
playingMotion.StartTime = Time.time;
playingMotion.FadeInStartTime = Time.time;
playingMotion.EndTime = -1.0f;
playingMotion.IsLooping = animatorClipInfo[i].clip.isLooping;
playingMotion.Weight = 0.0f;
playingMotion.InstanceId = instanceId;
playingMotion.IsAnimationEndEventInvoked = false;
_playingMotions.Add(playingMotion);
}
}
/// <summary>
/// Called by Unity.
/// </summary>
/// <param name="animator">Animator.</param>
/// <param name="stateInfo">Animator state info.</param>
/// <param name="layerIndex">Index of the layer.</param>
public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
_isStateTransitionFinished = true;
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2df377c5758f8974aaed292833ec3ba0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8d1c056cbe0af0e41b9ee3a919e16b58
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,408 @@
/**
* 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.Editor;
using Live2D.Cubism.Editor.Importers;
using Live2D.Cubism.Framework.Json;
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
namespace Live2D.Cubism.Framework.MotionFade
{
internal static class CubismFadeMotionImporter
{
#region Unity Event Handling
/// <summary>
/// Register fadeMotion importer.
/// </summary>
[InitializeOnLoadMethod]
private static void RegisterMotionImporter()
{
CubismImporter.OnDidImportModel += OnModelImport;
CubismImporter.OnDidImportMotion += OnFadeMotionImport;
}
#endregion
#region Cubism Import Event Handling
/// <summary>
/// Create animator controller for MotionFade.
/// </summary>
/// <param name="importer">Event source.</param>
/// <param name="model">Imported model.</param>
private static void OnModelImport(CubismModel3JsonImporter importer, CubismModel model)
{
var dataPath = Directory.GetParent(Application.dataPath).FullName + "/";
var assetPath = importer.AssetPath.Replace(".model3.json", ".controller");
var animator = model.GetComponent<Animator>();
if (!File.Exists(dataPath + assetPath))
{
var controller = CreateAnimatorController(assetPath);
if (!CubismUnityEditorMenu.ShouldImportAsOriginalWorkflow)
{
if (animator != null)
{
animator.runtimeAnimatorController = controller;
}
}
}
else
{
if (animator != null)
{
if (CubismUnityEditorMenu.ShouldImportAsOriginalWorkflow)
{
animator.runtimeAnimatorController = null;
}
else
{
animator.runtimeAnimatorController = AssetDatabase.LoadAssetAtPath<AnimatorController>(assetPath);
}
}
}
var fadeController = model.GetComponent<CubismFadeController>();
if (importer.Model3Json.FileReferences.Motions.Motions == null || fadeController == null)
{
return;
}
var modelDir = Path.GetDirectoryName(importer.AssetPath).Replace("\\", "/");
var modelName = Path.GetFileName(modelDir);
var fadeMotionListPath = modelDir + "/" + modelName + ".fadeMotionList.asset";
var fadeMotions = GetFadeMotionList(fadeMotionListPath);
if (fadeMotions == null)
{
return;
}
fadeController.CubismFadeMotionList = fadeMotions;
var fileReferences = importer.Model3Json.FileReferences;
// Create pose animation clip
var motions = new List<CubismModel3Json.SerializableMotion>();
if (fileReferences.Motions.GroupNames != null)
{
for (var i = 0; i < fileReferences.Motions.GroupNames.Length; i++)
{
motions.AddRange(fileReferences.Motions.Motions[i]);
}
}
var shouldImportAsOriginalWorkflow = CubismUnityEditorMenu.ShouldImportAsOriginalWorkflow;
var shouldClearAnimationCurves = CubismUnityEditorMenu.ShouldClearAnimationCurves;
for (var i = 0; i < motions.Count; ++i)
{
var motionPath = Path.GetDirectoryName(assetPath) + "/" + motions[i].File;
var jsonString = string.IsNullOrEmpty(motionPath)
? null
: File.ReadAllText(motionPath);
if (jsonString == null)
{
continue;
}
var directoryPath = Path.GetDirectoryName(assetPath) + "/";
var motion3Json = CubismMotion3Json.LoadFrom(jsonString);
if (motion3Json == null)
{
continue;
}
var animationClipPath = directoryPath + motions[i].File.Replace(".motion3.json", ".anim");
animationClipPath = animationClipPath.Replace("\\", "/");
var animationName = Path.GetFileNameWithoutExtension(motions[i].File.Replace(".motion3.json", ".anim"));
var assetList = CubismCreatedAssetList.GetInstance();
var assetListIndex = assetList.AssetPaths.Contains(animationClipPath)
? assetList.AssetPaths.IndexOf(animationClipPath)
: -1;
var animationClip = (shouldImportAsOriginalWorkflow)
? (assetListIndex >= 0)
? (AnimationClip)assetList.Assets[assetListIndex]
: AssetDatabase.LoadAssetAtPath<AnimationClip>(animationClipPath)
: null;
if (animationClip == null)
{
animationClip = motion3Json.ToAnimationClip(shouldImportAsOriginalWorkflow, shouldClearAnimationCurves, true);
animationClip.name = animationName;
}
var instanceId = 0;
var isExistInstanceId = false;
var events = animationClip.events;
for (var k = 0; k < events.Length; ++k)
{
if (events[k].functionName != "InstanceId")
{
continue;
}
instanceId = events[k].intParameter;
isExistInstanceId = true;
break;
}
if (!isExistInstanceId)
{
// [Unity6.5] GetInstanceID() obsolete error 회피. EntityId→int 암시적 변환도 막혀
// GetHashCode()를 쓴다 — 이 값은 아래 "InstanceId" 애니메이션 이벤트와
// MotionInstanceIds 배열에 함께 구워지고 런타임은 구운 값끼리만 비교하므로 안전하다.
instanceId = animationClip.GetEntityId().GetHashCode();
}
var motionName = Path.GetFileName(motions[i].File);
var motionIndex = -1;
for (var fadeMotionIndex = 0; fadeMotionIndex < fadeMotions.CubismFadeMotionObjects.Length; fadeMotionIndex++)
{
if (Path.GetFileName(fadeMotions.CubismFadeMotionObjects[fadeMotionIndex].MotionName) != motionName)
{
continue;
}
motionIndex = fadeMotionIndex;
break;
}
// Create fade motion.
CreateFadeMotionData(motionIndex, instanceId, fadeMotions, motionPath, motion3Json, animationClip, importer.Model3Json);
}
}
/// <summary>
/// Create oldFadeMotion.
/// </summary>
/// <param name="importer">Event source.</param>
/// <param name="animationClip">Imported motion.</param>
private static void OnFadeMotionImport(CubismMotion3JsonImporter importer, AnimationClip animationClip)
{
// Add reference of motion for Fade to list.
var directoryName = Path.GetDirectoryName(importer.AssetPath);
var modelDir = Path.GetDirectoryName(directoryName);
var modelName = Path.GetFileName(modelDir);
var fadeMotionListPath = modelDir + "/" + modelName + ".fadeMotionList.asset";
var fadeMotions = GetFadeMotionList(fadeMotionListPath);
if (fadeMotions == null)
{
Debug.LogError("CubismFadeMotionImporter : Can not create CubismFadeMotionList.");
return;
}
var instanceId = 0;
var isExistInstanceId = false;
var events = animationClip.events;
for (var k = 0; k < events.Length; ++k)
{
if (events[k].functionName != "InstanceId")
{
continue;
}
instanceId = events[k].intParameter;
isExistInstanceId = true;
break;
}
if (!isExistInstanceId)
{
// [Unity6.5] GetInstanceID() obsolete error 회피 (위 주석 참고)
instanceId = animationClip.GetEntityId().GetHashCode();
}
var motionName = Path.GetFileName(importer.AssetPath);
var motionIndex = -1;
for (var i = 0; i < fadeMotions.CubismFadeMotionObjects.Length; i++)
{
if (Path.GetFileName(fadeMotions.CubismFadeMotionObjects[i].MotionName) != motionName)
{
continue;
}
motionIndex = i;
break;
}
// Create fade motion.
CreateFadeMotionData(motionIndex, instanceId, fadeMotions, importer.AssetPath, importer.Motion3Json, animationClip);
// Add animation event
{
var sourceAnimationEvents = AnimationUtility.GetAnimationEvents(animationClip);
var index = -1;
for(var i = 0; i < sourceAnimationEvents.Length; ++i)
{
if(sourceAnimationEvents[i].functionName != "InstanceId")
{
continue;
}
index = i;
break;
}
if(index == -1)
{
index = sourceAnimationEvents.Length;
Array.Resize(ref sourceAnimationEvents, sourceAnimationEvents.Length + 1);
sourceAnimationEvents[sourceAnimationEvents.Length - 1] = new AnimationEvent();
}
sourceAnimationEvents[index].time = 0;
sourceAnimationEvents[index].functionName = "InstanceId";
sourceAnimationEvents[index].intParameter = instanceId;
sourceAnimationEvents[index].messageOptions = SendMessageOptions.DontRequireReceiver;
AnimationUtility.SetAnimationEvents(animationClip, sourceAnimationEvents);
}
}
#endregion
#region Functions
/// <summary>
/// Create animator controller for MotionFade.
/// </summary>
/// <param name="assetPath"></param>
/// <returns>Animator controller attached CubismFadeStateObserver.</returns>
public static AnimatorController CreateAnimatorController(string assetPath)
{
var animatorController = AnimatorController.CreateAnimatorControllerAtPath(assetPath);
animatorController.layers[0].stateMachine.AddStateMachineBehaviour<CubismFadeStateObserver>();
return animatorController;
}
/// <summary>
/// Load the .fadeMotionList.
/// If it does not exist, create a new one.
/// </summary>
/// <param name="fadeMotionListPath">The path of the .fadeMotionList.asset relative to the project.</param>
/// <returns>.fadeMotionList.asset.</returns>
private static CubismFadeMotionList GetFadeMotionList(string fadeMotionListPath)
{
var assetList = CubismCreatedAssetList.GetInstance();
var assetListIndex = assetList.AssetPaths.Contains(fadeMotionListPath)
? assetList.AssetPaths.IndexOf(fadeMotionListPath)
: -1;
CubismFadeMotionList fadeMotions = null;
if (assetListIndex < 0)
{
fadeMotions = AssetDatabase.LoadAssetAtPath<CubismFadeMotionList>(fadeMotionListPath);
if (fadeMotions == null)
{
// Create reference list.
fadeMotions = ScriptableObject.CreateInstance<CubismFadeMotionList>();
fadeMotions.MotionInstanceIds = new int[0];
fadeMotions.CubismFadeMotionObjects = new CubismFadeMotionData[0];
AssetDatabase.CreateAsset(fadeMotions, fadeMotionListPath);
}
assetList.Assets.Add(fadeMotions);
assetList.AssetPaths.Add(fadeMotionListPath);
assetList.IsImporterDirties.Add(true);
}
else
{
fadeMotions = (CubismFadeMotionList)assetList.Assets[assetListIndex];
}
return fadeMotions;
}
/// <summary>
/// Create an instance of <see cref="CubismFadeMotionData"/> and save it as .fade.asset.
/// </summary>
/// <param name="motionIndex">The index in fadeMotions.CubismFadeMotionObjects.</param>
/// <param name="instanceId">Motion's instance id.</param>
/// <param name="fadeMotions">Target CubismFadeMotionList.</param>
/// <param name="motion3JsonAssetsPath">Path of target.motion3.json</param>
/// <param name="motion3Json">Target <see cref="CubismMotion3Json"/> instance.</param>
/// <param name="animationClip">Imported motion.</param>
/// <param name="model3Json"><see cref="CubismModel3Json"/> instance for get FadeInTime and FadeOutTime.</param>
private static void CreateFadeMotionData(int motionIndex, int instanceId, CubismFadeMotionList fadeMotions, string motion3JsonAssetsPath, CubismMotion3Json motion3Json, AnimationClip animationClip, CubismModel3Json model3Json = null)
{
// Create fade motion.
CubismFadeMotionData fadeMotion;
if (motionIndex != -1)
{
var oldFadeMotion = fadeMotions.CubismFadeMotionObjects[motionIndex];
fadeMotion = CubismFadeMotionData.CreateInstance(
oldFadeMotion,
motion3Json,
motion3JsonAssetsPath,
animationClip.length,
CubismUnityEditorMenu.ShouldImportAsOriginalWorkflow,
CubismUnityEditorMenu.ShouldClearAnimationCurves,
model3Json);
EditorUtility.CopySerialized(fadeMotion, oldFadeMotion);
fadeMotions.MotionInstanceIds[motionIndex] = instanceId;
fadeMotions.CubismFadeMotionObjects[motionIndex] = fadeMotion;
}
else
{
// Create fade motion instance.
fadeMotion = CubismFadeMotionData.CreateInstance(
motion3Json,
motion3JsonAssetsPath,
animationClip.length,
CubismUnityEditorMenu.ShouldImportAsOriginalWorkflow,
CubismUnityEditorMenu.ShouldClearAnimationCurves,
model3Json);
AssetDatabase.CreateAsset(
fadeMotion,
motion3JsonAssetsPath.Replace(".motion3.json", ".fade.asset"));
motionIndex = fadeMotions.MotionInstanceIds.Length;
Array.Resize(ref fadeMotions.MotionInstanceIds, motionIndex + 1);
fadeMotions.MotionInstanceIds[motionIndex] = instanceId;
Array.Resize(ref fadeMotions.CubismFadeMotionObjects, motionIndex + 1);
fadeMotions.CubismFadeMotionObjects[motionIndex] = fadeMotion;
}
EditorUtility.SetDirty(fadeMotion);
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 103865783cfc2dd4eafa65a9def73dcd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
{
"reference": "GUID:e5d337e0b3581c343aafde08e6e1727e"
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 40cb8dcf56e2a45479c9a740d8fd36ff
AssemblyDefinitionReferenceImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,53 @@
/**
* 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 System.Collections.Generic;
namespace Live2D.Cubism.Framework.MotionFade
{
/// <summary>
/// Cubism fade state interface.
/// </summary>
public interface ICubismFadeState
{
/// <summary>
/// Get cubism playing motion list.
/// </summary>
/// <returns>Cubism playing motion list.</returns>
List<CubismFadePlayingMotion> GetPlayingMotions();
/// <summary>
/// Is default state.
/// </summary>
/// <returns><see langword="true"/> State is default; <see langword="false"/> otherwise.</returns>
bool IsDefaultState();
/// <summary>
/// Get layer weight.
/// </summary>
/// <returns>Layer weight.</returns>
float GetLayerWeight();
/// <summary>
/// Get state transition finished.
/// </summary>
/// <returns><see langword="true"/> State transition is finished; <see langword="false"/> otherwise.</returns>
bool GetStateTransitionFinished();
/// <summary>
/// Set state transition finished.
/// </summary>
/// <param name="isFinished">State is finished.</param>
void SetStateTransitionFinished(bool isFinished);
/// <summary>
/// Stop animation.
/// </summary>
/// <param name="index">Playing motion index.</param>
void StopAnimation(int index);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d3334b81e0ea7914c8f03981942eab17
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: