반응형

 

Collider 편에서 배운 기능으로 점수 획득 오브젝트에 캐릭터가 닿으면 점수를 얻고, 캐릭터가 맞으면 HP를 깎는 총알을 발사하는 디스펜서 기능을 구현해 봅시다.

반응형
반응형

Physics 

레이어로 Collider의 충돌 범위를 설정해서 특정한 충돌만 받아들이거나 무시하기

 

작성 기준 버전 :: 2019.2

 

[이 포스트의 내용은 유튜브 영상으로도 시청하실 수 있습니다]

 

이번 포스트에서는 유니티의 레이어로 Collider의 충돌 범위를 설정하는 방법을 알아보도록 하자.

 

두 개의 Collider가 충돌하면 OnCollision 혹은 OnTrigger 이벤트가 발생하며 개발자는 이 이벤트를 통해서 충돌을 감지했을 때 처리되어야 할 기능을 구현한다는 것을 기억할 것이다.

 

과연 게임에서는 어떤 상황과 이유에서 Collider의 충돌 범위를 설정해서 특정한 충돌을 받아들이거나 무시해야 할까?

 

만약 두 캐릭터가 서로를 향해서 총알을 발사한다고 생각해보자. 그런데 서로에게 발사한 총알끼리의 충돌을 무시하지 않고 그대로 두면 어떻게 될까? 총알끼리 충돌하면 서로에게 발사된 총알이 없어져 버리거나 튕겨져 나갈 것이다.

 

와! 총알을 쏴서 상대방의 총알을 막을 수 있는 게임이라니! 잘 만들면 꽤나 재밌고 멋있을 것 같은 컨셉이다.

 

하지만 대부분의 게임에서는 총알끼리의 충돌같은 건 구현하지 않고 무시하게 만들어버린다.

 

그 이유는 여러 가지가 있을 수 있는데 수많은 총알이 발생시키는 충돌로 게임의 성능이 저하될 수 있다는 것과 이런 총알로 총알을 맞출 수 있는 컨트롤 중심적인 시스템에서는 팬티만 입고 권총을 든 무시무시하고 고일 대로 고여버린 고인물이 달려와서 초보자가 쏜 총알을 모조리 막아버리고 초보자의 뚝배기를 터뜨려 버릴 수 있다는 것이다.

 

총알과 총알이 부딪히는 것 외에도 많은 문제가 있다.

 

어떠한 예외가 있을 수 있는지 살펴보자면 열심히 체력을 깎아놓은 몬스터가 힐팩에 스쳐서 건강해진다던가 플레이어는 던전 입구에서 헤매고 있는데 다른 층으로 넘어가는 콜라이더 앞에서 서성거리던 몬스터가 그 트리거를 건드려서 플레이어가 다음 층으로 넘아가던가 하는 많은 문제가 발생할 수 있다.

 

이걸 코드 레벨에서 막으려면 총알 클래스에는 충돌 검사를 할 때 충돌한 대상이 같은 총알이면 무시하는 코드를, 힐팩 클래스과 던전 층 이동용 트리거 클래스에서는 트리거에 닿은게 플레이어가 아니면 무시하는 코드를 작성해야 할 것이다. 이렇게 수동으로 일일이 예외를 막아야하는 경우가 많으면 많을수록 앞에서 언급한 것과 같은 어처구니가 없게 느껴지는 버그가 발생할 확률이 상승한다.

 

만약 이걸 별도의 코드 작업 없이 간단하고 일괄적으로 막을 수 있다면 당연히 그 방법을 써야될 것이다.

 

그게 바로 이번에 배울 유니티 레이어를 이용한 Collider 충돌 무시하기이다.

 

본격적인 내용에 들어가기에 앞서 아래에 있는 unity-mouse-input-practice.zip 파일을 다운로드 받아서 패키지를 임포트하도록 한다.

 

unity-mouse-input-practice.zip
다운로드

 

그리고 패키지에 포함되어 있는 Simple Character Test 씬을 열도록 한다.

 

 

먼저 게임를 플레이시키고 게임 뷰에 클릭해보면 클릭을 한 번 할 때마다 총알이 한 발씩 나가는 것을 볼 수 있다.

 

void Fire()
{
    //if(Input.GetMouseButtonDown(0))
    if(Input.GetMouseButton(0))
    {
        Vector3 firePos = transform.position + animator.transform.forward + new Vector3(0f, 0.5f, 0f);
        var bullet = Instantiate(bulletPrefab, firePos, Quaternion.identity).GetComponent<Bullet>();
        bullet.Fire(animator.transform.forward);
    }
}

 

SimpleCharacterController 스크립트를 열어서 Fire 함수 안에 있는 GetMouseButtonDown 함수를 GetMouseButton으로 바꾼 뒤, 코드를 저장하고 에디터로 돌아간다.

 

 

다시 게임을 플레이시키고 게임 뷰에서 마우스 왼쪽 버튼을 꾹 누르고 있으면 총알이 쏟아져 나오는 것을 볼 수 있다.

 

private void OnTriggerEnter(Collider other)
{
    if(other.GetComponent<Bullet>() == null)
    {
        Destroy(gameObject);
    }
}

 

그 다음에는 Bullet을 찾아서 스크립트 에디터를 열어보면 아래 쪽에 있는 트리거 감지 이벤트인 OnTriggerEnter에 앞에서 말한 것처럼 충돌 감지 예외를 코드 레벨에서 수동으로 처리하고 있는 것이 보일 것이다. 같은 총알끼리 부딪혔을 때는 무시하도록 작성되어 있다.

 

private void OnTriggerEnter(Collider other)
{
    //if(other.GetComponent<Bullet>() == null)
    {
        Destroy(gameObject);
    }
}

 

이 부분을 주석 처리 해버리면 어떻게 될까? 한 번 테스트 해보자.

 

 

게임을 플레이시키고 마우스를 클릭해보면 발사된 총알끼리 부딪혀서 앞으로 나가지 못하고 바로 사라져버리는 걸 볼 수 있다.

 

그럼 이걸 어떻게 코드 레벨의 예외처리 없이 원래대로 동작하게 만들 수 있을까?

 

이제부터 그걸 알아보자.

 

  

프로젝트 뷰에서 Prefabs 폴더 안에 있는 Bullet 프리팹을 더블클릭해서 프리팹 수정 씬을 열어보자. 그럼 선택된 Bullet 프리팹 게임 오브젝트의 내용을 인스펙터 뷰에서 볼 수 있는데 게임 오브젝트의 이름 아래를 보면 태그와 함께 Default라고 표시된 레이어를 찾을 수 있다.

 

레이어를 클릭해보면 Default, TrasparentFX, Ignor Raycast, Water, UI가 있다.

 

이 레이어에는 여러가지 역할이 있지만 대표적인 것이 바로 지금 배우고 있는 충돌 무시 설정이다.

 

항목들 중에서 제일 아래에 있는 [Add Layer]를 선택하면 레이어를 직접 만들 수 있다.

 

 

Bullet이라는 레이어를 만들어보자.

 

 

그리고 다시 Bullet 프리팹으로 돌아가서 레이어를 Bullet으로 설정한다.

 

그 다음은 레이어의 충돌 설정을 할 차례이다.

 

 

상단 메뉴 바에서 [Edit > Project Settings]를 선택해서 프로젝트 세팅 뷰를 연다.

 

 

이중에 Physics 탭으로 들어가면 레이어끼리의 충돌 설정을 할 수 있다. 가장 아래 쪽을 보면 직각삼각형 형태로 배치된 체크박스들을 볼 수 있는데 행이나 열마다 레이어의 이름이 적혀있는 것을 알 수 있다.

 

제일 앞 칸을 기준으로 보면 Default와 Bullet 사이는 체크가 되어있는데 이것은 Bullet 레이어와 Default 레이어의 오브젝트가 충돌하면 이 충돌을 감지하겠다는 뜻이다. 반대로 체크가 해제되면 두 레이어 사이의 충돌을 무시하겠다는 뜻이 된다.

 

우리는 Bullet끼리의 충돌을 무시할 계획이기 때문에 Bullet과 Bullet이 만나는 지점의 체크를 해제해준다.

 

프로젝트 세팅 뷰를 닫고 플레이를 시킨 뒤 총알을 발사해보자.

 

 

그럼 총알끼리 충돌해서 사라지지 않고 아주 잘 발사되는 것을 볼 수 있다.

 

단 레이어를 사용할 때 주의할 점은 사용자가 만들 수 있는 최대 레이어 갯수는 총 24개 뿐이기 때문에 불필요한 레이어를 함부로 남발하면 나중에 정작 필요한 레이어의 자리가 부족해지는 문제가 발생할 수 있다는 것이다.

 

반응형
  1. 료용 2020.04.22 01:51 신고

    베르님 유투브 파셨네요 좋은영상 기대하겠습니다.

    • wergia 2020.04.22 17:02 신고

      글만으로는 모자란 감이 있어서 구현 과정을 보여드리려고 만들었어요ㅎㅎ

반응형

Physics 

Collider 컴포넌트로 충돌체크하기

 

작성 기준 버전 :: 2019.2

 

[본 포스트의 내용은 유튜브 영상을 통해서도 확인할 수 있습니다]

 

게임에서는 많은 것들이 충돌한다.

 

캐릭터가 총알이나 화살에 맞기도 하고, 달리던 자동차가 건물에 부딪히기도 하며 약간은 다른 개념으로 보안용 레이저에 도둑인 캐릭터가 감지되어 경보가 울리기도 한다.

 

이런 오브젝트의 충돌들을 처리하기 위해서는 물리적인 충돌을 처리하는 방법을 알아야 한다.

 

Collider 컴포넌트

 

유니티 엔진에서 Collider라는 컴포넌트를 이용해서 충돌을 체크한다.

 

 

유니티 엔진에서 가장 대표적으로 사용되는 콜라이더로는 Box Collider, Sphere Collider, Capsule Collider가 있다.

 

 

씬 뷰에서 Collider 컴포넌트가 붙어있는 게임 오브젝트를 선택해보면 위의 이미지와 같은 초록색 선으로 표시되는 영역이 보이는데, 이것은 Collider 컴포넌트가 충돌을 감지하는 영역의 크기를 보여준다.

 

Collider 컴포넌트의 프로퍼티

 

이번에는 각 Collider 컴포넌트의 프로퍼티들을 살펴보자.

 

우선 Collider 컴포넌트들은 공통적으로 Is Trigger 프로퍼티와 Material 프로퍼티, Center 프로퍼티를 가지고 있다. 하지만 이 중에서 Is Trigger와 Material 프로퍼티는 좀 더 뒤에서 설명하도록 하고 먼저 Center 프로퍼티부터 하나씩 살펴보자.

 

Center 프로퍼티와 그 아래에 있는 프로퍼티들은 콜라이더 영역의 위치와 크기를 조절하는데 쓰인다.

 

Center

 

 

먼저 Center 프로퍼티는 게임 오브젝트의 중심으로부터 어느 위치에 콜라이더의 중심을 둘 것인가를 설정한다. 위 이미지처럼  Y 값을 바꾸면 게임 오브젝트의 중심보다 위쪽에 약간 위쪽에 콜라이더 영역이 표시된다. 단 이때의 좌표 기준을 게임 오브젝트의 로컬 좌표를 기준으로 동작한다.

 

Box Collider의 프로퍼티

 

Center 아래의 프로퍼티들은 콜라이더의 종류마다 조금씩 다르니 하나씩 설명해보도록 하겠다.

 

Size

 

 

Box Collider의 Size 프로퍼티는 콜라이더 영역의 크기를 정하는데 쓰인다. Vector3 타입이며 xyz 각 값은 게임 오브젝트의 로컬 좌표계의 축에 일치하게 동작하며, 이 사이즈의 1 단위는 1 유니티 미터를 의미한다.

 

Sphere Collier의 프로퍼티

 

Radius

 

 

Sphere Collider의 Radius 프로퍼티 역시 콜라이더 영역의 크기를 정하는데 쓰이는데, 이 값은 구체의 반지름으로 동작한다. Radius를 1로 정하면 콜라이더 영역의 지름은 2유니티 미터가 된다.

 

Capsule Collider

 

Radius

 

 

Capsule Collider의 Radius 값은 콜라이더 영역의 두께를 정하는데 쓰인다. 참고로 이 값이 Height 값보다 커지면, 콜라이더의 영역이 Sphere Collider처럼 그냥 구체 모양이 되버린다.

 

Height

 

 

Height 값은 Capsule Collider의 길이를 정하는데 쓰인다.

 

Direction

 

 

Direction 프로퍼티는 Capsule Collider의 Height를 변경했을때 길어지는 방향을 정하는 프로퍼티다. 값으로는 X-Axis, Y-Axis, Z-Axis가 있는데, 단어 그대로 로컬 좌표계의 각 축의 방향을 따른다.

 

참고로 사람이 서있을 때의 모양이 위 아래로 길쭉한 모양이 일반적이기 때문에 사람 형태의 캐릭터에 콜라이더를 부착할 때는 Capsule Collider를 주로 사용한다. 그리고 뚱뚱한 캐릭터면 Radius 값을 늘려서 Capsule Collider의 두께를 두껍게하고, 키가 큰 캐릭터면 Height 값을 키워서 길이를 늘리는 방식으로 사용된다.

 

Is Trigger

 

그럼 이제 잠시 뒤로 미뤄두었던 Is Trigger 옵션을 보자. 이 옵션은 콜라이더가 트리거(Trigger)로 동작할지, 콜리전(Collision)으로 동작할지를 정하는 프로퍼티이다.

 

콜리전은 벽이나 바닥처럼 다른 물체를 통과하지 못하게 가로막는 장애물을 뜻하고, 트리거는 마트의 도난 방지 장치처럼 물체를 통과시키되 지나가는 물체를 감지하는 것을 의미한다.

 

 

Sphere 게임 오브젝트를 Cube 게임 오브젝트 위로 떨어지게 만든 뒤 Cube 게임 오브젝트가 가진 Box Collider의 Is Trigger 옵션을 켜둔 상태와 꺼둔 상태로 각각 플레이를 해보면 Is Trigger가 꺼져있을 때는 Sphere가 Cube위에 멈추고, 켜져있을 때는 Cube를 통과해버리는 것을 볼 수 있다.

 

 

참고로, 이렇게 게임 오브젝트가 중력과 같은 물리효과를 받게 하기 위해서는 Rigidbody 컴포넌트를 붙여줘야 한다.

 

Material

 

Material 프로퍼티는 콜라이더가 충돌할 때, 어떤 재료의 물리적인 특성을 보여줄 지를 설정할 수 있는 프로퍼티이다. 이런 종류의 머티리얼을 Physics Material이라고 하는데, 프로젝트 뷰에 우클릭하고 [Create > Physics Material]을 선택해서 생성할 수 있다.

 

 

오브젝트가 고무공처럼 튀는 것을 구현하기 위해 생성한 피직스 머티리얼에 여러가지 옵션이 있지만, 지금은 간단하게 Bounciness 옵션을 0.8로 변경하고 Bounce Combine을 "Maximum"으로 설정한다.

 

 

Sphere 게임 오브젝트의 Sphere Collider에 넣고 플레이 버튼을 눌러보면 아까 전에는 Cube 위에 얌전히 멈췄던 Sphere가 마치 고무공처럼 튀어오르는 것을 볼 수 있다.

 

Collider 충돌 감지하는 스크립트 작성하기

 

이제 이 충돌을 스크립트에서 감지하는 방법을 알아보자.


유니티 엔진에서는 콜라이더끼리 충돌했을 때, 특정한 함수를 호출해준다. 그런데 앞에서 Is Trigger 프로퍼티에 대해서 설명할 때, 이 옵션의 상태에 따라서 트리거와 콜리전으로 나뉜다고 이야기했던 것을 기억할 것이다.

 

유니티 엔진은 이 트리거와 콜리전이 충돌하는 경우를 다르게 취급하고 다른 함수를 호출해준다.

 

OnCollision 이벤트

 

public class ColliTest : MonoBehaviour

{

    // Collider 컴포넌트의 is Trigger가 false인 상태로 충돌을 시작했을 때

    private void OnCollisionEnter(Collision collision)

    {

        Debug.Log("충돌 시작!");

    }

 

    // Collider 컴포넌트의 is Trigger가 false인 상태로 충돌중일 때

    private void OnCollisionStay(Collision collision)

    {

        Debug.Log("충돌 중!");

    }

 

    // Collider 컴포넌트의 is Trigger가 false인 상태로 충돌이 끝났을 때

    private void OnCollisionExit(Collision collision)

    {

        Debug.Log("충돌 끝!");

    }

}

 

콜리전끼리 충돌했을 때는 OnCollision 계열의 이벤트가 호출된다. 충돌 과정은 3단계로 나눠서 호출되는데, 충돌이 시작할 때는 Enter, 충돌이 지속되는 동안에는 Stay, 충돌이 끝나는 순간에는 Exit가 호출된다.

 

private void OnCollisionEnter(Collision collision)

{

    // 이 컴포넌트가 부착된 게임 오브젝트의 콜라이더와 충돌한 게임 오브젝트 가져오기

    var obj = collision.gameObject;

    // 특정 컴포넌트 가져오기

    var component = collision.gameObject.GetComponent<SomeComponent>();

    // 콜라이더 가져오기

    var collider = collision.collider;

    Debug.Log("충돌 시작!");

}

 

이 콜리전과 충돌한 게임 오브젝트를 가져오기 위해서는 매개변수로 전달되는 collision에서 .gameObject 프로퍼티를 호출하면 된다. 그리고 .GetComponent 함수를 사용하면 이 게임 오브젝트에 부착된 컴포넌트를 가져올 수도 있다.

 

OnTrigger 이벤트

 

public class ColliTest : MonoBehaviour

{

    // Collider 컴포넌트의 is Trigger가 true인 상태로 충돌을 시작했을 때

    private void OnTriggerEnter(Collider other)

    {

        Debug.Log(other.name + "감지 시작!");

    }

 

    // Collider 컴포넌트의 is Trigger가 true인 상태로 충돌중일 때

    private void OnTriggerStay(Collider other)

    {

        Debug.Log(other.name + "감지 중!");

    }

 

    // Collider 컴포넌트의 is Trigger가 true인 상태로 충돌이 끝났을 때

    private void OnTriggerExit(Collider other)

    {

        Debug.Log(other.name + "감지 끝!");

    }

}

 

OnTrigger 이벤트 역시 Enter, Stay, Exit의 과정을 거친다. 이 OnTrigger 이벤트는 콜리전이 트리거에 닿았을 때, 콜리전과 트리거 양 쪽에서 모두 호출될 수 있고, 트리거와 트리거가 닿았을 때도 호출된다.

 

private void OnTriggerEnter(Collider other)

{

    // 이 컴포넌트가 부착된 게임 오브젝트의 콜라이더와 충돌한 게임 오브젝트 가져오기

    var obj = other.gameObject;

    // 특정 컴포넌트 가져오기

    var component = other.gameObject.GetComponent<SomeComponent>();

    Debug.Log(other.name + "감지 시작!");

}

 

OnTrigger 이벤트에서도 OnCollision 이벤트에서처럼 매개변수로 전달받은 Collider 타입의 other 변수를 통해서 감지한 게임 오브젝트와 거기에 부착된 컴포넌트를 가지고 올 수 있다.

 

기초적인 Collider의 사용법을 이해했다면 링크된 유튜브 영상을 통해서 이 Collider 컴포넌트의 기능을 응용해서 간단한 게임 기능을 구현하는 방법을 확인할 수 있다.

반응형
반응형

Cloth

-

유니티 2019 버전에서의 Cloth 컴포넌트 문제


작성 기준 버전 :: 2019.*


Cloth 컴포넌트는 유니티 엔진에서 지원하는 사실적인 천의 펄럭임을 구현하기 위한 컴포넌트다.


2018 버전의 Cloth

 

Cloth 컴포넌트를 사용하면 위 그림과 같이 메시(Mesh)를 천과 같은 움직임을 보이도록 시뮬레이션 할 수 있다.


다만, 유니티 2019 버전에 들어오면서 이 Cloth 컴포넌트와 관련해서 여러가지 문제점이 제기되고 있다. 제기된 문제의 주된 내용은 다음과 같다.


2019 버전에서 Cloth 컴포넌트를 사용할 때,

2019 버전에서 Cloth 컴포넌트가 부착된 프리팹을 인스턴스화 했을 때,

Cloth 컴포넌트를 사용하던 2018 버전의 프로젝트를 2019 버전으로 마이그레이션 했을 때,


- 초당 1프레임 수준으로 렌더링 속도가 심각하게 저하됨.

- 메시(Mesh)가 심각하게 찌그러짐.

- 자연스러운 움직임이 아닌 이상하게 꿈틀거리는 움직임을 보임.


사실상 2019 버전에서는 Cloth 컴포넌트를 사용하기 힘든 수준의 문제들이 보고되고 있다.


2019 버전의 Cloth

 

유니티 2019에서 Cloth 컴포넌트를 사용해보면 처음에는 정상적으로 동작하는 것으로 보인다.


 

하지만 Cloth 컴포넌트의 Stretching Stifness 값을 변경해보면 문제가 바로 눈에 띈다.

2018 버전의 Cloth2019 버전의 Cloth

 

2018버전에서는 Stretching Stifness 값을 0으로 바꾸면 펄럭일때 주름이더 세밀해지는 정도의 변화를 보이지만, 2019 버전에서는 Stretching Stifness 값을 0으로 변경하면 버텍스가 찢어지면서 형태가 완전히 무너지는 것을 볼 수 있다. 


때문에 2019 버전에서는 이 문제가 완전히 해결되기 전에는 Cloth 컴포넌트를 사용하지 않을 것을 권장한다.




관련 유니티 포럼 글


Cloth physics problems when migrate from Unity 2018 to 2019

[Cloth] [AR] Unity 2019.2.4f1 broke Cloth

[Cloth] Sudden cloth performance issues in AR

Cloth self-collision: Selection not saving / unselects after clicking Play


반응형
반응형

Physics 

컴포지트 콜라이더 2D(Composite Collider 2D)

 

작성 기준 버전 :: 2019.1.4f1

 

컴포지트 콜라이더 2D 컴포넌트는 다수의 2D 콜라이더를 하나의 콜라이더로 합쳐주는 역할을 하는 컴포넌트이다. 이 컴포넌트는 주로 여러 개의 콜라이더가 하나의 콜라이더로 취급하기 위해서 사용된다.

 

두 콜라이더의 충돌 감지

 

 

그림 1

 

[그림 1]을 보면 원형의 폴리곤 콜라이더를 가진 오브젝트와 사각 콜라이더를 가진 오브젝트가 함께 겹쳐져 있으며 두 오브젝트가 같은 부모 게임 오브젝트 밑에 있는 것을 확인할 수 있다. 위와 같은 상태에서는 두 개의 각 콜라이더가 충돌을 각각 감지한다.

 

public class CollisionTest : MonoBehaviour

{

    private void OnCollisionEnter2D(Collision2D collision)

    {

        Debug.Log(string.Format("OnCollisionEnter2D :: / ", gameObject.name, collision.gameObject.name));

    }

 

    private void OnCollisionStay2D(Collision2D collision)

    {

        Debug.Log(string.Format("OnCollisionStay2D :: / ", gameObject.name, collision.gameObject.name));

    }

 

    private void OnCollisionExit2D(Collision2D collision)

    {

        Debug.Log(string.Format("OnCollisionExit2D :: / ", gameObject.name, collision.gameObject.name));

    }

}

 

위와 같이 충돌을 감지하는 코드를 작성하고,

 

그림 2
그림 3

 

콜라이더가 있는 각 오브젝트에 부착한 뒤,

 

그림 4

 

[그림 4]와 같이 아래로 떨어지는 게임 오브젝트를 만들어서 충돌 테스트를 해보면,

 

그림 5

 

[그림 5]의 로그와 같이 각각의 오브젝트가 따로 충돌을 감지하는 것을 확인할 수 있다.

 

 

컴포지트 콜라이더 2D(Composite Collider 2D)

 

이런 식으로 각각의 콜라이더가 따로 충돌을 감지하는 것이 아니라 두 콜라이더가 하나로 취급되어 충돌을 감지하고자 할 때, 컴포지트 콜라이더 2D를 사용하게 된다.

 

그림 6

 

컴포지트 콜라이더 2D 컴포넌트를 사용하는 방법은 위와 같이 통합하고자하는 콜라이더들의 상위에 컴포지트 콜라이더를 추가해주고,

 

그림 7

 

[그림 7]과 같이 하위에 속하는 콜라이더 컴포넌트들의 Used By Composite 프로퍼티를 true로 설정해주면 된다.

 

그림 8

 

그렇게 하면 상위 오브젝트를 선택했을때, [그림 8]과 같이 하위의 콜라이더들이 하나로 통합되어서 보이는 것을 볼 수 있다.

 

그림 9

 

로그에서도 상위 오브젝트에서의 충돌 체크만 발생하는 것을 알 수 있다.

 

리지드바디 2D(Rigidbody 2D)

 

그림 10

 

컴포지트 콜라이더 2D 컴포넌트를 게임 오브젝트에 부착하면 자동으로 리지드바디 2D 컴포넌트 역시 게임 오브젝트에 부착된다. 때문에 지형지물의 요소에 컴포지트 콜라이더 2D를 사용할 때는 이 리지드바디 2D 컴포넌트의 옵션에 신경써야 한다.

 

그림 11

 

옵션에 신경쓰지 않을 경우, [그림 11]처럼 캐릭터가 지형에 닿자마자 지형이 떨어지거나, 게임이 시작하자마자 지형이 추락해서 지형이 사라지는 모습을 보게 될 수도 있다.

 

그림 12

 

이렇게 컴포지트 콜라이더 2D 컴포넌트를 적용한 게임 오브젝트를 고정된 지형요소로 사용하고자 한다면 리지드바디2D의 바디타입을 Static으로 설정해야한다.

 

그림 13

 

 

생성 타입(Generation Type)

 

그림 14

 

컴포지트 콜라이더 2D 컴포넌트에는 생성 타입이라는 프로퍼티가 존재하는데 Synchronous와 Manual 옵션이 존재한다.

 

그림 15

 

Synchronous는 [그림 15]처럼 하위 콜라이더들이 변동되는 때마다 바로 통합된 콜라이더를 새로 만드는 옵션이고, 

 

그림 16

 

Manual은 컴포지트 콜라이더 2D 컴포넌트가 처음 부착되는 시점 호은 콜라이더 재생성(Regenerate Collider) 버튼을 누른 시점에만 콜라이더를 만드는 옵션이다.

 

그림 17

 

[그림 17]과 같이 아래에 있는 노란 원이 애니메이션에 의해서 위치가 바뀌어도 콜라이더는 따라서 움직이지 않기 때문에 빈 공간에 충돌하는 것을 알 수 있다.

 

위의 설명에서 알 수 있듯이 Synchronous 옵션은 통합된 콜라이더가 변경되는 애니메이션에 따라서 지속적으로 업데이트가 되어야하는 경우에 사용하고, 반대로 Manual은 통합된 콜라이더가 일반 지형과 같이 변경되는 경우가 없는 경우에 사용된다.

 

지오메트리 타입(Geometry Type)

 

그림 18

 

그 다음 중요한 옵션은 지오메트리 타입이다. 이 프로퍼티는 콜라이더를 생성할 때 어떤 형태로 생성할 것인가를 결정한다. 옵션 값은 Outlines와 Polygons가 있다. 

 

그림 19
그림 20

 

Outlines로 콜라이더를 생성하면 [그림 19]와 같이 내부에 아무선 선이 없이 생성되고, Polygons로 콜라이더를 생성하면 [그림 20]과 같이 내부에 선이 그어져서 나온다. 이 차이가 의미하는 것은 Outlines 옵션의 경우에는 콜라이더가 외부에 선만 그어져 있고 속은 비어있다는 뜻이고, Polygons 옵션은 내부가 꽉 차있다는 뜻이다.

 

콜라이더의 내부를 굳이 채울 필요가 있냐고 생각할 수도 있지만, 이것은 확실히 필요한 개념이다. 게임 내에서의 발생하는 충돌 감지나 레이캐스트는 주로 옆에서 날아오기 때문에 Outlines 옵션 만으로도 충분하지만 화면 밖에서 들어오는 터치나 사용자의 클릭 같은 이벤트는 개념적으로 위에서 들어오기 때문에 콜라이더의 내부가 차있는 것이 좋다.

 

public class Picker : MonoBehaviour

{

    private void Update()

    {

        if (Input.GetMouseButtonDown(0))

        {

            var hitResult2D = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), transform.up, 0.1f);

            Debug.Log("2D Raycast Result :: " + hitResult2D.collider.name);

        }

    }

}

 

위와 같이 마우스를 클릭한 지점에서 레이캐스트를 발사해서 콜라이더를 검출하는 코드를 작성해서 Outlines 옵션으로 만들어진 컴포지트 콜라이더와 Polygons 옵션으로 만들어진 컴포지트 콜라이더를 클릭하는 테스트를 진행해보면 그 차이를 명확하게 인지할 수 있다.

반응형
  1. 료용 2019.12.26 02:05

    안녕하세요 베르님 좋은글잘보고있습니다.

반응형

BoxCast, SphereCast, CapsuleCast 제대로 사용하기


지난 Physics 섹션에서는 매 프레임 콜라이더 충돌을 검출하지 않고 원하는 순간에만 콜라이더를 검출해낼 수 있는 Cast 계열의 함수들에 대해서 알아보았다. 이번 섹션에서는 그 중에서도 BoxCast, SphereCast, CapsuleCast를 제대로 사용하는 방법에 대해서 알아보자.


지난 섹션에서 설명했듯이 Cast 계열의 함수는 오브젝트에서 콜라이더 컴포넌트를 가질 필요도 없고, OnCollision이나 OnTrigger같은 이벤트를 통해 매 프레임 동작하는 낭비를 줄일 수 있다. 하지만 이러한 Cast 계열의 함수에는 작은 단점이 하나 존재한다.


게임 오브젝트가 콜라이더 컴포넌트를 가지고, 이벤트를 통해 작동하는 경우에는 Scene 뷰에서 콜라이더의 위치나 크기, 방향 등을 확인할 수 있지만, 이 Cast 계열의 함수의 경우에는 위치, 방향, 크기 등을 직관적으로 확인할 수 없다는 것이다. 즉, 이 Cast 계열 함수에 익숙하지 않은 사람이라면 이 Cast의 위치, 방향, 크기가 자신이 의도한대로 작동되고 있는지 확인하기가 쉽지가 않다는 의미이다. 당연하게도 이것은 확인하기 어려운 문제를 발생시킬 것이다.


그리고 이 Cast의 범위를 확인하기 어려운 문제는 함수의 매개변수의 이름이 불분명한 것과 함수의 매개변수 중 지정하지 않은 값의 기본값이 얼마라고 명시되어 있지 않은 점과 맞물려 전혀 예측하지 못한 형태로 작동하게 만들어 버린다.





문제


캐릭터 주변의 반지름 내의 범위에 존재하는 모든 적들을 찾아서 데미지를 입히는 스킬을 구현한다고 가정해보자. 이 스킬을 구현하기 위해서는 우선 SphereCastAll 함수를 사용해야 할 것이다. 우선 SphereCastAll 함수의 제일 간단한 형태를 보자면 다음과 같다.


RayCastHit[] Physics.SphereCastAll(Vector3 origin, float radius, Vector3 direction);


그림 1 : 파란색 화살표는 forward를 의미한다.


origin과 radius는 간단하게 이해할 수 있지만, 일반적으로 SphereCast가 위의 그림처럼 캐릭터 중심으로 구형으로 작동할 것이라고 받아들이기 쉽기 때문에 SphereCast에 익숙하지 않거나 처음 사용하는 사람의 경우에는 'Sphere가 방향이 바뀌면 뭐가 달라지지?'하는 생각에 direction에서 당황하게 될 것이다. 그리고 일반적으로 아래의 예시 코드와 같이 RayCast를 사용하듯이 direction에 character의 정면에 해당하는 transform.forward를 넣게 될 확률이 높다.


var hits = Physics.SphereCastAll(transform.position, radius, transform.forward);


이렇게 코드를 작성하면 잘 모르는 사용자는 위의 이미지처럼 캐릭터를 중심으로 구형의 범위 내에서만 캐스팅을 할 것이라고 생각하겠지만, 이것은 놀랍게도 전혀 다른 결과를 가져온다.



그림 2


첫번째 이미지처럼 동작할 것이라고 생각한 코드는 사실은 바로 위의 두번째 이미지처럼 구가 캐릭터의 전방으로 무한히 늘어선 형태로 캐스팅을 해버린다. 마치 드래곤볼의 손오공이 에너지파를 전방으로 쏘아낸 것처럼 말이다.


왜 이런 결과가 나왔는지에 대해서 알아보자면 다음의 좀 더 복잡한 매개변수를 가지는 SphereCastAll의 오버로드를 살펴보아야 한다.


RayCastHit[] Physics.SphereCastAll(Vector3 origin, float radius, Vector3 direction, float maxDistance, int layerMask);


위 코드를 살펴보면 제일 처음 보았던 매개변수에 maxDistance라는 매개변수가 추가된 것이 보인다. 이것은 원점인 origin으로부터 direction 방향으로 얼마나 되는 거리까지 캐스팅할 것인지를 의미한다. 그리고 이 값의 기본값은 Infinity, 즉 무한대이다. 그렇기 때문에 그림 2처럼 캐릭터의 정면을 향해서 무한히 확장되는 형태로 캐스팅이 되는 것이다.


이것들이 의미하는 바를 살펴보자면 BoxCast, SphereCast, CapsuleCast 등은 사실 가장 기본적인 RayCast와 똑같이 동작하며, 그 범위만 하나의 점에서 육면체, 구, 캡슐 모양으로 확장된 형태로 동작한다는 것이다.


그렇다면, Cast대신에 다시 콜라이더를 사용해야 하는가? 아니다. 몇가지 방법을 이용하면 충분히 캐릭터 주변의 반지름 내에서 적들을 찾아낼 수 있다.





1. direction을 Vector3.up으로


var hits = Physics.SphereCastAll(transform.position, radius, Vector3.up)


첫번째 방법은 매개변수 중에 direction 값을 Vector3.up으로 주는 것이다.

이렇게 하면 캐스팅이 위쪽으로 확장되면서 캐릭터의 주변에 있는 적들만 검출해낼 수 있게 된다. 다만, 이 방법을 썼을 때는 캐릭터의 위쪽에 있으면서 반지름 내의 범위에 있는 적도 검출되기 때문에, 캐릭터를 중심으로 완전히 구형 내에서만 적을 찾고자 한다면 다음의 방법을 쓰는게 좋다. 그리고 성능적인 면에서도 다음 방법이 훨씬 좋다.





2. maxDistance 값을 0으로


var hits = Physics.SphereCastAll(transform.position, radius, Vector3.up, 0f)


캐스팅의 범위를 캐릭터의 주변으로 제한하는 방법 중 가장 확실하고 좋은 방법은 maxDistance 값을 0으로 설정하는 것이다. 이렇게 하면 SphereCast나 BoxCast, CapsuleCast의 범위가 direction 방향으로 확장되지 않고 아래의 그림처럼 설정된 크기 내에서만 캐스팅이 이루어지게 된다.



이번 섹션에서 이야기한 문제는 예시를 보인 SphereCast뿐만 아니라 BoxCast, CapsuleCast에서도 똑같이 작용한다. 다만 SphereCast가 훨씬 간단하게 사용하고 예시를 보여줄 수 있기 때문에 SphereCast로만 예시를 보였다.

반응형
  1. 정보모아모아 2020.07.22 01:31 신고

    매우 유용한 설명 감사합니다.

  2. 초보유니티 2020.12.15 18:19

    설명을 쉽게 해주셔서 정말 감사합니다!

  3. 234 2020.12.24 22:58

    최고에요 !!!! 감사하비다

반응형

게임 오브젝트에 Collider 컴포넌트를 추가하지 않고 한번만 충돌체를 찾아내는 Physics의 Cast 계열 함수들의 사용법


유니티 엔진에서 충돌체(Collider)를 찾아내는 방법은 여러 가지가 있다. 일반적으로는 게임 오브젝트에 캡슐(Capsule), 박스(Box), 구(Sphere) 형태의 콜라이터 컴포넌트를 달아서 OnTrigger나 OnCollision 계열의 이벤트를 사용해서 충돌체를 찾아내게 된다. 하지만 위 방법의 경우에는 콜라이더 컴포넌트를 지속적으로 유지해야하고 OnTrigger나 OnCollision 계열의 이벤트는 매 프레임 실행되기 때문에 필요한 순간에 한번만 충돌체를 찾아내려는 경우에는 성능상 부적절할 수 있다.


이렇게 필요한 순간에 단 한번 충돌체를 찾아내는 함수는 Physics라는 클래스가 static으로 가지고 있고 여기에 해당하는 함수들은 Cast라는 이름이 붙어있다. 이 Cast 함수에는 크게 4가지의 충돌체를 찾아내는 모양이 있는데 Ray, Box, Sphere, Capsule이 그것이다.


Ray

가장 일반적으로 사용되는 형식으로 특정 지점에서 시작하여 특정한 방향으로 향하는 직선 형태의 Cast이다. 이 직선에 닿은 Collider를 찾아낸다. 이 Ray Cast는 주로 사용자가 클릭한 지점이나 오브젝트를 3D 공간 상에서 찾아내기 위해서 주로 사용된다.


Box

설정한 중심점을 시작으로 하여 지정한 가로, 세로, 높이에 해당하는 직육면체 형태의 Cast이다. 이 직육면체의 면에 닿거나 직육면체의 안에 있는 Collider를 찾아낸다.


Sphere

설정한 중심점을 기준으로 지정한 반지름 내의 구 형태의 Cast이다. 이 구의 표면에 닿거나 구 안에 있는 Collider를 찾아낸다.


Capsule

설정한 두 점을 있는 선을 중심으로 지정한 반지름만큼의 캡슐 형태의 Cast이다. 이 캡슐의 표면에 닿거나 캡슐 안에 있는 Collider를 찾아낸다.


앞서서 살펴본 내용이 충돌체를 찾아내는 모양에 따른 분류였다면 지금 이야기하는 것은 찾아낼 충돌체의 개수에 따른 분류이다. 이 분류에는 Cast, CastAll, CastNonAlloc이 있다.


Cast

Cast는 찾아낸 충돌체 하나만을 반환한다. Ray Cast를 예로 들자면 제일 처음 선에 충돌한 물체만을 반환하는 형식이다. 그 결과는 RayCastHit이라는 구조체로 반환된다.


CastAll

CastAll은 찾아낸 충돌체 전부를 반환한다. 찾아낸 결과는 찾아낸 오브젝트의 개수와 딱맞는 RayCastHit 배열로 반환된다.


CastNonAlloc

CastNonAlloc은 약간 독특한 방식인데 반환이 return을 통해서 이루어지는 것이 아니라, 매개변수를 통해서 이루어진다. 사용자가 RayCastHit의 배열을 만들어서 함수의 매개변수에 넣어주면, 함수가 그 배열에 찾아낸 오브젝트를 채워서 돌려주는 방식이다. 그렇기 때문에 찾아낸 오브젝트의 수가 배열의 수보다 적을 수도 많을 수도 있기 때문에 항상 주의해서 사용해야 한다.


각 형태와 방식에 따른 Cast의 사용법은 다음과 같다.




Cast 사용법

// Raycast

Vector3 startVect = Vector3.zero;
Vector3 direction = Vector3.forward;
float distance = 10f;

Ray ray = new Ray(startVect, direction);
RayCastHit hit = Physics.Raycast(ray, distance);



// RaycastAll

Vector3 startVect = Vector3.zero;
Vector3 direction = Vector3.forward;
float distance = 10f;

Ray ray = new Ray(startVect, direction);
RaycastHit[] hits = Physics.RaycastAll(ray, distance);

foreach(var hit in hits)
{
    Debug.Log(hit.collider.name);
}



// RaycastNonAlloc

Vector3 startVect = Vector3.zero;
Vector3 direction = Vector3.forward;
float distance = 10f;
Ray ray = new Ray(startVect, direction);
RaycastHit[] hits = new RaycastHit[10];     // 여기서 할당한 배열 수 이상은 가지고 오지 못한다.
int hitCount = Physics.RaycastNonAlloc(ray, hits, distance);

// foreach를 사용할 경우, 찾아낸 숫자가 배열 길이보다 작으면 에러가 발생한다.
for (int i = 0; i < hitCount; i++)
{
    Debug.Log(hits[i].collider.name);
}



// BoxCast

Vector3 boxCenter = Vector3.zero;
Vector3 boxHalfSize = new Vector3(1f, 1f, 1f);  // 캐스트할 박스 크기의 절반 크기. 이렇게 하면 가로 2 세로 2 높이 2의 크기의 공간을 캐스트한다.
Vector3 direction = Vector3.up;


bool isCollide = Physics.BoxCast(boxCenter, boxHalfSize, direction);    // 일반적으로 BoxCast는 충돌 여부만 반환한다.


RaycastHit hit;

Physics.BoxCast(boxCenter, boxHalfSize, direction, out hit);    // 충돌 결과에 대한 내용을 가져오려면 RaycastHit 구조체를 out 매개변수로 넣어주어야 한다.

Debug.Log(hit.collider.name);



// BoxCastAll

Vector3 boxCenter = Vector3.zero;
Vector3 boxHalfSize = new Vector3(1f, 1f, 1f);  // 캐스트할 박스 크기의 절반 크기. 이렇게 하면 가로 2 세로 2 높이 2의 크기의 공간을 캐스트한다.
Vector3 direction = Vector3.up;
RaycastHit[] hits = Physics.BoxCastAll(boxCenter, boxHalfSize, direction);    // BoxCastAll은 찾아낸 충돌체를 배열로 반환한다.

foreach (var hit in hits)
{
    Debug.Log(hit.collider.gameObject.name);
}



// BoxCastNonAlloc

Vector3 boxCenter = Vector3.zero;
Vector3 boxHalfSize = new Vector3(1f, 1f, 1f);  // 캐스트할 박스 크기의 절반 크기. 이렇게 하면 가로 2 세로 2 높이 2의 크기의 공간을 캐스트한다.
Vector3 direction = Vector3.up;
RaycastHit[] hits = new RaycastHit[10];
int hitCount = Physics.BoxCastNonAlloc(boxCenter, boxHalfSize, direction, hits);

for (int i = 0; i < hitCount; i++)
{
    Debug.Log(hits[i].collider.name);
}



// SphereCast

Vector3 origin = Vector3.zero;
Vector3 direction = Vector3.up;
Ray ray = new Ray(origin, direction);
float radius = 5f;
bool isCollide = Physics.SphereCast(ray, radius);

RaycastHit hit;
Physics.SphereCast(ray, radius, out hit);

Debug.Log(hit.collider.name);



// SphereCastAll

Vector3 origin = Vector3.zero;
Vector3 direction = Vector3.up;
Ray ray = new Ray(origin, direction);
float radius = 5f;
RaycastHit[] hits = Physics.SphereCastAll(ray, radius);

foreach (var hit in hits)
{
    Debug.Log(hit.collider.gameObject.name);
}



// SphereCastNonAlloc

Vector3 origin = Vector3.zero;
Vector3 direction = Vector3.up;
Ray ray = new Ray(origin, direction);
float radius = 5f;
RaycastHit[] hits = new RaycastHit[10];
int hitCount = Physics.SphereCastNonAlloc(ray, radius, hits);

for (int i = 0; i < hitCount; i++)
{
    Debug.Log(hits[i].collider.name);
}



// CapsuleCast

Vector3 v1 = new Vector3(0f, 0f, 0f);    // 캡슐 시작 부분의 구에 대한 중심점
Vector2 v2 = new Vector3(0f, 3f, 0f);    // 캡슐 끝 부분의 구에 대한 중심점
Vector3 direction = Vector3.up;
float radius = 5f;
bool isCollide = Physics.CapsuleCast(v1, v2, radius, direction);

RaycastHit hit;
Physics.CapsuleCast(v1, v2, radius, direction, out hit);

Debug.Log(hit.collider.name);



// CapsuleCastAll

Vector3 v1 = new Vector3(0f, 0f, 0f);
Vector2 v2 = new Vector3(0f, 3f, 0f);
Vector3 direction = Vector3.up;
float radius = 5f;
RaycastHit[] hits = Physics.CapsuleCastAll(v1, v2, radius, direction);

foreach (var hit in hits)
{
    Debug.Log(hit.collider.gameObject.name);
}



// CapsuleCastNonAlloc

Vector3 v1 = new Vector3(0f, 0f, 0f);
Vector2 v2 = new Vector3(0f, 3f, 0f);
Vector3 direction = Vector3.up;
float radius = 5f;
RaycastHit[] hits = new RaycastHit[10];
int hitCount = Physics.CapsuleCastNonAlloc(v1, v2, radius, direction, hits);

for (int i = 0; i < hitCount; i++)
{
    Debug.Log(hits[i].collider.name);
}


추가적인 이야기로는 캡슐 캐스트를 사용할 때, v1과 v2의 정의에 대해서 헷갈리는 경우가 발생할 수 있는데 그것은 캡슡의 상단과 하단에 가상의 구가 존재한다고 생각했을 때, 그 구의 중심 위치라고 생각하면 된다.

반응형
  1. 료용 2020.02.21 01:19 신고

    Vector3 startVect = Vector3.zero;
    Vector3 direction = Vector3.forward;
    float distance = 10f;

    Ray ray = new Ray(startVect, direction);
    RaycastHit hit = Physics.Raycast(ray, distance);
    이거 이렇게쓰니까
    RaycastHit hit = Physics.Raycast(ray, distance);
    이부분에서 암시적으로 bool형식을 unityEngine.RaycastHit 형식으로 변환할수없다고뜨는데 뭐가문제인거죠?


    • wergia 2020.02.23 00:07 신고

      꽤나 구버전 코드라 그런지 RayCast가 약간 바뀐것 같습니다.
      최신 버전 유니티에서는
      Physics.RayCast 함수가 충돌을 감지했는지에 대한 bool 값을 반환하고 감지 결과는 out 매개변수로 받도록 변경되었습니다.
      따라서 아래 코드와 같이 작성하시면 될것 같습니다.
      Vector3 startVect = Vector3.zero;
      Vector3 direction = Vector3.forward;
      float distance = 10f;

      Ray ray = new Ray(startVect, direction);
      RaycastHit hit;
      if(Physics.Raycast(ray, out hit, distance))
      {

      }

반응형

Character Joint - 캐릭터에 물리효과를 받는 본 추가하기

 

지난 섹션들에서 래그돌을 만들고 사용하는 방법을 배우면서 Character Joint의 존재와 사용법에 대해서 알게 되었을 것이다. 그리고 이전 섹션까지는 애니메이션이 적용된 오브젝트와 Character Joint가 적용된 래그돌 오브젝트를 따로 사용한 것 역시 기억할 것이다. 테스트를 해보면 알겠지만 앞 섹션의 예시처럼 애니메이션이 적용된 모델링에는 일반적으로 Character Joint를 추가해도 물리 기능이 작동하지 않는 것을 알 수 있다.

 

하지만 디테일한 묘사를 원하는 개발자의 경우라면, 캐릭터의 허리 춤에 매달린 장비나 포니테일 같은 묶인 머리가 물리 효과를 받아서 자연스럽게 흔들리는 것을 원할 것이다. 이번 섹션에서 다룰 주제는 바로 그것에 대한 내용이다.

 

 

 

3D 모델 준비

 

 

 

 

흔들리는 '포니테일'을 구현하기 위해 간단한 말 모델을 만들어 봤다. 꼬리의 물리 효과에만 집중할 것이기 때문에 다른 부위는 크게 구현하지 않았다. 우선 모델 파일은 스키닝만 한 상태로 애니메이션을 포함하지 않고 익스포트했다.

 

 

그리고 말이 달릴 때 사용할 애니메이션을 간단하게 만들었다. 이 애니메이션을 만드는 과정에서 주의할 점은 Character Joint로 물리 효과를 사용할 본에는 절대로 애니메이션 키를 잡아서는 안된다. 만약 애니메이션을 만드는 과정에서 물리 효과를 적용하려고 하는 본에 키를 잡아버리면 그 본은 Character Joint를 적용해도 애니메이션 대로만 움직이고 원하는 움직임을 보이지 않게 된다. 이런 방식으로 애니메이터가 애니메이션 작업을 할 때, 애니메이션을 넣지 않은 본을 Jiggle bone이라고 부른다.

 

테스트할 때 사용할 이 귀여운 말의 모델과 애니메이션은 여기에서(

Horse.zip
다운로드

) 다운받을 수 있다.

 

 

 

 

유니티에 모델링과 애니메이션 넣기

 

3ds 맥스에서 필요한 모델과 애니메이션을 모두 익스포트했다면, 이제는 이것들을 유니티 엔진에 적용할 차례다.

 

 

우선은 모델과 애니메이션 파일을 유니티에 넣어준다.

 

 

모델과 애니메이션 파일을 가져왔다면 씬을 다음과 같이 구성해보자. 씬 한가운데에 말의 모델링을 넣고 추가된 말의 오브젝트에 애니메이션을 관리할 애니메이터를 넣어주는 것이다.

 

 

그리고 나서 플레이 버튼을 누르고 애니메이터의 isMove 파라메터를 활성화시키면 말이 달리듯이 위아래로 움직이는 것을 볼 수 있다. 하지만 말의 꼬리는 전혀 움직이지 않는 것을 확인할 수 있을 것이다.

 

 

 

꼬리에 Character Joint 적용하기

 

이제부터 꼬리에 물리 효과를 주기 위해 아까 전에 모델과 애니메이션을 만들 때, 애니메이션 키를 잡아주지 않았던 본들에 Character Joint와 Collider를 넣을 것이다.

 

 

유니티의 Hierarchy 뷰에서 말의 본들을 확인할 수 있다. 이 중에서 실제로 Character Joint가 들어갈 본은 3~5번 본이고 2번 본은 애니메이션 키가 적용된 본으로서 단지 Character Joint가 적용된 본들이 연결될 본이며, 2번 본에는 물리효과가 들어가지 않을 것이다.

 

물리 효과를 적용하기 위해서 다음의 순서를 따라해보자.

 

1. 2번 본에 Rigidbody를 추가한다.

 

 

2. 3~5번 본에 Character Joint를 추가해준다. Connected Body의 경우는 각 본마다 Rigidbody가 적용된 바로 한 단계 전의 본을 넣어주면 된다. 예를 들자면 3번 본의 경우에는 2번 본, 4번 본의 경우에는 3번 본을 넣어주는 식이다.

 

 

3. Character Joint가 추가된 본에 충돌 처리를 해줄 Collider를 넣어주고 적절한 크기로 만들어준다.

 

 

위의 과정을 모두 마치고 난후 플레이 버튼을 눌러보면 말의 꼬리가 자연스럽게 아래로 처지는 것을 볼 수 있다. 그리고 애니메이션을 실행시켰을때에도 물리효과를 받고 있는 것을 알 수 있는데, 아직 모자란 점이 눈에 보일 것이다.

 

 

분명 꼬리는 물리 효과를 받아서 흔들리고 있지만 애니메이션이 위아래로 움직이고 있지만 꼬리는 그 영향을 전혀 받고 있지 않음을 알 수 있다. 이 부분에 대해서는 관성을 처리해주는 별도의 스크립트를 추가로 작성해야 한다.

 

using UnityEngine;

public class JiggleBonePhysics : MonoBehaviour
{
    /// <summary>
    /// 부모 본의 트랜스폼
    /// </summary>
    Transform parentTransform;
    /// <summary>
    /// 이 스크립트가 포함된 본 오브젝트의 리지드 바디
    /// </summary>
    Rigidbody boneRigidbody;
    /// <summary>
    /// 이전 프레임까지의 부모 본의 위치
    /// </summary>
    Vector3 prevFrameParentPosition = Vector3.zero;
    /// <summary>
    /// 관성 가중치
    /// </summary>
    public float power = 0f;
    /// <summary>
    /// 변경된 위치의 크기의 제한. 제한 값이 너무 크면 이 본이 제대로 따라가지 못해서
    /// 각 관절들이 이상한 위치로 날아가는 문제가 발생할 수 있다.
    /// </summary>
    public float clampDist = 0.03f;

    void Start()
    {
        parentTransform = transform.parent;
        prevFrameParentPosition = parentTransform.position;

        boneRigidbody = GetComponent<Rigidbody>();
    }

    void FixedUpdate()
    {
        Vector3 delta = (prevFrameParentPosition - parentTransform.position);
        boneRigidbody.AddForce(Vector3.ClampMagnitude(delta, clampDist) * power);

        prevFrameParentPosition = parentTransform.position;
    }
}

 

위의 코드가 바로 관성을 처리해주는 코드이다. 이전 부모 본의 위치와 현재 부모 본의 위치로 계산해서 이전 위치에 남아있으려는 힘을 본의 Rigidbody에 전해주는 방식이다. 이 스크립트를 Character Joint 컴포넌트를 가진 모든 본에 추가해주면 된다.

 

 

그리고 나서 애니메이션을 동작시켜보면 말의 움직임에 따라 꼬리가 물리효과를 받는 것을 볼 수 있다. 여기에 추가적으로 간단하게 말을 움직이게 만들어 본다면 다음과 같은 장면을 얻을 수 있다.

 

 

이 Jiggle Bone 기능을 만들 때에는 Rigidbody와 Character Joint의 프로퍼티 값들을 세밀하게 살피면서 잘 조절해주어야 자연스러운 움직임을 연출할 수 있다. 위의 과정을 응용하면 이런 말의 꼬리 이외에도 앞서 말한 캐릭터의 허리춤에 매달린 도구, 묶은 머리, 쇠사슬 등 캐릭터에 추가적으로 달린 여러 가지를 흔들리게 만들 수 있다. 물론 잘 설정하면 신체 부위도 가능하다. 가슴이라던가... 바스트라던가... 엉덩이라던가... 개발자라면 도전해볼만한 기능이다.

반응형

+ Recent posts