유니티 Rigidbody의 AddForce와 등가속도 원리를 이용한 [점프 구현]
오늘의 키워드
- 점프
- 등가속도 운동
최근에 참여하고 있는 사이드 프로젝트에서 메모해두면 좋을 것 같은 내용을 이번에 포스팅해보고자 합니다.
오늘 구현할 것은 캐릭터의 점프 입니다.
이번에 사용할 방식은 누구나가 보편적으로 사용하는 Rigidbody의 AddForce, 모드는 Impulse입니다.
단, 사이드 프로젝트 진행 시, 기획서에 적힌 조건이 있습니다.
점프 조건
- 캐릭터의 Y축 크기 (즉 Height : 키) 만큼 점프를 할 것.
추가적으로 자연스러운 점프를 만들기 위해서 고려한 것
- 2단 점프가 가능하며, 2단 점프 시 떨어지는 가속도를 초기화할 것
- 자연스러운 점프
생각한 점프 이미지를 가진 레퍼런스 게임 : Skull
첫 점프 시, 최대한 높은 곳에서 2단 점프를 시도하여 높이 뛰는 경우
빠르게 2단 점프를 시도하여 낮은 높이로 뛰는 경우
점프 키를 입력하여 AddForce를 한 프레임에 1번 실행하여, 원하는 높이까지 캐릭터가 도달하는 코드를 구현해야 합니다.
이를 위해서 AddForce에 입력할 Force값을 public 변수로 설정하여 Inspector에서 값을 조정하는 방법도 하나의 방법이지만, 이는 캐릭터의 Height 즉, 키가 바뀌면 Force값도 바뀌기 때문에 문제가 발생합니다.
이를 위해 정확한 Height 값에 따라서 정확힌 힘이 주어져야 합니다.
등가속도 운동
등가속도 운동이란 물체가 일정한 가속도를 가지고 움직이는(운동하는) 것을 뜻합니다.
제가 사용한 방식은 등가속도 운동 공식을 이용하여 원하는 목표까지 도달하기 위한 힘(Force)를 구하는 방식을 채택했습니다.
등가속도 운동은 3가지 공식이 존재합니다.
위 공식 3가지 중 저는 3번, 속도 - 변위 공식을 사용할 것입니다.
그리고 이 공식을 이용하여 V0(초기 속도). 즉 AddForce에 파라미터로 들어갈 순간적인 힘(Force)의 수치를 찾아낼 것입니다.
3번 : 속도 - 변위 공식을 채택한 이유
위에서 거론했지만, 점프 키를 한 번 입력하여 AddForce 함수를 한 번 실행하여, 목표 높이까지 도달하는 것.
위 방식은 1 Frame이라는 시간적 요소가 주어지지만 이를 정확하게 수치화 하기는 힘듭니다.
그렇기에 t(시간)에 영향을 받지 않는 3번 공식을 사용하겠습니다.
공식을 사용한 풀이
점프를 하게되면 당연하게도 중력에 의해 점점 위로 향하는 속도(Velocity)는 줄어들게 될 것이며,
최종 높이에 도달했을 때, 캐릭터의 속도(Velocity)는 0이 될 것이라는 것은 고민하지 않아도 알 수 있습니다.
그렇기에 3번 공식에서 V는 최종 목적지(변위)의 속도이기에 V에 0을 대입할 수 있습니다.
그 결과는 다음과 같습니다.
S (변위) : 이동거리는 캐릭터의 Y축 크기이기에 Height 값을 대입 합니다.
점프를 한다는 가정하에 등가속도 운동을 적용한다면 a(가속도)는 중력이 있습니다.
중력은 아래로 작용하기 때문에 음의 값을 가지게 됩니다.
이러한 값들을 모두 대입하여 최종적 대입하면 공식은 아래와 같아집니다.
g : 중력 가속도
h : 캐릭터 Y축 크기
V0 : 초기 속도 ( AddForce에 입력할 Force)
점프 구현
1. 먼저 점프를 할 캐릭터와 캐릭터가 서있을 땅을 구현합니다.
- 이번에는 2D Object / Sprite / Square를 사용하여 바닥과 캐릭터를 간단하게 만들었습니다.
2. Rigidbody, Collider2D, SpriteRenderer 3가지의 Components를 사용합니다.
(이 때 캐릭터가 Z축으로 회전되지 않게 반드시 Rigidbody2D의 Freeze Rotation을 체크합니다.
3. 캐릭터의 점프 입력과 로직을 작성할 스크립트를 생성합니다.
코드 구현
1. 먼저 Rigidbody2D와 SpriteRenderer를 스크립트 내에서 사용할 것이기에 GetComponent를 통해서 가져옵니다. (public으로 설정하고 Inspector창에서 드래그 앤 드랍해도 상관없습니다.)
또한 캐릭터의 키(Height) 값은 SpriteRenderer.bounds.size.y에서 가져옵니다.
localScale값을 가져오지 않은 이유는, 만약 실제 프로젝트라면 이미지의 Y축 크기만큼 점프해야 할 필요가 있기 때문.
private Rigidbody2D rigid;
private SpriteRenderer spriteRenderer;
public float Height => spriteRenderer.bounds.size.y;
private void Awake()
{
rigid = GetComponent<Rigidbody2D>();
spriteRenderer = GetComponent<SpriteRenderer>();
}
2. Rigidbody2D에서 지원하는 캐릭터가 적용받을 중력의 Scale을 입력합니다.
Physics2D.gravity.y값은 지구의 중력값인 9.807m/s² 입니다.
단, Rigidbody2D.gravityScale에 적용하려면 양의 수로 적어야 하며, Physics2D.gravity.y값은 음수이기에 마이너스를 붙여줍니다.
private void Start()
{
rigid.gravityScale = -Physics2D.gravity.y;
}
3. AddForce에 입력할 초기 속도 즉, Force값을 계산하는 함수를 구현해줍니다.
아까 세운 공식에서 g값은 중력의 Scale이 아닌 중력 가속도 값을 대입해줘야 하기 때문에 Gravity 제곱하여 중력 가속도를 구합니다.
그 후, 값에 루트를 씌우는 Mathf.Sqrt()를 이용하여 V0(초기 속도를 반환합니다.)
private float CalculateInitialVelocity(float height)
{
float gravityVelocity = Physics2D.gravity.y * Physics2D.gravity.y;
return Mathf.Sqrt(2f * gravityVelocity * height); // 루트(2gh)
}
4. Jump 함수를 만들고 AddForce에 해당 초기 속도 값을 넣어줍니다.
이 때 AddForce를 실행하기 전에 반드시 Y축 가속도를 0으로 초기화 해준 후 실행해야 합니다.
-> 이렇게 해야 위 레퍼런스인 Skull과 같은 점프를 구현할 수 있습니다.
-> 0으로 초기화 하지 않을 경우, 추락 시 0 미만인 yVelocity 값에 영향을 받아서, 부자연스럽고 불편한 점프가 됩니다.
private void Jump()
{
float initialVelocity = CalculateInitialVelocity(Height);
rigid.linearVelocityY = 0;
rigid.AddForce(Vector2.up * initialVelocity, ForceMode2D.Impulse);
}
5. 마지막으로 점프 키를 입력 받고 Jump()를 실행할 수 있는 코드를 구현합니다.
이 포스팅에서는 Update() 코드에 InputManager 방식을 사용했지만, InputSystem을 사용하는 것을 추천합니다.
private void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
Jump();
}
}
최종 결과
결과 캐릭터의 키에 가까운 값 만큼 점프하는 것을 볼 수 있으며,
캐릭터의 Y Scale을 1만큼 증가시키고 점프하면,
캐릭터의 키를 반영하여 점프하는 것을 볼 수 있습니다.
전체 코드
public class TestPlayer : MonoBehaviour
{
private Rigidbody2D rigid;
private SpriteRenderer spriteRenderer;
public float Height => spriteRenderer.bounds.size.y;
private void Awake()
{
rigid = GetComponent<Rigidbody2D>();
spriteRenderer = GetComponent<SpriteRenderer>();
}
private void Start()
{
rigid.gravityScale = -Physics2D.gravity.y;
}
private void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
Jump();
}
}
private void Jump()
{
float initialVelocity = CalculateInitialVelocity(Height);
rigid.linearVelocityY = 0;
rigid.AddForce(Vector2.up * initialVelocity, ForceMode2D.Impulse);
}
private float CalculateInitialVelocity(float height)
{
float gravityVelocity = Physics2D.gravity.y * Physics2D.gravity.y;
return Mathf.Sqrt(2f * gravityVelocity * height);
}
}
오늘은 Rigidbody의 AddForce를 이용하여 점프를 구현해봤습니다.
점프를 구현하는 방법은 정말 다양하게 있습니다.
Rigidbody를 사용하지 않는 경우도 있습니다.
Rigidbody를 사용할 경우 원하는 방식의 물리 연산을 커스텀하기 어려울 수 있기 때문에,
자신만의 캐릭터 물리 연산을 구현하기도 합니다.
오랜만에 코딩 관련 포스팅을 해보는 것 같은데,
최근에 너무 바쁘다 보니 자주 못해서 아쉬운 것 같습니다. 다음 포스팅은 열심히 연구중인 기술에 대해서 포스팅 해보도록 하겠습니다.
이상으로 오늘의 포스팅을 마치겠습니다.
감사합니다.
