モーションについて

最終更新: 2024年11月28日

モーションを再生するまでに必要なクラス

1、モーションデータを保持し、モデルに対して操作するクラス

a.インスタンスの作成(motion3.jsonファイルの読み込み)

b.モーションの再生方法についての設定

c.インスタンスの破棄

2、モーション管理クラス

a.インスタンスの作成

b.モーション再生

c.モデルのパラメータ更新

d.モーションの終了

e.ユーザトリガーの受取り

1-a.モーションのインスタンスの作成(.motion3.jsonファイルの読み込み)

モーションの再生にはACubsimMotionクラスから派生したCubismMotionクラスを使用します。
モーションに利用するデータは拡張子が「.motion3.json」となっているモーションファイルです。
Cubism2.1の「.mtn」は使用できません。
この.motion3.jsonファイルの読み込みには以下のいずれかの関数を利用します。

        – Native(C++)のCubismMotion::Create関数

        – Web(TypeScript)のCubismMotion.create関数

        – JavaのCubismMotion.create関数

jsonファイルを一度ロードしてからバッファとサイズを渡してロードします。

SDK for Javaの場合はバッファのみをメソッドに渡します。

// C++
    csmString path = "example.motion3.json";
    csmByte* buffer;
    csmSizeInt size;
    buffer = CreateBuffer(path.GetRawString(), &size);
    CubismMotion* motion = CubismMotion::Create(buffer, size);
// TypeScript
let path: string = "example.motion3.json";

fetch(path).then(
    (response) => 
    {
        return response.arrayBuffer();
    }
).then(
    (arrayBuffer) =>
    {
        let buffer: ArrayBuffer = arrayBuffer;
        let size = buffer.byteLength;
        let motion: CubismMotion = CubismMotion.create(buffer, size);
    }
);
// Java
	String path = "example.motion3.json";
	byte[] buffer = createBuffer(path);
	CubismMotion motion = loadMotion(buffer);

1-b.モーションファイルごとの再生方法設定

モーションには主に以下の項目を設定します。
これらの設定は行わなくても再生することができます。

モーション開始時のフェードインの時間:

設定は以下のいずれかの関数で行います。

        – Native(C++)のACubismMotion::SetFadeInTime関数

        – Web(TypeScript)のACubismMotion.setFadeInTime関数

        – JavaのACubismMotion.setFadeInTime関数

以下のいずれかの関数で取得できます。

        – Native(C++)のACubismMotion::GetFadeInTime関数

        – Web(TypeScript)のACubismMotion.getFadeInTime関数

        – JavaのACubismMotion.getFadeInTime関数

フェードインの時間を秒で指定します。

モーション終了時のフェードアウトの時間:

設定は以下のいずれかの関数で行います。

        – Native(C++)のACubismMotion::SetFadeOutTime関数

        – Web(TypeScript)のACubismMotion.setFadeOutTime関数

        – JavaのACubismMotion.setFadeOutTime関数

以下のいずれかの関数で取得できます。

        – Native(C++)のACubismMotion::GetFadeOutTime関数

        – Web(TypeScript)のACubismMotion.getFadeOutTime関数

        – JavaのACubismMotion.getFadeOutTime関数

フェードアウトの時間を秒で指定します。

ループ再生のON/OFF:

設定は以下のいずれかの関数で行います。

        – Native(C++)のvoid CubismMotion::IsLoop(csmBool loop)関数

        – Web(TypeScript)のCubismMotion.setIsLoop関数

        – JavaのCubismMotion.isLoop関数

以下のいずれかの関数で現在の値を取得できます。

        – Native(C++)のcsmBool CubismMotion::IsLoop()関数

        – Web(TypeScript)のCubismMotion.isLoop関数

        – JavaのCubismMotion.isLoop関数

trueを設定すると終了時に最初から再生します。
他のモーションが割り込むか終了命令が呼ばれるまで無限にループ再生し続けます。
設定しない場合の初期値はfalse(ループしない)です。

※Frameworkのループの動作はエディターのループ動作と完全な一致を保証していません。
※現在Animatorはmotion3.jsonファイルにループ設定を反映させられないため、
 Frameworkはmotion3.jsonファイルのループ設定を無視してfalseを設定します。

設定例 (※これらの設定はモーションの再生前に行ってください)

// C++
    motion->SetFadeInTime( 1.0f );
    motion->SetFadeOutTime( 1.0f );
    motion->IsLoop( true );
// TypeScript
    motion.setFadeInTime(1.0);
    motion.setFadeOutTime(1.0);
	motion.setIsLoop(true);
// Java
    motion.setFadeInTime(1.0f);
    motion.setFadeOutTime(1.0f);
    motion.isLoop(true);

1-c.インスタンスの破棄

// C++
    ACubismMotion::Delete(motion);

Cubism SDK for Web及びJavaでは明示的に破棄する必要はありません。


モーションのフェード値をファイルから設定する方法については

A、.motion3.jsonファイルに全体値として設定する方法(全体のフェード)

B、.motion3.jsonファイルにパラメータ個別の値として設定する方法(パラメータフェード)

C、.model3.jsonファイルに全体値として設定する方法

この3つがあり、優先度はB、C、Aの順で適用されます。
いずれの指定もない場合はデフォルト値の1秒が設定されます。

2-a.モーション管理クラスのインスタンスの作成

前項で作成したCubismMotionクラスのインスタンスをモデルに適用する(アニメーションさせる)ためには、 CubismMotionManagerクラスを使用します。

// C++
	CubismMotionManager* motionManager = CSM_NEW CubismMotionManager();
// TypeScript
	let motionManager: CubismMotionManager = new CubismMotionManager();
// Java
	CubismMotionManager motionManager = new CubismMotionManager();

2-b.モーションの再生

モーションの再生には以下のいずれかの関数を使用します。

        – Native(C++)のCubismMotionManager::StartMotionPriority関

        – Web(TypeScript)のCubismMotionManager.startMotionPriority関数

        – JavaのCubismMotionManager.startMotionPriority関数

第一引数:ACubismMotionインスタンス、モーションデータ

モーションデータのインスタンスを渡します。
この引数にはACubismMotionの派生インスタンスであるCubismMotionとCubismExpressionMotionのインスタンスが指定できます。
一般的に一つのモーションマネージャが取り扱うインスタンスタイプは一方だけにします。

第二引数:Boolean、自動削除のフラグ

再生が終了したときに自動的にモーションのデータを削除するかどうかのフラグです。
一度だけ再生されるようなモーションに使用します。

第三引数:Int、優先度

CubismMotionManagerで管理する再生の際の優先度設定を指定します。
優先度による再生拒否はCubismMotionManagerの外部で行う必要があります。
優先度に関してはページ下部にあるCubismMotionManagerの項目をご覧ください。

// C++
	csmBool autoDelete = true;
    csmInt32 priority = PriorityNormal;// 2
    motionManager->StartMotionPriority( motion, autoDelete, priority);
// TypeScript
    let autoDelete: boolean = true;
    let priority: number = priorityNormal; // 2
    motionManager.startmotionPriority(motion, autoDelete, priority);
// Java
	boolean autoDelete = true;
	int priority = LAppDefine.Priority.NORMAL; // 2
	motionManager.startMotionPriority(motion, autoDelete, priority);

モーションを複数同時に再生させたい場合は、CubismMotionManagerインスタンスを増やしてください。
これは右手と左手のモーションを別々に制御するなどのことに使えます。
モーションを同時に再生する場合は、できるだけ同じパラメータについては設定しないでください。
その場合最後に更新したモーションのパラメータが有効になります。
また、フェードが綺麗にかからない場合があります。

2-c.モデルのパラメータ更新

– Native(C++)のCubismMotionManager::StartMotionPriority関数

– Web(TypeScript)のCubismMotionManager.startMotionPriority関数

– JavaのCubismMotionManager.startMotionPriority関数

以上の関数でモーションを再生しただけでは、モデルはアニメーションしません。

現在再生中のモーションのパラメータをモデルに設定するためには、毎描画時に以下のいずれかの関数を呼び出します。

        – Native(C++)のCubismMotionManager::UpdateMotion関数

        – Web(TypeScript)のCubismMotionManager.updateMotion関数

        – JavaのCubismMotionManager.updateMotion関数

第一引数:CubismModelインスタンス、モーションを適用するモデル

パラメータ情報の取得、操作のみに使用されます。

第二引数:Float、前回実行時からの差分時間

Updateなどで計算される差分時間を入力します。
Float実数で秒単位で入力します。 60FPSでの実行ならば1/60秒で0.016を、30FPSならば1/30秒で0.03を入力します。

// C++
	motionUpdated = motionManager->UpdateMotion(model, deltaTimeSeconds);
// TypeScript
	motionUpdated = motionManager.updateMoiton(model, deltaTimeSeconds);
// Java
	isMotionUpdated = motionManager.updateMotion(model, deltaTimeSeconds);

入力するdeltaTimeSecondsを調整することでスローや停止、早送りをすることができます。
ただし、マイナスの値を使った逆再生は考慮の範囲外の設計になっています。

// C++
    const csmFloat32 playSpeed = pow(2, (csmFloat32)_motionSpeed / 10.0);
    motionUpdated = _motionManager->UpdateMotion(_model, deltaTimeSeconds * playSpeed); // モーションを更新
// TypeScript
	let playSpeed:number = Math.pow(2, _motionSpeed / 10.0);
	motionUpdated = motionManager.updateMoiton(model, deltaTimeSeconds);
// Java
	final float playSpeed = Math.pow(2, (float)motionSpeed / 10.0f);
	isMotionUpdated = motionManager.updateMotion(model, deltaTimeSeconds * playSpeed); // モーションを更新

2-d.モーションの終了

モーションは再生時間を過ぎたときに自動で終了しますが
任意のタイミングで終了したいときは以下のいずれかの関数を使用します。

        – Native(C++)のCubismMotionQueueManager::StopAllMotions関数

        – Web(TypeScript)のCubismMotionQueueManager.stopAllMotions関数

        – JavaのCubismMotionQueueManager.stopAllMotions関数

フェードの途中などで、同時に2つ以上のモーションを再生している場合、それらすべてを終了します。

// C++
	motionManager->StopAllMotions();
// TypeScript
	motionManager.stopAllMotions();
// Java
	motionManager.stopAllMotions();

2-e.イベントの受取り

モーションに設定された「イベント」が再生されたときに、CubismMotionQueueManagerに以下のいずれかの関数で登録したコールバックで呼び出しを受けることができます。

        – Native(C++)のSetUserTriggerCallback関数

        – Web(TypeScript)のsetUserTriggerCallback関数

        – JavaのsetUserTriggerCallback関数

登録できるコールバックは1つのみです。
インスタンスの関数を呼び出したい時にはstaticな関数をインスタンスのポインタと一緒に登録し、
staticな関数から登録しておいたインスタンスのポインタを使って目的の関数を呼び出すようにしてください。
複数の動作をさせたい時には一つのコールバックから順に呼び出すようにしてください。

//C++
    /**
    * @brief ユーザトリガーのコールバック関数定義
    *
    * ユーザトリガーのコールバックに登録できる関数の型情報
    *
    * @param[in]   caller           発火したユーザトリガーを再生させたCubismMotionQueueManager
    * @param[in]   userTriggerValue 発火したユーザトリガーの文字列データ
    * @param[in]   customData       コールバックに返される登録時に指定されたデータ
    */
    typedef void(*UserTriggerFunction)(const CubismMotionQueueManager* caller, const csmString& userTriggerValue, void* customData);
// C++
    class SampleClass
    {
      public:
       void   UserTriggerEventFired(const csmString& userTriggerValue) 
       {
         //処理
       }

       static void SampleCallback(
           const CubismMotionQueueManager* caller, const csmString& userTriggerValue, void* customData)
       {
         SampleClass* sample = reinterpret_cast<SampleClass*>(customData);
         if (sample != NULL)
         {
             sample->UserTriggerEventFired(userTriggerValue);
         }
       }
    };

    SampleClass sampleA;
    motionManager->SetUserTriggerCallback(SampleClass::SampleCallback, &sampleA);
// TypeScript
    /**
     *  ユーザトリガーのコールバックに登録できる関数の型情報
     *
     * @param caller 発火したユーザトリガーを再生させたCubismMotionQueueManager
     * @param userTriggerValue 発火したユーザートリガーの文字列データ
     * @param customData コールバックに返される登録時に指定されたデータ
     */
    export interface UserTriggerFunction
    {
        (
            caller: CubismmotionQueueManager,
            userTriggerValue: string,
            customData: any
        ): void;
    }
// TypeScript
    class SampleClass
    {
        public userTriggerEventFired(userTriggerValue): void
        {
            // 処理
        }

        public static sampleCallback(caller: CubismMotionQueueManager,
            userTriggerValue: string, customData: any): void
        {
            let sample: SampleClass = <SampleClass>customData;
            if(sample != null)
            {
                sample.userTriggerEventFired(userTriggerValue);
            }
        }
    };


    let sampleA: SampleClass = new SampleClass();
    motionManager.setUserTriggerCallback(SampleClass.sampleCallback, sampleA);
// Java
	public interface ICubismMotionEventFunction {
      public void apply(
        CubismMotionQueueManager caller,
        String eventValue,
        Object customData);
    }
// Java
    public class SampleClass {
        public void userTriggerEventFired(final String userTriggerValue) {
        // 処理
    }

    public static class SampleCallback implements ICubismMotionEventFunction {
        @Override
        public void apply(
            CubismMotionQueueManager caller,
            String eventValue,
            Object customData)
        {
            if (customData != null) {
                ((CubismUserModel) customData).motionEventFired(eventValue);
            }
        }
    }

CubismUserModelクラスには標準でこの仕組みが組み込まれています。

        – Native(C++)の (CubismUserModel::MotionEventFired関数、CubismUserModel::CubismDefaultMotionEventCallback関数)

        – Web(TypeScript)の (CubismUserModel.cubismDefaultMotionEventCallback関数)

        – Javaの (CubismUserModel.motionEventFired関数、CubismUserModel.cubismDefaultMotionEventCallback変数)

モーションの自動削除

Native(C++)のCubismMotionQueueManager::StartMotion関数、またはWeb(TypeScript)のCubismMotionQueueManager.startMotion関数の呼び出し時に第二引数のautoDeleteにtrueを入れると、
モーションが再生終了するときにCubismMotionQueueEntryの削除とともにモーションも削除されます。
一度のみ再生されるファイルに対して使用されることを想定しています。

// C++
    csmBool CubismMotionQueueManager::DoUpdateMotion(CubismModel* model, csmFloat32 userTimeSeconds)
    {
        csmBool updated = false;

        // ------- 処理を行う --------
        // 既にモーションがあれば終了フラグを立てる

        for (csmVector<CubismMotionQueueEntry*>::iterator ite = _motions.Begin(); ite != _motions.End();)
        {
            CubismMotionQueueEntry* motionQueueEntry = *ite;

/*省略*/

            // ----- 終了済みの処理があれば削除する ------
            if (motionQueueEntry->IsFinished())
            {
                CSM_DELETE(motionQueueEntry);
                ite = _motions.Erase(ite);          // 削除
            }
            else
            {
                ++ite;
            }
        }

        return updated;
    }
// C++
    CubismMotionQueueEntry::~CubismMotionQueueEntry()
    {
        if (_autoDelete && _motion)
        {
            ACubismMotion::Delete(_motion); 
        }
    }
// TypeScript
	/**
	 * モーションを更新して、モデルにパラメータ値を反映する。
	 *
	 * @param   model   対象のモデル
	 * @param   userTimeSeconds   デルタ時間の積算値[秒]
	 * @return  true    モデルへパラメータ値の反映あり
	 * @return  false   モデルへパラメータ値の反映なし(モーションの変化なし)
	 */
    public doUpdateMotion(model: CubismModel, userTimeSeconds: number): boolean
    {
      let updated: boolean = false;

      // ------- 処理を行う --------
      // 既にモーションがあれば終了フラグを立てる

      for(let ite: iterator<CubismMotionQueueEntry> = this._motions.begin(); ite.notEqual(this._motions.end());)
      {
        let motionQueueEntry: CubismMotionQueueEntry = ite.ptr();

/*省略*/    
        
        // ------ 終了済みの処理があれば削除する ------
        if(motionQueueEntry.isFinished())
        {
          motionQueueEntry.release();
          motionQueueEntry = void 0;
          motionQueueEntry = null;
          ite = this._motions.erase(ite); // 削除
        }
        else
        {
          ite.preIncrement();
        }
      }

      return updated;
    }
// TypeScript
	/**
     * デストラクタ相当の処理
     */
    public release(): void
    {
        if(this._autoDelete && this._motion)
        {
            ACubismMotion.delete(this._motion); //
        }
    }

SDK for JavaではCubismMotionQueueEntryへの参照がなくなった時点で、そのフィールドであるモーションインスタンスも自動的に削除されるため、モーションの自動削除のフラグは不要です。

CubismMotionQueueManagerクラスとCubismMotionManagerクラス

モーションの管理クラスにはCubismMotionQueueManagerクラスと、
CubismMotionQueueManagerクラスを継承したCubismMotionManagerクラスがあります。

CubismMotionQueueManager

CubismMotionQueueManagerはモーションの値の適用度合いにフェードが効いた切り替えを担当します。

以下のいずれかの関数でモーション再生追加時にすでに再生しているモーション群に対してStartFadeoutによって終了時間の前倒しを行います。

        – Native(C++)のLAppModel::StartMotion関数

        – Web(TypeScript)のLAppModel.startMotion関数

        – JavaのLAppModel.startMotion関数

この操作によって再生中のモーションがフェードをもって新しいモーションとの切り替わりを実現します。

// C++
    CubismMotionQueueEntryHandle CubismMotionQueueManager::StartMotion(ACubismMotion* motion, csmBool autoDelete, csmFloat32 userTimeSeconds)
    {
        if (motion == NULL)
        {
            return InvalidMotionQueueEntryHandleValue;
        }

        CubismMotionQueueEntry* motionQueueEntry = NULL;

        // 既にモーションがあれば終了フラグを立てる
        for (csmUint32 i = 0; i < _motions.GetSize(); ++i)
        {
            motionQueueEntry = _motions.At(i);
            if (motionQueueEntry == NULL)
            {
                continue;
            }

            motionQueueEntry->StartFadeout(motionQueueEntry->_motion->GetFadeOutTime(), userTimeSeconds); //フェードアウトを開始し終了する
        }

        motionQueueEntry = CSM_NEW CubismMotionQueueEntry(); // 終了時に破棄する
        motionQueueEntry->_autoDelete = autoDelete;
        motionQueueEntry->_motion = motion;

        _motions.PushBack(motionQueueEntry, false);

        return motionQueueEntry->_motionQueueEntryHandle;
    }
// TypeScript
	/**
     * 指定したモーションの開始
     *
     * 指定したモーションを開始する。同じタイプのモーションが既にある場合は、既存のモーションに終了フラグを立て、フェードアウトを開始させる。
     *
     * @param   motion          開始するモーション
     * @param   autoDelete      再生が終了したモーションのインスタンスを削除するなら true
     * @param   userTimeSeconds デルタ時間の積算値[秒]
     * @return                      開始したモーションの識別番号を返す。個別のモーションが終了したか否かを判定するIsFinished()の引数で使用する。開始できない時は「-1」
     */
    public startMotion(motion: ACubismMotion, autoDelete: boolean, userTimeSeconds: number) : CubismMotionQueueEntryHandle
    {
        if(motion == null)
        {
            return InvalidMotionQueueEntryHandleValue;
        }

        let motionQueueEntry: CubismMotionQueueEntry = null;

        // 既にモーションがあれば終了フラグを立てる
        for(let i: number = 0; i < this._motions.getSize(); ++i)
        {
            motionQueueEntry = this._motions.at(i);
            if(motionQueueEntry == null)
            {
                continue;
            }

            motionQueueEntry.startFadeout(motionQueueEntry._motion.getFadeOutTime(), userTimeSeconds); // フェードアウトを開始し終了する
        }

        motionQueueEntry = new CubismMotionQueueEntry(); // 終了時に破棄する
        motionQueueEntry._autoDelete = autoDelete;
        motionQueueEntry._motion = motion;

        this._motions.pushBack(motionQueueEntry);

        return motionQueueEntry._motionQueueEntryHandle;
    }
// Java
	public int startMotion(ACubismMotion motion, float userTimeSeconds) {
      if (motion == null) {
        return -1;
      }

      // 既にモーションがあれば終了フラグを立てる
      for (CubismMotionQueueEntry entry : motions){
        if (entry == null) {
          continue;
        }
        entry.setFadeOut(entry.getMotion().getFadeOutTime());
      }

      CubismMotionQueueEntry motionQueueEntry = new CubismMotionQueueEntry();
      motionQueueEntry.setMotion(motion);
      motions.add(motionQueueEntry);
      return System.identityHashCode(motionQueueEntry);
    }

CubismMotionManager

CubismMotionManagerクラスは再生するモーションの優先度を保存する機能と、これから再生する予定の優先度を整数として登録する機能があります。
この記録された優先度と比較することで優先度の低いモーション再生を規制する機能を作成することを想定しています。
再生規制をする部分に関してはCubismMotionManagerの外部に用意する必要があります。

以下のいずれかの関数は非同期スレッドからの再生に対応しています。

        – Native(C++)のLAppModel::StartMotion関数

        – Web(TypeScript)のLAppModel.startMotion関数

        – JavaのLAppModel.startMotion関数

関数の冒頭で再生の優先をSetReservePriorityやReserveMotionによって登録されます。
次に読み込みが実行されますが、非同期でこの関数を呼ばれた場合冒頭で優先度を登録されるので、
読み込み中に他のスレッドで他の低優先度の再生は規制される仕組みになっています。

再生終了時は優先度が0に設定されるのは固定で、その他の制御は外部に任されます。

// C++
	CubismMotionQueueEntryHandle LAppModel::StartMotion(const csmChar* group, csmInt32 no, csmInt32 priority)
    {
        if (priority == PriorityForce)
        {
            _motionManager->SetReservePriority(priority);
        }
        else if (!_motionManager->ReserveMotion(priority))
        {
            if (_debugMode)
            {
                LAppPal::PrintLog("[APP]can't start motion.");
            }
            return InvalidMotionQueueEntryHandleValue;
        }

/*モーションデータ準備部分省略*/
        return  _motionManager->StartMotionPriority(motion, autoDelete, priority);
    }
// TypeScript
    /**
     * 引数で指定したモーションの再生を開始する
     * @param group モーショングループ名
     * @param no グループ内の番号
     * @param priority 優先度
     * @return 開始したモーションの識別番号を返す。
     *         個別のモーションが終了したか否かを判定するisFinished()の引数で使用する。
     *         開始できない時は[-1]
     */
    public startMotion(group: string, no: number, priority: number) : CubismMotionQueueEntryHandle
    {
        if(priority == LAppDefine.PriorityForce)
        {
            this._motionManager.setReservePriority(priority);
        }
        else if(!this._motionManager.reserveMotion(priority))
        {
            if(this._debugMode)
            {
                LAppPal.printLog("[APP]can't start motion.");
            }
            return InvalidMotionQueueEntryHandleValue;
        }

/*モーションデータ準備部分省略*/
        return this._motionManager.startMotionPriority(motion, autoDelete, priority);
    }
// Java
	public int startMotion(final String group,
                           int number,
                           int priority,
                           IFinishedMotionCallback onFinishedMotionHandler
                          ) {
      if (priority == LAppDefine.Priority.FORCE.getPriority()) {
        motionManager.setReservationPriority(priority);
      } else if (!motionManager.reserveMotion(priority)) {

        if (debugMode) {
          LAppPal.printLog("Cannot start motion.");
        }
        return -1;
      }

      /*モーションデータ準備部分省略*/
      if (motionManager.startMotionPriority(motion, priority) != -1) {
        return motionManager.startMotionPriority(motion, priority);
      }
      return -1;
    }

Native・Web・Java との挙動の差について

SDK for Unity では .motion3.json を変換した AnimationClip を再生させることでモーションの再生を実現しています。
AnimatorController を利用せず CubismMotionController を利用した場合でも、 PlayableGraph を利用して AnimationClip を再生することで、モデルのパラメータ値を操作します。

対して、SDK for Native などでは .motion3.json から取得した値を利用し、現在の再生時間で補間して値を取得します。
この時、再生終了時刻を超えていたら必ず最後のキーフレームの値を一度返すように設計されています。

SDK for Unity は Unity の機能である PlayableGraph と AnimationClip を利用する関係上、再生中の任意のタイミングでその値に干渉することは出来ません。
また、再生終了のチェックを再生終了時刻で管理しているため、AnimationClip が最後のキーフレームを返したかどうかに関わらず終了フラグが立ちます。
そのため、再生終了時刻を超えていた場合の処理が SDK for Native などと異なり、更新時刻によっては最後のキーフレームが返されないことがあります。
この現象は特に CubismMotionController.PlayAnimation() の引数 isLoop を false として、FadeOutTime を0に指定した状態で再生すると現れることがあります。

なお、必要に応じて

  • Application.targetFramerate をモーションのフレームレートに合うように設定
  • CubismMotionController.Update() 内の Time.time の使用箇所を Time.fixedTime へ変更
  • ProjectSettings.Time 内の Fixed Timestep を Application.targetFramerate に合うように調整

以上の変更を行うことで比較的安定して最後のキーフレームを取得することが出来ます。

ただし、これらの設定はアプリケーション全体に影響を及ぼすため推奨されません。

CubismEditor とループの挙動を近づける

Cubism 5 SDK for Unity R2 時点では、モーションのループ再生は CubismEditor や Cubism Viewer (for OW) と挙動に差異が発生します。

この症状については将来的な対応を検討しておりますが、Cubism 5 SDK for Unity R2 以前の SDK for Unity においてはインポート処理の一部を変更することで、ループ時の挙動を CubismEditor の挙動に近づけることが出来ます。

CubismEditor の挙動に近づける方法は以下に記載します。
なお、これは暫定的な方法であり、この方法を利用して作成された AnimationClip やモデルの挙動については保証いたしかねます。ご利用は自己責任でお願いいたします。

主に有効となるシチュエーション

  • AnimatorController(Mecanim) を利用したモーション再生とループ処理を行うシチュエーション
  • CubismMotionController.PlayAnimation() の isLoop を true にして再生を行うシチュエーション

インポート時の処理の変更

以下の箇所を修正し、モーションを含むモデルをインポートすることで生成される AnimationClip のカーブが CubismEditor でループを行う場合に近いものに変換されます。
この方式はインポート及び再インポートする全ての .motion3.json に影響します。

CubismMotion3Json.cs

https://github.com/Live2D/CubismUnityComponents/blob/304d0d0c827b7bcb86c6f5d9b0a6398eaccda556/Assets/Live2D/Cubism/Framework/Json/CubismMotion3Json.cs#L245-L253

public AnimationClip ToAnimationClip(bool shouldImportAsOriginalWorkflow = false, bool shouldClearAnimationCurves = false,
                                     bool isCallFormModelJson = false, CubismPose3Json poseJson = null)
{
    // Check béziers restriction flag.
    if (!Meta.AreBeziersRestricted)
    {
        Debug.LogWarning("Béziers are not restricted and curves might be off. Please export motions from Cubism in restricted mode for perfect match.");
    }

    // --- Add ---
    var duration = Meta.Duration;
    // --- Add ---
    ...

https://github.com/Live2D/CubismUnityComponents/blob/304d0d0c827b7bcb86c6f5d9b0a6398eaccda556/Assets/Live2D/Cubism/Framework/Json/CubismMotion3Json.cs#L268

    // --- Add, Change ---
    var keyframes = ConvertCurveSegmentsToKeyframes(curve.Segments);
    if (Meta.Fps > 0)
    {
        duration = Meta.Duration + (1.0f / Meta.Fps);
        var oldLength = keyframes.Length;
        Array.Resize(ref keyframes, oldLength + 1);
        keyframes[oldLength] = new Keyframe(duration, curve.Segments[1]);
    }

    var animationCurve = new AnimationCurve(keyframes);
    // --- Add, Change ---
    ...

https://github.com/Live2D/CubismUnityComponents/blob/304d0d0c827b7bcb86c6f5d9b0a6398eaccda556/Assets/Live2D/Cubism/Framework/Json/CubismMotion3Json.cs#L322-L328

#if UNITY_EDITOR
    // --- Add, Change ---
    if (Meta.Fps > 0)
    {
        duration = Meta.Duration + (1.0f / Meta.Fps);
    }

    // Apply settings.
    var animationClipSettings = new AnimationClipSettings
    {
        loopTime = Meta.Loop,
        stopTime = duration
    };
    // --- Add, Change ---
    ...

この記事はお役に立ちましたか?
はいいいえ
この記事に関するご意見・
ご要望をお聞かせください。