Sumo Digital이 Spyder의 정교한 애니메이션 시스템을 개발한 방법 공유

Sumo Digital 수석 프로그래머 Nick Edwards 및 Sumo Digital 수석 프로그래머 Andy Chapman

2003년에 설립된 후로 수많은 상을 차지한 Sumo Digital은 영국의 셰필드, 노팅엄, 뉴캐슬, 브라이튼(The Chinese Room), 리즈(Red Kite Games), 리밍턴 스파(Sumo Leamington과 Lab42), 워링턴과 인도의 푸네에 있는 9개 스튜디오에서 700명 이상의 직원을 고용하고 있습니다. Sumo Digital은 모든 게임 플랫폼과 장르에서 성공을 거뒀으며 Microsoft, Sony, SEGA, 2K 등 대형 퍼블리셔의 다양한 게임 타이틀 제작에 참여하며 그 다재다능함과 고유의 기술력 그리고 창의력을 인정받았습니다.

Sumo Digital은 Sumo Group plc. 산하에 있습니다.

자세한 정보는 sumo-digital.com에서 확인하세요.
안녕하세요. 스모 디지털(Sumo Digital)의 수석 프로그래머 닉 에드워즈(Nick Edwards)와 앤디 채프먼(Andy Chapman)입니다. 스모 디지털(Sumo Digital)에서는 사내 게임잼을 정기적으로 개최해서 우승자에게는 게임 콘셉트를 더 키우고 개발하는 데 필요한 시간과 자원을 제공합니다. 언리얼 엔진을 기반으로 한 스네이크 패스(Snake Pass)는 이전 게임잼 우승작이 실제 상품으로 출시된 사례로, 멀티 플랫폼으로 성공을 거뒀습니다.

스파이더(Spyder)는 이러한 경로로 탄생한 최신작입니다. 스파이더가 게임잼에서 우승하면서 세 명의 디자이너로 구성되어 있었던 팀의 규모를 늘려 매력적인 소개용 데모를 제작하고 애플 아케이드(Apple Arcade)에 출시까지 할 수 있었습니다. 이를 위해 스파이더가 사양이 그리 높지 않은 아이폰 6(iPhone 6)와 같은 모바일 기기에서도 구동되도록 개발해야 했습니다. 타이틀이 제작 단계에 들어서면서 팀 규모는 더욱 커졌습니다. 그때 저희도 블루프린트 기반의 기존 에이전트 8(Agent 8) 프로토타입을 C++로 다시 작성하는 역할로 투입되어 더욱 개성 있고 관리 가능하며 성능에 문제 없게 만드는 것을 목표했습니다.

여기서 목표 달성에 필요했던 사항을 자세히 확인할 수 있습니다.
 

애니메이션

첫 단계
주인공 에이전트 8의 최종 버전을 개발하기 전에, 게임잼과 스파이더 프로토타입용으로 초기에 반복 작업했던 캐릭터를 검토해 봤습니다. 아티스트와 애니메이터들은 거미의 캐릭터성을 살리기 원했지만, 예전 버전들에서 사용한 프로시저럴 애니메이션으로는 쉽지 않았습니다.
 

프로시저럴 애니메이션 방식에서 벗어나려 하자 무수한 어려움에 직면하게 되었습니다. 다리가 여러 개인 캐릭터를 움직이기에 충분하면서도 목표 모바일 플랫폼에서 GPU 스키닝을 사용하기에 본이 너무 많지는 않은 스켈레톤이 필요했습니다. 개발, 디스크, 메모리 예산을 벗어나지 않으려면 방대한 보행 애니메이션 세트를 만드는 일을 피해야 했습니다. 따라서, 한정된 애니메이션 세트만으로 완전한 사용자 컨트롤을 통해 다양한 환경을 탐험할 수 있게 해야 했습니다.
 
에이전트 8 스켈레톤(외골격) 장착
아트를 고려한 캐릭터를 개발하는 데 있어 매우 중요했던 요건 중 하나는 애니메이터가 에이전트 8을 생동감 넘치게 만들 수 있도록 거미의 스켈레톤을 만드는 일이었습니다. 이런 스켈레톤을 만드는 일은 쉽지 않았습니다. 저희가 목표했던 모바일 플랫폼에서는 GPU 스키닝에 사용 가능한 본 개수를 75개로 제한했는데 거미 캐릭터의 다리 수가 제한을 초과했기 때문입니다. 제한을 넘어가면 언리얼 엔진은 CPU 스키닝으로 넘어가므로 퍼포먼스가 크게 저하됩니다.

다행히도 저희는 엔진 코드를 자세히 살펴볼 수 있었습니다. 그리고 Metal API를 사용하여 ES3.1 플랫폼에서 최대 256개의 본을 지원하도록 수정할 수 있었습니다. 로그인을 해야 접속 가능한 깃허브(GitHub)의 Pull Request에서 저희의 변경 내용을 확인할 수 있습니다. 스파이더가 애플 아케이드(Apple Arcade) 타이틀로 출시되며 Metal API를 모든 목표 플랫폼에 사용하여 이 문제를 해결할 수 있었습니다.
 
제자리 잡기
에이전트 8의 애니메이션을 제작하면서 가장 어려웠던 점은 여러 개의 다리를 처리하고 그 다리가 환경과 상호작용하게 만드는 것이었습니다. 대부분의 게임에서 캐릭터는 한 평면 위에서 움직입니다. 경사나 계단을 오르내릴 때, 캡슐은 수직을 유지하며, 개발자는 애니메이션된 발이 지면을 통과하는 것을 막기 위해 IK(역운동학)를 이용합니다. 현재 발의 위치에서부터 약간 아래쪽까지 피직스 트레이스를 수행하여 발을 제약할 표면을 발견할 수 있습니다.

에이전트 8에서는 환경 내 여러 평면을 동시에 밟고 다닐 캐릭터를 처리할 다른 솔루션이 필요했습니다. 수직 벽 위로 걷는 경우와 같이 에이전트 8이 다른 표면으로 이동할 것을 예상하고 다리 움직임의 아크를 조정하는 대신에 표면을 따라 발을 끌게 되기 때문에 '반응형' IK를 수행하는 것만으로는 부족했습니다. 많은 지오메트리 조합이 가능하다는 것은 평평한 단일 XY 평면에 사용할 애니메이션 세트만을 생성할 수 있다는 뜻이었습니다. 따라서 위의 벽 횡단 사례처럼 다른 표면으로의 이동을 예측하고 거기에 맞춰 애니메이션을 변형할 '예측형' IK를 개발해야 했습니다.
달성하려는 기본 결과를 보여주는 그림입니다.
외부 모퉁이에 가만히 서 있을 때 IK 에이전트 8의 적절한 발 위치를 찾는 일은 비교적 간단한 작업이었습니다. 문제는 이동할 때 발생했습니다. 워크 사이클 동작을 변행해 벽이나 가장자리로 구부릴 수 있도록 각 발이 놓일 위치를 예측해야 합니다. 그렇지 않으면, 캐릭터가 이동할 벽이나 모퉁이 속으로 발이 통과하며 끌게 될 수 있습니다.
에이전트 8의 예측형 IK는 발이 놓일 위치를 예측합니다.
컴포넌트 스페이스에서 발이 출발하고 도착하는 위치를 계산하고 저장하기 위해 로코모션 애니메이션에 애니메이션 모디파이어를 사용했습니다. 먼저, 애니메이터는 모든 애니메이션에 루트 모션을 제작해서, 각 애니메이션 프레임의 모션을 결정할 수 있게 만들었습니다. 그런 다음, 애니메이션 모디파이어가 발이 배치된(Z=0 XY 평면에 있거나 근처에 있는 발 본) 애니메이션 기간을 표시합니다. 그런 다음, 프레임마다 현재 루트 트랜스폼과 발이 바닥에 놓였다고 기록된 위치의 루트 트랜스폼 사이의 상대 트랜스폼을 계산할 수 있습니다. 그런 다음, 발이 놓이거나 올릴 때 발의 컴포넌트 스페이스 위치를 이 상대 트랜스폼에 곱하여 최종값을 산출합니다. 이러한 위치 벡터를 X, Y, Z 컴포넌트로 분해하고 애니메이션에서 별도의 플로트 커브에 저장합니다. 각 발이 놓인 위치들의 플로트 커브 3개와 발을 든 위치들의 플로트 커브 3개를 합하면 총 36개의 플로트 커브가 나옵니다.
타깃 발 위치는 각 다리 및 벡터 컴포넌트의 플로트 커브로 인코딩됩니다.
발을 놓고 든 위치를 애니메이션 모디파이어가 생성합니다.
모든 곳을 트레이스하기
발을 놓고 든 위치를 파악할 수 있는 데이터를 확보하면 예측형 IK가 가능해집니다. IK 타깃 위치를 찾는 모든 로직은 커스텀 AnimGraph 노드 안에 포함되어 있습니다. 처음에는 이 작업을 AnimInstance에서 수행했지만, 지연시간이 발생하여 부정확한 결과가 나왔습니다. 저희는 관련 애니메이션이 최신 커브 데이터에 맞게 틱을 돌려 샘플링한 후, LegIK 노드가 데이터를 끌어올 가상 본을 업데이트하기 전에 트레이스와 계산을 수행할 수 있게 해야 했습니다. 가상 본은 다른 AnimGraph 노드 간에 트랜스폼을 공유할 스크래치 영역의 역할을 합니다. 할 수만 있었다면 AnimGraph 노드를 더욱 재사용 가능한 섹션으로 분류했을 것입니다. 나중에 프로덕션에 사용 가능한 상태가 되면 ControlRig이 솔루션이 될 것으로 보입니다.

저희 커스텀 AnimGraph 노드가 데이터를 사용하는 방식을 구현하는 과정을 자세히 살펴보기 위해, 먼저 에이전트 8 스켈레톤의 각 다리에 스태틱 레퍼런스 소켓을 정의하여 발을 떼었다 놓는 위치까지 스윕 구체 트레이스를 수행했습니다. 이것들은 대략 각 고관절 조인트의 위치에 있었습니다. 단일 평면 표면에 있는 거미의 경우, 이러한 트레이스 결과는 엔드포인트여야 합니다. 옆의 표면과 직각을 이루는 표면에서는 일부 다리의 트레이스 결과가 표면의 히트를 반환할 것입니다. 가장자리 옆의 표면에서 일부 다리의 트레이스 결과는 히트를 반환하지 않을 것입니다.
표면 감지에 이용하는 많은 트레이스 중 세 개를 보여주는 그림입니다.
첫 번째 트레이스가 히트를 반환하지 않으면, 추가 트레이스를 수행하여 표면을 찾습니다. 저희는 성공 확률이 높은 순서로 정렬된 일련의 사전 정의 기법을 사용합니다. 발을 놓았다 떼는 위치를 쭉 따라 첫 번째 추가 트레이스를 수행하여 약간 굽은 지오메트리(예: 커다란 구체)에서 표면을 찾습니다. 이 트레이스가 실패하면, 이전 트레이스의 엔드포인트에서 거미를 향해 안쪽으로 다시 트레이스를 수행하여 여러 개의 단순한 외부 모퉁이를 처리합니다. 히트를 반환할 때까지 추가 트레이스를 수행합니다. 아무것도 찾지 못하면 조기 중단하고 아무것도 하지 않습니다.

표면을 찾으면, 계산을 해서 몇 가지 변수로 평면에 놓인 발의 위치를 알아냅니다. 이러한 변수 중 하나는 발을 멀리 놓을 수 있는 거리로, 고관절 조인트와 발이 놓이는 위치 사이의 현재 거리를 사용하여 발이 과신장되는 일 없이 발이 이동할 추정 범위를 결정합니다. 
과신장이란 다리가 과도하게 늘어나는 현상입니다.
충격 표면을 따라 타깃 발 위치를 '투영'한 다음, 발이 계속해서 표면에 위치해 있는지 확인하기 위해 추가적인 피직스 트레이스를 수행합니다. 표면에 없다면 위 과정을 반복하여 또 다른 표면을 찾은 다음, 그 표면에 대신 투영을 시도합니다. 저희 투영 로직은 노멀 벡터가 완전히 반대 편으로 향한 표면을 처리할 정도로 강력하므로, 에이전트 8가 시야에서 완전히 가려진 표면 쪽에 다리를 놓은 경우에도 발 위치를 제대로 파악할 수 있습니다.

한 가지 주목할 점은 대부분의 경우 트레이스를 두 배로 늘린다는 것입니다. 스파이더는 대부분의 환경과 오브젝트에 두 개의 별도 복합 콜리전 메시를 사용합니다. 하나는 렌더 메시의 '간소화된' 근사치입니다(여전히 삼각형 메시지만 디테일이 더 적음). 다른 하나는 풀 렌더 메시 콜리전 자체입니다. 캐릭터 움직임과 카메라 시스템이 간소화된 콜리전을 가장 많이 사용하며, 애니메이션 시스템만 렌더 메시 콜리전을 명시적으로 사용합니다. 간소화된 콜리전은 대부분 '걸을 수 있는' 콜리전 채널과 관련되며, 렌더 메시는 비저빌리티 채널과 관련됩니다. IK의 트레이스는 둘 다 사용합니다. 간소화된 콜리전은 발을 놓을 위치를 광범위하게 파악하는 데 더 유용합니다. 그 이유는 이 콜리전에서는 리벳 및 기타 환경 요소가 표현되지 않기 때문입니다. 보이는 지오메트리에 발이 있는지 확인하기 위해서 렌더 메시 콜리전을 사용합니다.

퍼포먼스에 대한 메모. 처음에는 일부 목표 플랫폼의 사양과 쿼리하는 콜리전의 복잡도를 고려하여 너무 많은 트레이스가 수행되지 않도록 상당한 주의를 기울였습니다. 사실 너무나도 신중하게 해서, 초기 IK 작업의 많은 부분에서 각 프레임의 각 다리에 단일 트레이스를 사용했으며, AnimNotifies를 사용하여 트레이스 대상을 전환했습니다. 심지어 타깃 발 위치가 변경될 때만 한 번 트레이싱하고 나머지 각 프레임은 전혀 트레이싱하지 않는 방안도 고려했습니다. 그러다 결국 트레이스 수행 비용이 생각보다 크지 않다는 것을 알게 되었습니다. 출시용으로 구현한 버전에서는 두 유형의 콜리전에서 트레이스 전략이 성공할 때까지 각 프레임에서 각 발이 떨어지고 놓일 위치를 트레이스했습니다. 결과적으로, 에이전트 8이 복잡한 지오메트리를 통과할 때 애니메이션 시스템은 프레임당 최대 200개의 피직스 쿼리를 실행할 수 있습니다. 필요하다면 이 수치를 최적화했겠지만, 한 캐릭터만 이 로직을 실행하는 경우 전체 프레임 시간에 크게 영향을 미치지 않았습니다.
 
모션 변형
발을 떼고 놓는 트랜스폼을 찾는 로직을 마련했으니 이제 이걸 사용하여 어떤 식으로든 애니메이션에 영향을 줘야 했습니다. 트랜스폼을 사용하여 원시 애니메이션의 지면을 생성하는 스플라인으로 워핑한다는 아이디어를 가지고 이 문제에 접근했습니다. 여전히 컴포넌트 스페이스에서 발을 떼고 놓는 위치의 기준점이 있으며, 이를 사용하여 발이 현재 이 두 위치 사이에 있는지에 대한 알파 값을 알아낼 수 있습니다. 이 알파 값을 사용하여 스플라인에서 사용할 포인트, 즉 새로운 '지상' 위치를 결정할 수 있습니다. 그 다음으로는, 기준 지상 위치 및 발의 현재 위치의 상대적인 변화를 계산한 다음, 이 상대적인 변화를 새 스플라인 지상 위치에 적용합니다. 그 결과, 스플라인을 새로 변형된 지상 '평면'으로 사용하여 발이 있어야 하는 위치가 결정됩니다.
횡단하는 지오메트리와 상관없이 똑같은 로코모션 애니메이션 세트를 사용합니다.
거미 발 전체에 이 과정을 수행한 다음, 계산 결과를 일련의 가상 본에 통합하여 FABRIK IK 솔버를 구현하는 LegIK 노드로 전달합니다. LegIK 노드는 4개의 다리당 본 체인이 있었고 바로 거미에 사용할 수 있었습니다.
발을 놓을 위치를 알아내려면 표면을 감지하는 일이 매우 중요합니다.
역동성
다리 작업 외에도 캐릭터 애니메이션의 다른 측면을 개선하는 데에도 노력을 기울였습니다. 에이전트 8가 환경 내에서 이동할 때 다른 몸의 부위가 어떻게 반응하는지를 주로 작업했습니다.

안테나
에이전트 8의 후면 해치에 장착된 라디오 안테나가 개선된 부위 중에 하나입니다. 안테나는 주인공이 무선 통신을 수신하는 기능을 제공하고 캐릭터의 방향을 보완하는 데 도움을 줍니다. 따라서 중력의 상대적인 방향에 따라 안테나에 역동성을 부여하는 것이 중요했습니다. 이를 구현하기 위해 AnimDynamics 노드를 활용하여 각 안테나에 적용된 본 체인에 동작을 추가했습니다. 저희 팀의 애니메이터는 노드의 다양한 세팅을 실험해보며 커스텀 코드를 개발할 필요 없이 원하는 결과물을 구현할 수 있었습니다. 안테나가 중력에 따라 기우는 현상을 구현했을 뿐만 아니라 에이전트 8이 주변을 돌아다니거나 유휴 동작을 할 때 자연스러운 부수적인 애니메이션을 별도의 작업 없이 얻을 수 있었습니다. 안테나는 캐릭터에 자연스러운 튕김과 개성을 더했고, 개발이 진행됨에 따라 딜리 바퍼(deeley boppers)라는 애칭까지 얻게 되었습니다.
 
방향의 변화에 따라 반응하는 에이전트 8의 안테나

바디

안테나를 역동적으로 바꾸고 다리 IK를 충분히 개선하자, 에이전트 8의 바디에 어떻게 물리적 반응을 적용할지 고민해 봤습니다. 이걸 고민한 이유는 거미가 거꾸로 매달려 있을 때 바디가 중력의 영향을 받아 표면에서 더 멀리 떨어지게 만드는 효과를 구현하고 싶었기 때문입니다. 이는 주 바디 본의 트랜스폼을 수정하여 이동/회전 오프셋을 제공해야 함을 의미했습니다. 달리 이용할 수 있는 시스템이 없었다면 다리를 포함한 전체 메시가 오프셋되었을 겁니다. 하지만 저희에겐 컴포넌트 스페이스 IK가 있어 문제가 없었습니다. 바디가 이동한 쪽으로 다리 방향을 재정렬할 수 있었으니까요.

바디에 중력의 영향을 가하는 데 성공하자, 이번에는 추가적인 효과를 구현해보기로 했습니다. 프로젝트 초기부터 캐릭터 팀원끼리 자주 논의한 아이디어가 있었는데 그게 바로 에이전트 8에 무게감을 부여하는 일이었습니다. 움직이기 시작하거나 멈출 때, 또는 이동하는 물체 위에 가만히 있을 때, 바디는 상황에 따라 반응하죠. 이걸 구현하기 위해 거미의 실제 위치를 타기팅하는 벡터 스프링을 활용하여 바디의 이전 스프링 위치로부터 새 위치 쪽으로 보간하는 겁니다. 이동 중 정지와 같은 특정 상황에서 바디의 스프링 위치에 힘을 가해서 바디가 흔들리는 정도를 늘렸습니다. 또한, 현재의 바디 스프링 오프셋은 피치와 롤 회전을 파생하는 데에도 사용되며, 이러한 요소들과 이동 오프셋은 중력 효과와 함께 누적된 후 바디의 본에 적용됩니다.
 
에이전트 8의 바디 스프링이 재미있고 역동적인 움직임을 만들어 냈습니다.
장치
에이전트 8이 세상과 주로 인터랙션하는 방법 중 하나가 바로 장치를 사용하는 것입니다. 게임에는 여러 장치가 있는데, 각 장치는 플레이어가 레벨을 진행해 나가면서 촉각 경험을 할 수 있게 만듭니다. 장치마다 각각의 고유한 경험을 선사하며, 각기 다른 방식으로 제어되고 애니메이션이 적용됩니다. 저희는 게임플레이 동작이 애니메이션 자체와 분리되도록 장치를 제작했습니다. 즉, 사용할 때 어떤 모습으로 보일지 완전히 제어할 수 있다는 의미입니다.
저게 어떻게 저기에 들어가죠? 거미 과학 덕분이죠.
각 장치에는 물리적 표현을 포함하는 액터 클래스가 연결되어 있으며, 각 장치는 두 개 이상의 스켈레탈 메시 컴포넌트(팔과 장치 머리에 하나씩)로 구성됩니다. 팔의 스켈레톤은 모든 장치가 공유하며, 각 머리에는 고유 스켈레톤이 있습니다. 메시에서 포즈 복사(Copy Pose From Mesh) AnimGraph 노드를 활용하여 공유된 본 트랜스폼을 에이전트 8에서 팔로, 팔에서 머리로 전파합니다. 대부분의 장치에는 수동으로 계산된 각종 IK를 구현하여 원하는 효과를 내는 애니메이션 블루프린트가 있습니다.
 
감정 반응
에이전트 8의 애니메이션을 완벽하게 제어할 수 있게 되자, 감정 반응 시스템을 개발해서 캐릭터 특성을 더욱 살리기로 했습니다. 특정 행동을 하거나 게임 속 특정 위치에 있을 때 감정을 유발하는 겁니다. 즉, 걸어 다니며 다양한 표정을 짓거나 가만히 있을 때 건들거리거나 유휴 상태의 애니메이션을 트리거합니다. 게임 속 12가지 감정 하나하나마다 최소 1개의 건들거리는 유휴 애니메이션이 있습니다. 이러한 애니메이션은 애님 그래프에 있는 커스텀 노드의 인스턴스를 통해 재생됩니다. 커스텀 노드는 Random Sequence Player 노드와 비슷하지만, 확률에 따라 목록에서 하나의 애니메이션만 선택하고 랜덤 재생 속도로 랜덤 횟수로 루핑한 다음 재생을 중지합니다. 이를 통해 애니메이션 버킷, 확률(%), 루프 횟수, 재생 속도가 서로 다른, 노드의 두 인스턴스 간에 전환이 가능해집니다.

각 유휴 애니메이션에서 IK가 제대로 작동하게 만드는 것이 핵심이었습니다. 이건 IK 시스템을 개발할 때 테스트해봤던 로코모션 애니메이션과는 상당히 다릅니다. 다행히도 저희가 만든 솔루션을 약간만 수정하니 새 애니메이션이 제대로 작동했습니다.

로코모션

360도 내비게이션과 자유로운 움직임은 축복이자 악몽이기도 합니다. 플레이어에게는 기존 3D 게임에서 보기 힘들던 다양하고 자유로운 움직임을 선사하는 반면, 개발자에게는 골칫거리가 되기도 합니다. 전통적인 1인칭/3인칭 게임에 사용하는 흔한 시스템(컨트롤, 카메라 등)이 플레이어가 벽이나 천장을 걷을 땐 망가져 버립니다.
 
문제
저희는 프로젝트를 시작할 때 이미 디자인 요구 사항과 공수(맨아워) 제한을 못 박아둔 제한 목록을 작성했습니다. 마감일과 제작할 레벨 콘텐츠 양을 고려해야 했으니까요.
 
  • 레벨 힌트나 표시를 만들지 않습니다('이건 벽', '이건 가장자리', '이건 모퉁이' 등). 이게 인시 제한이었습니다. 계속 제작되는 레벨 레이아웃을 따라갈 방법이 없었으니까요.
  • 상대적으로 자유로운 모양의 지오메트리: 실제 세상이 그리드로 만들어지진 않았으니까요.
  • 움직이는 표면: 가만히 있으면 흥미가 떨어지겠죠.

저희가 계획한 레벨 지오메트리에 내재된 이러한 잠재적 복잡도와 때로는 동적인 특성으로 인해 저희는 어떻게든 거미의 주변 환경을 실시간으로 확인할 수 있어야 했습니다.
 
간단한 시작
거미가 걸어 다닐 주변의 표면을 결정하기 위해 일종의 트레이싱이 필요하다는 것을 알았기에, 저희는 플로팅 폰 무브먼트(Floating Pawn Movement) 컴포넌트를 토대로 다중 구체 트레이스에 대한 단일 호출과 중력을 추가했습니다.

이건 부수적인 이야긴데, 길이가 0인 구체 트레이스를 수행할 때 문제가 있었습니다. 그리고 엔진이 문제를 감지해서 X축 IIRC에서 길이를 1로 강제로 지정했습니다. 그래서 이걸 코멘트 아웃으로 처리하자 더 이상 아무런 문제도 없었습니다.

이러한 플로팅 폰 솔루션은 여러 가질 서둘러 집어넣은 테스트 레벨에서 꽤 괜찮은 결과를 보여줬습니다. 이 트레이스 호출에서 상당한 정보를 얻고 있었습니다. 나중에 알고 보니 이렇게 되었던 이유는 수많은 개별 큐브로 레벨을 구축했기 때문이었습니다. 다중 구체 트레이스는 트레이스에 대해서는 여러 개의 히트 결과를 반환하지만 각 오브젝트 별로는 하나의 히트 결과만 반환하므로 첫 번째 테스트 레벨에선 적합했던 것이었습니다.

저희가 단일 스태틱 메시에 이 접근법을 시도하자마자, 트레이스에서 히트 결과가 하나만 나왔고 불충분한 정보를 얻을 수밖에 없었습니다.
 
교차하기
당연히 다음 접근법은 구체 트레이스 수를 늘리는 것이었습니다. 이는 거미의 수직축을 따라 내려오는 트레이스 1개와 거미의 동서남북에 위치한 나침반 트레이스 4개로 구성되었습니다.

이렇게 처리하니 상황이 훨씬 나아졌으며, 문자 그대로 5배 많은 정보를 얻을 수 있었습니다. 이제 이 정보를 어떻게 활용할지 알아내야 했습니다.
 
플라잉 카펫
변수 이름과 관련하여 뭔가 빼먹은 것 같습니다. 트레이싱할 때 수집하면서 발견한 표면 배열로 계산한 가상 '이동 표면(movement surface)'을 이름으로 사용했는데, 사실 '플라잉 카펫'이 더 어울렸을 것 같습니다.
감지된 표면 – 회색 정사각형은 표면을 나타내고, 빨간색 화살표는 표면이 얼마나 많은 영향을 주는지를 나타냅니다.
새로 감지된 표면 목록을 반복처리했습니다. 먼저, 부적합한 면을 모두 버립니다. 부적합한 면이란 거미 위에 있거나 반대 방향으로 되어 있어 거미가 걸을 수 없는 면을 말합니다. 그런 후, 거미의 오리엔테이션에 기여할 정도 그리고 거미의 위치에 영향을 줄 정도에 맞춰 나머지 면에 웨이트를 더했습니다. 이 웨이트를 계산하기 위해 다양한 공식을 넣어보고 시험한 결과, 최종 공식은 다음 변수의 웨이트 적용 합계로 구성했습니다:
 
  1. 표면 히트 위치와 거미의 원점 간의 거리는 표면을 찾기 위해 스캔한 최대 거리에 따라 정규화되었습니다.
  2. 표면 히트 노멀과 거미의 업 벡터의 내적입니다.
  3. 거미의 앞쪽 벡터와 감지된 표면의 히트포인트 방향의 내적입니다.

이 중 첫 번째가 아래와 같은 커브를 보여줍니다.
거리에 따라 거미의 방향에 더 약하게 적용되는 표면의 영향을 보여주는 그래프입니다.
두 번째와 세 번째를 더해 복잡한 3D 표면이 생겨났습니다. 이를 통해 더 중요하다고 생각되는 표면 쪽으로 거미의 회전을 편향되게 만들 수 있습니다.
그런 다음, 정제된 목록의 콘텐츠를 사용하여 가상 '이동 표면'의 위치와 방향을 계산했습니다.
여기에 가상 이동 표면이 노란색으로 표시되어 있습니다.
이 이동 표면은 거미의 위치 및 오리엔테이션의 타깃이 아니라 거미의 현재 레퍼런스 프레임을 결정하는 안내 역할을 맡습니다. 이 정보는 거미가 '매달릴' 방향과 방향 거미의 방향을 정할 방법을 알려줍니다.

이 시점에서 저희 시스템은 꽤 잘 작동했고, 이 시점의 샌드박스 테스트 수준에서 이리저리 이동하는 데에도 별 문제가 없었습니다. 아쉽게도 저희의 테스트 레벨은 아직 극단적인 경우에 발생할 문제를 보여주지 않고 있는 것 뿐이었습니다.

이동 시스템은 거의 박스 형태였던 초기 테스트 레벨의 미니어처 언덕, 계곡, 절벽, 벽에서는 훌륭하게 작동했습니다. 문제는 아트 팀에서 더 사실적인 레벨 지오메트리를 만들면서 불거지기 시작했습니다.

저희가 '핑퐁'이라고 부르는 문제가 있는데, 이 문제는 거미의 방향 시스템의 피드백 루프에서 나왔습니다. 거미가 레벨을 오갈 때 거미의 방향을 올바르게 제어하고 신속히 반응할 수 있는 시스템이 필요했기 때문에, 저희는 이 회전 운동을 전적으로 감쇠할 수 없었습니다.
다른 큰 문제는 270도보다 각도가 큰 외부 코너를 거미가 최고 속도로 이동할 때 발생했는데, 로드러너에게 속은 코요테처럼 거미가 지오메트리를 넘어 푸른 하늘로 날아가 버리는 현상이 있었습니다.

확실한 문제는 구체 트레이스의 현재 세트가 절벽을 '감지'하지 못하던 것이었습니다. 절벽을 봐야 빨리 이동하면서 그 면에 붙어 다닐 수 있는데 그렇지 못했던 거죠.
이 문제는 거미가 절벽 가장자리에 접촉할 때 그 주위를 둘러싸기 위해 거미의 방향을 조정하는 방식에 의존했기 때문에 발생했습니다. 이렇게 하면 거미의 수직 트레이스가 절벽을 감지할 수 있어야 하지만, 거미의 빠른 이동 속도 때문에 예상대로 되지 않은 것이었습니다. 한 마디로 더 많은 트레이스가 필요했습니다.
 
한숨 돌리기
결국에는 두 문제 모두 추가 트레이스를 수행하여 해결했지만, 아직도 시스템은 완전하지 않은 상태였습니다. 어디나 마찬가지지만 반복적인 작업을 통해 개선할 수밖에 없었죠.

이 시점에서 시스템은 거미에 상대적인 방향 벡터 테이블을 사용하고 있었습니다. 기존 시스템에 트레이스 한 개를 추가하려면 이 배열에 기존에 있던 각 항목에 벡터를 하나씩 추가해야 했습니다. 이렇게 해서 핑퐁 오류의 초기 테스트 사례는 해결했지만, QA에서 다른 문제가 발견됐습니다.
거미 아래에서 만나는 트레이스를 생성하니 문제가 해결된 것처럼 보였습니다. 하지만 여분의 트레이스를 추가하다 보니 시스템 업데이트가 상당히 까다롭다는 사실을 알게 됐습니다. 트레이서의 모양을 변경하는 것은 상당히 손이 많이 가는 작업이었으며, 트레이싱하는 각 방향의 개별 벡터를 계산해야만 했습니다. 한 마디로, 전체 업데이트 과정을 훨씬 더 간소화하려면 이 부분을 정교하게 개선할 필요가 있었습니다.

이 코드를 작게 리팩터링하여 연결된 벡터 배열을 통해 여러 개의 트레이스를 처리함으로써 커브를 효과적으로 트레이스할 수 있었으며, 여기에 약간의 코드만 추가해서 거미 주변으로 360도로 회전 스윕을 수행하여 개별 트레이스를 확보할 수 있었습니다.
캐시
공교롭게도, 여전히 월드의 모든 트레이스가 핑퐁 회전 진동 문제를 완전히 해결하지는 못했습니다. 문제가 크게 줄기는 했지만, 끝나지 않고 여전히 눈에 띄었습니다.

근본적인 원인은 다음과 같은 피드백 루프 때문이었습니다.
거미 방향 로직 피드백 루프입니다.
거미의 방향이 표면 지오메트리의 변화에 반응할 수 있어야 하니, 거미의 회전 감쇠 수준을 더 높일 수는 없었습니다.

피드백 루프의 진행을 늦출 수 없었기 때문에 저희의 해결법은 문제가 가장 두드러지게 나타나는 경우 즉, 거미가 가만히 있을 때 루프를 끊는 것이었습니다.

아래 다이어그램의 빨간색 화살표는 루프를 끊은 지점을 보여줍니다.
중단된 거미 방향 로직 피드백 루프입니다.
이렇게 하기 위해 감지된 표면의 캐시를 유지했습니다. 이는 거미가 고정된 거리를 초과 이동하거나 지정된 횟수를 초과하여 회전하거나 캐시된 표면 하나가 움직이지 않는 이상 유효했습니다. 이러한 일이 발생하면 캐시를 지우고 다시 시작했습니다.

이 방법으로 마침내 문제를 해결했고, 이로 인해 일정 기간 동안 수행되는 스캔의 양이 크게 줄어드는 긍정적인 효과도 발생했습니다.

 
피직스의 타협
스파이더 이동 시스템을 플로팅 폰 무브먼트 샘플 코드를 기반으로 개발하며 저희는 움직임이 사실적인 캐릭터를 만들고자 노력했습니다. 초기 게임 기획에는 피직스 관련 속임수가 필요하지 않았으며, 중력은 항상 우리가 걷는 표면 방향으로 작용했습니다.

따라서, 게임플레이를 위해 피직스 관련 기능을 게임에 영향을 주게 될 캐릭터의 조작법에 추가해야 할 상황이 되자, 저희는 두 방법 중 하나를 선택해야 했습니다.
 
  • 메인 캐릭터의 피직스를 활성화하고 힘을 사용하도록 모든 이동 코드를 변환한다.

또는
 
  • 게임플레이가 더 용이하도록 필요한 힘을 시뮬레이션한다.

저희의 선택은 후자였습니다.

결정에 영향을 미친 주된 요소는 프로젝트 시작의 계기였던 거미 캐릭터였습니다.

이 거미는 블루프린터로 작성되었으며, 사내 스모 디지털 게임잼 블루프린트의 초기 프로토타입에서 진화한 캐릭터였습니다. 이 캐릭터는 피직스 오브젝트로 구축되었고 모든 움직임, 회전, 표면 인력은 캐릭터에 힘을 추가해서 구현했습니다. 이 캐릭터를 사용해 봤던 모든 사람이 다 재미있고 흥미진진한 게임플레이와 물리적으로 정확한 시뮬레이션의 균형을 맞추는 일은 쉽지 않다고 입을 모았습니다.

저희에게 분발하라는 소리였죠. 저희는 쉽게 수정하고 균형을 맞출 시스템이 필요했습니다. 그래서 두 번째 옵션을 선택했습니다.
 

나머지 퍼즐 맞추기

앞서도 말씀드렸듯이 거미에 몇 가지 기능을 추가해야 했습니다.

디자인 팀에서는 저희가 언리얼 엔진의 피지컬 머티리얼, 특히 마찰 시뮬레이션 방식을 구현하기를 원했습니다.

저희는 거미의 피직스 시뮬레이션을 이미 작업 중이었으므로 요청받은 끈끈한 표면을 지원하기 위해 작업에 마찰 연산을 더하는 건 쉬운 일이었습니다.

두 번째 요청은 게임 내 커스텀 윈드 시스템을 지원해달라는 것이었습니다.

이 커스텀 윈드 시스템은 레벨에서 게임플레이 오브젝트들을 이리저리 날려 버릴 수 있도록 개발되었으므로, 거미도 같은 방식으로 바람에 반응하게 해야 했습니다. 이 시스템을 사용하여 거미를 주변으로 움직이는 것도 간단히 코드만 추가하면 됐습니다. 이미 AddForce 함수와 AddImpulse 함수를 구현해서 거미에 추가한 상태였으므로 이것들을 연결하기만 하면 됐죠.

하지만 생각처럼 순조롭게 진행되지는 않았습니다. 이러한 시스템이 다양한 프레임 속도에서 일관성을 유지하게 만드는 것은 생각보다 까다로웠습니다.

이 시스템은 빠른 프레임 속도가 상당히 빠른 개발용 PC에서는 잘 작동했습니다. 하지만 저희가 보유한 저사양 기기에서는 그렇지 못했습니다.

거미에만 바람을 적용하면 큰 문제가 없었습니다. 하지만 같은 시스템을 사용하여 게임플레이 오브젝트들도 움직였기 때문에 거미와 다른 오브젝트들 간의 동작이 불일치했습니다.

상당한 시간을 들여 디버깅하고 피직스 시뮬레이션을 다듬어 보다가 결국 그 안에 피직스 단계 분할을 구현하기로 했습니다. 마지막으로 추가한 이 기능 덕분에 원하던 결과를 얻을 수 있었습니다.
 

추가 작업

스파이더를 출시하고 나니 코드팀에는 개발 과정, 축적한 지식, 그리고 공개 질문에 대한 답변을 떠올릴 시간이 생겼습니다.

앞으로 할 작업을 생각해보면 두 가지 기능이 떠오릅니다:
 
  • 거미를 피직스 오브젝트로 만들기.
    • 이 작업은 언리얼 엔진의 프레임워크에 더 잘 맞습니다. 이를 통해 디자이너(블루프린트로만 작업하는 디자이너)에게 힘을 실어줄 수 있으며, 지금 바로 사용 가능한 언리얼 엔진 기능으로 더 자유롭게 프로토타입을 제작할 능력을 선사할 수 있습니다.
  • 이 접착식 보행 기술의 다른 용도.
    • 와이프아웃(Wipeout) 스타일의 호버 레이싱.
    • 슈퍼 마리오 갤럭시(Super Mario Galaxy) 스타일의 플랫폼 게임.
    • 완다와 거상(Shadow of the Colossus) 게임플레이.

애니메이션은 언리얼 엔진에서 활발히 개척되는 영역으로, 앞으로 작업할 때 시도해 볼 가치가 있는 흥미로운 신기술들이 있습니다. 앞서 스크립트 가능한 실험적 리깅 시스템인 컨트롤 릭에 대해 언급했었는데, 이는 저희의 IK 로직을 더 강력한 모듈식 방식으로 재구현할 좋은 후보가 될 것 같습니다. 관성 혼합을 통해 퍼포먼스와 혼합 퀄리티를 향상할 수 있습니다. 애니메이션 인사이트는 게임플레이 스테이트와 라이브 애니메이션 비헤이비어를 검사하는 새로운 프로파일링 툴로, 개발에 유용할 것 같습니다. 앞으로 게임을 향상할 방안으로 사용할 것은 많습니다.

저희 애니메이션 시스템의 향후 반복 작업 측면에서 보자면, 생성된 스플라인의 시간적 안정성을 개선하여 트레이스가 프레임마다 상당히 다른 결과들을 반환할 때 다리와 발이 튕길 확률을 줄여 보겠습니다. 앞으로 애니메이션 혼합 동작을 미세 조정해서 정지하는 동작과 같이 자세 사이에 너무 빨리 혼합되는 상황을 방지할 방법도 고안해보면 좋을 것 같습니다.

스파이더의 개발 과정을 즐겁게 알아보셨기를 바랍니다. 에이전트 8과 함께 세상을 구하고 싶다면 애플 아케이드에서 스파이더를 찾아보세요!
 

    지금 언리얼 엔진을 다운로드하세요!

    세계에서 가장 개방적이고 진보된 창작 툴을 받아보세요. 
    모든 기능과 무료 소스 코드 액세스를 제공하는 언리얼 엔진은 제작에 바로 사용할 수 있습니다.