치마의 애니메이션이 너무 찰랑거리는 느낌이 있어서 수정했다. 실제 교복 치마는 좀 더 뻣뻣한 재질을 가지므로, 말단부의 본 애니메이션 좀 더 약한 느낌이어야 한다. 그리 눈에 띄지는 않는 작업이지만, 안해두면 계속해서 거슬릴 것이므로 확실히 해두도록 하자.
이제 페이셜 애니메이션을 해야 할 시간이다. 몇몇 입이 그 전부터 마음에 들지 않았기 때문에 이를 고치는 일부터 시작해 보자. 입의 굴곡을 지나치게 살리는 것은 마치 ‘도라에몽’에 나오는 캐릭터처럼 보이기 때문에 꽤 올드해 보인다는 느낌을 받는다. 이를 단순하게 고치고, 다듬는다.
애니를 하다가 간헐적으로 나오는 이런 포즈들이 꽤 마음에 든다.
모션들이 짧기에 페이셜은 어렵지 않다. 다음 작업으로 넘어가자.
다음은 충돌 박스를 맞추는 일이다. 지금까지는 충돌박스가 움직이지 않았다.(그런데 놀랍게도 크게 어색하지 않다.) 하늘색박스는 바디, 노란색 박스는 어택박스다. 바디는 머리/몸통/다리로 나뉘어지고, 다리는 점프 시에 충돌체크를 하지 않는다. 이렇게 되면 머리는 사실상 별로 쓸 일이 없는데, 캡콤이 그렇게 해서 무지성으로 따라했다(…) 사실 등빨이 넓은 캐릭터라면 이게 의미가 있을 것 같은데 여캐밖에 없다 보니 이게 그렇게 쓸모가 있을 것 같지는 않지만… 뭔가 이유가 있겠지.
모션에
정작 작업을 하려 보니 심히 귀찮다… 자동화할 수 있지 않을까.
이미 박스는 처음부터 1m x 1m로 규격화 해 놓은 상태다. 이는 유니티로 넘어갔을 때 컬리전박스를 연동시키기 위함이다. 머리는 조건이 명확하다. 몸통도 비교적 괜찮다. 하지만 다리가 애매…하다. 될 것 같기도 하고, 안될 것 같기도 하다.
다리는 예상대로 잘 안된다. 그냥 수동 노가다 해줄수밖에 없나…
4.21
블렌더로 스크립트를 짜고 있노라니, 문득 드는 생각이 있었다. 컬리전 박스를 자동화 해줄 거라면, 그냥 실시간으로 처리해도 되지 않나…? 어택 컬라이더는 검기의 위치가 불안정해서 불가능하지만, 바디컬라이더라면 가능하지 않을까?
그래서 빠르게 러프하게 코드를 짜본 결과
…오? 되겠다. 이건
일단 구현까지는 했는데, 코드가 너무 지저분하다. 정리를 해야 한다.
4.21
코드를 구현하다가 일이 커졌다. 컬리전만 건드리려고 했던 건데, 전체적인 코드를 리팩토링 하게 됐다.
충돌을 정확히 계산하기 위해서는 게임매니저 – 캐릭터순으로 업데이트가 돌아야 했다. 현재는 게임매니저 아래에 캐릭터가 붙어있으므로, 이를 확신할 수 있는 정보가 필요해서 챗GPT에게 물어봤는데, 전혀 뜻밖의 힌트를 주었다. 업데이트를 명시적으로 관리하라는 내용. 그렇다! 업데이트를 왜 중앙관리할 생각을 못했지..?!
지금까지는 Update()의 순서가 뒤죽박죽이었다. 이를 게임매니저에 몰아준다. 컬라이더를 시스템에서 생성하도록 바꾸고, 발사체등의 외부요인이 캐릭터의 스테이트를 바꿀 수 있으니 LateUpdate에서 최종적으로 처리해준다. 깔끔하다! 이렇게 되면 최종 판정은 현재 기준으로 발사체-커맨드-잡기-데미지 순으로 처리된다. 잡기에 성공해도 타격이 들어왔으면 맞는다. 물론 이를 바꿀 수도 있다. 판정이 항상 애매했는데, 정리하니 한결 나은 느낌이다.
그런데…사이드이펙트가 굉-장하다! 버그 투성이.. 한동안은 이걸 잡아야할 것 같다.
리팩토링 완료! 생각보단 일찍 끝났다. 이제 다시 세컨더리 애니메이션에 힘써보자.
4.23
마참내! 앨리스의 세컨더리 애니메이션 완료.
바겉으로 변한 건 없어 보이는데 내부적으로 꽤 많이 바뀌었다. 충돌체크를 위해 무려 4번이나 검사하던 로직을 한 번으로 줄이고, 충돌체들을 본 기준으로 자동 생성한다. 다만 어택 컬라이더는 기존방식을 그대로 쓴다. 검기등의 공격범위는 정형화할 수 없기 때문이다.
격투게임이니 모션의 대부분이 공격일 것 같지만, 그것보단 데미지 모션이 더 많다. 잘 때리는 것보다 잘 맞는 게 중요하기 때문이다. 어쨌거나 고생한 보람이 있기를.
이제 끝판왕(튜토리얼 깨고 나니 바로 끝판왕) 닐리의 세컨더리 애니메이션으로 넘어가도록 하자.
회전 보간과 리버스키의 알고리즘을 고쳤다. 리버스키는 자식본들의 키를 오프셋할 때, 좀 더 자연스럽게 움직일 수 있도록 0프레임 이전에 역방향 회전을 넣어주는 기능이다. 그런데 이것이 쿼터니온에 대한 이해가 부족할 때 제작한 거라 엉망으로 작동되고 있었다.
며칠 째 버그가 계속 나와서 작업 진행이 더디다. 실무에서는 이것들이 아티스트와 TA간의 연계작업이 되는데, 이 툴을 아티스트에게 던져줬다고 생각하면 끔찍하다.
더 이상의 버그가 나오질 않기를 바라는 마음에서 글을 새로 적었다. 기도합니다. 나무아미타불. 아멘
…
기도는 기도에서 그쳤다. 또 기능을 수정하고 추가하고…
애드온을 만들었는데도 작업이 꽤 어렵다. 특히 앉기처럼 몸과 다리가 밀착되면 특히 더 어려워진다. 사실 이건 답이 없다. 천하의 마블러스도 몸이 겹치면 구겨지고 난리인데, 본 베이스의 작업은 오죽할까.
4.17
현재는 본과 본사이의 선형 보간을 할 때 euler값을 더해 평균을 구하고 있었다. 그런데 이것이 서 있을 땐 문제가 없는데, 뒤집혀 있을 땐 문제가 됐다. 그래서 오늘도 챗GPT에게 물어봤는데…
이 코드의 문제는 회전값을 오일러 각도로 선형 보간하는 데 있을 것 같아요. 오일러 각도는 주로 한 축을 기준으로 회전하는 경우에만 유효하고, 다중 축 회전에서는 문제가 발생할 수 있어요. 특히 180도 회전과 같은 경우에는 오일러 각도가 다중 해석이 가능하기 때문에 문제가 될 수 있어요.
이 문제를 해결하기 위해선 여러 가지 방법이 있지만, 가장 간단한 방법은 쿼터니언(quaternion)을 사용하는 것입니다. 쿼터니언은 회전을 나타내는 더욱 강력한 방법으로, 다중 축 회전에서도 유효하고 180도 회전과 같은 경우에도 제대로 작동합니다.
위의 코드에서 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
세컨더리 작업이 어려울 거라 생각했지만 예상보다 파장이 컸다. 이대로 작업을 진척시키기는 어렵다. 규칙을 확실하게 정립해 두어야 한다.
뭔 소린지 하나도 모르겠지만, 애드온이 계속해서 이벤트를 체크하면 문제를 일으키는 것 같다. 시간을 리셋하는 걸까… 여튼 이같은 애드온을 2개 알고 있다. Auto Reload와 KitBash3D이다. 범인은 KitBash3D였다. 외부프로그램과 계속 통신을 하느라 모달연산자를 실행하는 모양이다.
이제는 사용하지도 않는 애드온 때문에 골머리를 썩었다. 여튼 이제 잘 작동한다. 마음의 안정이 찾아왔다.
그룹본 보간하기는 공식을 바꿔야 한다. 컬리전을 계산하며 본이 벌어지는 경우, 이를 보완해주기 위한 기능이다. 보기엔 쉬운데 저게 참 어려웠다. 화려한 것은 모두 잡기술이다. 정말 중요한 것은 보기엔 단순해도 구현난이도가 무척 높을 때가 많다.
기본은 이것이다. 이제 가운데 본을 보간하면
그 중간으로 움직인다.
예정했던 기능은 완료. 기능은 이상이 없는데, 칼주름이라 뾰족한 부분이 파묻히는 문제가 있다. 이를 수동으로 끄집어내어주는 기능이 좀 더 필요할 것 같다.
기능은 거의 완성됐지만, 몇가지 편의기능이 더 있으면 좋겠다. 오늘은 타임오버.
4.12
보간 도중 본이 안쪽으로 파고드는 버그가 있었다.
이 버그는 충돌알고리즘으로 사용했던 shrink wrap제약조건이 문제가 된다. 시스템이 갑옷 아랫쪽에 튀어나온 단면을 충돌영역으로 잡음으로서 원치 않는 오류를 내고 있었다. 컬리전은 맨몸 그 자체여야 할 듯…?
애드온은 막바지 테스트 단계. 여기에 적지는 않았지만 버그가 무척이나 많았다. 이제 없겠지. 없어야 해…!
그런데 또 정작 쓰려고 보니 내가 필요한 건 Additive와 Opaque를 한 셰이더에서 처리하는 것인지라 셰이더를 분리할 수밖에는 없다. 일단 알아는 두자.
공용 이펙트 시스템의 구축. EffectManager:CreateCommonEffect()로 호출한다. 방향성을 타는 이펙트는 Forward와 Back을 붙여 구분해야 한다. 파티클의 운동량은 단순히 스케일 마이너스로는 해결이 되지 않기 때문에, 이렇게 따로 만들어 주어야 한다.
먼지는 배경에 따라 색이 달라져야 한다. 비오는 날엔 먼지 대신 물이 튀겨야 하지만, 느낌이 잘 안사니까 그냥 색만 맞추자. 무엇보다 귀찮다.
타격 효과부터 만들어 보자. 파티클은 써도 써도 헛갈린다.
오늘의 깨달음
파티클을 뉘이고 싶다면 렌더정렬을 Local로 해주면 된다.
하지만 방향성을 타는 파티클의 경우, 회전값을 주면 짐벌락 현상때문에 회전값이 뒤틀린다. 이것 때문에 이것저것 옵션을 찾아보았건만, 마땅한 해결책을 찾지는 못했다.
아무래도 블렌더에서커스텀 메쉬를 별도로 만들어 처리해 주어야 할 것 같다.
엥…그른데 반밖에 출력이 안된다.
회전계산을 할 때 부호가 바뀌는 걸 고려하지 않는 모양이다… 이래서 공식기능으로 회전이 없는건가.
일단은 셰이더에서 양면을 모두 찍게해서 해결하기로 하자. 이게 정답이 아닐 수도 있지만, 지금으로선 이게 최선이다.
앞으로 방향성을 타는 2D식 이펙트는 모두 오른쪽을 보게 만들어야 한다는 규칙이 생겼다.
테스트를 위해 이펙트를 붙인다. 이걸 붙이느라 또 리팩토링 한가득.
헤비 이펙트. 잘 작동한다.
풀관리도 잘되고 있다. 암만 때려도 이펙트가 5개가 넘어가진 못한다.
이제 꾸며보자.
이펙트의 마스크 이미지는 싱글채널을 사용한다. R과 A중 고를 수가 있다. 기본값이 A인걸로 보아 A를 추천하는 모양인데, 셰이더에서 정보를 받을 때 4채널로 받은 후 1개만 쓰는 구조가 마음에 들지 않았다. 그래서 지금까지는 번거로워도 R채널을 쓰고 있었는데… 샘플링하면 무조건 4채널로 받는 모양이다. (에러가 나는 건 아니지만, 경고가 뜬다.) 이래나 저래나 똑같다면 그냥 A를 쓰는 게 낫겠지만…
한가지 더 의문이 든다. 싱글채널은 작업은 편하지만 최적화에 도움이 될까?
샘플링이 무조건 4채널이라면 어쨌거나 비디오 메모리를 먹는다는 이야기가 된다. 비슷한 고민을 한 친구가 있다.
비를 만들 땐 유니티에서 기본 제공되는 이미지를 사용했다. 그래도 별 탈은 없지만 동그라미는 여기저기 많이 쓰니까 수학 계산으로 기본형을 만들어두는 게 편하다. 챗GPT에게 물어보니 length를 사용하면 된단다.
half4 frag (Varyings IN) : SV_Target
{
float2 center = float2(0.5, 0.5);
float circle = pow(saturate(1-length(IN.uv - center)-0.5)*2, 2.2);
return circle * _Color;
}
길이를 구한 후 그걸 토대로 필요한 계산들을 거쳐준다. 2.2제곱은 감마 스페이스를 인간의 눈에 예쁘게 보이도록 바꿔주는 것이다. 필요한 계산들은 그 전에 하는 편이 정확하다.
셰이더가 많아졌으므로 이펙트용 폴더를 분리한다. 이전에 망한 프로젝트의 경험을 떠올리면 툰렌더의 이펙트는 의외로 Additive가 적다. 두가지 이유가 있는데, 하나는 만화적인 표현을 위한 컷아웃을 주로 사용한다는 점, 다른 하나는 툰렌더의 특성상 분위기가 밝기 때문에 애디티브 셰이더가 범용적으로 어울리지 않는다는 점이다. 따라서 Additive는 별도의 식별자를 붙이는 편이 좋겠다.
따라서 이렇게 분리된다.
카메라에 맺히는 물방울을 정리하다가 대마왕님의 블로그에서 무려 1채널로! 돋보기 효과를 내는 포스트를 찾았다.
조명은 씬에 귀속된다. 그림자의 색깔도 마찬가지다. 환경에 의해 전체적인 색조가 탁해야 하므로 셰이더 드라이버의 Start()함수에 색 조절 함수를 추가해주어야 한다. 이를 위해 아무렇게나 지어놨던 셰이더의 변수명을 통일했다. 캐릭터가 많았다면 한 고생했을 것 같은 작업이다.
비가 떨어지는 효과를 위해 이펙트를 좀 배워야 한다. 파티클의 기본 기능이야 카노를 제작할 때 지겹도록 써봤으니 대충은 알고 있지만, SDF텍스처를 라이프타임에 맞게 컷아웃하는 기능을 모른다. 언리얼의 머티리얼엔 다이나믹 파라메터라는 것이 있는데, 유니티에선 이를 어떻게 쓸까? rito15님의 블로그에 그 해답이 있다.
해서 자료를 좀 더 찾아보니 셰이더 그래프가 눈에 띈다. 안본 사이 셰이더그래프도 많이 변했다. 그리고 포스트프로세싱용인 풀스크린 머티리얼도 추가된 것 같다!
노드의 장점은 버전호환성이 좋다는 것이다. 단점은 그만큼 기능이 적다는 것이다. 하지만 포스트프로세싱엔 고급기능을 사용하지 않아 굳이 HLSL머티리얼을 고집할 필요가 없다. 이걸 쓰자.
하지만 뭔가 단단히 잘못된 것만은 알겠다….
4.4
카메라에 파티클이 주르륵 내리는 현상의 주원인은 렌더러로 밝혀졌다. 현재 사용중인 렌더러는 포스트프로세싱을 거치는데, 이 프로세싱이 적용된 상태로 렌더텍스처에 중첩되어 그려진다. ( 그렇다고 해도 드로우 절차상 그래선 안될 것 같은데… 어쩐지 그러고 있다.) 때문에 메인 카메라의 렌더러와 보조카메라의 렌더러를 다르게 사용해야 한다.
하지만 그럼에도 불구하고 피가 주르르 흐르는 현상이 여전하다.
원인은 배경. 뚫린 배경이 문제이기 때문에 검은색 Solid로 채워주면 문제가 해결된다. 이걸 알아내느라 반나절이 걸렸다.. 아휴.
이제야 원하는 결과를 얻었다. 눈을 내리려는 건 아니다.
이제 이 파티클들로 카메라에 맺힌 물방을처럼 굴절을 일으키고 싶다. 씬컬러(셰이더 그래프에선 BlitSource라고 부른다.) 의 Screen Position에 UV왜곡치를 더해 볼록하게 표현하면 될 것이다. 3가지 정보가 필요하다. X, Y, 그리고 마스크. 공교롭게도 노말맵의 정보와 비슷하다. 물방울을 모델링해서 노말을 뽑자.
노말맵이 곡률을 계산하고 있어서 얼추 볼만하게는 나올 줄 알았는데 생각보다 밋밋한 결과를 보여줬다. 좀 더 정확한 구현을 위해선 사인함수를 추가로 사용해 곡률을 더해주어야 했다.
그런데 이렇게 쪼그맣게 쓸거라, 사실 저렇게까진 안해도 된다. 그냥 한 번 구현해 보고 싶었다. 실무라면 일정에 쫓겨서 안했을 듯.
오… 나쁘지 않은데 달이 너무 크다. 애초에 웹툰용 소스라 하늘이 넓게 표시되지는 않기 때문이다. 일단 밑져야 본전이니 AI를 돌려보자.
그게 무슨 밤이야…
못쓰겠다.
스케치는 완료. 이제 UV를 펼 차례. 하지만 타임오버이므로 오늘은 여기까지.
3.31
물건이 얼마 없어서 쉽게 구웠다. 유니티로 넘겨보자.
데이터가 가벼워서 큰 어려움은 없었다. 완성이 쉽게 돼서… 좀 성의없게 느껴지는 감은 있는데, 꼭 고생해야 정성가득한 작업은 아니라고 생각하기로 하고…(직업병이다.)
다음 배경의 주제는 ‘비오는 날’이었는데, 이것이 고민이다. 물에 젖은 캐릭터를 표현하려면 지금보다 많은 것이 필요하다. 옷도 젖어야 하고, 스펙큘러도 좀 더 표현되어야 하고, 바닥에 물반사, 카메라의 물방울 굴절, 번개가 치는 찰나의 흑백음영 셰이딩 등… 많은 것이 필요하다. 나열된 요소 중 몇몇개는 이미 불가능한 것들이다. 게다가 이것이 게임하기에 좋은가?… 에 대한 근본적인 고민이 있다.
하지만 우중충한 배경을 꼭 해보고 싶긴 하다. 비가 약간씩만 내리는 것은 어떨까. 그럼 캐릭터는 안젖어도 될테니까… 우선 그렇게 방향을 잡고 진행해 보기로 하자.