Raycasting

개발조하·2023년 11월 28일
0

Unity

목록 보기
10/30
post-thumbnail

1. Physics.Raycast()

Raycasting: 레이저를 쏴서 레이저를 부딪히는 물체의 유무 체크 (bool값 출력)

1.1 사용법

    private void Update()
    {
        //로컬 기준의 forward를 world기준값으로 변환해서 look에 넣어줌
        Vector3 look = transform.TransformDirection(Vector3.forward);
        //Ray 시각화 (Scene뷰에만 보임)
        Debug.DrawRay(transform.position + Vector3.up, look * 10, Color.red);
        
        RaycastHit hit;
        if (Physics.Raycast(transform.position + Vector3.up, look, out hit, 10))
        {
            Debug.Log($"Raycast! @{hit.collider.gameObject.name}");
        }
    }

1.2 중첩된 물체에 Ray를 쏘면?

ㄴ 디버그 상에서는 두 물체 모두에 Ray가 닿고 있지만 실제로는 앞에 있는 물체만 RayCasting된다.

❓ 만약, 관통하는 모든 물체를 체크하고 싶다면??

Physics.RaycastAll() 사용

    private void Update()
    {
        //로컬 기준의 forward를 world기준값으로 변환해서 look에 넣어줌
        Vector3 look = transform.TransformDirection(Vector3.forward);
        Debug.DrawRay(transform.position + Vector3.up, look * 10, Color.red);

        RaycastHit[] hits;
        hits = Physics.RaycastAll(transform.position + Vector3.up, look, 10);

        foreach(RaycastHit hit in hits)
        {
            Debug.Log($"Raycast! @{hit.collider.gameObject.name}");
        }

        //RaycastHit hit;
        //if (Physics.Raycast(transform.position + Vector3.up, look, out hit, 10))
        //{
        //    Debug.Log($"Raycast! @{hit.collider.gameObject.name}");
        //}      
    }

2. 투영

이처럼 Ray를 발사하여 물체를 체크하는 방식을 통해 2D와 3D좌표계를 상호 변환하여 체크할 수도 있다. 이를 위해 '투영'의 개념을 이해해야 한다.

2.1 좌표계

  • Local 좌표계: 특정 물체가 기준이 되는 고유좌표계
  • World 좌표계: 3차원 게임 세상의 절대좌표계
  • Screen 좌표계: 화면 2D 좌표계

❓ 화면을 클릭하면 그 클릭한 부분(2D)이 3차원 Game 씬에서 어떤 좌표인지 알 수 있는 방법?
💡 3D좌표값을 2D좌표계(Screen)로 변환하는 방법

2.2 Input.mousePosition: Screen좌표 확인

    private void Update()
    {
        //Local <-> World <-> Viewport <-> Screen(화면)

        Debug.Log(Input.mousePosition);
    }

ㄴ Input.mousePosition: 마우스의 위치를 screen 좌표(x,y,0)로 출력해준다. -> 화면의 픽셀좌표를 기준으로 출력해준다.

2.3 Camera.main.ScreenToViewportPoint(): Viewport좌표 확인

    private void Update()
    {
        //Local <-> World <-> Viewport <-> Screen(화면)
        Debug.Log(Camera.main.ScreenToViewportPoint(Input.mousePosition));
    }

ㄴ 0~1의 값으로 출력되며, screen 해상도에서의 비율로 표현(픽셀x).

2.4 screen 좌표 -> world 좌표로 변환

  • World 좌표를 Screen좌표로 변환한다는 것은, z축이 없어진다는 뜻!
    -> 이를 '투영'이라고 한다.

투영의 법칙: '비율'이 일정하다.

즉, 카메라로 바라봤을 때 Near Plane의 비율과 Far Plane의 비율이 일치한다.
그러므로 screen에서 유저가 화면 중간쯤에 서있는 player를 클릭하고자 화면 중간을 클릭을 하면, 카메라로 부터 발사된 Ray가 깊이감있게 투영되어 중간에 있는 player에 닿게 되는 원리이다.
이를 코드로 작성하면 아래와 같다.

    private void Update()
    {
        if(Input.GetMouseButton(0))
        {
            Vector3 mousePos = Camera.main.ScreenToWorldPoint
            (new Vector3(Input.mousePosition.x, Input.mousePosition.y, Camera.main.nearClipPlane));
            Vector3 dir = mousePos - Camera.main.transform.position;
            dir = dir.normalized;

            Debug.DrawRay(Camera.main.transform.position, dir * 100.0f, Color.red, 1.0f);
            RaycastHit hit;
            if (Physics.Raycast(Camera.main.transform.position, dir, out hit))
            {
                Debug.Log($"Raycast Camera @{hit.collider.gameObject.name}");
            }
        }
    }

위 코드는 원리를 설명하기 위해 자세하게 쓴 코드이고, Unity에서 제공하는 간편한 코드로 수정하면 아래와 같다.
즉, 이를 통해 Screen에서 클릭한 좌표가 World좌표에서도 구현되는 것을 체크할 수 있다.

3. LayerMask

현재는 UnityChan에 씌운 Capsule Collider와 Cube의 BoxCollider에 Ray를 쏘기 때문에 비교적 가벼운 연산작업이지만, 만약 복잡한 MeshCollider를 사용하는 물체에 Ray를 쏘게 된다면 또 성능에 부하를 주게 된다.

(만약 게임에서 복잡한 모양의 던전에 Collision 기능을 구현해야 한다면, 충돌전용 Mesh를 별도로 만들어서 충돌 시의 연산은 그 Mesh로 진행하도록 설계하여 연산 부하를 줄일 정도로 진지한 사안이다.)

하지만, 단순한 Box Collider라고 하더라도 물체가 많아지면 이 역시 Raycasting을 사용할 때 부하를 야기할 수 있다.

이를 해결하기 위해 구현하고자 하는 기능에 필요한 'collider'만을 Raycast가 연산할 수 있도록 LayerMask를 쓰는 방법이 있다.

  • Add Layer (레이터 추가)

3.1 bit 플래그 사용

ㄴ bit 플래그 사용
-> Layermask를 통해 원하는 오브젝트만 Raycast를 적용하도록 한다! (최적화를 위한 작업)

3.2 LayerMask.GetMask("Layer명")


❓ Raycast의 인자 중 layerMask는 int형으로 선언되어야 하는데, LayerMask 구조체로 선언된 mask가 문제없이 잘 실행된다??

💡 LayerMask 구조체는 애초에 int타입의 value 속성으로 만들어졌기 때문!

4. Tag

Layer를 살펴본 김에 옆에 있는 Tag의 쓰임도 살펴보자.
먼저, 'MainCamera'의 Tag를 보면 'MainCamera'로 되어있다.
⚠️ 만약, 이것을 'Untagged'로 바꾸고 실행한 뒤 Raycast를 사용하려고 하면 오류가 뜬다!
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
이 코드에서 Null값이 뜬다는 오류인데, 이는 Camera.main을 살펴보면 Tag가 "MainCamera"일 경우에만 실행가능하도록 설정되어있는데 현재 Untagged로 해놨기 떄문에 오류가 발생하는 것이다!

profile
Unity 개발자 취준생의 개발로그, Slow and steady wins the race !

0개의 댓글