/** * 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; using System; using UnityEngine; #if UNITY_2019_3_OR_NEWER using UnityEngine.LowLevel; using UnityEngine.PlayerLoop; #elif UNITY_2018_1_OR_NEWER using UnityEngine.Experimental.LowLevel; using UnityEngine.Experimental.PlayerLoop; #endif namespace Live2D.Cubism.Core { /// /// Runtime Cubism model. /// [ExecuteInEditMode, CubismDontMoveOnReimport] public sealed class CubismModel : MonoBehaviour { #region Delegates /// /// Handler for . /// /// Model the dymanic data applies to. /// New data. public delegate void DynamicDrawableDataHandler(CubismModel sender, CubismDynamicDrawableData[] data); #endregion #region Events /// /// Event triggered if new is available for instance. /// public event DynamicDrawableDataHandler OnDynamicDrawableData; #endregion #region Factory Methods /// /// Instantiates a . /// /// Cubism moc to instantiate. /// Instance. public static CubismModel InstantiateFrom(CubismMoc moc) { // Return if argument is invalid. if (moc == null) { return null; } // Create model. var model = new GameObject(moc.name) .AddComponent(); // Initialize it by resetting it. model.Reset(moc); return model; } #endregion /// /// Resets a reference in . /// /// Target Cubism model. /// Cubism moc to reset. public static void ResetMocReference(CubismModel model, CubismMoc moc) { model.Moc = moc; } /// /// backing field. /// [SerializeField, HideInInspector] private CubismMoc _moc; /// /// Moc the instance was instantiated from. /// public CubismMoc Moc { get { return _moc; } private set { _moc = value; } } /// /// TaskableModel for unmanaged backend. /// private CubismTaskableModel TaskableModel { get; set; } /// /// backing field. /// [NonSerialized] private CubismParameter[] _parameters; /// /// Drawables of model. /// public CubismParameter[] Parameters { get { if (_parameters == null) { Revive(); } return _parameters; } private set { _parameters = value; } } /// /// backing field. /// [NonSerialized] private CubismPart[] _parts; /// /// Drawables of model. /// public CubismPart[] Parts { get { if (_parts == null) { Revive(); } return _parts; } private set { _parts = value; } } /// /// backing field. /// [NonSerialized] private CubismDrawable[] _drawables; /// /// Drawables of model. /// public CubismDrawable[] Drawables { get { if (_drawables == null) { Revive(); } return _drawables; } private set { _drawables = value; } } /// /// backing field. /// [NonSerialized] private CubismCanvasInformation _canvasInformation; /// /// Canvas information of model. /// public CubismCanvasInformation CanvasInformation { get { if (_canvasInformation == null) { Revive(); } return _canvasInformation; } private set { _canvasInformation = value; } } /// /// Parameter store cache. /// CubismParameterStore _parameterStore; /// /// True if instance is revived. /// public bool IsRevived { get { return TaskableModel != null; } } /// /// True if instance can revive. /// private bool CanRevive { get { return Moc != null; } } #if UNITY_2018_1_OR_NEWER /// /// Model update functions for player loop. /// [NonSerialized] private static Action _modelUpdateFunctions; private bool WasAttachedModelUpdateFunction { get; set; } #endif /// /// True on the frame the instance was enabled. /// private bool WasJustEnabled { get; set; } /// /// Frame number last update was done. /// private int LastTick { get; set; } /// /// Revives instance. /// private void Revive() { // Return if already revive. if (IsRevived) { return; } // Return if revive isn't possible. if (!CanRevive) { return; } // Revive unmanaged model. TaskableModel = new CubismTaskableModel(Moc); // Revive proxies. Parameters = GetComponentsInChildren(); Parts = GetComponentsInChildren(); Drawables = GetComponentsInChildren(); Parameters.Revive(TaskableModel.UnmanagedModel); Parts.Revive(TaskableModel.UnmanagedModel); Drawables.Revive(TaskableModel.UnmanagedModel); CanvasInformation = new CubismCanvasInformation(TaskableModel.UnmanagedModel); _parameterStore = GetComponent(); } /// /// Initializes instance for first use. /// /// Moc to instantiate from. private void Reset(CubismMoc moc) { Moc = moc; name = moc.name; TaskableModel = new CubismTaskableModel(moc); // Create and initialize proxies. var parameters = CubismParameter.CreateParameters(TaskableModel.UnmanagedModel); var parts = CubismPart.CreateParts(TaskableModel.UnmanagedModel); var drawables = CubismDrawable.CreateDrawables(TaskableModel.UnmanagedModel); parameters.transform.SetParent(transform); parts.transform.SetParent(transform); drawables.transform.SetParent(transform); Parameters = parameters.GetComponentsInChildren(); Parts = parts.GetComponentsInChildren(); Drawables = drawables.GetComponentsInChildren(); CanvasInformation = new CubismCanvasInformation(TaskableModel.UnmanagedModel); } /// /// Forces update. /// public void ForceUpdateNow() { WasJustEnabled = true; LastTick = -1; Revive(); #if UNITY_2018_1_OR_NEWER OnModelUpdate(); #else OnRenderObject(); #endif } #if UNITY_2018_1_OR_NEWER /// /// Calls model update functions for player loop. /// private static void OnModelsUpdate() { if (_modelUpdateFunctions != null) { _modelUpdateFunctions.Invoke(); } } /// /// Register the model update function into the player loop. /// [RuntimeInitializeOnLoadMethod] private static void RegisterCallbackFunction() { // Prepare the function for using player loop. var myPlayerLoopSystem = new PlayerLoopSystem() { type = typeof(CubismModel), // Identifier for Profiler Hierarchy view. updateDelegate = OnModelsUpdate // Register the function. }; // Get the default player loop. var playerLoopSystem = #if UNITY_2019_3_OR_NEWER PlayerLoop.GetCurrentPlayerLoop(); #else PlayerLoop.GetDefaultPlayerLoop(); #endif var playerLoopIndex = -1; for (var i = 0; i < playerLoopSystem.subSystemList.Length; i++) { if (playerLoopSystem.subSystemList[i].type != typeof(PreLateUpdate)) { continue; } playerLoopIndex = i; break; } if (playerLoopIndex < 0) { Debug.LogError("CubismModel : Failed to add processing to PlayerLoop."); return; } // Get the "PreLateUpdate" system. var playerLoopSubSystem = playerLoopSystem.subSystemList[playerLoopIndex]; var subSystemList = playerLoopSubSystem.subSystemList; // Register the model update function after "PreLateUpdate" system. Array.Resize(ref subSystemList, subSystemList.Length + 1); subSystemList[subSystemList.Length - 1] = myPlayerLoopSystem; // Restore the "PreLateUpdate" sytem. playerLoopSubSystem.subSystemList = subSystemList; playerLoopSystem.subSystemList[playerLoopIndex] = playerLoopSubSystem; PlayerLoop.SetPlayerLoop(playerLoopSystem); } #endif #region Unity Event Handling /// /// Called by Unity. Triggers to update. /// private void Update() { #if UNITY_2018_1_OR_NEWER if (!WasAttachedModelUpdateFunction) { _modelUpdateFunctions += OnModelUpdate; WasAttachedModelUpdateFunction = true; } #endif // Return on first frame enabled. if (WasJustEnabled) { return; } // Return unless revived. if (!IsRevived) { return; } // Return if backend is ticking. if (!TaskableModel.DidExecute) { return; } // Sync parameters back. TaskableModel.TryReadParameters(Parameters); // restore last frame parameters value and parts opacity. if (_parameterStore != null) { _parameterStore.RestoreParameters(); } // Trigger event. if (OnDynamicDrawableData == null) { return; } OnDynamicDrawableData(this, TaskableModel.DynamicDrawableData); } /// /// Called by Unity. Blockingly updates on first frame enabled; otherwise tries async update. /// private void OnRenderObject() { #if !UNITY_2018_1_OR_NEWER OnModelUpdate(); #endif } /// /// Update model states. /// private void OnModelUpdate() { // Return unless revived. if (!IsRevived) { return; } // Return if already ticked this frame. if (LastTick == Time.frameCount && Application.isPlaying) { return; } LastTick = Time.frameCount; // Try to sync parameters and parts (without caring whether task is executing or not). TaskableModel.TryWriteParametersAndParts(Parameters, Parts); // Return if task is executing. if (TaskableModel.IsExecuting) { return; } // Force blocking update on first frame enabled. if (WasJustEnabled) { // Force sync update. TaskableModel.UpdateNow(); // Unset condition. WasJustEnabled = false; // Fetch results by calling own 'Update()'. Update(); return; } // Enqueue update task. TaskableModel.Update(); } /// /// Called by Unity. Revives instance. /// private void OnEnable() { WasJustEnabled = true; Revive(); } private void OnDisable() { #if UNITY_2018_1_OR_NEWER if (WasAttachedModelUpdateFunction) { _modelUpdateFunctions -= OnModelUpdate; WasAttachedModelUpdateFunction = false; } #endif } /// /// Called by Unity. Releases unmanaged memory. /// private void OnDestroy() { if (!IsRevived) { return; } TaskableModel.ReleaseUnmanaged(); TaskableModel = null; } /// /// Called by Unity. Triggers . /// private void OnValidate() { OnEnable(); } #endregion } }