이전 글에서는 회전 플랫폼 위에서 상자를 밀고 당길 때 생긴 문제를 정리했다.
상자도 발판의 이동량을 따라가게 만들고, 잡기 축과 잡은 간격도 발판 회전만큼 같이 돌려주었다.
이제 회전 플랫폼 위에서 플레이어와 상자는 꽤 자연스럽게 움직인다.
(완벽하지는 않지만 프로토타이핑을 하는데는 문제가 없을 것 같다.)
하지만 아직 하나가 남아 있었다.
거대한 회전 플랫폼 위에서 점프하면 움직임이 이상했다.
같은 방향으로 점프하면 거의 앞으로 나가지 않는 것처럼 보이고, 반대 방향으로 점프하면 너무 멀리 날아가는 느낌이 있었다.
처음에는 점프 힘이나 공중 이동 속도 문제라고 생각했다.
하지만 실제 원인은 따로 있었다.
문제는 점프하는 순간 발판 기준 이동이 끊긴다는 점이었다.
회전 플랫폼 위에 있을 때는 잘 따라간다
이전 글에서 플레이어는 MovingPlatformSurface를 통해 발판의 이동량을 따라가도록 만들었다.
void ApplyMovingPlatformMotion()
{
MovingPlatformSurface activePlatform = GetActiveMovingPlatform();
if (activePlatform == null)
{
return;
}
Vector3 platformDelta = activePlatform.GetDeltaPositionAt(rb.position);
float platformYawDelta = activePlatform.GetDeltaYaw();
if (platformDelta.sqrMagnitude > 0.000001f)
{
rb.MovePosition(rb.position + platformDelta);
}
if (!Mathf.Approximately(platformYawDelta, 0.0f))
{
Quaternion yawDelta = Quaternion.AngleAxis(platformYawDelta, Vector3.up);
rb.MoveRotation(yawDelta * rb.rotation);
}
}
여기서 핵심은 두 가지다.
platformDelta는 발판이 이번 물리 프레임에 플레이어 위치를 얼마나 이동시켜야 하는지 나타낸다.
platformYawDelta는 발판이 이번 물리 프레임에 Y축으로 얼마나 회전했는지 나타낸다.
플레이어가 발판 위에 서 있으면 이 이동량과 회전량을 계속 받는다.
그래서 회전 플랫폼 위에서도 발판에 실려 가는 느낌이 난다.
그런데 점프하는 순간 기준이 끊긴다
문제는 점프하는 순간 생겼다.
플레이어가 발판 위에 서 있을 때는 currentMovingPlatform을 가지고 있다.
private MovingPlatformSurface currentMovingPlatform;
하지만 점프하면 더 이상 발판과 접촉하지 않는다.
충돌에서 벗어나면 현재 발판 참조가 비워진다.
void OnCollisionExit(Collision collision)
{
isGrounded = false;
// 바닥에서 떨어지면 현재 밟고 있는 움직이는 표면은 없어진다.
currentMovingPlatform = null;
}
이렇게 되면 플레이어는 공중에 뜬 순간부터 더 이상 발판의 이동량을 받지 않는다.
발판 위에 있을 때는 발판 이동량을 따라가지만, 점프한 직후에는 발판 참조가 끊기면서 그 이동량을 더 이상 받지 않는다.
처음에는 이게 당연해 보였다.
공중에 있으니 발판에서 떨어진 것이 맞기 때문이다.
하지만 실제 조작감은 이상했다.
지하철 안에서 점프하는 느낌
생각해보면 지하철 안에서 점프한다고 해서 갑자기 뒤로 밀려나지는 않는다.
달리는 지하철 안에서 뛰어도, 사람은 지하철과 같은 기준 속도를 이미 가지고 있다.
그래서 지하철 안에서는 그냥 제자리에서 뛴 것처럼 느껴진다.
회전 플랫폼도 비슷하게 생각할 수 있었다.
회전 플랫폼 위에 서 있다는 것은 이미 발판의 움직임을 따라가고 있다는 뜻이다.
그 자리에서 점프했다면, 점프하는 순간에도 그 발판 기준 움직임은 어느 정도 유지되어야 자연스럽다.
즉, 점프했다고 해서 바로 월드 기준으로 독립시키면 어색하다.
플랫폼 위에서 점프했다면, 착지하기 전까지는 그 플랫폼 기준 이동을 유지하는 편이 더 자연스럽다.
처음 생각했던 방법: 시간으로 유지하기
처음에는 점프 후 일정 시간 동안만 발판 이동량을 계속 따라가게 하는 방식을 생각했다.
예를 들면 이런 값이다.
- Moving Platform Air Carry Duration
이 값으로 점프 후 몇 초 동안 발판 기준 이동을 유지할지 정할 수 있다.
하지만 곧 애매하다고 느꼈다.
플랫폼 위에서 짧게 점프하면 0.2초면 충분할 수도 있다.
하지만 더 높게 점프하거나, 활공처럼 공중에 오래 떠 있으면 시간이 부족할 수 있다.
반대로 너무 길게 잡으면 이미 발판과 관계없는 상황에서도 계속 발판을 따라가는 것처럼 보일 수 있다.
그래서 시간으로 자르는 대신, 더 단순한 기준을 사용했다.
점프한 발판 기준 이동은 다음 착지 전까지 유지한다.
공중에서도 이전 발판을 기억하기
그래서 currentMovingPlatform과 별개로, 공중에서 유지할 발판 참조를 하나 더 만들었다.
// 움직이는 발판에서 점프한 뒤, 착지 전까지 같은 발판 좌표계를 따라가기 위한 참조.
private MovingPlatformSurface airborneMovingPlatform;
현재 밟고 있는 발판은 currentMovingPlatform이다.
하지만 점프한 뒤 공중에서 사용할 발판은 airborneMovingPlatform이다.
currentMovingPlatform은 현재 발로 밟고 있는 발판이고, airborneMovingPlatform은 점프 직전에 밟고 있던 발판이다.
이제 움직이는 발판을 가져오는 함수도 바꿨다.
MovingPlatformSurface GetActiveMovingPlatform()
{
if (isGrounded)
{
return currentMovingPlatform;
}
return airborneMovingPlatform;
}
이 함수는 현재 상태에 따라 사용할 발판을 반환한다.
지상에 있으면 현재 밟고 있는 발판을 사용하고, 공중에 있으면 점프 직전에 기억해둔 발판을 사용한다.
이렇게 하면 ApplyMovingPlatformMotion()은 지상인지 공중인지 몰라도 된다.
그냥 GetActiveMovingPlatform()으로 받은 발판의 이동량을 적용하면 된다.
점프하는 순간 발판 기억하기
그럼 airborneMovingPlatform은 언제 저장해야 할까?
점프하는 순간이다.
void BeginMovingPlatformAirCarry()
{
if (!isGrounded || currentMovingPlatform == null)
{
return;
}
airborneMovingPlatform = currentMovingPlatform;
}
지상에 있고, 현재 밟고 있는 움직이는 발판이 있을 때만 저장한다.
즉, 일반 바닥에서 점프하면 아무 일도 하지 않는다.
움직이는 발판 위에서 점프할 때만 공중 보정이 시작된다.
이 함수는 실제 점프를 수행하는 곳에서 호출한다.
void PerformJump(float force, bool isDoubleJump)
{
EndGlide();
CancelGlideHold();
umbrellaController?.CloseUmbrella();
if (cancelDownwardVelocityOnJump && rb.linearVelocity.y < 0.0f)
{
rb.linearVelocity = new Vector3(rb.linearVelocity.x, 0.0f, rb.linearVelocity.z);
}
// 회전 발판 위에서 점프했다면 착지 전까지 그 발판 기준 이동을 유지한다.
BeginMovingPlatformAirCarry();
rb.AddForce(Vector3.up * force, ForceMode.Impulse);
jumpCount = Mathf.Min(jumpCount + 1, maxJumpCount);
isGrounded = false;
groundIgnoreTimer = jumpGroundIgnoreTime;
if (!isDoubleJump)
{
onJump.Invoke();
return;
}
BeginDoubleJumpSpin();
StartGlideHold();
onDoubleJump.Invoke();
}
여기서 순서가 중요하다.
BeginMovingPlatformAirCarry()는 isGrounded = false로 바꾸기 전에 호출한다.
그래야 아직 현재 밟고 있는 발판인 currentMovingPlatform을 기억할 수 있다.
점프 직전에는 currentMovingPlatform이 아직 남아 있다.
그 상태에서 BeginMovingPlatformAirCarry()가 airborneMovingPlatform에 발판을 저장한다.
그 다음에 isGrounded = false로 바꾼다.
이 순서가 바뀌면 공중에서 따라갈 발판을 저장하지 못할 수 있다.
착지하면 공중 보정을 끝낸다
공중에서 발판 기준 이동을 계속 유지하되, 영원히 유지하면 안 된다.
플레이어가 다른 바닥에 착지하면 이제 새로운 바닥 기준으로 움직여야 한다.
그래서 착지했을 때 airborneMovingPlatform을 비운다.
void ClearMovingPlatformAirCarry()
{
airborneMovingPlatform = null;
}
착지 판정은 충돌 중 바닥 접촉이 있을 때 처리한다.
void OnCollisionStay(Collision collision)
{
if (groundIgnoreTimer > 0.0f)
{
return;
}
if (TryGetGroundContact(collision))
{
isGrounded = true;
currentMovingPlatform = collision.collider.GetComponentInParent<MovingPlatformSurface>();
// 새 바닥에 닿았으니 이전 발판 기준 공중 보정은 끝낸다.
ClearMovingPlatformAirCarry();
}
}
이제 흐름은 이렇게 된다.
회전 플랫폼 위에 서 있을 때는 currentMovingPlatform을 사용한다.
점프하면 currentMovingPlatform을 airborneMovingPlatform에 저장한다.
공중에서는 airborneMovingPlatform을 사용한다.
착지하면 airborneMovingPlatform을 비우고, 새로 밟은 바닥을 currentMovingPlatform으로 사용한다.
점프 직후 바로 착지로 판정되는 문제
점프 처리에서 하나 더 신경 써야 할 부분이 있었다.
점프한 바로 직후에는 아직 이전 바닥과의 충돌 정보가 남아 있을 수 있다.
이 상태에서 바로 OnCollisionStay()가 실행되면, 방금 점프했는데 다시 바닥에 있다고 판정될 수 있다.
그러면 점프 카운트가 꼬이거나, 공중 보정이 바로 끊길 수 있다.
그래서 점프 직후 짧은 시간 동안은 바닥 판정을 무시한다.
public float jumpGroundIgnoreTime = 0.1f;
private float groundIgnoreTimer;
점프할 때 타이머를 설정한다.
groundIgnoreTimer = jumpGroundIgnoreTime;
그리고 매 프레임 줄인다.
if (groundIgnoreTimer > 0.0f)
{
groundIgnoreTimer -= Time.deltaTime;
}
착지 판정에서도 이 시간이 남아 있으면 바로 return 한다.
void OnCollisionStay(Collision collision)
{
if (groundIgnoreTimer > 0.0f)
{
return;
}
...
}
이렇게 하면 점프한 직후의 남은 접촉 때문에 다시 땅으로 판정되는 문제를 줄일 수 있다.
왜 속도를 더하지 않았나
처음에는 발판의 속도를 플레이어에게 더하는 방식도 생각할 수 있었다.
점프 순간 발판 속도를 플레이어 속도에 더하는 방식이다.
이 방식은 물리적으로 자연스러워 보인다.
하지만 지금 프로젝트의 회전 플랫폼 처리는 단순 속도 하나로 보기 어렵다.
회전 플랫폼은 중심을 기준으로 돌기 때문에, 발판 위 어느 위치에 있느냐에 따라 이동 방향이 달라진다.
그래서 이미 MovingPlatformSurface에는 특정 월드 위치가 발판 회전으로 얼마나 이동해야 하는지 계산하는 함수가 있다.
Vector3 platformDelta = activePlatform.GetDeltaPositionAt(rb.position);
이 함수는 특정 월드 위치가 이번 프레임에 발판 회전으로 얼마나 이동해야 하는지 알려준다.
그래서 공중에서도 같은 방식을 유지했다.
점프 순간의 속도만 한 번 더하는 대신, 착지 전까지 같은 발판 좌표계의 이동량을 계속 적용하는 방식이다.
이번 구현에서는 이쪽이 더 자연스럽다고 판단했다.
전체 흐름
최종적으로 회전 플랫폼 위에서 점프할 때의 흐름은 이렇게 된다.
- 플레이어가 회전 플랫폼 위에 서 있다.
- currentMovingPlatform에 현재 발판이 저장되어 있다.
- 점프 입력을 받는다.
- BeginMovingPlatformAirCarry()로 현재 발판을 airborneMovingPlatform에 저장한다.
- 점프 힘을 위로 준다.
- 공중에서는 currentMovingPlatform 대신 airborneMovingPlatform을 사용한다.
- 착지하면 airborneMovingPlatform을 비운다.
이제 플레이어는 회전 플랫폼 위에서 점프해도, 착지하기 전까지는 점프를 시작한 발판 기준 이동을 유지한다.
그래서 같은 방향으로 뛰면 너무 짧게 나가고, 반대 방향으로 뛰면 너무 멀리 나가는 느낌이 줄었다.
남은 고민
현재 방식은 프로토타입 단계에서는 꽤 자연스럽다.
하지만 몇 가지 고민은 남아 있다.
정말 착지 전까지 유지하는 게 항상 맞을까?
지금은 발판에서 점프하면 착지 전까지 airborneMovingPlatform을 계속 사용한다.
짧은 점프에서는 자연스럽다.
하지만 나중에 활공 시간이 길어지거나, 상승기류를 타고 오래 떠 있는 상황이 생기면 다르게 느껴질 수도 있다.
짧은 점프에서는 착지 전까지 유지해도 자연스럽다.
하지만 긴 활공에서는 너무 오래 이전 발판에 끌려가는 느낌이 날 수 있다.
나중에 문제가 된다면 이런 방식도 생각할 수 있다.
- 착지 전까지 유지한다.
- 점프 후 일정 시간만 유지한다.
- 착지 전까지 유지하되, 시간이 지날수록 영향력을 줄인다.
지금은 단순함을 위해 1번을 선택했다.
플랫폼 밖으로 완전히 벗어난 뒤에도 따라가는 문제
회전 플랫폼에서 점프한 뒤 멀리 떨어진 곳으로 날아가도, 착지 전까지는 이전 플랫폼 기준 이동을 받는다.
짧은 구간에서는 문제가 되지 않지만, 큰 맵에서는 어색할 수도 있다.
나중에는 플레이어와 플랫폼 사이의 거리를 보고 너무 멀어지면 공중 보정을 끊는 방식도 가능해 보인다.
플랫폼 중심과 너무 멀어짐
→ airborneMovingPlatform 해제
하지만 지금 단계에서는 오히려 규칙이 늘어나면 테스트하기 어려워진다.
그래서 일단은 착지 전까지 유지하는 단순한 규칙으로 두었다.
다른 움직이는 발판으로 착지하는 경우
공중에서 이전 발판 기준 이동을 유지하다가, 다른 움직이는 발판에 착지할 수도 있다.
이 경우 OnCollisionStay()에서 새 발판을 currentMovingPlatform으로 저장하고, airborneMovingPlatform을 비운다.
currentMovingPlatform = collision.collider.GetComponentInParent<MovingPlatformSurface>();
ClearMovingPlatformAirCarry();
즉, 착지한 순간부터는 새 발판 기준으로 전환된다.
이 흐름은 현재 코드에서 자연스럽게 처리된다.
정리
이번 문제는 점프 힘이나 공중 이동 속도 문제가 아니었다.
문제는 회전 플랫폼 위에서 점프하는 순간, 발판 기준 이동이 바로 끊긴다는 점이었다.
수정 전에는 발판 위에서는 잘 따라가지만, 점프하는 순간 발판 기준 이동이 끊겼다.
그 결과 점프 방향에 따라 거리감이 이상해졌다.
수정 후에는 점프 직전 발판을 기억하고, 공중에서도 착지 전까지 그 발판 기준 이동을 유지한다.
핵심은 currentMovingPlatform과 airborneMovingPlatform을 나눈 것이다.
currentMovingPlatform은 지금 밟고 있는 발판이다.
airborneMovingPlatform은 점프 직전에 밟고 있던 발판이고, 착지 전까지 공중 기준으로 사용한다.
이렇게 나누니 회전 플랫폼 위에서 이동, 상자 밀기, 점프까지 하나의 흐름으로 이어졌다.
회전 플랫폼을 만들면서 느낀 점은, 움직이는 발판은 단순히 오브젝트 하나를 회전시키는 기능이 아니라는 것이다.
플레이어가 그 위에 있을 때, 상자가 그 위에 있을 때, 그리고 그 위에서 점프할 때까지 전부 같은 좌표계를 어떻게 유지할지 고민해야 했다.
이번 작업으로 프로토타이핑에 쓸 회전 플랫폼 관련 기본 처리는 어느 정도 마무리된 것 같다.
(계속 구현하더라도 결국 언젠간 또 바뀔 것이기에.. 그만하기로 했다. 너무 매몰될꺼 같아서..)
'Unity - '한 우산 아래'' 카테고리의 다른 글
| 맵툴 제작기 4 - Unity 에디터 툴로 프리팹 배치 개선하기 (0) | 2026.05.01 |
|---|---|
| 양팔저울 퍼즐 만들기 - 무게 조건과 시각 연출 분리하기 (0) | 2026.04.24 |
| 회전 플랫폼 위에서 상자의 대각선 밀림 고쳐보기 (0) | 2026.04.17 |
| 회전 플랫폼 위에서 플레이어를 자연스럽게 움직이기 (7) | 2026.04.16 |
| 회전 플랫폼 만들기 - AutoRotator와 MovingPlatformSurface (1) | 2026.04.16 |