모션 정보
업데이트: 2024/11/28
모션을 재생하기 전에 필요한 클래스
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; }
Native, Web, Java의 동작 차이
SDK for Unity에서는 .motion3.json을 변환한 AnimationClip을 재생시켜 모션 재생을 실현하고 있습니다.
AnimatorController를 이용하지 않고 CubismMotionController를 이용한 경우에도 PlayableGraph를 이용해 AnimationClip을 재생함으로써 모델의 파라미터값을 조작합니다.
이에 비해 SDK for Native 등에서는 .motion3.json에서 얻은 값을 사용하며 현재 재생 시간으로 보간하여 값을 얻습니다.
이때 재생 종료 시간을 초과하면 항상 마지막 키 프레임의 값을 한 번 반환하도록 설계되었습니다.
SDK for Unity는 Unity의 기능인 PlayableGraph와 AnimationClip을 이용하는 관계로, 재생 중 임의의 타이밍에 해당 값에 간섭할 수 없습니다.
또한 재생 종료 체크를 재생 종료 시간으로 관리하고 있기 때문에 AnimationClip이 마지막 키 프레임을 반환했는지 여부와 관계없이 종료 플래그가 발생합니다.
따라서 재생 종료 시간을 초과했을 경우의 처리가 SDK for Native 등과 달라 업데이트 시간에 따라서는 마지막 키 프레임이 반환되지 않을 수 있습니다.
이 현상은 특히 CubismMotionController.PlayAnimation()의 인수 isLoop를 false로, FadeOutTime을 0으로 지정한 상태로 재생하면 나타날 수 있습니다.
또한 필요에 따라
- Application.targetFramerate를 모션의 프레임 레이트에 맞게 설정
- CubismMotionController.Update()의 Time.time 사용 위치를 Time.fixedTime으로 변경
- ProjectSettings.Time의 Fixed Timestep을 Application.targetFramerate에 맞게 조정
이상의 변경을 실시하면 비교적 안정적으로 마지막 키 프레임을 취득할 수 있습니다.
단, 이러한 설정은 애플리케이션 전체에 영향을 미치므로 권장되지 않습니다.
CubismEditor와 루프의 동작을 비슷하게 만들기
Cubism 5 SDK for Unity R2 시점에서 모션의 루프 재생은 CubismEditor 및 Cubism Viewer (for OW)와 동작에 차이가 발생합니다.
이 증상에 대해서는 장래의 대응을 검토하고 있지만, Cubism 5 SDK for Unity R2 이전의 SDK for Unity에서는 가져오기 처리의 일부를 변경하여 루프 시의 동작을 CubismEditor의 동작과 비슷하게 만들 수 있습니다.
CubismEditor의 동작과 비슷하게 만드는 방법은 다음과 같습니다.
참고로 이는 잠정적인 방법이며, 이 방법을 이용해 생성된 AnimationClip이나 모델의 동작에 대해서는 보증하기 어렵습니다. 본인 책임하에 이용해 주시기 바랍니다.
주로 유효한 상황
- AnimatorController(Mecanim)를 이용한 모션 재생과 루프 처리를 실시하는 상황
- CubismMotionController.PlayAnimation()의 isLoop를 true로 하여 재생하는 상황
가져오기 시의 처리 변경
다음 부분을 수정하고 모션이 포함된 모델을 가져오면 생성되는 AnimationClip 커브가 CubismEditor에서 루프를 실행하는 경우와 비슷한 것으로 변환됩니다.
이 방법은 가져오기 및 다시 가져오는 모든 .motion3.json에 영향을 줍니다.
CubismMotion3Json.cs
public AnimationClip ToAnimationClip(bool shouldImportAsOriginalWorkflow = false, bool shouldClearAnimationCurves = false, bool isCallFormModelJson = false, CubismPose3Json poseJson = null) { // Check béziers restriction flag. if (!Meta.AreBeziersRestricted) { Debug.LogWarning("Béziers are not restricted and curves might be off. Please export motions from Cubism in restricted mode for perfect match."); } // --- Add --- var duration = Meta.Duration; // --- Add --- ...
// --- Add, Change --- var keyframes = ConvertCurveSegmentsToKeyframes(curve.Segments); if (Meta.Fps > 0) { duration = Meta.Duration + (1.0f / Meta.Fps); var oldLength = keyframes.Length; Array.Resize(ref keyframes, oldLength + 1); keyframes[oldLength] = new Keyframe(duration, curve.Segments[1]); } var animationCurve = new AnimationCurve(keyframes); // --- Add, Change --- ...
#if UNITY_EDITOR // --- Add, Change --- if (Meta.Fps > 0) { duration = Meta.Duration + (1.0f / Meta.Fps); } // Apply settings. var animationClipSettings = new AnimationClipSettings { loopTime = Meta.Loop, stopTime = duration }; // --- Add, Change --- ...