구글에서 unity ragdoll strech 라고 검색해보면 대부분관절에 붙어있는 Enable proejction을 켜주라고 말한다.
Enable projection을 하면 무슨 원리로 안정적으로 되는 건지 찾아보았으나 정확한 정보는 얻지 못했고 디컴파일러로 열어보니 해당 불린 변수에 대한 주석으로 위와 같이 나와있었다. 아마 정상적이지 못한 래그돌의 흔들림처리를 이전 상태로 다시 되돌려주는 것이 아닐까 추측해본다.
2. 래그돌 재생성
나의 경우 래그돌이 두 개의 문제가 있었다. 하나는 래그돌의 양팔이 벌려지고나서부터는 접혀지지가 않는 문제였고 다른 문제는 신체의 일부가 가만히 있고 연결부위가 엿처럼 쭈욱 늘어나는 문제였는데 둘다 래그돌 생성 시 올바른 오브젝트를 매칭 시키지 않아서 발생한 문제였다.
첫 번째 방법은 래그돌의 rigidbody를 kinematic으로 바꾸고 현재 가해져 있는 힘을 전부 없애버리는 것이다.
두 번째 방법은 래그돌을 꺼버리는 방법이다.
첫 번째 방법은 자연스러운 멈춤이고 두 번째 방법은 인위적인 멈춤이라고 생각한다.
왜 이렇게 생각이 드는지는 자세한 부분을 듣다 보면 알게 될 것이다.
또한 둘 다 장단점이 있는데 자세한 부분은 설명하면서 기재하도록 하겠다.
목차
1. 첫 번째 방법(자연스러운 멈춤)
2. 두 번째 방법(인위적인 멈춤)
3. 각 멈춤에 대한 설명
1. 첫 번째 방법(자연스러운 멈춤)
먼저 구현해보고자 하는 멈춤 방식은 다음과 같다.
public Rigidbody body;
public void Freeze()
{
body.isKinematic = true;
body.velocity = Vector3.zero;
body.angularVelocity = Vector3.zero;
}
public void UnFreeze()
{
body.isKinematic = false;
}
Freeze 함수를 호출하면 동영상처럼 멈추게 되는 것을 알 수 있다.
추가로 알아야 하는 점이라면 물체가 가진 힘을 전부 없애고 싶은 경우(동영상의 세 번째 테스트처럼 래그돌이 날아가다가 모든 힘을 잃고 현재 자리에서 멈추도록 하는 것) Freeze함수를 호출하고 바로 UnFreeze함수를 호출하면 아무 일도 일어나지 않는다.
곰곰이 생각해봤는데 Freeze함수를 호출해서 힘을 전부 다 잃고 나서 프레임이 지나서 해당 물리가 적용되기도 전에 UnFreeze로 다시 원상복구가 돼서 그런 것이 아닐까 생각이 든다.
만약 해당 사항을 스크립트로 적용하고 싶다면 아래처럼 하면 된다.
Freeze();
Invoke("UnFreeze",0.1f);
Freeze에 대한 물리가 적용되고 나서(아마 한 프레임밖에 소요되지 않을 것이라고 생각한다.(UnFreeze에 대한 내용을 FixedUpdate에다가 넣어줘도 되지 않을까 싶다.)
2. 두 번째 방법(인위적인 멈춤)
위와 같은 멈춤은 매우 간단하다. 위의 멈춤은 뭐랄까 메쉬는 남겨두고 래그돌을 꺼준다고 생각하면 된다.
body.gameObject.SetActive(false);
3. 각 멈춤에 대한 설명
각 멈춤은 매우 차이가 크다.
자연 멈춤과 인위 멈춤이라고 명칭을 정하겠다.
자연 멈춤은 멈추고 다시 복원해도 힘을 다 잃어서 연속성이 없다. 그리고 인위 멈춤에 비해서 완전히 얼음!!! 처럼 멈춘다는 느낌은 부족하다. 아마도 모델의 모든 rigidbody를 배열로 받아서 모든 포지션을 저장하고 코루틴으로 해당 rigidbody들의 위치를 계속해서 유지하도록 하면 비슷하게나마 구현할 수 있지 않을까 생각 든다.
인위 멈춤은 멈추고 다시 복원하면 이전의 힘을 그대로 유지한다. 잠깐 래그돌을 껐다가 켜주는 거라서 물리 사항에는 변동이 없다. 굉장히 좋다고 생각할 수 있지만 아래 사진을 보면 인위 멈춤의 부작용이 하나 있다.
아래 사진은 각각의 멈춤을 계속해서 진행하고 나서 래그돌의 상태를 캡처한 것이다.
몇 번밖에 멈춤을 반복하지 않았다. 자연 멈춤은 아무 문제가 없고 인위 멈춤은 완전히 뒤틀려있음을 볼 수 있다.
인위 멈춤의 경우 충돌로 본이 꼬였을 때 멈추면 해당 상태가 그대로 저장이 된다. 마치 고무가 탄성을 잃는 것이다.
만약에 래그돌을 한두 번만 사용하는 경우라면 인위멈춤을 사용해도 괜찮지만 계속해서 사용해야 하는 경우 비정상적인 결과를 초래하기 때문에 전자의 자연 멈춤을 사용할 수밖에 없을 것이다.
인위 멈춤 방식을 사용해도 계속해서 부위가 꼬이지 않도록 하는 방법을 찾아봤으나 정보가 없었다.
public class testGoat : MonoBehaviour
{
public Rigidbody body;
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
Vector3 diffVector = new Vector3(1, 1, 0);
body.AddForce(diffVector * 1500);
}
}
}
위 스크립트는 키보드 A를 누르면 body라는 리지드바디 오브젝트를 1,1,0 벡터로 쏴주는 스크립트다.
해당 스크립트의 Rigidbody를 지정해줘야한다.
힘을 어디에 주느냐는 본인의 상황에 맞춰서인데 나는 척추에다가 힘을 줘서 몸통이 날아가는 느낌을 주고 싶었다.
rigidbody 컴포넌트에 작동을 시켜야 하니 래그돌을 생성할 때 지정했던 오브젝트 부분에 다가만 힘을 줄 수 있음을 추측할 수 있다.
근데 오브젝트를 날리다가 보면 충돌로 인해서 다른 방향으로 가게 되는데 이 문제를 해결하는 방법은 다음에 소개하도록 하겠다.