컨트롤 릭

MoOrY·2024년 5월 9일
0

언리얼 엔진

목록 보기
41/41

전신 IK를 사용하여 각 다리의 발이 특정 위치를 타겟팅 해야 한다.

0. 월드 스페이스와 rig 스페이스의 차이

월드 스페이스는 레벨 내의 공간. 실제 게임의 좌표계이며 무한으로 확장되는 큰 좌표계라고 생각하자. 그리고 rig 스페이스는 그 큰 좌표계 위에 올려진 작은 좌표계라고 생각하자. 릭 스페이스는 캐릭터 메쉬 위치를 따라 월드 스페이스 내의 어느 좌표에도 있을 수 있고 자유롭게 회전가능하다.
릭 스페이스 역시 하나의 좌표계이므로, 좌표를 가질 수 있는데, 이 좌표는 월드 공간의 좌표와 반드시 일치하지는 않는다. 따라서 우리는 릭 스페이스 좌표를 월드 공간상의 좌표로 변환하거나, 그 반대가 필요하다. 릭 스페이스는 Rig 내의 전역 공간(global space)하고 한다. 이름 때문에 모든 것을 담고 있고 더 전역적이고 큰 공간이어야 할 것처럼 들리지만, 그렇지는 않다.

1. 발 트랜스폼 초기값 저장

애니메이션 프레임을 업데이트 하기 전에, 발들의 디폴트 트랜스폼을 배열에 저장하고자 한다. 이를 위해 발 체인의 각 마지막 end bone 이름을 배열로 저장하고, 이를 배열로 돌려 초기값들을 구해 발 디폴트 위치 배열에 저장한다. 이는 한번만 수행하면 되므로, Construction Event에서 수행한다. 트랜스폼은 글로벌 스페이스(릭 스페이스)로 저장하였는데, 컨트롤 릭은 로컬 스페이스에서 작동하므로, 캐릭터와 관련된 것만 관심을 신경쓴다. 따라서 위치는 모두 캐릭터가 실제로 있는 위치를 기준으로 한다.

Global Space

컨트롤 릭의 GetTransform에서 Global Space가 선택되어 있으면, 그 본이 위치하는 월드 공간상의 좌표가 아니라, 캐릭터 루트 본을 기준으로 얼마나 떨어져 있는지를 나타내는 릭 공간 상의 좌표가 나온다
Relative Space를 선택하면 부모 본에 비해 얼마나 떨어져 있는지 나타내는 상대 좌표가, Global Space를 선택하면 릭 스페이스 공간에 비한 상대 좌표가 나온다.
즉 뭐를 선택하든 상대 좌표가 나오는 것

따라서 얻은 글로벌 스페이스를 월드 공간 좌표로 변환하기 위해 To World 노드를 사용하였다.

이제 본격적인 구현에 앞서, 발 위치를 시각화하고 싶다. 방금 만든 WorldFinalFootTransform배열을 forward solver에서 루프를 돌리고, DrawTransform 노드를 사용하여 시각화하였다.

2. Full Body IK

Full Body IK 노드 작동 원리

캐릭터 포지션 위치를 Solve하기 위해 코스 전체에서 사용될 것이다. 한 본을 이동시키고자 하는 트랜스폼을 설정하면, 노드는 그 목표에 도달하기 위해 몸 전체를 Solve하고자 노력할 것이다.

만약 이동시키고자 하는 포인트(본, 컨트롤러...)이 하나일때, 예를 들어 발을 특정 위치로 옮기고자 할 때, 가장 간단한 방법은 발 위치에 맞게 몸 전체를 이동시키는 것일것이다.

하지만 이동시키고자 하는 포인트가 여러개 있을 때는 흥미로워진다.
다리가 6개 있는 전갈이 있고, 각 다리 모두에 도달하고자 하는 목표 지점이 있으면, 다리의 모든 끝점이 목표 위치에 닿는 방식으로 몸의 위치를 잡고, 이는 모든 목표 지점에 도달하기 위해 가능한 최소한의 조정으로 이를 수행한다. 따라서 Full Body IK Solver는 원래 극점에 대한 회전 또는 이동 정도를 최소화하여 모든 지점에 도달하도록 노력할 것이다.

6개의 다리 중 하나의 목표 지점을 움직여보면, 이전과 다르게 몸 전체가 움직이지 않는다. 한 다리의 목표 지점을 달성하는 동시에, 나머지 다섯 다리 역시 목표 지점을 달성해야 하므로, 나머지 다리를 살짝 피거나, 몸을 기울이는 등의 최소한의 행동으로 움직일 것이다.

캐릭터 세팅에 따라 더 많거나 적은 포인트를 사용할 수 있지만, 일반적으로 각 다리 또는 엔드 이펙터에 대해 하나의 목표 지점이 있다고 생각할 수 있다.

Full Body IK 노드 사용

이제 직접 Full Body IK노드를 사용하여 IK를 세팅해보았다.
Full Body IK 노드를 꺼내고, Effectors 파라미터에 다리 개수만큼 엘리먼트를 만든 뒤, 본 이름과 디폴트 포지션 배열의 각 인덱스를 가져와 연결해주었다. 이 상태로도 IK가 훌륭하게 작동함을 확인할 수 있다. 노드가 굉장히 길어지므로, 함수로 접어 정리한다.

이제 뷰포트에서 확인해보면, 메쉬가 캐릭터 캡슐 위치와 상당히 떨어져 있음을 볼 수 있다. 그 이유는 월드 공간과 리그 공간 사이의 큰 차이 때문인데, 우리는 분명 디폴트 위치를 월드 포지션으로 변경하여 저장했는데, 왜 다른 위치에 가있는걸까?
전신 IK 노드는 월드 공간이 아닌, 릭 공간(글로벌 스페이스)에서 작동한다. 릭 공간에서 작동하는 함수에 월드 공간 좌표를 전달하였으니, 오류가 나는 것이다. 따라서 From World 노드를 사용하여, 전신 IK 노드에 전달하는 위치를 릭 공간으로 변환하여 전달한다.

처음부터 월드 공간으로 변환하지 않고 저장했으면 안되나요?
그러면 처음에 어느 위치에 있던간에 같은 값만을 저장했을 것이다. 글로벌 스페이스는 루트에 비례한 본의 상대 좌표이므로, 본을 기준으로 발이 얼마만큼 떨어져 있느냐를 나타내는데, 그건 스켈레탈 메쉬에 따라 항상 같을테니까.. 따라서 현재 발 위치를 월드 공간으로 가지고 있다가, 그걸 다시 글로벌 스페이스로 변환하여 전달하는 것이다.)

이제 뷰포트에서 시뮬레이션으로 실행하고, 캐릭터를 움직여보면, 드드득 거리며 스켈레탈 메쉬는 항상 초기 위치에 머무려고 하므로 캡슐과 메쉬가 멀어지게 된다. 항상 초기 위치로 업데이트 하도록 전신 IK를 세팅했으니 정상적으로 작동하는 것이다.

3. 캐릭터 속도 계산

속도를 계산해야 하는 이유

속도를 계산하기 위해선, 캐릭터가 이전에 있었던 위치와 현재 위치의 차이를 계산해야한다. 이전 프레임과 현재 프레임의 위치, 그리고 프레임이 처리된 시간(Delta Time)을 구하고, 두 점 사이의 거리를 deltaTime으로 나누면 속도를 구할 수 있다.

distance / Delta Time = unit/s

캐릭터의 속도를 구하면, 캐릭터가 이동할 위치를 구할 수 있다. 만약 캐릭터가 1초에 100유닛을 이동했다고 하면, 다음 1초 후에도 같은 100유닛만큼 이동했을 것이다. 같은 정보로 다음 프레임에 있을 위치를 구할 수 있고, 1프레임 당 이동거리를 알면 반대로 1초 후의 이동 거리도 알 수 있을 것이다. 하지만 어디까지나 "예측"이므로, 완벽하게 정확하지 않다. 이전의 가정은 같은 속도와 방향으로 움직일 것으로 가정하고 세운 것이기 때문. 하지만 캐릭터의 발이 어디에 있어야 할 지 예측할 만큼은 정확하다.
이것으로 한 방향으로 움직일 떄, 발을 그 움직임 방향에 놓을 수 있고, 캐릭터가 실제로 다른 위치로 이동하는 도중에, 사소한 조정이 있는 경우, 해당 예측을 업데이트할 수 있다.
한번 예측 했다고 해도 그것이 확정되는 것이 아니며, 애니메이션이 진행되는 동안 계속 예측을 업데이트할 수 있다.

속도 계산 함수 만들기

우리는 캐릭터가 어떤 방향으로 어느정도 속도로 움직이는지 계산한다. 즉 Speed(속력)가 아니라 힘과 방향을 갖는 Velocity(속도)를 계산한다.
매 틱마다 호출되는 함수를 만들고, 현재 위치를 PrevWorldTransform이란 변수에 저장한다. 이 변수는 다음 프레임에서 이전 프레임의 위치가 될 것이다. 현재 위치는 Transform()을 ToWorld하는 것으로 구할 수 있다.(릭 스페이스의 0,0,0는 캐릭터 메쉬 위치가 되고, 그것을 월드 좌표로 변환하므로) 그 앞에서는 이전 프레임에 비해 현재 프레임이 얼마나 멀리 이동했는지 계산한다. 현재 월드 공간 위치에 이전 프레임의 월드 공간 위치의 음수(inverse 노드)를 곱하여 결과적으로 현 위치에서 이전 위치를 빼고, 해당 값을 WorldDelta라는 새 변수에 저장한다. 그리고 이 값을 1초에 얼마나 움직였는지 나타내는 속도로 변환할 것이다.

왜 트랜스 폼을 곱하면 벡터의 덧셈이 나올까?
트랜스폼 A와 B를 곱한 값 C의 위치값을 확인해보면, A 트랜스폼의 위치값 + B 트랜스폼의 위치값의 결과가 나온다.
이는 컨트롤 릭에서 제공하는 트랜스폼 끼리의 곱셈 노드의 내부 구조가 그렇게 출력하도록 짜여져 있기 때문.

왜 그런 아웃풋을 내도록 짰을까?
컨트롤 릭에서 트랜스폼 곱셈은 위치 변환이라는 개념에서 나왔기 때문이다.
위치 변환에서 두 벡터를 곱하는 것은, 두 위치의 거리를 계산하는 것과 동일하다.
만약 트랜스폼 곱셈이 일반적인 두 벡터의 곱처럼 각 요소들을 서로 곱한 값을 내놓았다면,
개발자가 위치 변환이 예상과 다르게 작동한다고 혼란스러워 할 수 있기 때문에,
컨트롤 릭에서는 직관적인 이해를 위해 트랜스폼 곱셈 결과를 두 벡터 덧셈으로 명시적으로 표현한 것.

따라서 현재 위치 A 트랜스폼의 위치 a에 이전 트랜스폼 B의 위치 b의 음수를 곱하면, a-b.
즉 b에서 a로 향하는 방향과 거리를 구할 수 있다.

이후 worldDelta에 DeltaTime을 나눠 프레임 당 속도를 계산하고 CalculatedVelocity 벡터 변수에 저장하고, 눈으로 확인해보기 위해 DrawLine 노드에 현재 위치에서 CalculatedVelocity까지 그리도록 설정하였다.

콘솔 창에 위 두 명령어를 입력하면, 컨트롤 릭 디버그 드로우를 인게임에서 확인할 수 있다.
a.AnimNode.ControlRig.Debug 1
ControlRig.EnableDrawInterfaceInGame 1(5.3 이상)

이제 시뮬레이션을 키고 캐릭터를 이동해보면, 이동방향에 맞게 선이 그려지는것을 확인할 수 있다.

속도 값 부드럽게 적용

하지만 선들이 엄청나게 깜빡거리고, 방향을 빠르게 바꾸면 즉시 선이 바뀐다.
그 이유는 매 프레임마다 마지막 프레임에서 이동한 거리를 기준으로 속도를 계산하기 때문.
따라서 한 프레임이 빠르게 움직이고, 다음 프레임에서 느리게 움직이면, 매 프레임마다 즉각적으로 긴 선에서 매우 짧은 선으로 이동하게 된다. 따라서 계산된 속도를 부드럽게 만들어 좀 더 점진적인 속도를 갖게 할 것이다.
이를 위해 Spring Interpolate라는 노드를 사용한다.

Spring Interpolate

간단한 스프링 모델을 사용하여 현재에서 대상까지 벡터를 보간한다.

해당 노드는 Target과 Current를 취하는데, Target은 방금 계산한 CarculatedVelocity, Current는 이전 프레임의 CarculatedVelocity이다. 나머지 파라미터로는 속도 등을 조절한다.
해당 노드가 점진적으로 CarculatedVelocity노드를 점차 증가 혹은 감소시키며 마지막에 계산한 값으로 도달하게 해 줄 것이다.

다시 시뮬레이팅 해보면 훨씬 선이 부드럽게 그려지는 것을 볼 수 있다.

다리 움직이기

싸이클 이론

시간에 따라 0에서 1로 증가하고, 1에 도달하면 다시 0으로 초기화되어 다시 증가하는 타이머를 생성하고, 이를 모든 다리를 제어하는 마스터 싸이클로 한다.

다리 1은 오프셋 없이, 마스터 싸이클과 완전히 일치한다고 가정한다.

두번째 다리는 50%의 오프셋을 가진다고 가정한다. 이러면 다리 1이 Locked되어 지면에 고정되어 있을 때, 다리 2는 다리를 위로 올리는 행동을 하게 된다.

이를 더 많은 다리에 각각 다른 오프셋을 적용한다.
모든 다리들은 마스터 싸이클에 동기화 되어 있지만, 각기 다른 수치로 오프셋 되어있다.
싸이클이나 타이머가 증가하는 속도를 수정하여 전체 속도를 변경할 수도 있다.

이러한 방식은 각 다리가 서로 각각 다르게 움직여 유기적인 움직임을 생성하면서도, 하나의 타이머에 제어될 것이다.

싸이클 만들기

시간에 따라 0에서 1로 증가하고, 1에 도달하면 다시 0으로 초기화되어 다시 증가하는 마스터 싸이클을 만들어보자.
CalculateMasterCycle이라는 함수와 MasterCyclePercent라는 float 변수를 만든다.
함수 내에는 MasterCyclePercent에 deltaTime을 더해주고, Modulo 노드를 연결한 뒤 B에 1을 입력한다.
Modulo는 %와 같은 역할을 하는, A를 B로 나눈 나머지를 리턴하는 노드이고, B에 1을 넣어줬으므로, 1을 초과하면 1의 자리 숫자는 0으로 초기화되고, 그 나머지로만 다시 값이 재설정 되어 0과 1 사이를 순환하면서도, 뒤 프레임에서 초과분이 적용될 것이다.

발 위치 트랜스폼 업데이트

현재 모든 발들은 초기 위치 월드 스페이스 좌표에 고정되어 있으므로, 캐릭터를 움직여도 메쉬가 따라오지 않는 상태이다.
이제 주기에 따라 발이 좌표에 고정되거나, 자유롭게 움직일 수 있고, 캐릭터를 따라 움직이도록 할 것이다.

MoveFeetTransform이란 함수를 만들고, FootNames 배열 변수를 For each 돌린다. 그리고 Index와 Element를 각각 int와 네임 로컬 변수에 저장했다.

그 다음, 현재 발이 잠긴 상태인지 아닌지를 판단할 bool 변수 배열이 필요하다. 싸이클에 따라 현재 발이 잠긴 상태인지 아닌 상태인지 정하고, 해당 배열과 WorldFinalFootTransform 트랜스폼 등의 값을 업데이트할 것이다.

FootLockedArray라는 이름의 부울 배열을 만들고, 생성자에 배열들을 초기화하는 부분에서 발 갯수만큼 false로 초기화하도록 설정한다.
다시 MoveFeetTransform로 돌아가, for 루프 안에서 FootLockedArray의 해당 반복문 인덱스 요소가 true인지 false인지 branch로 분기를 나눈다. 발이 잠긴 상태라면 발이 지정된 위치에 잠겨 있으므로 WorldFinalFootTransform를 업데이트할 필요가 없고 움직이지 않아도 된다. 만약 풀려있다면 움직이기를 원하는 상태이므로 WorldFinalFootTransform를 업데이트할 것이다.

0에서 1 사이를 순환하는 싸이클에서, 싸이클의 어느 지점까지 Unlocked이고, Locked인지 판단하기 위한 변수, SwingPercent를 선언한다. 0에서 1 사이의 float변수이며, 만약 이 값이 0.5라면, 싸이클이 50% 이하일때 Unlocked, 50% 이상이면 Locked일 것이다.

Locked 상태일때는, 싸이클에 따라 발의 Lock을 풀어주는 것 외에는 크게 할 일이 없다. Locked 브랜치에서 MasterCyclePercent와 SwingPercent를 비교해서, SwingPercent보다 작으면 해당 인덱스의 FootLockedArray값을 false로 바꿔준다.

반대로 Unlocked상태일 때, MasterCyclePercent가 SwingPercent보다 크면 해당 인덱스의 FootLockedArray값을 true로 바꿔줬다.

발 동기화

MasterCyclePercent에 따라 락을 풀어주거나 잠그는 기능을 구현했지만, 현재 이 값을 사용하고 있지 않으므로, 가시적으로 보이지는 않는다. 이제 이를 사용하기 위해 릭 그래프로 이동한다.

먼저 ForwardSolver에서 WorldFinalFootTransform를 표시하던 디버그 드로우를, FootLockedArray의 각 인덱스가 true일때만 표시하도록 바꿨다. 그리고 CalculatedCycle과 SetFullBodyIk 함수 사이에 MoveFeetTransfrom 함수를 실행하도록 배치한다.

이제 다시 MoveFeetTransform으로 넘어와서, UnLocked이고 MasterCyclePercent가 SwingPercent보다 크지 않을 때는 발 위치를 원래 있어야 하는 자리로 돌릴 수 있도록, 현재 발 위치를 월드 좌표로 변환 후 GetWorldFinalFootTransform의 해당 인덱스값에 적용한다.

이제 인게임에서 시뮬레이션을 누르고 객체를 움직여보면, Locked되어 있을때는 제자리에 머물다가 Unlocked되는 순간 메쉬가 캐릭터 위치를 따라잡으며 업데이트 되는것을 볼 수 있다.

지금은 모든 다리가 같은 싸이클에 영향받으므로 동시에 움직인다. 각 다리별로 오프셋을 주기 위해 플로트 배열을 선언하고, 각 다리마다 다른 값을 부여한다. 어떤 값을 넣느냐에 따라 다른 움직임을 보일 것이고, 해당 프로젝트에선 0, 0.165, 0.33, 0.495, 0.66, 0.825로 세팅하였다.
이후 각 다리 별 싸이클 현재 값을 저장할 float형 변수 PerFootCyclePercent를 선언하고, CalculateCircles에서 루프를 사용하여 해당 배열에 값을 저장했다. 저장한 값은 마스터 싸이클에 각 FootTimingOffset 인덱스를 더한 값에 Modulo(1) 한 값이다.

이후 SwingPercent와 비교하는 값을 PerFootCyclePercent의 각 인덱스 값으로 바꿔주면 각 다리별로 따로따로 켜지고 꺼지는 모습으 볼 수 있으며, 뷰포트에서 이동시켜보면 뚝뚝 끊기긴 하지만 다리가 움직이며 이동한다.

뚝뚝 끊기며 이동하는 이유는 Lock이 풀리자마자 타겟 위치에 바로 스냅되기 때문. 따라서 부드럽게 움직이게 하기 위해 움직임을 보간해보자.

움직임 보간

부드러운 움직임을 주기 위해, MoveFeetTransform 함수의 발 위치 업데이트 구현 부분에서, 현재 발 본의 트랜스폼을 바로 꼽지 않고, Interpolate 노드를 사용하여 WorldFinalFootTransform과 현재 본 트랜스폼을 넣고, T에 적절한 알파값을 취한 뒤, 이를 WorldFinalFootTransform배열에 Set하도록 하였다.
이 함수는 매 프레임 당 호출되기 때문에, T에 따라 부드럽게 다리들이 움직이지만, 메쉬가 실제 캐릭터 액터보다 항상 뒤쳐져 있는 문제가 있다. 즉 움직이지 않을 때까지 올바른 위치에 있을 수 없다.
따라서 우리는 캐릭터의 속도를 기반으로 다음 위치를 예상하여 적용해야 한다.

다음 위치 예측

각 다리의 다음 예상 위치를 저장하는 트랜스폼 배열 PredictedFootTransforms와, 이 배열 값을 업데이트 하는 함수 UpdateFootPredictions를 선언한다. UpdateFootPredictions는 MoveFeetTransform 전에 호출된다.
해당 함수 안에서는 발 이름으로 루프를 돌리고, PredictedFootTransforms의 각 값을 업데이트한다
값은 단순히 현재 발 본 위치에 이전에 계산했던 CalculatedVelocity를 더하는 것으로 이루어진다.
이 값을 MoveFeetTransform 함수에서 사용하면 이전보다 훨씬 부드럽게 이동하는 모습을 볼 수 있다.

예측 정확도 높이기

하지만 빠르게 움직이면 다리가 쭉 늘어나거나, 몸이 이상하게 뒤틀리는 현상이 생기거나, 혹은 너무 과하게 예측하여 목표 위치보다 훨씬 뒤로 갔다가 다시 돌아오는 등, 불안한 모습을 보이고 있다. 그 이유는 우리가 단순히 계산된 속도만 추가하였지, 발이 착지하는 시간이나 주기가 얼마나 지속되어야 하는지 등, 기타 고급 계산을 기반으로 만들지 않았기 때문이다.

더 정확하게 예측하기 위해, 단순히 계산된 속도만 더하는 대신, 발이 그 발 고유의 싸이클을 기준으로 다리를 움직일 수 있는 시간이 얼마나 남았는지 그 주기에 따라 값을 적용해 줄 것이다.

현재 다리는 1초 주기로 움직이고, 0.5초는 실제로 다리를 움직이고(UnLocked), 나머지 0.5초는 땅을 딛고 가만히 있는다(Locked).
초당 속도를 그냥 더한다면, 주기와 상관없이 1초 전부 움직이는 것으로 기준을 잡고 발 위치를 과도하게 먼 곳으로 예측하는 것이다.
따라서 우리는 앞으로 이동할 수 있는 시간이 얼마나 남았는지 체크하고, 이 시간을 기준으로 예측 발 위치를 정할것이다.

PerFootCyclePercent을 가져와 현재 싸이클이 어느정도 진행됐는지 구할 수 있다. 또 구해야 하는것은 싸이클이 Locked되기까지 남은 비율이다. 싸이클이 언제 잠기는지는 SwingPercent가 기준이므로, SwingPercent에서 현재 싸이클이 진행된 퍼센트(PerFootCyclePercent)를 빼면 구할 수 있다.(음수면 잠겨있는 상태)
만약 해당 산술의 값이 0.1이라면, 싸이클이 잠기기까지 전체 주기의 10%만큼의 시간이 남았다는 것을 의미한다.
현재 주기는 1초로 고정되어 있어 0.1초가 남은 것이지만 이후에는 속도가 빠르면 더 짧은 주기를 사용할 수도 있다.
이후 해당 값을 Reasonable한 값으로 만들기 위해 0과 1 사이로 클램프한다.(이미 잠겨있는 상태면 PerFootCyclePercent > SwingPercent이므로 산술값이 음수가 나오고, 이는 잘못된 결과를 초래할 수 잇다.)

보다 자연스러운 움직임을 위해 약간의 오프셋을 추가한다. 왜냐하면 현재 기준에서 발은 0.5초만 움직이고, 0.5초는 잠겨서 움직이지 않는데, 캐릭터는 주기와 상관없이 계속 움직이기 때문이다. 다음 프레임이 처리되는 순간 방금 정한 위치는 바로 구식 데이터가 되고, 발의 위치가 몸보다 뒤에 있게 될 것 이다.

실제 곤충의 움직임을 보면 미리 발의 위치를 예상하여 발을 뻗어 착지하여 고정하고, 그 다음 몸을 끌어당겨 이동한다.
좀 더 자연스럽게 보이는 효과도 있을 듯

따라서 추가해주어야 하는 거리는 전체 주기에서 잠기는 시간을 기준으로 한다. 만약 0.5초 동안 걷고 0.5초동안 잠긴다면, 잠기는 시간의 1/2배의 시간만큼 이동하는 거리를 추가한다.

잠기는 시간은 움직이는 시간을 1-x하는 것으로 구할 수 있고, 그 값을 2로 나눈 뒤, 이전에 구한 SwingPercent에서 남은 시간과 더한다. 이 시간은 전체 주기에 대한 퍼센트 단위이므로, 이를 시간으로 변환시키기 위해 전체 주기 길이(시간)을 곱한다.
그렇게 곱한 값을 초당 속도에 곱하고(Scale), 현재 본 위치에 더하면 더 정밀하게 계산한 예측 위치를 구할 수 있다.

싸이클 주기 변화하기

현재 주기가 1초에 고정되어 있는데, 캐릭터에 이동 속도에 맞춰 주기를 변경할 것이다. 속도가 느리면 주기가 길어지고, 반대로 속도가 빨라지면 주기가 짧아진다.

주기를 초로 나타낼 float변수 CycleLengthInSeconds를 선언한다. 이 값을 CalculateCycle함수에서 메인 싸이클에 더해지는 DeltaSecond에 나눈다. 만약 주기가 1초라면 원래 속도로 가지만, 주기가 2초라면 2가 나눠져 더 적은 값을 더하게 되고, 그만큼 느려지게 된다. CycleLengthInSeconds은 CalculateCycle함수에서 업데이트할 예정이고, UpdateFootPredictions 함수에서 사용하도록 설정해준다.

싸이클 주기 계산

profile
필기용 블로그입니다.

0개의 댓글