모션에 대해
업데이트: 2023/01/26
모션을 재생하기 전에 필요한 클래스
1. 모션 데이터를 유지해 모델에 대해서 조작하는 클래스
a. 인스턴스 생성(motion3.json 파일 불러오기)
b. 모션 재생 방법에 대한 설정
c. 인스턴스 파기
2. 모션 관리 클래스
a. 인스턴스 생성
b. 모션 재생
c. 모델의 파라미터 업데이트
d. 모션 종료
e. 사용자 트리거 수신
1-a. 모션 인스턴스 생성(.motion3.json 파일 불러오기)
모션 재생에는 ACubsimMotion 클래스에서 파생된 CubismMotion 클래스를 사용합니다.
모션에 이용하는 데이터는 확장자가 「.motion3.json」인 모션 파일입니다.
Cubism2.1의 「.mtn」은 사용할 수 없습니다.
이 .motion3.json 파일 불러오기에는 다음 함수 중 하나를 사용합니다.
– Native(C++)의 CubismMotion::Create 함수
– Web(TypeScript)의 CubismMotion.create 함수
– Java의 CubismMotion.create 함수
json 파일을 한 번 로드한 다음 버퍼와 크기를 전달하여 로드합니다.
SDK for Java 의 경우는 버퍼만 메소드에 전달합니다.
// C++ csmString path = "example.motion3.json"; csmByte* buffer; csmSizeInt size; buffer = CreateBuffer(path.GetRawString(), &size); CubismMotion* motion = CubismMotion::Create(buffer, size);
// TypeScript let path: string = "example.motion3.json"; fetch(path).then( (response) => { return response.arrayBuffer(); } ).then( (arrayBuffer) => { let buffer: ArrayBuffer = arrayBuffer; let size = buffer.byteLength; let motion: CubismMotion = CubismMotion.create(buffer, size); } );
// Java String path = "example.motion3.json"; byte[] buffer = createBuffer(path); CubismMotion motion = loadMotion(buffer);
1-b. 모션 파일별 재생 방법 설정
모션에는 주로 다음 항목을 설정합니다.
이 설정은 수행하지 않고도 재생할 수 있습니다.
모션 시작 시의 페이드 인 시간:
설정은 다음 함수 중 하나로 수행됩니다.
– Native(C++)의 ACubismMotion::SetFadeInTime 함수
– Web(TypeScript)의 ACubismMotion.setFadeInTime 함수
– Java의 ACubismMotion.setFadeInTime 함수
다음 중 하나의 함수로 취득할 수 있습니다.
– Native(C++)의 ACubismMotion::GetFadeInTime 함수
– Web(TypeScript)의 ACubismMotion.getFadeInTime 함수
– Java의 ACubismMotion.getFadeInTime 함수
페이드 인 시간을 초 단위로 지정합니다.
모션 종료 시의 페이드 아웃 시간:
설정은 다음 함수 중 하나로 수행됩니다.
– Native(C++)의 ACubismMotion::SetFadeOutTime 함수
– Web(TypeScript)의 ACubismMotion.setFadeOutTime 함수
– Java의 ACubismMotion.setFadeOutTime 함수
다음 중 하나의 함수로 취득할 수 있습니다.
– Native(C++)의 ACubismMotion::GetFadeOutTime 함수
– Web(TypeScript)의 ACubismMotion.getFadeOutTime 함수
– Java의 ACubismMotion.getFadeOutTime 함수
페이드 아웃 시간을 초 단위로 지정합니다.
루프 재생 ON/OFF:
설정은 다음 함수 중 하나로 수행됩니다.
– Native(C++)의 void CubismMotion::IsLoop(csmBool loop) 함수
– Web(TypeScript)의 CubismMotion.setIsLoop 함수
– Java의 CubismMotion.isLoop 함수
다음 함수 중 하나로 현재 값을 얻을 수 있습니다.
– Native(C++)의 csmBool CubismMotion::IsLoop() 함수
– Web(TypeScript)의 CubismMotion.isLoop 함수
– Java의 CubismMotion.isLoop 함수
true를 설정하면 종료 시 처음부터 재생합니다.
다른 모션이 인터럽트되거나 종료 명령이 호출될 때까지 무한 반복 재생합니다.
설정하지 않는 경우의 초기값은 false(루프하지 않는다)입니다.
※Framework의 루프 동작은 에디터의 루프 동작과 완전한 일치를 보증하지 않습니다.
※현재 Animator는 motion3.json 파일에 루프 설정을 반영시킬 수 없기 때문에,
Framework는 motion3.json 파일의 루프 설정을 무시하고 false를 설정합니다.
설정 예(※이 설정은 모션 재생 전에 수행하십시오)
// C++ motion->SetFadeInTime( 1.0f ); motion->SetFadeOutTime( 1.0f ); motion->IsLoop( true );
// TypeScript motion.setFadeInTime(1.0); motion.setFadeOutTime(1.0); motion.setIsLoop(true);
// Java motion.setFadeInTime(1.0f); motion.setFadeOutTime(1.0f); motion.isLoop(true);
1-c. 인스턴스 파기
// C++ ACubismMotion::Delete(motion);
Cubism SDK for Web 및 Java에서는 명시적으로 파기할 필요가 없습니다.
모션의 페이드값을 파일로 설정하는 방법에 대해서는
A, .motion3.json 파일에 전체 값으로 설정하는 방법 (전체 페이드)
B, .motion3.json 파일에 파라미터 개별 값으로 설정하는 방법(파라미터 페이드)
C, .model3.json 파일에 전체 값으로 설정하는 방법
이 세 가지가 있으며 우선순위는 B, C, A 순으로 적용됩니다.
지정하지 않으면 기본값인 1초가 설정됩니다.
2-a. 모션 관리 클래스의 인스턴스 생성
전항에서 작성한 CubismMotion 클래스의 인스턴스를 모델에 적용하기(애니메이션시키기) 위해서는 CubismMotionManager 클래스를 사용합니다.
// C++ CubismMotionManager* motionManager = CSM_NEW CubismMotionManager();
// TypeScript let motionManager: CubismMotionManager = new CubismMotionManager();
// Java CubismMotionManager motionManager = new CubismMotionManager();
2-b. 모션 재생
모션 재생에는 다음 중 하나의 함수를 사용합니다.
– Native(C++)의 CubismMotionManager::StartMotionPriority 함수
– Web(TypeScript)의 CubismMotionManager.startMotionPriority 함수
– Java의 CubismMotionManager.startMotionPriority 함수
첫 번째 인수: ACubismMotion 인스턴스, 모션 데이터
모션 데이터의 인스턴스를 전달합니다.
이 인수는 ACubismMotion의 파생 인스턴스인 CubismMotion 및 CubismExpressionMotion의 인스턴스를 지정할 수 있습니다.
일반적으로 하나의 모션 매니저가 취급하는 인스턴스 타입은 한쪽만 지정합니다.
두 번째 인수: Boolean, 자동 삭제 플래그
재생이 끝날 때 자동으로 모션 데이터를 삭제할지 여부를 나타내는 플래그입니다.
한 번만 재생되는 모션에 사용합니다.
세 번째 인수: Int, 우선순위
CubismMotionManager에서 관리하는 재생 시 우선순위 설정을 지정합니다.
우선순위에 의한 재생 거부는 CubismMotionManager의 외부에서 실시할 필요가 있습니다.
우선순위에 관해서는 페이지 하단에 있는 CubismMotionManager 항목을 참조하십시오.
// C++ csmBool autoDelete = true; csmInt32 priority = PriorityNormal;// 2 motionManager->StartMotionPriority( motion, autoDelete, priority);
// TypeScript let autoDelete: boolean = true; let priority: number = priorityNormal; // 2 motionManager.startmotionPriority(motion, autoDelete, priority);
// Java boolean autoDelete = true; int priority = LAppDefine.Priority.NORMAL; // 2 motionManager.startMotionPriority(motion, autoDelete, priority);
모션을 여러 번 동시에 재생하려면 CubismMotionManager 인스턴스를 늘리십시오.
이것은 오른손과 왼손의 모션을 따로 제어하는 경우 등에 사용할 수 있습니다.
모션을 동시에 재생하려면 가능한 한 동일한 파라미터를 설정하지 마십시오.
그 경우 마지막으로 업데이트한 모션의 파라미터가 유효하게 됩니다.
또한 페이드가 깔끔하게 걸리지 않는 경우가 있습니다.
2-c. 모델의 파라미터 업데이트
– Native(C++)의 CubismMotionManager::StartMotionPriority 함수
– Web(TypeScript)의 CubismMotionManager.startMotionPriority 함수
– Java의 CubismMotionManager.startMotionPriority 함수
이상의 함수로 모션을 재생한 것만으로는 모델은 애니메이션하지 않습니다.
현재 재생 중인 모션의 파라미터를 모델로 설정하려면 그릴 때마다 다음 함수 중 하나를 호출합니다.
– Native(C++)의 CubismMotionManager::UpdateMotion 함수
– Web(TypeScript)의 CubismMotionManager.updateMotion 함수
– Java의 CubismMotionManager.updateMotion 함수
첫 번째 인수: CubismModel 인스턴스, 모션을 적용하는 모델
파라미터 정보의 취득, 조작에만 사용됩니다.
두 번째 인수: Float, 마지막 실행 시로부터의 차분 시간
Update 등으로 계산되는 차분 시간을 입력합니다.
Float 실수로 초 단위로 입력합니다. 60FPS로 실행하려면 1/60초에 0.016을 입력하고, 30FPS라면 1/30초에 0.03을 입력합니다.
// C++ motionUpdated = motionManager->UpdateMotion(model, deltaTimeSeconds);
// TypeScript motionUpdated = motionManager.updateMoiton(model, deltaTimeSeconds);
// Java isMotionUpdated = motionManager.updateMotion(model, deltaTimeSeconds);
입력하는 deltaTimeSeconds를 조정하여 슬로우, 정지, 빨리 감기를 할 수 있습니다.
단, 마이너스의 값을 사용한 역재생은 고려 범위 외로 설계되어 있습니다.
// C++ const csmFloat32 playSpeed = pow(2, (csmFloat32)_motionSpeed / 10.0); motionUpdated = _motionManager->UpdateMotion(_model, deltaTimeSeconds * playSpeed); // 모션 업데이트
// TypeScript let playSpeed:number = Math.pow(2, _motionSpeed / 10.0); motionUpdated = motionManager.updateMoiton(model, deltaTimeSeconds);
// Java final float playSpeed = Math.pow(2, (float)motionSpeed / 10.0f); isMotionUpdated = motionManager.updateMotion(model, deltaTimeSeconds * playSpeed); // 모션 업데이트
2-d. 모션 종료
모션은 재생 시간이 지나면 자동으로 종료되지만
임의의 타이밍에 종료하고 싶을 때는 다음 중 하나의 함수를 사용합니다.
– Native(C++)의 CubismMotionQueueManager::StopAllMotions 함수
– Web(TypeScript)의 CubismMotionQueueManager.stopAllMotions 함수
– Java의 CubismMotionQueueManager.stopAllMotions 함수
페이드 도중 등에 동시에 두 개 이상의 모션을 재생하고 있는 경우 모두 종료합니다.
// C++ motionManager->StopAllMotions();
// TypeScript motionManager.stopAllMotions();
// Java motionManager.stopAllMotions();
2-e. 이벤트 수신
모션에 설정된 「이벤트」가 재생되었을 때에, CubismMotionQueueManager에 이하의 함수 중 하나로 등록한 콜백으로 호출을 받을 수 있습니다.
– Native(C++)의 SetUserTriggerCallback 함수
– Web(TypeScript)의 setUserTriggerCallback 함수
– Java의 setUserTriggerCallback 함수
등록할 수 있는 콜백은 하나뿐입니다.
인스턴스의 함수를 호출하고 싶을 때는 static인 함수를 인스턴스의 포인터와 함께 등록해,
static 함수에서 등록해 둔 인스턴스의 포인터를 사용해 목적의 함수를 호출하도록 해 주세요.
복수의 동작을 시키고 싶을 때에는 하나의 콜백부터 순서대로 호출하도록 해 주세요.
//C++ /** * @brief 사용자 트리거의 콜백 함수 정의 * * 사용자 트리거 콜백에 등록할 수 있는 함수 유형 정보 * * @param[in] caller 발화된 사용자 트리거를 재생시킨 CubismMotionQueueManager * @param[in] userTriggerValue 발화된 사용자 트리거의 문자열 데이터 * @param[in] customData 콜백으로 반환되는 등록 시에 지정된 데이터 */ typedef void(*UserTriggerFunction)(const CubismMotionQueueManager* caller, const csmString& userTriggerValue, void* customData);
// C++ class SampleClass { public: void UserTriggerEventFired(const csmString& userTriggerValue) { //처리 } static void SampleCallback( const CubismMotionQueueManager* caller, const csmString& userTriggerValue, void* customData) { SampleClass* sample = reinterpret_cast<SampleClass*>(customData); if (sample ! = NULL) { sample->UserTriggerEventFired(userTriggerValue); } } }; SampleClass sampleA; motionManager->SetUserTriggerCallback(SampleClass::SampleCallback, &sampleA);
// TypeScript /** * 사용자 트리거 콜백에 등록할 수 있는 함수 유형 정보 * * @param caller 발화된 사용자 트리거를 재생시킨 CubismMotionQueueManager * @param userTriggerValue 발화된 사용자 트리거의 문자열 데이터 * @param customData 콜백으로 반환되는 등록 시에 지정된 데이터 */ export interface UserTriggerFunction { ( caller: CubismmotionQueueManager, userTriggerValue: string, customData: any ): void; }
// TypeScript class SampleClass { public userTriggerEventFired(userTriggerValue): void { // 처리 } public static sampleCallback(caller: CubismMotionQueueManager, userTriggerValue: string, customData: any): void { let sample: SampleClass = <SampleClass>customData; if(sample ! = null) { sample.userTriggerEventFired(userTriggerValue); } } }; let sampleA: SampleClass = new SampleClass(); motionManager.setUserTriggerCallback(SampleClass.sampleCallback, sampleA);
// Java public interface ICubismMotionEventFunction { public void apply( CubismMotionQueueManager caller, String eventValue, Object customData); }
// Java public class SampleClass { public void userTriggerEventFired(final String userTriggerValue) { // 처리 } public static class SampleCallback implements ICubismMotionEventFunction { @Override public void apply( CubismMotionQueueManager caller, String eventValue, Object customData) { if (customData ! = null) { ((CubismUserModel) customData).motionEventFired(eventValue); } } }
CubismUserModel 클래스에는 표준으로 이 메커니즘이 임베디드 되어 있습니다.
– Native(C++)의 (CubismUserModel::MotionEventFired 함수, CubismUserModel::CubismDefaultMotionEventCallback 함수)
– Web(TypeScript)의 (CubismUserModel.cubismDefaultMotionEventCallback 함수)
-Java의 (CubismUserModel.motionEventFired 함수, CubismUserModel.cubismDefaultMotionEventCallback 변수)
모션 자동 삭제
Native(C++)의 CubismMotionQueueManager::StartMotion 함수 또는 Web(TypeScript)의 CubismMotionQueueManager.startMotion 함수를 호출할 때 두 번째 인수의 autoDelete에 true를 넣으면,
모션 재생이 끝나면 CubismMotionQueueEntry의 삭제와 동시에 모션도 삭제됩니다.
한 번만 재생되는 파일에 사용되는 것을 상정하고 있습니다.
// C++ csmBool CubismMotionQueueManager::DoUpdateMotion(CubismModel* model, csmFloat32 userTimeSeconds) { csmBool updated = false; // ------- 처리 수행 -------- // 이미 모션이 있으면 종료 플래그를 설정 for (csmVector<CubismMotionQueueEntry*>::iterator ite = _motions.Begin(); ite ! = _motions.End();) { CubismMotionQueueEntry* motionQueueEntry = *ite; /*생략*/ // ----- 종료된 처리가 있으면 삭제 ------ if (motionQueueEntry->IsFinished()) { CSM_DELETE(motionQueueEntry); ite = _motions.Erase(ite); // 삭제 } else { ++ite; } } return updated; }
// C++ CubismMotionQueueEntry::~CubismMotionQueueEntry() { if (_autoDelete && _motion) { ACubismMotion::Delete(_motion); } }
// TypeScript /** * 모션을 업데이트하여 모델에 파라미터값을 반영한다. * * @param model 대상 모델 * @param userTimeSeconds 델타 시간의 누적값 [초] * @returntrue모델에 대한 파라미터값 반영 있음 * @return false 모델에 대한 파라미터값 반영 없음(모션 변화 없음) */ public doUpdateMotion(model: CubismModel, userTimeSeconds: number): boolean { let updated: boolean = false; // ------- 처리 수행 -------- // 이미 모션이 있으면 종료 플래그를 설정 for(let ite: iterator<CubismMotionQueueEntry> = this._motions.begin(); ite.notEqual(this._motions.end());) { let motionQueueEntry: CubismMotionQueueEntry = ite.ptr(); /*생략*/ // ------ 종료된 처리가 있으면 삭제 ------ if(motionQueueEntry.isFinished()) { motionQueueEntry.release(); motionQueueEntry = void 0; motionQueueEntry = null; ite = this._motions.erase(ite); // 삭제 } else { ite.preIncrement(); } } return updated; }
// TypeScript /** * 소멸자 상당의 처리 */ public release(): void { if(this._autoDelete && this._motion) { ACubismMotion.delete(this._motion); // } }
SDK for Java에서는 CubismMotionQueueEntry에 대한 참조가 없어진 시점에 그 필드인 모션 인스턴스도 자동으로 삭제되기 때문에, 모션 자동 삭제 플래그는 불필요합니다.
CubismMotionQueueManager 클래스와 CubismMotionManager 클래스
모션의 관리 클래스에는 CubismMotionQueueManager 클래스와
CubismMotionQueueManager 클래스를 상속한 CubismMotionManager 클래스가 있습니다.
CubismMotionQueueManager
CubismMotionQueueManager는 모션값의 적용 정도에 페이드가 적용된 전환을 담당합니다.
다음 함수 중 하나를 사용하여 모션 재생을 추가할 때 이미 재생 중인 모션 그룹에 대해 StartFadeout으로 종료 시간을 앞당깁니다.
– Native(C++)의 LAppModel::StartMotion 함수
– Web(TypeScript)의 LAppModel.startMotion 함수
– Java의 LAppModel.startMotion 함수
이 조작에 의해 재생 중의 모션이 페이드로 새로운 모션과의 전환을 실현합니다.
// C++ CubismMotionQueueEntryHandle CubismMotionQueueManager::StartMotion(ACubismMotion* motion, csmBool autoDelete, csmFloat32 userTimeSeconds) { if (motion == NULL) { return InvalidMotionQueueEntryHandleValue; } CubismMotionQueueEntry* motionQueueEntry = NULL; // 이미 모션이 있으면 종료 플래그를 설정 for (csmUint32 i = 0; i < _motions.GetSize(); ++i) { motionQueueEntry = _motions.At(i); if (motionQueueEntry == NULL) { continue; } motionQueueEntry->StartFadeout(motionQueueEntry->_motion->GetFadeOutTime(), userTimeSeconds); // 페이드 아웃을 시작하고 종료 } motionQueueEntry = CSM_NEW CubismMotionQueueEntry(); // 종료 시 파기 motionQueueEntry->_autoDelete = autoDelete; motionQueueEntry->_motion = motion; _motions.PushBack(motionQueueEntry, false); return motionQueueEntry->_motionQueueEntryHandle; }
// TypeScript /** * 지정한 모션 시작 * * 지정된 모션을 시작한다. 동일한 유형의 모션이 이미 있으면 기존 모션에 종료 플래그를 설정하고 페이드 아웃을 시작하게 한다. * * @param motion 시작하는 모션 * @param autoDelete 재생이 끝난 모션 인스턴스를 삭제하면 true * @param userTimeSeconds 델타 시간의 누적값 [초] * @return 시작된 모션의 식별 번호를 반환한다. 개별 모션이 종료했는지 아닌지를 판정하는 IsFinished()의 인수로 사용한다. 시작할 수 없을 때는 「-1」 */ public startMotion(motion: ACubismMotion, autoDelete: boolean, userTimeSeconds: number) : CubismMotionQueueEntryHandle { if(motion == null) { return InvalidMotionQueueEntryHandleValue; } let motionQueueEntry: CubismMotionQueueEntry = null; // 이미 모션이 있으면 종료 플래그를 설정 for(let i: number = 0; i < this._motions.getSize(); ++i) { motionQueueEntry = this._motions.at(i); if(motionQueueEntry == null) { continue; } motionQueueEntry.startFadeout(motionQueueEntry._motion.getFadeOutTime(), userTimeSeconds); // 페이드 아웃을 시작하고 종료 } motionQueueEntry = new CubismMotionQueueEntry(); // 종료 시 파기 motionQueueEntry._autoDelete = autoDelete; motionQueueEntry._motion = motion; this._motions.pushBack(motionQueueEntry); return motionQueueEntry._motionQueueEntryHandle; }
// Java public int startMotion(ACubismMotion motion, float userTimeSeconds) { if (motion == null) { return -1; } // 이미 모션이 있으면 종료 플래그를 설정 for (CubismMotionQueueEntry entry : motions){ if (entry == null) { continue; } entry.setFadeOut(entry.getMotion().getFadeOutTime()); } CubismMotionQueueEntry motionQueueEntry = new CubismMotionQueueEntry(); motionQueueEntry.setMotion(motion); motions.add(motionQueueEntry); return System.identityHashCode(motionQueueEntry); }
CubismMotionManager
CubismMotionManager 클래스는 재생할 모션의 우선순위를 저장하는 기능과 지금부터 재생할 예정의 우선순위를 정수로 등록하는 기능이 있습니다.
이 기록된 우선순위와 비교하여 우선순위가 낮은 모션 재생을 규제하는 기능 작성을 상정하고 있습니다.
재생 규제를 하는 부분에 관해서는 CubismMotionManager의 외부에 준비할 필요가 있습니다.
다음 함수 중 하나는 비동기 스레드에서의 재생에 대응합니다.
– Native(C++)의 LAppModel::StartMotion 함수
– Web(TypeScript)의 LAppModel.startMotion 함수
– Java의 LAppModel.startMotion 함수
함수의 첫머리에 재생 우선순위가 SetReservePriority 또는 ReserveMotion에 의해 등록됩니다.
그런 다음 로드가 수행되지만 비동기로 이 함수를 호출하는 경우 첫머리에 우선순위가 등록되므로
로드하는 동안 다른 스레드에서 다른 저우선도 재생은 규제되는 메커니즘입니다.
재생 종료 시에 우선도가 0으로 설정되는 것은 고정이며, 그 외의 제어는 외부에 맡겨집니다.
// C++ CubismMotionQueueEntryHandle LAppModel::StartMotion(const csmChar* group, csmInt32 no, csmInt32 priority) { if (priority == PriorityForce) { _motionManager->SetReservePriority(priority); } else if (! _motionManager->ReserveMotion(priority)) { if (_debugMode) { LAppPal::PrintLog("[APP]can't start motion."); } return InvalidMotionQueueEntryHandleValue; } /*모션 데이터 준비 부분 생략*/ return _motionManager->StartMotionPriority(motion, autoDelete, priority); }
// TypeScript /** * 인수로 지정한 모션의 재생을 개시한다 * @param group 모션 그룹 이름 * @param no 그룹 내의 번호 * @param priority 우선순위 * @return 시작된 모션의 식별 번호를 반환한다. * 개별 모션이 종료했는지 아닌지를 판정하는 isFinished()의 인수로 사용한다. * 시작할 수 없을 때는 [-1] */ public startMotion(group: string, no: number, priority: number) : CubismMotionQueueEntryHandle { if(priority == LAppDefine.PriorityForce) { this._motionManager.setReservePriority(priority); } else if(!this._motionManager.reserveMotion(priority)) { if(this._debugMode) { LAppPal.printLog("[APP]can't start motion."); } return InvalidMotionQueueEntryHandleValue; } /*모션 데이터 준비 부분 생략*/ return this._motionManager.startMotionPriority(motion, autoDelete, priority); }
// Java public int startMotion(final String group, int number, int priority, IFinishedMotionCallback onFinishedMotionHandler ) { if (priority == LAppDefine.Priority.FORCE.getPriority()) { motionManager.setReservationPriority(priority); } else if (!motionManager.reserveMotion(priority)) { if (debugMode) { LAppPal.printLog("Cannot start motion."); } return -1; } /*모션 데이터 준비 부분 생략*/ if (motionManager.startMotionPriority(motion, priority) ! = -1) { return motionManager.startMotionPriority(motion, priority); } return -1; }