부위별로 다른 모션 관리하기

이 페이지에서는 오른손, 왼손 간에 다른 모션 관리를 실현하기 위해
LAppModel의 CubismMotionManager 보유를 늘려 LAppModel::Update에 통합하는 경우를 예로 들어,
관리나 모션 작성상의 주의점 등을 살펴봅니다.

CubismMotionManager 늘리기

모델에 관리자 추가

우선 프로그램 측의 구현을 살펴보겠습니다.
처음에 실시할 것은 모델에 관리 클래스를 추가하는 것입니다.
샘플 LAppModel에 CubismMotionManager를 추가합니다.
생성자와 소멸자에도 생성 및 해제 처리를 추가합니다.

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을 여러 번 실행하면 업데이트하는 파라미터가 겹칠 수 있습니다.
이 경우 나중에 실행된 UpdataMotion의 내용이 우선되므로 주의하십시오.

재생 시작

그리고 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);
        }
        // 여기까지 추가
    }
}

담당 파라미터의 배분

업데이트 처리에서도 나타낸 바와 같이, 재생하는 복수의 모션 간에 파라미터가 중복되면 나중에 실행된 갱신 내용이 우선되어 파라미터가 덮어쓰기됩니다.
이 때문에 모션 매니저를 복수 사용할 때에는 어느 모션 매니저가 어느 부위의 동작을 담당하는가?
나아가서는 어느 파라미터 조작을 담당할지 결정해 둘 필요가 있습니다.

위 동영상은 우선순위가 높은 모션 매니저(왼팔·오른팔 모션을 담당)의 재생이 종료된 직후에 우선순위가 낮은 모션 매니저(모델 전체의 아이들 모션을 담당)에 의해 왼팔·오른팔의 파라미터가 업데이트된 사례입니다.
우선순위가 높은 모션 매니저에 의해 업데이트되는 파라미터를, 모션 재생 종료 직후의 상태로 유지해 두고 싶은 경우에는 바람직한 표현이라고는 할 수 없습니다.
이 현상은 우선순위가 낮은 모션 매니저로 재생하는 모션 데이터 내에 업데이트 대상으로서는 의도하지 않은 파라미터에 기준값이 설정되어 있는 경우에 나타납니다.
이때 모션 데이터별로 업데이트하는 파라미터를 분리해 두는 것으로, 우선순위가 높은 모션 매니저에 의해 업데이트되는 파라미터를 재생 종료 직후의 상태로 유지해 둘 수 있습니다.

파라미터별로 분리해 둘지, 덮어쓰기를 전제로 할지,
방대한 수정을 하지 않기 위해서라도 모션을 작성하기 전에 사양을 명확히 정해 두는 것이 중요합니다.

이 기사가 도움이 되었나요?
아니요
이 기사에 관한 의견 및 요청사항을 보내 주시기 바랍니다.