버그 픽스

5.13

작업을 하며 버그가 발견된다고 해도 바로 손보지는 않는다. 하던 작업을 중단하고 다른 작업에 손을 대는 것은 꽤나 피로한 일이다. 대신, 적어둔다. 그리고 나중에 한 번에 처리한다. 이는 생각을 한 분야로 집중할 수 있어 효율적이다.

장풍은 아직 본격적인 구현은 이르지만, 오래전 구현해둔 바가 있다. 하지만 중간에 추가된 시스템들이 많아 역시 리팩토링이 필요했다.

스파2를 기준으로 하기 때문에 장풍을 맞았을 땐 슬로우 모션이 적용된다. 생각해보니 대부분의 게임이 그렇다. 굳이 느려질 필요는 없는데… 전통인가? 전통이면 따라야지!

스샷을 찍고 나니 보이는, 바로 앞에서 쏘면 밀려나는 문제는 버그다. 적어두자.

삐죽이 문제를 해결하는 김에 잡기할 때의 히트 포인트문제도 고쳐두자. 기존까지는 잡기후 타격이 일어날 때, 히트포인트 갱신이 안돼서 지저분하게 삐죽이가 생겼다.

이제 히트 포인트 갱신으로 히트포인트와 삐죽이가 제대로 표시된다. 이펙트까지 붙이니 좀 더 아파보인다.

그 다음 작업. 캐릭터 별 히트컬러 분리도 완료. 이 컬러는 1P와 2P가 고정이다.

해적의 집 깃발도 움직여 주자. 그렇게 예쁘게 움직이진 않지만, 안움직이는 것보다야 낫다.

타격형 잡기의 때리는 위치에서 이펙트가 나오도록 개선.

플레이어2의 입력처리는 예정에 없었지만, 버그 픽스를 위해서 필요했다.

짠손대전. 리치깡패 닐리. 어…달래랑은 어떨까…

..?! 달래도 이긴다.

야호!

다음 작업은 세컨더리 애니메이션이 보강이다.

오로라의 세컨더리 애니메이션

5.9

으아아아. 생각보다 잘 안되는 머리카락 본.

앉을 때나 일어날 때 모션의 변화가 급격하다보니, 물리엔진이 이를 급격한 애니메이션으로 보고 본을 크게 움직이는 경우가 있는데, 지인이 ‘그렇다면 스테이트를 바꿀 때 초기화를 해주면 어떤가?’라는 아이디어를 줘서 적용해봤다.

앉을 땐 비교적 괜찮으므로, 일어설 때만 적용해봤다. 가슴은 여전히 흔들리게 하고 싶었으므로 가슴을 제외하고 나머지는 초기화. 오. 결과가 괜찮아보인다.

페이셜 작업. 영상편집은 할 줄 모르기 때문에 날 것 그대로 기록.

예쁘게 나온 컷을 기록

마참내! 끝났다! 목표했던 1차 마일스톤 작업의 끝은 여기다. 캐릭터 4개를 만들고 각각의 기초 공격 애니메이션과 이펙트, 잡기, 세컨더리 애니메이션 제작 시스템의 정립까지. 빌드해보자.

버그나지 마라.버그나지 마라.버그나지 마라.버그나지 마라.버그나지 마라.

예쁜 메세지

잘된다!

백업부터 하자. 집컴퓨터 옆에 있는 나스에 꾸역꾸역

달래의 세컨더리 애니메이션

5.5

달래는 닐리보다는 쉬울 것이다. 앨리스보다도 쉬울 것으로 예상한다.

지금까지 작업을 하며 했던 삽질들을 한 번 정리하고 넘어가도록 하자.

  • Flipped애니메이션들은 모든 정방향 애니메이션이 끝난 후에 제작하는 편이 좋다.
  • 본의 마디가 많을 때, 루트와 중간본의 애니메이션은 따로 제작하자. 물론 루트 적용해보고 잘 나오면 좋고.
  • 세컨더리가 필요한 부분에 대해서는 Idle에 초기값을 넣어둘 뿐, 애니메이션 하지 않는다.

달래의 이슈는 스커트다. 대부분의 문제가 치마에서 발생하는데, 3D는 원래 몸에 딱 달라붙는 옷을 표현하는 것이 어렵다. 조금만 어긋나도 뚫리고, 오차값을 너무 허용하면 순식간에 펑퍼짐해져서 테가 안난다.

이번에 세컨더리 애니메이션 작업을 하며 Shrink Wrap에 대한 걸 배웠다. 달래의 스커트에 응용할 수 있지 않을까?

….는 테스트 결과, 안된다. 애초에 Shrink Wrap이 완전하지 않기는 하다. 하지만 적어도, 이 제약조건은 충돌스크립트를 탄생시켰고, 이에 따라 수동 노가다의 길을 더 원만하게 해줄 것 같긴 하다. 따라서…

치마 본의 컨트롤러 제약은 삭제하고 일반적인 방법으로 대처할 수 있을 것 같다.

허나 이를 위해선 치마본 설계를 다시 하여야 한다. 기억이 가물가물한데… 여기서 세로 본은 컨트롤러일 뿐이고, 실제로 치마를 움직이는 것은 가로 본이다. 기존까진 2마디였는데, 닐리와 같이 3마디를 쓰자.

이미 앨리스보다 쉬울거라는 예상은 벗어난 것처럼 보인다. 차근차근 웨이팅해보자.

치마본의 스케일에 사용된 드라이버는 상당히 복잡하다. skirt_scaler에 할당된 저 값들은 대체 무엇을 의미하더라….

def skirt_scaler(z, x, y, front, side, back, level, limit):

코드를 봐도 모르겠네?! 한 분야를 너무 깊숙히 들어가면 이런 코드가 나온다…

5.6

코드를 건들수록 망가지는 것 같아서 다시 롤백했다. 과거의 나를 믿어야 한다.

어디까지 다이나믹 본으로 돌려야 할까? 를 고민하다가 루트본만 애니메이션을 하는 쪽으로 가닥을 잡았다. 그런데 잠깐… 닐리도 이렇게 하면 다이나믹본을 사용할 수 있지 않을까?

사실 문제가 되는 모션들은 바로 이런 것이었다. 사정없이 깨지고 뚫리는 것이 싫어서 수동노가다를 선택했던 것인데, stiffness값을 올려주니 이런 부분들이 많이 상쇄되는 것을 발견했다.

엉덩이 부분이 뚫리는 것은 컬라이더를 조금 보강하면 될 것이다. 필요한 경우, 애니메이션 이벤트에서 동적으로 설정해줄 수도 있을 것 같다. 다이나믹본은 기존의 애니메이션에 물리계산을 덧붙인다. 그렇다는 것은 의도적인 천의 모양도 잡아줄 수 있다는 이야기이다. 생각보다 더 좋은 것 같다. 지금까지 난 뭘 한거지…흑흑

과감히 노선을 틀어보자. 달래를 중단하고 다시 닐리로

5.8

닐리와 앨리스의 작업을 해결했으므로 다시 이어서 해보자.

가슴본에 저고리 고름이 달린 형태라 애를 좀 먹었는데, 다이나믹본은 본에 본을 달았을 경우 exclusion을 통해 자식본을 계산에서 제외해도 작동이 안된다. 이유는 모르겠지만, 물리계산에 필수적인 end본 계산부분에서 뭔가 오류가 있거나, 아니면 원래 그렇게 사용하는 것이 아니거나.

이럴 경우 똑같은 본을 복사해서 해당 본의 자식으로 달아주고, 그 본을 연산에서 제외시키면 된다. 이 과정에서 사본의 본 움직임을 원본과 똑같이 하는 Constraint도 적용해보았는데, 작동되지 않는다. 애니메이션 키만 따라가는 모양. 혹은 Update()시점의 차이일지도 모른다. 뭐든 안된다. 어차피 더 좋은 방법을 알았으니 쓸 필요가 없다.

아직은 순조롭게 진행 중. 세컨더리 애니메이션의 대부분을 다이나믹 본에 의지함으로서 그동안 만들었던 애드온의 기능 중 많은 기능이 쓸모없어졌다. 오로라까지 작업한 이후에 쳐낼껀 쳐내도록 하자.

5.9

캐릭터가 앞으로 이동할 때 머리카락이 캐릭터를 가린다. 아이고, 예쁜 얼굴을 가리면 어떡하니. 해결방법을 찾아보자.

컬라이더를 조금 앞으로 빼주는 것으로 해결. 그렇게 예쁘지는 않은 것 같지만… 그래도 가성비가 좋은 방법이다.

입셰이더에는 각도에 따라 입이 돌아가는 코드가 들어가 있다. 난 입을 그릴 때, 특히 옆모습을 그릴 때 얼굴 옆에 그리는 습관이 있기 때문에 이런 코드를 넣은 것이다. 그런데 이것이 오작동해서 입이 비뚤게 보이는 버그가 있었다.

// 뷰에 따른 오프셋 계산. 0.04는 임의의 속도.
float3 leftWS = normalize(TransformObjectToWorld(float3(1,0,0)));
float3 view = _WorldSpaceCameraPos.xyz - OUT.positionWS.xyz; 
float3 V = normalize(float3(view.x, 0, view.z));
offset.x += dot(leftWS, V) * 0.04;

이것이 문제의 코드. 원리는 오른쪽과 카메라를 Dot시켜 나오는 값을 더한 것. 그런데 leftWS의 벡터를 위치로 계산하고 있는 것이 문제였다. 셰이더에서 벡터를 다룰 때, 위치인가? 방향인가?는 계산방법이 조금 다르다. 복잡한 건 모르겠으니 그냥 유니티에서 제공해주는 걸 사용하면 된다. 따라서 이 코드는 이렇게 수정되어야 한다.

// 뷰에 따른 오프셋 계산. 0.04는 임의의 속도.
float3 leftWS = normalize(TransformObjectToWorldDir(float3(1,0,0)));
float3 view = _WorldSpaceCameraPos.xyz - OUT.positionWS.xyz; 
float3 V = normalize(float3(view.x, 0, view.z));
offset.x += dot(leftWS, V) * 0.04;

이제 정상작동하는 것을 확인할 수 있다.

달래가 은근 달린 것이 많다 보니 타격등에서 캐릭터경직이 일어날 때 다이나믹본이 움직이는 것이 꽤 거슬린다. 그래서 경직중엔 업데이트가 되지 않도록 수정했는데, 이렇게 되니 세컨더리본들의 움직임이 초기화되기 때문에 자연스럽게 보이기 위해 맞을 때 세컨더리본을 ‘예쁘게’수정해 주어야 한다. 내일은 이걸 해보자.

맞을 때 세컨더리본을 수정하기 위해선 꽤나 많은 품이 드니까 다이나믹본 코드를 수정해보자. 그런데 코드에 타임스케일을 곱해주면 오류가 난다. 하지만 변수를 별도로 할당해서 처리하면 오류가 나지 않는다. 왜지…?!

뭐 일단 작동하니까 넘어가도록 하자.

닐리의 세컨더리 애니메이션 #3

5.7

난 스프링본에 대해 부정적이었다. 스프링본 특유의 달랑거리는 느낌이 싫었고, 충돌이 항상 말썽을 일으키기도 했기 때문이다. 무엇보다도 본 움직임의 제어권을 시스템에 빼앗기는 것이 싫었다. 키를 원하는대로 움직일 수 없다면, 결국은 언젠가 문제가 생긴다. 하지만 다이나믹본은 키애니메이션위에 물리적인 움직임이 블렌딩된다. 이럴 수가! 이걸 진작에 알았더라면, 시간을 아주 많이 절약할 수 있었을텐데!

다이나믹본은 캐릭터의 움직임에 따른 운동량을 계산해준다. 하지만 기본적으로는 스프링이기 때문에, 원심력과 중력을 계산하지는 못한다. 엄밀히는 중력 계산을 할 수 있지만, 닐리가 상대를 들어올렸을 때처럼 특이한 상황에만 중력이 적용되어야 하기에, 의도적으로 연출된 움직임을 제어하려면 키애니메이션이 편하다.

그렇다면, 할 일은 원심력과 중력만 적용된 데이터를 유니티에 넘기는 것이다. 해보자.

바닥에 드리워지는 옷자락의 표현. 플랜 콜라이더는 별 기대 안했는데 예상 외의 괜찮은 퀄리티를 보여준다.

움직이는 게 많으니 뭔가 좀 어지럽긴한데, 제작이 느무 쉽다! 달래도 이렇게 해보자.

닐리의 세컨더리 애니메이션 #2

4.29

땅에 끌리는 로브자락을 위해 Floor제약조건을 시도했지만 실패. Floor는 이동값만 제약이 걸린다. 궁여지책으로 바닥에 충돌박스를 만들어서 충돌을 사용했는데, 생각보단 결과가 괜찮다. 다만, 덜덜 떨리는 애니메이션은 피할 수 없다. 너무 많은 시간을 소요하고 있기 때문에 어느 정도 선에서 타협을 보아야 할 것 같다.

오늘의 고민은 이 자세에서 모자를 떨어뜨려야 하나, 말아야 하나.(중요한 것은 그게 아니겠지만서도…)

끝판왕다운 면모를 보이고 있는 닐리. 아직 절반도 안끝났다.

아이고.. 되다. 다이나믹 본 알아보자…

개인계정으로는 구입안해놨었네..?! 일단 구입

테스트삼아 로브에만 적용. 음. 생각보다 충돌을 잘 못거른다.

그럼 지금까지 했던 로브의 애니메이션을 적용해 보자.

다소 딱딱한 느낌이 들긴 하지만, 역시 이 쪽으로 해야 한다는 느낌…

잠깐 한눈을 팔아봤는데 역시 안될 것 같다. 흑흑

4.30

벌써 4월의 끝인가…. 시간 참 빠르구나. 닐리는 정-말 공이 많이 들어간다. 내일은 좀 많이 진행할 수 있기를

5.1

몇 번을 뒤엎었는지 모르겠다. 분명 내가 만든 툴인데 스스로 툴에 익숙치가 않아서 괜찮은 수치를 얻기까지 시행착오가 길었다. 모든 원인은 루트 애니메이션을 모든 본에 재활용하려는 욕심에서 비롯되었다.

루트는 보통 머리, 혹은 허리에 달려있다. 이 말은 세컨더리 본의 부모와 자식본의 회전값이 현저하게 다르다는 걸 뜻한다. 이 툴을 제대로 사용하려면, 루트와 차일드본을 분리해서 애니메이션하여야 한다. 생각해보면 당연한 이야기인데, 왜 이걸 깨닫지 못했을까!

어쨌거나 지금은 별다른 시행착오 없이 애니메이션을 할 수 있게 됐다. 이제 다시 달려보자.

5.2

디버깅용으로 캐릭터를 실시간으로 바꿔보려고 삽질을 하다가 실패하고 롤백. 생각보다 얽혀있는게 많다. 괜히 시간만 버렸네.

우여곡절 끝에 1차 제작은 끝났지만, 퇴고를 거쳐야 한다. 이 작업이 또 한참이다. 흐아…

착지 모션은 앨리스에게 별로 중요한 모션이 아니었지만 닐리는 중요할 것 같다. 그냥 그럴 것 같다.

초반에 작업했던 앞머리가 마음에 안드는 경우가 많다. 계속 수정 중. 몇 번이나 고쳤을까…

5.3

가슴의 애니메이션을 수정하던 중 문득 떠오른 생각. 대부분의 물리애니메이션의 문제는 충돌에서 일어난다. 그렇다면 충돌이 필요없는 본의 애니메이션은 물리를 사용해도 되는 것 아닌가…?!

그..그렇다. 다이나믹본은 이걸 위해 만들어진 것이로구나! 마찬가지로 앨리스의 머리도 처리할 수 있을 것 같다.

5.4

닐리의 세컨더리 애니메이션 완료. 모자와 가슴부분의 애니메이션은 다이나믹 본으로, 나머지는 수동으로 키를 잡았다. 정말 힘들었던 작업이었다. 아휴…다시 안보면 좋겠는데, 아직 애니가 많이 남았지…

뒷 일은 나중에 생각하고 일단 달래로 넘어가도록 하자.

닐리의 세컨더리 애니메이션 #1

4.23

앨리스는 튜토리얼에 불과했다. 튜토리얼 끝나자마자 바로 끝판왕이다.닐리의 세컨더리 본은 지금까지 제작했던 모든 캐릭터를 통틀어 가장 많으며 앞으로도 그럴 것 같다. 차근차근 진행해보도록 하자.

어려움은 항상 충돌에서 온다. 그런데 그 어려움이 예상했던 부위가 아니었으니…

머리카락이 가슴에 묻힌다… 가슴이 작으면 머리카락을 살짝 앞으로 빼기만 하면 될 것이지만, 그것이 안된다.품이 꽤 들어간다.

그리고 이걸 위해 또 애드온을 개량. 차일드 본 교정은 부모본을 위로 올렸을 경우, 자식본의 회전값이 너무 커지는 것을 막기 위함이다. 예를 들어 부모를 5도 돌려줬다면 자식본은 -5도를 해준다. 반복키 자동교정은 대기처럼 루프애니메이션을 만들 때 수정한 프레임을 기준으로 양쪽에 재생시간만큼의 키를 넣어서 루프키를 자동으로 생성해주는 기능이다.

이로서 대기애니메이션 완성. 코트의 칼라가 묻히는 것이 좀 아쉬운데, 들어봤더니 더 이상해서 그냥 냅두기로.

작업 순서가 앨리스와는 조금 다르다. 치마의 뚫림이 중요했던 앨리스는 충돌체크를 많이 이용했지만, 닐리는 그것보단 그냥 회전 더하기를 많이 사용하게 된다. 툴이 없었다면 힘들었을 작업이긴 하지만, 툴을 사용해도 꽤나 시간이 걸린다.

4.25

닐리의 로브가 워낙 커서 작업의 덩어리가 커지니, 디테일이 좀 더 필요했다. 이로 인해 부족한 기능을 추가했다. 행렬연산은 건들기 싫지만, 원하는 기능을 위해 기꺼이 두통을 감수해야 한다.

이제 회전값은 로컬/월드 혹은 섞어서 작업할 수 있다.

하지만 그렇다고 작업이 쉬워지지는 않았다는 후문.

머리카락은 상대적으로 쉽다. 모양도 예쁘게 잘 나오는 것 같고.

결국 또 기능을 추가. 그룹본을 통째로 위로 올리는 기능과, 중간에 끼인 본의 회전값을 보간하는 기능이다.

4.26

큰 로브의 말단부는 다소 독립적으로 움직인다고 보아야 한다. 이를 위해 통일된 키복사로는 한계가 있다는 사실을 절감하고 인덱싱 기준으로 중간부터 키를 물려주는 기능을 만들었다.

옵션은 하나지만, 구현이 꽤 힘들었다.

닐리의 로브는 몸에 가까운 부분일수록 경직되어 움직여야 하고 말단부로 갈수록 좀 더 자유로운 움직임을 보여야 한다. 보다 섬세한 컨트롤이 필요한 것이다. 이를 위해 추가한 기능이다. 다만 이것은 키 인덱싱을 기준으로 하기 때문에 몇 가지 제약조건이 있다.

  • 키를 물려줄 때 간격축소와 보간비율을 0으로 놓고 사용할 것
  • 중간에 임의의 키를 넣지 않을 것

4.27

마침내 작업에 불편한 점이 없다. 작업의 퀄리티는 무수한 수정에서 나오므로 툴의 편의성은 매우 중요하다고 할 수 있다.

모든 초안은 쓰레기다. – 헤밍 웨이

4.28

지인 몇 명이 내게 왜 물리를 사용하지 않느냐고 물었다. 그럴 때마다 원하는 움직임을 내기 힘들다고 대답했지만, 손으로 작업하자니 너무나 품이 많이 드는 건 사실이다. 지금이라도 갈아탈까… 흑흑

앨리스의 세컨더리 애니메이션 #4

4.20

치마의 애니메이션이 너무 찰랑거리는 느낌이 있어서 수정했다. 실제 교복 치마는 좀 더 뻣뻣한 재질을 가지므로, 말단부의 본 애니메이션 좀 더 약한 느낌이어야 한다. 그리 눈에 띄지는 않는 작업이지만, 안해두면 계속해서 거슬릴 것이므로 확실히 해두도록 하자.

이제 페이셜 애니메이션을 해야 할 시간이다. 몇몇 입이 그 전부터 마음에 들지 않았기 때문에 이를 고치는 일부터 시작해 보자. 입의 굴곡을 지나치게 살리는 것은 마치 ‘도라에몽’에 나오는 캐릭터처럼 보이기 때문에 꽤 올드해 보인다는 느낌을 받는다. 이를 단순하게 고치고, 다듬는다.

애니를 하다가 간헐적으로 나오는 이런 포즈들이 꽤 마음에 든다.

모션들이 짧기에 페이셜은 어렵지 않다. 다음 작업으로 넘어가자.

다음은 충돌 박스를 맞추는 일이다. 지금까지는 충돌박스가 움직이지 않았다.(그런데 놀랍게도 크게 어색하지 않다.) 하늘색박스는 바디, 노란색 박스는 어택박스다. 바디는 머리/몸통/다리로 나뉘어지고, 다리는 점프 시에 충돌체크를 하지 않는다. 이렇게 되면 머리는 사실상 별로 쓸 일이 없는데, 캡콤이 그렇게 해서 무지성으로 따라했다(…) 사실 등빨이 넓은 캐릭터라면 이게 의미가 있을 것 같은데 여캐밖에 없다 보니 이게 그렇게 쓸모가 있을 것 같지는 않지만… 뭔가 이유가 있겠지.

모션에

정작 작업을 하려 보니 심히 귀찮다… 자동화할 수 있지 않을까.

이미 박스는 처음부터 1m x 1m로 규격화 해 놓은 상태다. 이는 유니티로 넘어갔을 때 컬리전박스를 연동시키기 위함이다. 머리는 조건이 명확하다. 몸통도 비교적 괜찮다. 하지만 다리가 애매…하다. 될 것 같기도 하고, 안될 것 같기도 하다.

다리는 예상대로 잘 안된다. 그냥 수동 노가다 해줄수밖에 없나…

4.21

블렌더로 스크립트를 짜고 있노라니, 문득 드는 생각이 있었다. 컬리전 박스를 자동화 해줄 거라면, 그냥 실시간으로 처리해도 되지 않나…? 어택 컬라이더는 검기의 위치가 불안정해서 불가능하지만, 바디컬라이더라면 가능하지 않을까?

그래서 빠르게 러프하게 코드를 짜본 결과

…오? 되겠다. 이건

일단 구현까지는 했는데, 코드가 너무 지저분하다. 정리를 해야 한다.

4.21

코드를 구현하다가 일이 커졌다. 컬리전만 건드리려고 했던 건데, 전체적인 코드를 리팩토링 하게 됐다.

충돌을 정확히 계산하기 위해서는 게임매니저 – 캐릭터순으로 업데이트가 돌아야 했다. 현재는 게임매니저 아래에 캐릭터가 붙어있으므로, 이를 확신할 수 있는 정보가 필요해서 챗GPT에게 물어봤는데, 전혀 뜻밖의 힌트를 주었다. 업데이트를 명시적으로 관리하라는 내용. 그렇다! 업데이트를 왜 중앙관리할 생각을 못했지..?!

지금까지는 Update()의 순서가 뒤죽박죽이었다. 이를 게임매니저에 몰아준다. 컬라이더를 시스템에서 생성하도록 바꾸고, 발사체등의 외부요인이 캐릭터의 스테이트를 바꿀 수 있으니 LateUpdate에서 최종적으로 처리해준다. 깔끔하다! 이렇게 되면 최종 판정은 현재 기준으로 발사체-커맨드-잡기-데미지 순으로 처리된다. 잡기에 성공해도 타격이 들어왔으면 맞는다. 물론 이를 바꿀 수도 있다. 판정이 항상 애매했는데, 정리하니 한결 나은 느낌이다.

그런데…사이드이펙트가 굉-장하다! 버그 투성이.. 한동안은 이걸 잡아야할 것 같다.

리팩토링 완료! 생각보단 일찍 끝났다. 이제 다시 세컨더리 애니메이션에 힘써보자.

4.23

마참내! 앨리스의 세컨더리 애니메이션 완료.

바겉으로 변한 건 없어 보이는데 내부적으로 꽤 많이 바뀌었다. 충돌체크를 위해 무려 4번이나 검사하던 로직을 한 번으로 줄이고, 충돌체들을 본 기준으로 자동 생성한다. 다만 어택 컬라이더는 기존방식을 그대로 쓴다. 검기등의 공격범위는 정형화할 수 없기 때문이다.

격투게임이니 모션의 대부분이 공격일 것 같지만, 그것보단 데미지 모션이 더 많다. 잘 때리는 것보다 잘 맞는 게 중요하기 때문이다. 어쨌거나 고생한 보람이 있기를.

이제 끝판왕(튜토리얼 깨고 나니 바로 끝판왕) 닐리의 세컨더리 애니메이션으로 넘어가도록 하자.

앨리스의 세컨더리 애니메이션 #3

4.16

회전 보간과 리버스키의 알고리즘을 고쳤다. 리버스키는 자식본들의 키를 오프셋할 때, 좀 더 자연스럽게 움직일 수 있도록 0프레임 이전에 역방향 회전을 넣어주는 기능이다. 그런데 이것이 쿼터니온에 대한 이해가 부족할 때 제작한 거라 엉망으로 작동되고 있었다.

며칠 째 버그가 계속 나와서 작업 진행이 더디다. 실무에서는 이것들이 아티스트와 TA간의 연계작업이 되는데, 이 툴을 아티스트에게 던져줬다고 생각하면 끔찍하다.

더 이상의 버그가 나오질 않기를 바라는 마음에서 글을 새로 적었다. 기도합니다. 나무아미타불. 아멘

기도는 기도에서 그쳤다. 또 기능을 수정하고 추가하고…

애드온을 만들었는데도 작업이 꽤 어렵다. 특히 앉기처럼 몸과 다리가 밀착되면 특히 더 어려워진다. 사실 이건 답이 없다. 천하의 마블러스도 몸이 겹치면 구겨지고 난리인데, 본 베이스의 작업은 오죽할까.

4.17

현재는 본과 본사이의 선형 보간을 할 때 euler값을 더해 평균을 구하고 있었다. 그런데 이것이 서 있을 땐 문제가 없는데, 뒤집혀 있을 땐 문제가 됐다. 그래서 오늘도 챗GPT에게 물어봤는데…

이 코드의 문제는 회전값을 오일러 각도로 선형 보간하는 데 있을 것 같아요. 오일러 각도는 주로 한 축을 기준으로 회전하는 경우에만 유효하고, 다중 축 회전에서는 문제가 발생할 수 있어요. 특히 180도 회전과 같은 경우에는 오일러 각도가 다중 해석이 가능하기 때문에 문제가 될 수 있어요.

이 문제를 해결하기 위해선 여러 가지 방법이 있지만, 가장 간단한 방법은 쿼터니언(quaternion)을 사용하는 것입니다. 쿼터니언은 회전을 나타내는 더욱 강력한 방법으로, 다중 축 회전에서도 유효하고 180도 회전과 같은 경우에도 제대로 작동합니다.

다음은 쿼터니언을 사용하여 회전값을 보간하는 코드의 예시입니다:

pythonCopy codeimport bpy
from mathutils import Quaternion

def interpolate(bone):        
    location = [bone.location.x, bone.location.y, bone.location.z]
    scale = [bone.scale.x, bone.scale.y, bone.scale.z]        

    rot1 = side_bones[0].matrix_channel.to_quaternion()
    rot2 = side_bones[1].matrix_channel.to_quaternion()

    # Interpolate quaternions
    lerp_quat = rot1.slerp(rot2, 0.5)

    # Convert quaternion to euler
    lerp_euler = lerp_quat.to_euler()

    bone.matrix = lerp_euler.to_matrix().to_4x4() @ bone.bone.matrix_local
            
    bone.location.x = location[0]
    bone.location.y = location[1]
    bone.location.z = location[2]
            
    bone.scale.x = scale[0]
    bone.scale.y = scale[1]
    bone.scale.z = scale[2]            
            
    bpy.context.view_layer.update()

위의 코드에서 rot1.slerp(rot2, 0.5) 부분은 쿼터니언의 선형 보간을 수행합니다. 이 방법을 사용하면 회전의 중간값을 구할 때 오일러 각도의 문제를 회피할 수 있습니다.

그리하여 오늘의 깨달음. 쿼터니온을 선형보간할 수 있구나! slerp를 사용하면 된다! 챗GPT는 대단해!그아아아(대흥분)

보간 코드는 여러번 말썽을 일으키는 코드였는데 쿼터니온 베이스라면 믿을 수 있을 것 같다. 앓던 이가 빠진 느낌이다.

1차 작업을 완료했다. 이제 엔진에 붙여보자.

엔진에 붙이기 위해 키를 정리해야 한다. 모든 모션은 프레임이 정해져 있기 때문에, 뒷프레임을 짤라낼 필요가 있다. 그렇지 않으면 유니티에서 일일이 숫자를 다시 기입해줘야 한다. 또한 프레임이 달라지면 유니티에서 설정해놓은 이벤트들이 어긋나기 때문에 기존의 프레임을 맞춰주는 편이 좋다. 그렇다면 작업공정이 안정된 지금은 세컨더리 애니메이션까지 한 번에 만들어주는 편이 좋을까…싶다가도 모션이 이상하면 어차피 수정해야 하기 때문에 지금 방식이 맞는 것 같다.

이 또한 기계적인 작업이기 때문에 스크립트에 기능을 추가했다.

짜르기는 현재 선택한 프레임을 기준으로 새 키를 찍고 이전 혹은 이후 프레임을 날려준다. 그런데 이 코드를 작성하며 깨달은 사실. 키를 순차적으로 지우면 리스트가 동적이라 결과가 변한다. 때문에 거꾸로 지워야 한다.

for fcurve in action.fcurves:
        if group_name in fcurve.data_path:
            for keyframe in reversed(fcurve.keyframe_points):
                if ((side == 'left' and keyframe.co.x < frame_current) or 
                    (side == 'right' and keyframe.co.x > frame_current)):
                    fcurve.keyframe_points.remove(keyframe)

for문을 돌 때 반드시 reversed()로 돌도록 하자.

하지만 이 방법도 문제가 있는게, 중간에 있는 키프레임을 지우면 같은 문제가 생긴다. 결국 키프레임을 별도의 리스트로 저장해서 해결해야 한다.

이제 엔진에 적용 중. 닐리의 발 잡기는 앨리스를 위해 만든 것이라고 해도 과언이 아닌 것!

고생한 것 치고는 퀄리티가 성에 차질 않는다. 모션사이가 생각보다 많이 끊겨 보이는데…이것만 보간할 수 있는 방법이 없을까?..

4.17

유니티의 애니메이터는 레이어라는 개념이 있다. 주로 FPS등에서 상하체를 분리해 애니메이션을 조합하는 용도로 사용된다. 이것을 이용하면 되지 않을까? 속성으로 레이어 마스크를 배우고, 블렌딩을 적용해본 결과… 블렌딩 자체가 스테이트가 투명하게 관리되지 않아 오래전에 발생한 문제가 발생했다. FSM을 직접 만들기로 결심했었던 바로 그 문제. 블렌딩이 2-3개 겹치면 모션이 엉망이 된다.

이를 해결하려면 스테이트가 겹치지 않게 하면 된다. 그런데 잠깐…애니메이션은 대체로 격렬한 움직임을 보인다. 애니메이션이 튀는 구간은 거의 모두 Idle로 돌아오는 구간이다. CrossFade()를 Idle에만 적용한다면 어떨까.

성공적! 거의 튀지 않는다.

….인줄 알았는데, 블렌딩이 빠르게 될 때의 시간이 보간될 때 여전히 문제를 일으킨다. 으.어…

결국 CrossFade()를 포기. 조사해 본 바로는 블렌딩을 바로 취소할 수 있는 방법은 없다. 결국 다시 Play()로 돌리고 끊김을 완화하기 위해선 다시 끝프레임을 Idle로 맞춰주는 방법을 택할 수 밖에 없다.

문제가 하나 더 있는데, 잡기에서 Idle로 돌아올 때 방향이 바뀌는 문제이다. 이건 어차피 예전에도 한번 바꾸고 싶긴 했다. 각오하고 해보자.

이 기능은 예전에 한 번 실패했던 오퍼레이터이다. 몸통은 어렵지 않은데 양쪽 팔이 굉장히 번잡시러워서 포기했던 기억이 난다. 구르고 굴러 돌아오니 그냥 데이터 패스를 맞교환해주면 되는 일이었다. 하지만 여전히 손가락이나 IK등은 번잡시럽기 때문에, 큰 줄기만 뒤집어놓고 세부수정을 하는 편이 좋겠다.

이런 저런 삽질 끝에 정한 방향성

  • 애니메이션은 CrossFade()대신 Play()를 사용.
  • 대기로 이어지는 애니메이션의 경우, 맨끝프레임을 Idle의 첫프레임으로 보간
  • 방향이 달라지는 잡기의 반전

데이터가 커지면 작은 변화 하나도 부담되는 작업량이 된다. 차근차근 진행해보자.

블렌더 특성 상 ChildOf컴포넌트가 2개이상 붙은 본은 익스포트 시 프레임 지연이 일어난다. 게다가 IK가 정상작동하지 않는 문제도 있다. 이렇게 되면 결국 파일을 분리하는 것이 속편하다. 잡기용 파일은 별도로 분리해두자.

4.19

세컨더리 작업이 어려울 거라 생각했지만 예상보다 파장이 컸다. 이대로 작업을 진척시키기는 어렵다. 규칙을 확실하게 정립해 두어야 한다.

  • 순차적 키밀기가 마냥 좋지만은 않다. 과유불급. 사용 시 주의하자.
  • 첫 본이 가급적 90도를 넘지 않게 애니메이션 하자.
  • 두번째 본이 충돌에 오작동을 일으키는 경우가 있다. 팔의 컬리전은 사용하지 말 것
  • 짧은 길이는 단순 블렌딩한다. 그걸로 충분하다.
  • 추가로 표정을 애니메이션 하자.

앨리스의 세컨더리 애니메이션 #2

4.12

애드온 보완작업이 끝났으니 이제 실전으로 들어가보자. 역시 들어가자마자 에러가 난다.[…] 고치고.. 또 고치고

이제 에러가 없기를.(그런데 재생속도가 왤케 빠르지…?)

4.13

대기자세에서 앞머리를 휘날릴 때 순차키를 주려고 시도했는데, 본 갯수가 많다보니 1프레임 단위가 너무 크다. 때문에 더 세분화할 수 있는 기능이 필요했다. 결국은 또 애드온을 수정.

에이…스샷으로 봐선 1도 티가 안난다[…] 여튼 작업 완료. 루핑키 적용부분에 버그가 있어서 이것도 같이 수정

여튼 이 기능은 이처럼 최종폴리싱에 쓸 예정

뒤로 걷기를 작업하던 도중, 중복해서 키가 생기는 버그가 있어 코드를 확인해보니, 이럴 수가!! 계산을 제곱으로 하고 있었다! 이 버그를 수정하고 나니 10초 가량 걸리던 작업이 2~3초면 끝난다. 야호!

웹에 올리면 속도가 2배는 빨라진다. 이유는 모르겠다.

작업하다보니 터무니없는 코드가 돌아가고 있었다. 잘도 이런 게 문제없이 돌아가고 있었어..?!

버그잡다가 끝났다.. 오늘은 여기까지

4.14

검질한 후에 치마가 다리를 뚫지만… 뭐 중요한 건 그게 아니지?!!

대부분의 모션은 다시 Idle로 연결된다. 빠른 격투게임의 템포상 본의 움직임이 잘 보이지는 않지만, Idle로 갔을 때 어색한 건 역시 거슬린다. 이 경우 세컨더리 본의 키를 어찌하는 것이 좋을까?

  • Idle의 0프레임으로 연결
  • 궤적에 따른 잔여 움직임으로 내비둠
  • 공용으로 쓰는 무난한 키프레임으로 연결

원래 2번으로 연결하려 했다가, 3번으로 연결하려 했다가, 1번도 해봤다가…결국 다시 2번이 가장 괜찮아 보인다.

역시 중요한 것은 궤적이다. 이렇게 되면 애니메이션을 짜르기 위해 중간에 키를 넣어야 한다. 어차피 그룹내 모든 본에 넣게 되니 이를 위한 스크립트를 추가하자.

필요에 의해 만든 기능은 모두 쓴다. 하지만 ‘이건 쓰겠지…’싶어서 만든 기능은 안쓰는 게 꽤 있다.

오늘의 깨달음. ARP에선 숨김처리 한 것은 익스포트 하지 않는다. 바디 컬라이더를 익스포트할 때마다 지워줘야겠네… 싶어 귀찮겠다 싶었는데. 오오..!

세컨더리 애니메이션이 들어가니 비로소 캐릭터가 완성되어 보인다.

4.15

본 회전 보간 시 오류가 나는 경우는 드물지만, 꽤 성가시다. 생각보다 잦은 빈도로 나타나길래 옵션을 만들었다.

오류가 날 땐 보간만 하고 충돌을 사용하지는 말자.

앞머리의 본을 아낌없이 사용한 선택은 옳았다. 세컨더리 애니메이션을 만나니 정말 다양한 실루엣을 만들어준다.

점점 옵션이 많아진다. 초기값 보간도 옵션이 적용되도록 수정. 이 기능은 alt+r과 같은데, 좀 덜 적용되도록 해서 회전값을 원래 형태에 가깝게 만드는 용도로 사용한다.

타임라인에서 현재 프레임이 회전키에 얼마나 가까운가를 확인하기 위해 추가. 사실 이건 굳이…? 라며 긴가민가 했는데… 결국 추가하게 되는구나.

찰랑찰랑. 고생한 보람은 있다.

키는 대체로 월드기준으로 복사해 주는 편이 좋은 결과를 보여준다. 허나 회전이 들어가면 원심력이 작용하기 때문에 로컬이 어울린다. 애니메이션이 짧을 때는 괜찮았던 것들이 길어지니 부족한 부분이 생긴다. 이를 위해 또 기능을 추가해야 한다.

앨리스의 세컨더리 애니메이션

4.10

세컨더리 애니메이션이란 본체의 애니메이션에 따라 부차적으로 필요한 애니메이션을 뜻한다. 캐릭터가 몸을 움직였을 때 따라오는 망토의 애니메이션이 대표적인 세컨더리 애니메이션이다. 애니메이터들이 매우 싫어하는 작업이다.

이를 위해 프로젝트 작업 초반에 세컨더리 애니메이션 전용 애드온을 만들었었는데, 역시 고장났다(…)

다행히 큰 고장은 아니었다.

경직은 충돌을 수동으로 처리하기 위해 만든 기능이다. 치마가 저렇게 뚫리는 걸 해결하기 위해 만들어둔 기능이었지만, 전혀 도움이 되지 않는다. 역시 이론과 실전은 다르다.

어떻게 할 지 고민하던 끝에 이런걸 찾았다.

메쉬에 오브젝트를 스냅해주는 것이다. 이걸 다리에 스냅하고, 본이 포인트를 보게 하면…잘 쓰면 뭔가 될 것 같은데..

설계 중… 완벽하진 않지만, 어쨌거나 본을 ‘밖으로 끄집어 내는 일’은 가능해 보인다.

하지만 롤(Roll)이 고려되지 않는다는 단점이 있다.

그른데 트랙 투를 이렇게 하면 어찌 될 듯…?

구상은 끝났다. 이제 만들어보자. 작업을 하다보니 그룹본 보간 기능이 좌/우 한 쪽에만 적용되는 옵션이 있으면 좋겠다.

그럭저럭 해결은 됐지만, 몇가지 기능이 더 필요하다.

  • 몸에 본을 붙일 때의 이격치
  • 경직구간 기능 제거

4.11

러프한 구현. 오퍼레이터를 사용하기 때문에 속도가 꽤 느리다! 실전은 역시 이론과 다른 법이지만, 본이 루트에 가까울 수록 더 괜찮은 결과를 보여줬다. 일단 계산을 최소화하기 위해 최적화 코드를 넣고, 보간법을 기존과 다르게 해주어야 할 것 같다.

계산 시 불필요한 걸 걸러줬더니 60초 -> 15초로 단축됐다! 상황에 따라 다르긴 하지만, 충분히 사용할 수 있을 것 같다.

최근 블렌더 사용 시에 오토세이브가 작동하지 않는 문제가 있었다. 처음엔 대수롭지 않게 생각하다가 몇 번 작업을 날리고 나니 생각이 바뀌었다. 대체 왜 그래…

몇몇 포럼을 뒤적여보니 모달연산자를 실행할 경우 오토세이브가 작동하지 않는다고 되어 있다.

https://docs.blender.org/api/current/bpy.types.Operator.html#modal-execution

뭔 소린지 하나도 모르겠지만, 애드온이 계속해서 이벤트를 체크하면 문제를 일으키는 것 같다. 시간을 리셋하는 걸까… 여튼 이같은 애드온을 2개 알고 있다. Auto Reload와 KitBash3D이다. 범인은 KitBash3D였다. 외부프로그램과 계속 통신을 하느라 모달연산자를 실행하는 모양이다.

이제는 사용하지도 않는 애드온 때문에 골머리를 썩었다. 여튼 이제 잘 작동한다. 마음의 안정이 찾아왔다.

그룹본 보간하기는 공식을 바꿔야 한다. 컬리전을 계산하며 본이 벌어지는 경우, 이를 보완해주기 위한 기능이다. 보기엔 쉬운데 저게 참 어려웠다. 화려한 것은 모두 잡기술이다. 정말 중요한 것은 보기엔 단순해도 구현난이도가 무척 높을 때가 많다.

기본은 이것이다. 이제 가운데 본을 보간하면

그 중간으로 움직인다.

예정했던 기능은 완료. 기능은 이상이 없는데, 칼주름이라 뾰족한 부분이 파묻히는 문제가 있다. 이를 수동으로 끄집어내어주는 기능이 좀 더 필요할 것 같다.

기능은 거의 완성됐지만, 몇가지 편의기능이 더 있으면 좋겠다. 오늘은 타임오버.

4.12

보간 도중 본이 안쪽으로 파고드는 버그가 있었다.

이 버그는 충돌알고리즘으로 사용했던 shrink wrap제약조건이 문제가 된다. 시스템이 갑옷 아랫쪽에 튀어나온 단면을 충돌영역으로 잡음으로서 원치 않는 오류를 내고 있었다. 컬리전은 맨몸 그 자체여야 할 듯…?

애드온은 막바지 테스트 단계. 여기에 적지는 않았지만 버그가 무척이나 많았다. 이제 없겠지. 없어야 해…!

애드온은 이제 정말 길어졌다. 많이도 만들었어.

애드온 완성!…일까?