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

업데이트: 2022/10/06

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

CubismMotionManager를 늘리는 이유

CubismMotionManager는 페이드에 대응하기 때문에 일시적으로 복수의 모션을 재생하는 기능이 있습니다.
그러나 재생 시작 API는 재생 중인 모션을 페이드 아웃시켜 종료하기 때문에,
병렬로 모션을 계속 재생할 수 있는 기능은 없습니다.

이 문제를 해결하기 위해 모션 관리자의 여러 인스턴스를 준비하고,
재생할 모션 매니저를 구분하여 병렬 모션 재생을 실현시킵니다.

CubismMotionManager 늘리기

모션 관리자 추가

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

public class LAppModel extends CubismUserModel{
    ...
    private CubismMotionManager rightArmMotionManager; // 추가
    private CubismMotionManager leftArmMotionManager; // 추가
}
public LAppModel(){
    ...
    rightArmMotionManager = new CubismMotionManager(); // 추가
    leftArmMotionManager = new CubismMotionManager(); // 추가
}

업데이트 처리

update의 처리에도 추가해 재생된 모션이 파라미터에 영향을 주도록 합니다.

public void update(){
    ...
    // 마지막으로 저장된 상태를 로드
    _model.loadParameters();

    // 모션 재생이 없으면 대기 모션 중에서 랜덤 재생
    if(_motionManager.isFinished()){
        startRandomMotion(LAppDefine.MotionGroup.IDLE.getId(), LAppDefine.Priority.IDLE.getPriority());
    } else{
        // 모션 업데이트
        isMotionUpdated = _motionManager.updateMotion(_model, deltaTimeSeconds);
    }
  
    isMotionUpdated |= rightArmMotionManager.updateMotion(_model, deltaTimeSeconds); // 추가
    isMotionUpdated |= leftArmMotionManager.updateMotion(_model, deltaTimeSeconds); // 추가

    // 모델 상태 저장
    _model.saveParameters();
}

LAppModel.update 내부에 추가할 때 _model.loadParameters();와 _model.saveParameters(); 사이에 updateMotion을 설치하십시오.
또한 updateMotion을 여러 번 실행하면 업데이트할 파라미터가 중복될 수 있습니다.
이 경우 나중에 실행된 updataMotion의 내용이 우선되므로 주의하십시오.

재생 시작

그리고 startMotion계의 함수를 복제, 개조하는 형태로 추가한 모션 매니저로 모션 재생을 지시할 수 있도록 해 둡니다.
startHandMotion 함수에서는 인수로 모션 관리자를 지정하는 점에 주의하십시오.
덧붙여 이하의 예에서는, .motion3.json으로 지정된 모션(preLoad 함수로 로드되는 모션)만 재생하도록 구현했습니다.

// startMotion으로 복제하여 생성
public int startHandMotion(
    CubismMotionManager targetManage,
    final String group,
    int number,
    int priority
) {
    if (priority == LAppDefine.Priority.FORCE.getPriority()) {
        targetManage.setReservationPriority(priority);
    } else if (!targetManage.reserveMotion(priority)) {
        if (_debugMode) {
            LAppPal.logger.print("Cannot start motion.");
            return -1;
        }
    }
    final String fileName = _modelSetting.getMotionFileName(group, number);

    // ex) idle_0
    String name = group + "_" + number;
    CubismMotion motion = (CubismMotion) _motions.get(name);

    if (motion == null) {
        // PreLoad로 읽지 않은 경우 재생하지 않음
        return -1;
    }
    if (_debugMode) {
        LAppPal.logger.print("Start motion: [" + group + "_" + number + "]");
    }
    
    return targetManage.startMotionPriority(motion, priority);
}

// startRandomMotion으로 복제하여 생성
public int startRandomRightHandMotion(final String group, int priority) {
    if(_modelSetting.getMotionCount(group) == 0){
        return -1;
    }

    Random random = new Random();
    int number = random.nextInt(Integer.MAX_VALUE) % _modelSetting.getMotionCount(group);
    return startHandMotion(rightArmMotionManager, group, number, priority);
}

// startRandomMotion으로 복제하여 생성
public int startRandomLeftHandMotion(final String group, int priority) {
    if(_modelSetting.getMotionCount(group) == 0){
        return -1;
    }

    Random random = new Random();
    int number = random.nextInt(Integer.MAX_VALUE) % _modelSetting.getMotionCount(group);
    return startHandMotion(leftArmMotionManager,group,number,priority);
}


모션 재생은 모델에 충돌 감지를 추가하여 탭으로 동작하도록 합니다.

public void onTap(float x, float y) {
    if (DEBUG_LOG_ENABLE) {
        CubismFramework.coreLogFunction("tap point: {" + x + ", y: " + y);
    }

    for (LAppModel model : _models) {
        // 머리를 탭하면 표정을 랜덤으로 재생
        if (model.hitTest(HitAreaName.HEAD.getId(), x, y)) {
            if (DEBUG_LOG_ENABLE) {
                LAppPal.logger.print("hit area: " + HitAreaName.HEAD.getId());
            }
            model.setRandomExpression();
        }
        // 몸을 탭하면 랜덤 모션 시작
        else if (model.hitTest(HitAreaName.BODY.getId(), x, y)) {
            if (DEBUG_LOG_ENABLE) {
                LAppPal.logger.print("hit area: " + HitAreaName.BODY.getId());
            }
            model.startRandomMotion(MotionGroup.TAP_BODY.getId(), Priority.NORMAL.getPriority(), _finishedMotion);
        }
      
        // 여기부터 추가
        else if (model.hitTest("Right", x, y)) {
            if (DEBUG_LOG_ENABLE) {
                LAppPal.logger.print("hit area: [" + HitAreaName.BODY.getId() + "]");
            }
            model.startRandomRightHandMotion("Right", Priority.FORCE.getPriority());
        } else if (model.hitTest("Left", x, y)) {
            if (DEBUG_LOG_ENABLE) {
                LAppPal.logger.print("hit area: [" + HitAreaName.BODY.getId() + "]");
            }  
            model.startRandomLeftHandMotion("Left", Priority.FORCE.getPriority());
        }
        // 여기까지 추가
    }
}

담당 파라미터의 배분

업데이트 처리에서도 나타낸 바와 같이, 재생하는 복수의 모션 간에 파라미터가 중복되면 나중에 실행된 갱신 내용이 우선되어 파라미터가 덮어쓰기됩니다.
따라서 모션 매니저를 여러 개 사용할 때는 어떤 모션 매니저가 어떤 부위의 동작을 담당할지, 그리고 어떤 파라미터 조작을 담당할지 결정해야 합니다.

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

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

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