Use in stop motion

What is stop motion?

Stop motion is a technique that creates a puppet theater-like effect by intentionally moving character animation frame by frame.
Here we will explain how to make MagicaCloth compatible with stop motion.
Please note that implementing stop motion requires some coding knowledge.

General technique

A common technique for implementing stop motion in Unity is to record the animation content in movement frames and write back the recorded animation content in stop frames.
This is done by recording the entire character’s Transform pose and writing it back.

Keep in mind that the character’s animation is always in motion with this method.
At the stop frame, the animation result is forcibly overwritten with the recorded pose before drawing.
This allows you to create a state where the internal animation is updated but the appearance remains static.
These two operations are commonly performed in LateUpdate() after the animation is executed.

How to deal with MagicaCloth

Applying MagicaCloth to common methods requires some manipulation.
MagicaCloth similarly needs to allow writing in the working frame and prohibit writing in the stopping frame.
By prohibiting writing, simulation results will not be written to Transform or Mesh.
Keep in mind that MagicaCloth simulations, like animations, are always running.
Only the reflection of the results is suppressed.

In addition, a series of operations using a general method were performed using LateUpdate(), but when using MagicaCloth, it is necessary to change the operation location.
Basically, you need to perform each process before and after the MagicaCloth simulation as follows.

Before cloth simulation

Here you need to set whether to write or prohibit the current simulation results.
Instructs MagicaCloth to allow writing if it is a moving frame and to prevent writing if it is a stop frame.

After cloth simulation

Here, we will record and write back the character’s posture as in the general method.
If it is a movement frame, the character’s posture is recorded, and if it is a stop frame, the recorded posture is written back.
This part is the same as the general method.

Example

Here, as an example, we will explain the stop motion method along with sample code.

Sample code

The following is a sample code for implementing stop motion when using MagicaCloth.
This component is assumed to be attached to the same GameObject as the character’s Animator.

using MagicaCloth2;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// MagicaClothを利用したストップモーションのテスト
/// このスクリプトをキャラクターのAnimatorと同じ場所に追加してください
/// Stop motion testing using MagicaCloth.
/// Add this script to the same location as your character's Animator.
/// </summary>
public class StopMotionTest : MonoBehaviour
{
  /// <summary>
  /// 姿勢を記録するルートTransform
  /// Route Transform that records posture.
  /// </summary>
  public Transform RootBone;

  /// <summary>
  /// 停止するフレーム数
  /// Number of frames to stop.
  /// </summary>
  public int StoppedFrameCount = 5;

  //=============================================================================================
  int recordedFrame = -1;
  bool isSkipWriting = false;
  List<Transform> transforms = null;
  List<STransform> renderedPositions = null;
  List<MagicaCloth> clothList = null;

  /// <summary>
  /// Transformの状態を記録および復元する構造体
  /// Record and restore Transform state.
  /// </summary>
  struct STransform
  {
    public Vector3 LocalPosition;
    public Quaternion LocalRotation;
    public Vector3 LocalScale;

    public static STransform FromTransform(Transform t)
    {
      return new STransform
      {
        LocalPosition = t.localPosition,
        LocalRotation = t.localRotation,
        LocalScale = t.localScale
      };
    }

    public void WriteTo(Transform t)
    {
      t.localPosition = LocalPosition;
      t.localRotation = LocalRotation;
      t.localScale = LocalScale;
    }
  }

  //=============================================================================================
  private void Awake()
  {
    // MagicaClothのシミュレーション前後にイベントを登録する
    // Register events before and after MagicaCloth simulation.
    MagicaManager.OnPreSimulation += OnPreSimulation;
    MagicaManager.OnPostSimulation += OnPostSimulation;

    clothList = new List<MagicaCloth>(GetComponentsInChildren<MagicaCloth>());
    transforms = new List<Transform>(RootBone.GetComponentsInChildren<Transform>());
  }

  private void OnDestroy()
  {
    // MagicaClothへのイベントを解除する
    // Cancel the event registered in MagicaCloth.
    MagicaManager.OnPreSimulation -= OnPreSimulation;
    MagicaManager.OnPostSimulation -= OnPostSimulation;
  }

  private void Update()
  {
    // ストップモーションの状態を更新
    // Update stop motion status.
    if (recordedFrame < 0 || Time.frameCount - recordedFrame >= StoppedFrameCount)
    {
      // 書き込み許可
      // write permission.
      isSkipWriting = false;
      recordedFrame = Time.frameCount;
    }
    else
    {
      // 書き込み停止
      // Write stop.
      isSkipWriting = true;
    }
  }

  void Reset()
  {
    var smr = GetComponentInChildren<SkinnedMeshRenderer>();

    if (smr != null)
    {
      RootBone = smr.rootBone;
    }
    else
    {
      RootBone = null;
    }
  }

  //=============================================================================================
  /// <summary>
  /// MagicaClothのシミュレーション実行前に呼び出されるイベント
  /// Event called before MagicaCloth simulation execution.
  /// </summary>
  void OnPreSimulation()
  {
    if (isSkipWriting)
    {
      // MagicaClothの書き込みを停止
      // Stop writing MagicaCloth.
      SetMagicaClothSkipWriting(true);
    }
    else
    {
      // MagicaClothの書き込みを許可
      // Allow MagicaCloth to write.
      SetMagicaClothSkipWriting(false);
    }
  }

  /// <summary>
  /// MagicaClothの書き込み停止フラグを更新する
  /// 書き込みを停止するとTransformおよびMeshへの書き込みが行われない
  /// Update MagicaCloth write stop flag.
  /// When writing is stopped, writing to Transform and Mesh is not done.
  /// </summary>
  /// <param name="sw"></param>
  void SetMagicaClothSkipWriting(bool sw)
  {
    if (clothList == null)
      return;

    foreach (var cloth in clothList)
    {
      cloth.SetSkipWriting(sw);
    }
  }

  //=============================================================================================
  /// <summary>
  /// MagicaClothのシミュレーション実行後に呼び出されるイベント
  /// Event called after MagicaCloth simulation run.
  /// </summary>
  void OnPostSimulation()
  {
    if (isSkipWriting)
    {
      // 停止時は記録したポーズを復元する
      // Restore the recorded pose when stopped.
      RestoreRecord();
    }
    else
    {
      // 更新時はアニメーションのポーズを記録する
      // Record animation pose when updating.
      RecordTransform();
    }
  }

  /// <summary>
  /// キャラクターの姿勢を記録する
  /// Record the character's posture.
  /// </summary>
  void RecordTransform()
  {
    if (transforms == null)
      return;

    if (renderedPositions == null)
    {
      renderedPositions = new List<STransform>(new STransform[transforms.Count]);
    }

    for (int i = 0; i < transforms.Count; i++)
    {
      renderedPositions[i] = STransform.FromTransform(transforms[i]);
    }
  }

  /// <summary>
  /// キャラクターの姿勢を復元する
  /// Restore the character's posture.
  /// </summary>
  void RestoreRecord()
  {
    if (renderedPositions == null)
      return;

    for (int i = 0; i < transforms.Count; i++)
    {
      renderedPositions[i].WriteTo(transforms[i]);
    }
  }
}

Register for an event

private void Awake()
{
  // MagicaClothのシミュレーション前後にイベントを登録する
  // Register events before and after MagicaCloth simulation.
  MagicaManager.OnPreSimulation += OnPreSimulation;
  MagicaManager.OnPostSimulation += OnPostSimulation;

  clothList = new List<MagicaCloth>(GetComponentsInChildren<MagicaCloth>());
  transforms = new List<Transform>(RootBone.GetComponentsInChildren<Transform>());
}

private void OnDestroy()
{
  // MagicaClothへのイベントを解除する
  // Cancel the event registered in MagicaCloth.
  MagicaManager.OnPreSimulation -= OnPreSimulation;
  MagicaManager.OnPostSimulation -= OnPostSimulation;
}

First, register an event so that you can add processing before and after cloth simulation.
For this, use MagicaManager’s OnPreSimulation/OnPostSimulation.

Stop motion status update

private void Update()
{
  // ストップモーションの状態を更新
  // Update stop motion status.
  if (recordedFrame < 0 || Time.frameCount - recordedFrame >= StoppedFrameCount)
  {
    // 書き込み許可
    // write permission.
    isSkipWriting = false;
    recordedFrame = Time.frameCount;
  }
  else
  {
    // 書き込み停止
    // Write stop.
    isSkipWriting = true;
  }
}

Determine whether the current frame is a motion frame or a stop frame.
This sample simply stops only StoppedFrameCount frames.
The isSkipWriting flag is now set when stopping.

Pre-simulation event

/// <summary>
/// MagicaClothのシミュレーション実行前に呼び出されるイベント
/// Event called before MagicaCloth simulation execution.
/// </summary>
void OnPreSimulation()
{
  if (isSkipWriting)
  {
    // MagicaClothの書き込みを停止
    // Stop writing MagicaCloth.
    SetMagicaClothSkipWriting(true);
  }
  else
  {
    // MagicaClothの書き込みを許可
    // Allow MagicaCloth to write.
    SetMagicaClothSkipWriting(false);
  }
}

/// <summary>
/// MagicaClothの書き込み停止フラグを更新する
/// 書き込みを停止するとTransformおよびMeshへの書き込みが行われない
/// Update MagicaCloth write stop flag.
/// When writing is stopped, writing to Transform and Mesh is not done.
/// </summary>
/// <param name="sw"></param>
void SetMagicaClothSkipWriting(bool sw)
{
  if (clothList == null)
    return;

  foreach (var cloth in clothList)
  {
    cloth.SetSkipWriting(sw);
  }
}

Describe the processing before starting cloth simulation.
Use the SetSkipWrite() API to instruct whether or not to write to the character’s MagicaCloth.
If writing is stopped, Transform and Mesh will no longer be written to after simulation.

Post-simulation event

/// <summary>
/// MagicaClothのシミュレーション実行後に呼び出されるイベント
/// Event called after MagicaCloth simulation run.
/// </summary>
void OnPostSimulation()
{
  if (isSkipWriting)
  {
    // 停止時は記録したポーズを復元する
    // Restore the recorded pose when stopped.
    RestoreRecord();
  }
  else
  {
    // 更新時はアニメーションのポーズを記録する
    // Record animation pose when updating.
    RecordTransform();
  }
}

/// <summary>
/// キャラクターの姿勢を記録する
/// Record the character's posture.
/// </summary>
void RecordTransform()
{
  if (transforms == null)
    return;

  if (renderedPositions == null)
  {
    renderedPositions = new List<STransform>(new STransform[transforms.Count]);
  }

  for (int i = 0; i < transforms.Count; i++)
  {
    renderedPositions[i] = STransform.FromTransform(transforms[i]);
  }
}

/// <summary>
/// キャラクターの姿勢を復元する
/// Restore the character's posture.
/// </summary>
void RestoreRecord()
{
  if (renderedPositions == null)
    return;

  for (int i = 0; i < transforms.Count; i++)
  {
    renderedPositions[i].WriteTo(transforms[i]);
  }
}

Describe the processing after the cross simulation is completed.
Following the general technique, we record the pose of the animation if it is a motion frame, and restore the recorded pose if it is a stop frame.

Result

The result of running this sample is as follows.
The left side is the normal state, and the right side is the state with stop motion applied.
Notice that both the animation and MagicaCloth visually stop at the stop frame.