Manage Motion Differently for Each Part

This page includes some notes on management and motion creation, using the example of increasing the number of LAppModel CubismMotionManager holdings and incorporating them into LAppModel::Update in order to manage different motions for the right and left hands.

Increase CubismMotionManager

Adding managers to the model

First, let’s look at the implementation on the program side.
The first step is to add a management class to the model.
Add CubismMotionManager to the sample LAppModel.
Add creation and deallocation to the constructor and destructor as well.

class LAppModel : public Csm::CubismUserModel
{
  ・
  ・
  ・

    Live2D::Cubism::Framework::CubismMotionManager* _rightArmMotionManager; ///< Add!
    Live2D::Cubism::Framework::CubismMotionManager* _leftArmMotionManager; ///< Add!

};
LAppModel::LAppModel()
    : CubismUserModel()
    , _modelSetting(NULL)
    , _userTimeSeconds(0.0f)
{
  ・
  ・
  ・
    _rightArmMotionManager = new CubismMotionManager(); ///< Add!
    _leftArmMotionManager = new CubismMotionManager(); ///< Add!
}

LAppModel::~LAppModel()
{
  ・
  ・
  ・
    CSM_DELETE(_rightArmMotionManager); ///< Add!
    CSM_DELETE(_leftArmMotionManager); ///< Add!
}

Update process

The Update process also allows the motion that is added and played to affect the parameters.

void LAppModel::Update()
{
 ・
 ・
 ・
    //-----------------------------------------------------------------
    _model->LoadParameters(); // Load previously saved state.
    if (_motionManager->IsFinished())
    {
        // If there is no motion playback, playback is performed at random from among the standby motions.
        StartRandomMotion(MotionGroupIdle, PriorityIdle);
    }
    else
    {
        motionUpdated = _motionManager->UpdateMotion(_model, deltaTimeSeconds); // Update motion.
    }
    motionUpdated |= _rightArmMotionManager->UpdateMotion(_model, deltaTimeSeconds); ///< Add!
    motionUpdated |= _leftArmMotionManager->UpdateMotion(_model, deltaTimeSeconds); ///< Add!
    _model->SaveParameters(); // Save the state.
    //-----------------------------------------------------------------
 ・
 ・
 ・
}

When adding into LAppModel::Update, please place UpdateMotion between _model->LoadParameters() and _model->SaveParameters().

Note that if UpdateMotion is run multiple times, the parameters to be updated may overlap.
In this case, the content of the later executed UpdateMotion takes precedence.

Playback start

Next, duplicate or modify the member functions of the StartMotion system so that motion playback can be directed by the motion manager that has been added.

Note that in the StartHandMotion function, the motion manager is specified as an argument.
In the following example, only the motion specified in .motion3.json (the motion loaded by the PreLoad function) is implemented for playback.

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

    const csmString fileName = _modelSetting->GetMotionFileName(group, no);

    //ex) idle_0
    csmString name = Utils::CubismString::GetFormatedString("%s_%d", group, no);
    CubismMotion* motion = static_cast<CubismMotion*>(_motions[name.GetRawString()]);
    csmBool autoDelete = false;

    if (motion == NULL)
    {
        // If not read in PreLoad, it will not play.
        return InvalidMotionQueueEntryHandleValue;
    }
    
    if (_debugMode)LAppPal::PrintLog("[APP]start motion: [%s_%d]", group, no);
    return  targetManage->StartMotionPriority(motion, autoDelete, priority);
}

// Duplicate and create from StartRandomMotion
CubismMotionQueueEntryHandle LAppModel::StartRandomRightHandMotion(const csmChar* group, csmInt32 priority)
{
    if (_modelSetting->GetMotionCount(group) == 0)
    {
        return InvalidMotionQueueEntryHandleValue;
    }

    csmInt32 no = rand() % _modelSetting->GetMotionCount(group);

    return StartHandMotion(_rightArmMotionManager,group, no, priority);
}

// Duplicate and create from StartRandomMotion
CubismMotionQueueEntryHandle LAppModel::StartRandomLeftHandMotion(const csmChar* group, csmInt32 priority)
{
    if (_modelSetting->GetMotionCount(group) == 0)
    {
        return InvalidMotionQueueEntryHandleValue;
    }

    csmInt32 no = rand() % _modelSetting->GetMotionCount(group);

    return StartHandMotion(_leftArmMotionManager,group, no, priority);
}

Motion playback adds collision detection to the model so that it is triggered by a click.

void LAppLive2DManager::OnTap(csmFloat32 x, csmFloat32 y)
{
    if (DebugLogEnable) LAppPal::PrintLog("[APP]tap point: {x:%.2f y:%.2f}", x, y);

    for (csmUint32 i = 0; i < _models.GetSize(); i++)
    {
        if (_models[i]->HitTest(HitAreaNameHead, x, y))
        {
            if (DebugLogEnable) LAppPal::PrintLog("[APP]hit area: [%s]", HitAreaNameHead);
            _models[i]->SetRandomExpression();
        }
        else if (_models[i]->HitTest(HitAreaNameBody, x, y))
        {
            if (DebugLogEnable) LAppPal::PrintLog("[APP]hit area: [%s]", HitAreaNameBody);
            _models[i]->StartRandomMotion(MotionGroupTapBody, PriorityNormal);
        }
        // Additions from here.
        else if (_models[i]->HitTest("Right", x, y))
        {
            if (DebugLogEnable) LAppPal::PrintLog("[APP]hit area: [%s]", HitAreaNameBody);
            _models[i]->StartRandomRightHandMotion("Right", PriorityForce);
        }
        else if (_models[i]->HitTest("Left", x, y))
        {
            if (DebugLogEnable) LAppPal::PrintLog("[APP]hit area: [%s]", HitAreaNameBody);
            _models[i]->StartRandomLeftHandMotion("Left", PriorityForce);
        }
        // Additions up to here
    }
}

Assign Responsible Parameters

As indicated in the update process, if parameters overlap among multiple motions to be played back, the update performed later takes precedence and the parameters are overwritten.
Therefore, when using multiple motion managers, it is necessary to decide which motion manager is responsible for which part of the body, i.e., which parameter operation.

The video above shows an example where the parameters of the left and right arms were updated by a lower priority motion manager (responsible for idle motion of the entire model) immediately after the higher priority motion manager (responsible for motion of the left and right arms) finished playback.
This is not a desirable expression if you want to keep parameters that are updated by a motion manager with a higher priority in the state immediately after the motion playback ends.
This phenomenon is seen when a reference value is set for a parameter that is not intended to be updated in the motion data played by a motion manager with a lower priority.
By separating the parameters to be updated for each motion data, the parameters that are updated by the motion manager with the higher priority can be maintained in the state immediately after the end of playback.


It is important to clearly define the specifications before creating the motion in order to avoid extensive modifications, whether the decision is to keep each parameter separate or assume overwriting.

Please let us know what you think about this article.