对各部位执行不同的动态管理

在本页面上,以为了实装左右手不同的动态管理,
增加LAppModel的CubismMotionManager的占有,并嵌入LAppModel::Update的情况为例,
看一看管理和创建动态时的注意事项等。

增加CubismMotionManager

将管理器追加到模型

首先,我们看一下程序端的实装。
首先要做的是向您的模型追加一个管理类。
我们将CubismMotionManager追加到范例LAppModel。
也向构造函数和析构函数追加生成和释放处理。

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

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

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

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

更新处理

还要追加到Update处理中,使播放的动态影响参数。

void LAppModel::Update()
{
 ・
 ・
 ・
    //-----------------------------------------------------------------
    _model->LoadParameters(); // 读取上次保存的状态
    if (_motionManager->IsFinished())
    {
        // 如果没有动态播放,则从待机动态开始随机播放
        StartRandomMotion(MotionGroupIdle, PriorityIdle);
    }
    else
    {
        motionUpdated = _motionManager->UpdateMotion(_model, deltaTimeSeconds); // 更新动态
    }
    motionUpdated |= _rightArmMotionManager->UpdateMotion(_model, deltaTimeSeconds); /// <追加!
    motionUpdated |= _leftArmMotionManager->UpdateMotion(_model, deltaTimeSeconds); /// <追加!
    _model->SaveParameters(); // 保存状态
    //-----------------------------------------------------------------
 ・
 ・
 ・
}

在LAppModel::Update内部追加时,请在_model->LoadParameters();和_model->SaveParameters();之间设置UpdateMotion。

另外,如果多次执行UpdateMotion,可能会导致要更新的参数重叠。
请注意,在这种情况下,后面执行的UploadMotion内容将优先。

开始播放

然后,可以通过复制和修改StartMotion系统的成员函数的形式,
使用追加的动态管理器来指示动态播放。

请注意,StartHandMotion函数将动态管理器作为参数指定。
在以下示例中,仅播放.motion3.json指定的动态(由PreLoad函数读取的动态)。

// 通过从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)
    {
        // 如果没有被 PreLoad读取则不播放
        return InvalidMotionQueueEntryHandleValue;
    }
    
    if (_debugMode)LAppPal::PrintLog("[APP]start motion: [%s_%d]", group, no);
    return  targetManage->StartMotionPriority(motion, autoDelete, priority);
}

// 通过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);
}

// 通过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);
}

动态播放为模型追加了重叠检测,使其通过单击动作。

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);
        }
        // 从这里追加
        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);
        }
        // 追加到这里
    }
}

负责参数的分布

如更新处理所示,如果多个要播放的动态之间的参数重复,则后面执行的更新内容将被优先执行,参数将被覆盖。
因此,在使用多个动态管理器时,需要决定哪个动态管理器负责哪个部位的动作、
以及负责哪个参数操作。

上述视频中,刚刚退出播放优先顺序较高的动态管理器(负责左臂和右臂的动态)后,通过优先顺序较低的动态管理器(负责模型全体闲置动态)更新了左臂和右臂的参数。
如果您希望将优先顺序较高的动态管理器更新的参数保持在刚刚退出动态播放后的状态,这不是一个理想的表达方式。
当优先顺序较低的动态管理器播放的动态数据中不打算更新的参数设置参考值时,就会出现这种现象。
此时,通过将要更新的参数对各动态数据进行分开,可以将优先顺序较高的动态管理器更新的参数保持在刚刚退出播放后的状态。

针对是否分开各参数或以覆盖为前提,
在创建动态之前明确定义规范非常重要,以免进行巨大的修正。

请问这篇文章对您有帮助吗?
关于本报道,敬请提出您的意见及要求。