1. 개요
2. Transform, Rigidbody 방식 차이
3. Transform 방식으로 포물선 운동 구현
3.1 Tween
3.2 단순 함수
3.3 떨어지는 방향 보도록 하기
4. Rigidbody 방식으로 포물선 운동 구현
4.1 포물선 운동 물리 공식 이해
4.2 도착지점 알고 발사 각도 알 때
4.3 도착 시간 알고 각도 알 때
4.4 Rigidbody 방식의 한계 - 중력이 정해져 있다
4.5 중력가속도를 바꿔서 투사체마다 다른 발사 방식 적용하기
4.6 떨어지는 방향 보도록 하기
5. 결론과 요약
1. 개요
유니티로 게임을 만드는 사람이라면 한번쯤은 접해보고 포물선 운동 공식을 찾아보고 뭔개소리지 하며 절망했을만한 주제에 대해서 다뤄보도록 하겠다.
다양한 포물선 운동을 나타내는 방식과 코드에 대해서 작성해보았다. 나도 배워가는 단계라서 충분히 부족하지만 그나마 어느정도 이해는 한 상태로서 처음부터 배우고자 하는 사람에게 도움이 되고자 글을 써본다.
포물선 운동에 관한 포괄적인 내용정리를 목표로 글을 써봤다.
2. Transform, Rigidbody 방식 차이
그 전에 먼저 유니티의 물리 엔진을 사용한다는 것에 대해서 이해를 해야한다.
유니티에서 게임 오브젝트를 이동하는 방식은 크게 두 가지가 있다.
첫번째는 Transform을 기반으로 움직이는 방식
transform.poisiton = transform.poisiton + Vector3.one; 을 행하게 되면 오브젝트가 순간이동을 하게 된다. 이를 반복적으로 행하면 연속적으로 이동하는 것 처럼 보이는데 그 원리를 이용하여 오브젝트를 움직이는 것 이다. 이런 방식을 Transform 기반으로 움직인다고 칭한다.
두 번째는 Rigidbody를 이용해서 움직이는 것이다.
오늘의 주제인 유니티 엔진의 물리를 이용해서 움직인다고 생각하면 된다.
Rigidbody.addforce(Vector3.one); 을 행하게 되면 물체가 물리작용을 받아서 움직이게 된다.
두 방식은 작게 봤을때는 기호의 차이처럼 보이지만 상당한 차이를 가지고 있으며 여기서 어떠한 방식으로 접근하냐에 따라 추후에 문제가 발생할 수도 있고, 발생한 문제를 해결해나가는 양상은 전혀 다르게 전개된다.(즉 여기서 내가 어떤 방식을 써야하는지 잘 골라라 이말이야)
간단한 예를들어 아래 글은 유니티 트레일 작동 방식에 대한 글 인데 Transform 이동방식과 Rigidbody 이동방식 차이에 따라 트레일이 생성되냐 안되냐 차이가 생긴다.
3. Transform 방식으로 포물선 운동 구현
Transform 방식으로 오브젝트를 움직이는 방식으로 이동하는 경우에 대해서 살펴보고 특징과 어떤 코드를 사용해야 포물선 움직임을 구현할 수 있는지 알아보자.
특징 1 : 유니티 물리엔진인 Rigidbody를 사용하지 않고 직접 움직이면 유니티 엔진의 한계없이 게임을 만들 수 있다.(여기서 말하는 한계란 유니티 물리 엔진을 활용하기 시작하면 복잡한 구현을 할 때마다 더욱 큰 난관에 부딪힌다는 말이다. 만약에 움직임을 addforce로 구현한다고 생각해보자, 이후 공기저항에 따른 이동속도 감소, 회전을 줘야할때 자체적인 스크립트가 아닌 유니티 Rigidbody 방식에서 해결하려면 미세한 컨트롤이 힘들어진다.)
특징 2 : 이동 단위(속도)가 큰 경우 충돌 판정이 정상적으로 이뤄지지 않음, 일반적으로 생각하는 물리적 결과와는 다른 결과가 나올 수 있음, 트리거 Enter 및 Collision 이벤트가 정상적으로 작동하지 않을 수 있음.
왼쪽과 오른쪽은 단순히 translate 해주는 크기의 차이(속도)로 인해서 발생하는 문제다.
둘다 텔레포트 하느 방식으로 이동하다보니 벽보다 이동속도가 빠른 경우 벽을 뚫고 지나가 버리는 경우가 허다하다.
이런 경우는 자체적으로 Collision 및 Enter를 판별하기 위하여 이동할때마다 물체간의 좌표에 대한 정보를 확인하여 해당 이벤트를 스스로 처리하는 시스템을 구현해야한다.
3.1 Tween
Transfom 이동방식중 한가지인 Tween 방식이다.
Tween을 모르는 분을 위해서 간단히 설명하자면 iTween, Dotween과 같은 라이브러리가 있는데 흔히 오브젝트를 부드럽게 이동하는 것을 구현하기 위해 IEnumerator를 위치를 이동해주는 것을 구현해주는데 그런 기능을 코드 한줄로 구현할 수 있도록 도와주는 유틸리티다.
그리고 Tween에는 보통 포물선 운동을 구현할 수 있는 함수가 내장되어있다.
Dotween의 경우 DOPath라는 함수를 사용하여 포물선 운동을 시각적으로 표현할 수 있다.
void Start()
{
Vector3 firstPos = transform.position;
Vector3 secondPos = firstPos + new Vector3(5, 5, 0);
Vector3 thirdPos = firstPos + new Vector3(10, 0, 0);
transform.DOPath(new[] {secondPos, firstPos+Vector3.up, secondPos + Vector3.left * 2, thirdPos, secondPos + Vector3.right * 2, thirdPos + Vector3.up}, 1f,PathType.CubicBezier).SetEase(Ease.Unset);
}
위와 같은 코드를 사용하면 된다. 코드 사용방법은 Tween 라이브러리마다 다르다.
위의 운동이 어색해보이는 이유는 속도가 처음에 빨랐다가 최고점에서 가장 느렸다가 마지막에 빨라지는 효과를 넣어줘야하는데 그 부분을 넣어주지 않아서 그렇다.
Easing이라는 옵션을 넣어줘야하는데 포물선에 적합한 Easing이 없어서 함수를 직접 구현해줘야하는데 요청이 있으면 해당 부분을 구현해서 올리도록 하겠다.
3.2 단순 함수
직접 정의한 공식을 사용하여 함수형태로 움직이는 수식을 만들어서 처리하는 것이다.
어찌보면 Tween과 비슷한 처리 방식이지만 Tween에서 해주는 함수를 직접 정의해서 돌려주는 느낌이다.
private Vector3 startPos, endPos;
//땅에 닫기까지 걸리는 시간
protected float timer;
protected float timeToFloor;
protected static Vector3 Parabola(Vector3 start, Vector3 end, float height, float t)
{
Func<float, float> f = x => -4 * height * x * x + 4 * height * x;
var mid = Vector3.Lerp(start, end, t);
return new Vector3(mid.x, f(t) + Mathf.Lerp(start.y, end.y, t), mid.z);
}
protected IEnumerator BulletMove()
{
timer = 0;
while (transform.position.y>=startPos.y)
{
timer += Time.deltaTime;
Vector3 tempPos = Parabola(startPos, endPos, 5, timer);
transform.position = tempPos;
yield return new WaitForEndOfFrame();
}
}
private void Start()
{
startPos = transform.position;
endPos = startPos + new Vector3(5, 0, 0);
StartCoroutine("BulletMove");
}
위와 같다. (gist.github.com/ditzel/68be36987d8e7c83d48f497294c66e08)
필요한 변수는 시작지점, 착지지점, 높이, 걸리는 시간이다.(현재 수식에서는 1초로 고정)
해석
위의 식을 분석해보고 그에 대한 해석을 나름 해보았는데 궁금하면 읽어보자(패스해도 무방)
위 식에서 필요한 변수를 다시한번 생각해보자.
시작지점, 착지지점, 높이, 걸리는 시간
이 식이 갖는 특이한 점이라면 포물선 운동을 구현하는데 사인함수와 코사인함수 그리고 각도 정보가 없다는 것이다.
어떻게 그것이 가능할까?
return new Vector3(mid.x, f(t) + Mathf.Lerp(start.y, end.y, t) mid.z); 부분을 보자.
여기서 두 부분으로 나눌 수 있다.
x,z축 그리고 y축이다.
x,z축은 앞과 뒤를 나타내는 평면좌표계고 y축은 높이를 나타낸다.
x, z 값은 직선운동처럼 시작지점부터 끝지점까지 Lerp함수를 이용해서 1초를 기준으로 0초면 시작지점, 1초면 끝지점이 되도록 값을 할당받는다.
그리고 y값은 직접 정의한 f(t)함수를 이용해서 받아오는데(y값 뒤의 Mathf.Lerp(start.y, end.y, t)는 높이가 다른 경우를 위해서 만들어 준 것인데 처음의 높이와 끝 부분의 높이를 맞춰주는 것으로 큰 의미는 없다.
그러면 직접 정의한 함수 f(x)를 살펴보자. height가 10일때 x(시간)에 따른 그래프는 위와 같이 나온다.
함수 내 변수 x는 lerp함수를 거치기 떄문에 무조건 0에서 1까지만 들어가도록 되어 있어서 x가 0.5일때는 무조건 고점 height값을 가지도록 되어있다.
즉 이 수식은 포물선 이동에 대한 물리 함수라기보다는 x이동 따로(직선운동), y 이동은 이차곡선 형태로 이동하도록 된 식이다.
3.3 떨어지는 방향 보도록 하기
떨어지는 방향 보는거 코드 짜주기
4. Rigidbody 방식으로 포물선 운동 구현
다음은 유니티 물리 엔진을 직접 이용해서 구현하는 방식에 대해서 알아보자.
사용하기 쉽고 직관적이지만 너무 애용하다보면 복잡한 구현이 필요할수록 점차 나락으로 빠질것이다.(나처럼)
장점 : 이동 단위(속도)가 큰 경우에도 충돌 판정이 정상적으로 이뤄짐(단 TriggerEnter는 속도가 말도안되게 너무 빠르면 정상적으로 작동하지 않을 수 있음)
오른쪽을 보면 물체의 이동속도가 벽의 두께보다 훨씬 높은데도 정상적으로 못넘어가게 막아주고 있다.
단점 : 유니티 엔진이 제공하는 것 이상의 기능을 구현할때 힘들어짐.(이 부분은 직접 경험해봐야 안다.)
4.1 포물선 운동 물리 공식 이해
인터넷에 쳐보면 바로 나오는 공식이지만 설명이 친절하지는 않다.
한번 내가 포물선 운동 물리 공식에 대하여 설명해보도록 하겠다.(내가 친절하게 설명해준다고는 안함)
그 전에 이해해야하는 개념이 몇 가지 있다. 몰라도 되긴하는데 궁금하면 읽어보자.
등속 운동과 가속운동
등속 운동은 말 그대로 속도가 일정한 운동을 의미한다.
총을 초속 10m/s로 쐈다.
해당 총알은 10m/s의 등속 운동을 하게 된다.(공기저항 제외)
t초 뒤 총알의 이동 거리는 10m * t이다.
등가속 운동은 일정하게 속도가 증가하는 운동을 의미한다. 짱짱한 자동차가 있다고 했을때 풀악셀을 밟아서 초당 10m/s의 속도로 증가하는 운동을 하는 경우가 그에 해당한다.
해당 자동차의 t초 뒤 속도는 10 * t이다.
그리고 자동차의 t초 뒤 이동 거리는 5t^2 이다.(해당 공식을 유도하는 방법은 정적분을 참고하자)
포물선 운동의 이동거리 공식을 보면 식을 이해하려고 하지말고 따라 붙는 변수의 형태만 확인해보자.
x축 이동은 등속운동 y축 이동은 등가속 운동을 한다는 것이다. 포물선 운동에 대해서 조금 더 이해가 되지않는가? x축 운동은 그냥 평범한 이동인것이다.
y축은 계속해서 중력만큼의 힘이 더해져서 아래로 떨어지는 등가속운동을 하는 것이다.
포물선 운동 공식 이해
그렇다면 대망의 포물선 운동 공식을 이해해보자.
중력이 작용하는 세계에서 x와, y축 방향 성분을 가지고 있는 v라는 힘을 주게 되면 아래와 같이 설명할 수 있다.
각 축에 대한 속도식은 아래와 같다. V의 벡터분해에 대한 것은 따로 찾아보자
여기서 t에 대한 값을 넣으면 그 시간대에 해당하는 각축별 속도를 의미한다.
x축은 어떤 시간값을 넣더라도 항상 동일한 값을 나타내고 y축은 시간이 값이 작아진다.
그리고 이동 거리에 대한 정적분 결과는 아래와 같다.
5개의 변수 그리고 2개의 식
포물선 운동 설명
포물선 운동 공식에는 2개의 식과 5개의 변수가 나온다.
V, 세타, t, x위치, y위치
우리는 x위치, y위치를 알고있다.
변수는 3개인데 식은 2개다. 따라서 세 변수중 하나를 결정해주면 나머지 2개의 변수가 스스로 결정된다.
세타를 정해주면 속도와 시간이 정해지고
속도를 정해주면 각도와 시간이 정해지고
시간을 정하면 속도와 세타가 정해진다.
그에 따라 생기는 문제 원하는 투사체 발사를 못만든다.
하지만 중력을 변수로 바꿔버리고 세타, t를 지정해주면? V와 중력에 대한 값이 연립해서 나오게 된다.
이하 작성하다가 말음 ... ( 아마 쭈욱 작성 안될 가능성 높음 )
4.2 도착지점 알고 발사 각도 알 때
인터넷에 가장 많이 나와있는 유형
4.3 도착 시간 알고 각도 알 때
4.4 Rigidbody 방식의 한계 - 중력이 정해져 있다
일단 그 전에 3차원 포물선 운동을 이해하기 쉽게 쉬운 운동으로 변환해서 설명하겠다. 여기서 말하는 3차원은 유니티라고 가정하겠다.
3차원(x y z 좌표평면) 포물선 운동 -> 2차원(x y좌표평면) x축 운동 + 2차원 y축 운동
3차원 운동 예시 : new Vector3 (10, 10, 0) -> 2차원 운동 : new Vector2(10,0) + new Vector2(0, 10) 이렇게 쪼개진다.
그렇다면 포물선 운동 공식에 대해서 얘기하기 전에 직선 운동에 대해서 먼저 얘기해보자
물리에서는 -> 직선운동 설명
포물선 운동 설명
포물선 운동 공식에는 2개의 식과 5개의 변수가 나온다.
V, 세타, t, x위치, y위치
우리는 x위치, y위치를 알고있다.
변수는 3개인데 식은 2개다. 따라서 세 변수중 하나를 결정해주면 나머지 2개의 변수가 스스로 결정된다.
세타를 정해주면 속도와 시간이 정해지고
속도를 정해주면 각도와 시간이 정해지고
시간을 정하면 속도와 세타가 정해진다.
그에 따라 생기는 문제 원하는 투사체 발사를 못만든다.
하지만 중력을 변수로 바꿔버리고 세타, t를 지정해주면? V와 중력에 대한 값이 연립해서 나오게 된다.
4.5 중력가속도를 바꿔서 투사체마다 발사 속도와 각도를 정해주기
4.6 떨어지는 방향 보도록 하기
5. 결론과 요약
물리 기반 게임이 아닌 이상 그냥 4.2번 방식으로 가는 게 좋다
짬밥 높은 개발자가 결국 코어 물리로 가게 되면 이해하기 힘든 영역(drag)이 추가되면 그때부터 나락으로 빠지기 시작함
'Unity' 카테고리의 다른 글
유니티 블루스택 구동 (1) | 2020.09.27 |
---|---|
유니티 파이어베이스 이벤트 로깅 (0) | 2020.09.22 |
유니티 파이어베이스 로그 비활성화 (2) | 2020.02.05 |
유니티 꿀팁 (0) | 2019.02.27 |
유니티 물체 따라다니도록 하기 (0) | 2019.02.24 |