부위별로 다른 모션 관리하기 (Web)
업데이트: 2020/01/10
이 페이지에서는 오른손, 왼손 간에 다른 모션을 병렬로 관리하는 것을 실현하기 위해
LAppModel.cubismMotionManager의 보유 수를 늘려 LAppModel.update에 통합하는 경우를 예로 들어,
관리나 모션 작성상의 주의점 등을 살펴봅니다.
CubismMotionManager를 늘리는 이유
CubismMotionManager는 페이드에 대응하기 때문에 일시적으로 복수의 모션을 재생하는 기능이 있습니다.
그러나 재생 시작 API는 재생 중인 모션을 페이드 아웃시켜 종료하기 때문에,
병렬로 모션을 계속 재생할 수 있는 기능은 없습니다.
이 문제를 해결하기 위해 모션 관리자의 여러 인스턴스를 준비하고,
재생할 모션 매니저를 구분하여 병렬 모션 재생을 실현시킵니다.
CubismMotionManager 늘리기
모션 관리자 추가
우선 프로그램 측의 구현을 살펴보겠습니다.
첫 번째로 할 것은 모델에 모션 관리 클래스를 추가하는 것입니다.
샘플 LAppModel에 CubismMotionManager를 추가합니다.
생성자와 release 함수에도 생성 및 해제 처리를 추가합니다.
export class LAppModel extends CubismUserModel { /*생략*/ /** * 생성자 */ public constructor() { super(); /*생략*/ this._rightArmMotionManager = new CubismMotionManager(); // <<<추가! this._leftArmMotionManager = new CubismMotionManager(); // <<<추가! } _rightArmMotionManager: CubismMotionManager; /// <<< 추가! _leftArmMotionManager: CubismMotionManager; /// <<< 추가! }
업데이트 처리
Update의 처리에도 추가해 재생된 모션이 파라미터에 영향을 주도록 합니다.
public update(): void { /*생략*/ //-------------------------------------------------------------------------- this._model.loadParameters(); // 마지막으로 저장된 상태를 로드 if(this._motionManager.isFinished()) { // 모션 재생이 없으면 대기 모션 중에서 랜덤 재생 this.startRandomMotion(LAppDefine.MotionGroupIdle, LAppDefine.PriorityIdle); } else { motionUpdated = this._motionManager.updateMotion(this._model, deltaTimeSeconds); // 모션 업데이트 } motionUpdated ! = this._rightArmMotionManager.updateMotion(this._model, deltaTimeSeconds); // <추가 motionUpdated ! = this._leftArmMotionManager.updateMotion(this._model, deltaTimeSeconds); // <추가 this._model.saveParameters(); // 상태 저장 //-------------------------------------------------------------------------- /*생략*/ }
LAppModel.update 내부에 추가할 때 _model.loadParameters()와 _model.saveParameters() 사이에
updateMotion을 설치하십시오.
또한 동시에 여러 모션을 재생하면 두 개 이상의 모션에서 동일한 파라미터의 값이 업데이트될 수 있습니다.
이 경우 나중에 실행된 updateMotion의 내용이 우선됩니다.
재생 시작
그리고 startMotion계의 멤버 함수를 복제, 개조하는 형태로 추가한 모션 매니저로 모션 재생을 지시할 수 있도록 해 둡니다.
startHandMotion 함수에서는 인수로 모션 관리자를 지정하는 점에 주의하십시오.
덧붙여 이하의 예에서는, .motion3.json으로 지정된 모션(preLoad 함수로 로드되는 모션)만 재생하도록 구현했습니다.
// startMotion으로 복제하여 생성 public startHandMotion(targetManage: CubismMotionManager, group: string, no: number, priority: number): CubismMotionQueueEntryHandle { if(priority == LAppDefine.PriorityForce) { targetManage.setReservePriority(priority); } else if(!targetManage.reserveMotion(priority)) { if(this._debugMode) { LAppPal.printLog("[APP]can't start motion."); } return InvalidMotionQueueEntryHandleValue; } const fileName: string = this._modelSetting.getMotionFileName(group, no); //ex) idle_0 let name: string = Utiles.CubismString.getFormatedString("{0}_{1}", group, no); let motion: CubismMotion = this._motions[name]; let autoDelete = false; if(motion == null) { // preLoad로 읽지 않은 경우 재생하지 않음 return InvalidMotionQueueEntryHandleValue; } if(this._debugMode) { LAppPal.printLog("[APP]start motion: [{0}_{1}]", group. no); } return targetmanage.startMotionPriority(motion, autoDelete, priority); } // startRandomMotion으로 복제하여 생성 public startRandomRightHandMotion(group: string, priority: number): CubismMotionQueueEntryHandle { if(this._modelSetting.getMotionCount(group) == 0) { return InvalidMotionQueueEntryHandleValue; } let no: number = Math.floor(Math.random() * this._modelSetting.getMotionCount(group)); return this.startHandMotion(this._rightArmMotionManager, group, no, priority); } // startRandomMotion으로 복제하여 생성 public startRandomLeftHandMotion(group: string, priority: number): CubismMotionQueueEntryHandle { if(this._modelSetting.getMotionCount(group) == 0) { return InvalidMotionQueueEntryHandleValue; } let no: number = Math.floor(Math.random() * this._modelSetting.getMotionCount(group)); return this.startHandMotion(this._leftArmMotionManager, group, no, priority); }
모션 재생은 모델에 충돌 감지를 추가하여 클릭에 의해 동작하도록 합니다.
public onTap(x: number, y: number): void { if(LAppDefine.DebugLogEnable) { LAppPal.printLog("[APP]tap point: {x: {0} y: {1}}", x.toFixed(2), y.toFixed(2)); } for(let i: number = 0; i < this._models.getSize(); i++) { if(this._models.at(i).hitTest(LAppDefine.HitAreaNameHead, x, y)) { if(LAppDefine.DebugLogEnable) { LAppPal.printLog("[APP]hit area: [{0}]", LAppDefine.HitAreaNameHead); } this._models.at(i).setRandomExpression(); } else if(this._models.at(i).hitTest(LAppDefine.HitAreaNameBody, x, y)) { if(LAppDefine.DebugLogEnable) { LAppPal.printLog("[APP]hit area: [{0}]", LAppDefine.HitAreaNameBody); } this._models.at(i).startRandomMotion(LAppDefine.MotionGroupTapBody, LAppDefine.PriorityNormal); } // 여기부터 추가 else if(this._models.at(i).hitTest("Right", x, y)) { if(LAppDefine.DebugLogEnable) { LAppPal.printLog("[APP]hit area: [{0}]", LAppDefine.HitAreaNameBody); } this._models.at(i).startRandomRightHandMotion("Right", LAppDefine.PriorityForce); } else if(this._models.at(i).hitTest("Left", x, y)) { if(LAppDefine.DebugLogEnable) { LAppPal.printLog("[APP]hit area: [{0}], LAppDefine.HitAreaNameBody"); } this._models.at(i).startRandomLeftHandMotion("Left", LAppDefine.PriorityForce); } // 여기까지 추가 } }
담당 파라미터의 배분
업데이트 처리에서도 나타낸 바와 같이, 재생하는 복수의 모션 간에 파라미터가 중복되면 나중에 실행된 갱신 내용이 우선되어 파라미터가 덮어쓰기됩니다.
이 때문에 모션 매니저를 복수 사용할 때에는 어느 모션 매니저가 어느 부위의 동작을 담당하는가?
나아가서는 어느 파라미터 조작을 담당할지 결정해 둘 필요가 있습니다.
위 동영상은 우선순위가 높은 모션 매니저(왼팔·오른팔 모션을 담당)의 재생이 종료된 직후에 우선순위가 낮은 모션 매니저(모델 전체의 아이들 모션을 담당)에 의해 왼팔·오른팔의 파라미터가 업데이트된 사례입니다.
우선순위가 높은 모션 매니저에 의해 업데이트되는 파라미터를, 모션 재생 종료 직후의 상태로 유지해 두고 싶은 경우에는 바람직한 표현이라고는 할 수 없습니다.
이 현상은 우선순위가 낮은 모션 매니저로 재생하는 모션 데이터 내에 업데이트 대상으로서는 의도하지 않은 파라미터에 기준값이 설정되어 있는 경우에 나타납니다.
이때 모션 데이터별로 업데이트하는 파라미터를 분리해 두는 것으로, 우선순위가 높은 모션 매니저에 의해 업데이트되는 파라미터를 재생 종료 직후의 상태로 유지해 둘 수 있습니다.
파라미터별로 분리해 둘지, 덮어쓰기를 전제로 할지,
방대한 수정을 하지 않기 위해서라도 모션을 작성하기 전에 사양을 명확히 정해 두는 것이 중요합니다.