유니티 레이캐스트 각도 - yuniti leikaeseuteu gagdo

게임스쿨 수업

( Unity쨩 - 11일차 ) Raycast를 이용해 클릭한 좌표로 움직이기

이번엔 Raycast를 이용해서 이동을 구현해보겠다.

구현하기에 앞서 무대를 셋팅해보았다.

커여운 몬스터찡을 올려놓았다.

자 이제 마우스 클릭을 하였을 경우 어떻게 되는지 스크립트를 보고 확인해보자.

구조가 매우 간단한 것을 볼 수 있다.

클릭했을 경우, 그곳에 레이저를 쏘고 충돌이 일어나면 그곳에 캐릭터가 움직일 Object라는 녀석의 좌표를 충돌이 일어난 곳으로 보내버리는 역할을 한다.

먼저 Hierarchy에서 어떻게 되어있는지 구조를 보고오자.

아름다운 셋팅이다. Target을 보면 알겠지만, ClickPoint가 들어가게 된다.

ClickPoint는 구형 객체라고 생각하면 되겠다.

두 가지의 스크립트를 보고가자 !

먼저 첫번째 방식이다.

여기선 LookAt 함수를 사용하였는데, 이는 자동으로 target의 방향으로 rotation을 해주는 기능이다.

그리고 카메라에서도 설명하였뜻이 FindChild를 이용해서 모델을 데리고 왔다.

그 외에 특별한 것은 없으니 결과를 확인해보자.

매우 잘 이동하는 것을 볼 수 있다.

근데 문제가 하나 있다.

바로 클릭을 하면 바로 바로 캐릭터가 휙 돌아서 그 곳으로 이동을 하게 된다는 것이다.

즉, 회전의 중간 과정이 없다는 것이다.

이를 보완한 중간과정이 있는 스크립트가 바로 두번째 방법이다. 

스크립트를 확인해보자.

이번에 사용한 방식은 Quaternion을 이용한 방식이다.

주의깊게 생각할 것은 LookRotation을 한 부분의 뒤에 있는 eulerAngles인데,

이녀석은 회전에 대한 값들을 360기준으로 변환시켜주는 녀석이다.

만약 Degree의 값이 540이 들어오면 이를 180으로 바꿔준다고 생각하면 편하겠다.

그다음 눈여겨 볼것은 transform.rotation 부분이다.

이건 깨알 상식이자 정보인데,

transform.position과 transform.localScale은 Vector3의 값이 들어간다.

하지만 transform.rotation에는 Quaternion값이 들어간다.

때문에 Quaternion.Slerp는 Quaternion에 대한 회전을 시켜주는 함수라고 생각하면 편하겠다.

이건 Slerp함수에 대한 정보이다. 

정보를 보면 알 수 있듯이, 매개변수로 Quaternion의 값이 들어간다.

하지만 문제는 우리가 구한 fixEulers라는 변수는 Vector3 값이라는 것이다.

이런 각도를 Quaternion으로 바꿔주는 녀석이 있다.

그녀석이 바로 Quaternion.Euler이다. 이정도만 알면 되시겠다 !! 

Quaternion은 알면 알수록 심오한 녀석이니 깊게 알수록 좋은것 같다.

자 이제 이렇게 변환된 녀석의 결과를 확인해보자.

매우 자연스럽게 도는 모습을 확인할 수 있었다.

- Unity3D 11일차 끝 -

Unity

23일차. Unity 023 - 레이캐스트, 스크린 픽, 마우스 이벤트

문서소속 Exam_009 - Scene_02

1. 레이캐스트

- LayerMask.NameToLayer("Red") : 인자값 이름의 레이어 값이 리턴된다.

- int layerMask = ( 1<< LayerMask.NameToLayer("Red")); : Red의 레이어만 체크하는 레이어 마스크.

- 32비트로된 비트플래그이다. +, | 비트연산자 등으로 사용하면 된다.

- 모든 레이어를 체크하는 레이어 값 = -1;

- int layerMast = ~(1 << LayerMask.NameToLayer("Blue")) : 특정마스크만 제외하는 마스크.

- public LayerMask checkLayer : 와같이 인자값을 주는 경우에 인스펙터 뷰에서 레이어 체크 항목이 생긴다.

- Physics.RaycastAll(ray, this.rayLength, checkLayer.value) : 레이 선상에 걸리는 모든 오브젝트의 힛트정보를 hit[]배열로 리턴한다.

- 리턴되는 배열은 가까운 순서대로 정렬되어 나온다.

using UnityEngine;
using System.Collections;

public class RayCastTest2 : MonoBehaviour
{
    // Ray의 사정거리
    public float rayLength = 5.0f;

    // RayCast의 Hit정보를 알아낼 구조체
    private RaycastHit[] hits;

    public LayerMask checkLayer;

// Use this for initialization
void Start ()
    {

}

// Update is called once per frame
void Update ()
    {
     // Ray구조체
        Ray ray = new Ray(this.transform.position, this.transform.forward);

        // ray 선상의 모든 충돌정보를 hit 배열로 전달한다.
        this.hits = Physics.RaycastAll(ray, this.rayLength, checkLayer.value);

}

    void OnDrawGizmos()
    {
        if (this.hits != null && this.hits.Length > 0)
        {
            foreach(RaycastHit hit in this.hits)
            {
            // 레이 출력
            Gizmos.color = Color.red;
            Gizmos.DrawLine(transform.position, transform.position + transform.forward * hit.distance);

            // 충돌 지점에 구 출력
            Gizmos.DrawSphere(hit.point, 0.3f);

            // 충돌 지점의 노말벡터
            Gizmos.DrawLine(hit.point, hit.point +hit.normal * 2.0f);

            // 충돌 지점의 반사벡터
            Gizmos.color = Color.green;
            Vector3 reflect = Vector3.Reflect(this.transform.forward, hit.normal);
            Gizmos.DrawLine(hit.point, hit.point + reflect * 2.0f);
            }
        }
        else
        {
            Gizmos.color = Color.yellow;
            Gizmos.DrawLine(this.transform.position, this.transform.position + this.transform.forward * this.rayLength);
        }
    }
}

2. 스크린 픽

- 스크린픽을 위해서는 기준이되는 카메라가 필요하다.

- Ray pickRay = this.pickCamera.ScreenPointToRay(Input.mousePosition) : 카메라의 다음과 같은 함수로 레이를 얻는다.

- 이후에는 일반적인 레이캐스트와 같다.

- this.pickCamera = Camera.main : 와 같이 메인 카메라를 얻어올 수 있다. (메인 카메라 태그가 붙어있는 카메라..)

using UnityEngine;
using System.Collections;

public class ScreenPick : MonoBehaviour
{
    // 스크린의 기준이 되는 카메라.
    private Camera pickCamera;

// Use this for initialization
void Start ()
    {
        // 스크린의 기준이되는 카메라는 메인카메라다.
        this.pickCamera = Camera.main;
}

// Update is called once per frame
void Update ()
    {
        if (Input.GetMouseButtonDown(0))
        {
            // 스크린 위치를 카메라로 투영하 월드 Ray 구조체를 얻는다.
            Ray pickRay = this.pickCamera.ScreenPointToRay(Input.mousePosition);

            RaycastHit hit;

            // 충돌이 되었다면.
            if(Physics.Raycast(pickRay, out hit))
            {
                Destroy(hit.collider.gameObject);
            }
        }

            }
}

3. 마우스 이벤트 함수

- 컬리더가 있는 객체만 실행된다.

- 내부적으로 레이 처리를 거쳐서 실행된다.

- 가장 가까운 오브젝트 우선으로 실행된다.

- OnMouseEnter - 처음 오브젝트 위에 올라왔을 때. 
- OnMouseOver - 오브젝트위에 있을때 프레임마다 호출
- OnMouseExit - 마우스가 처음 밖으로 나갔을 때.
- OnMouseDown - 마우스 버튼을 눌렀을 때 호출.
- OnMouseUp - 마우스 버튼을 땟을 때 호출. 이전에 한번 다운 이벤트가 일어난 오브젝트에게만 호출.
    // 해당 지점이 오브젝트를 벗어나도 발생됨.
- OnMouseDrag - 마우스 버튼을 누른 상태로 있을 때 매 프레임마다 호출.

using UnityEngine;

public class RealDragObject : MonoBehaviour
{

// Use this for initialization
void Start ()
    {
 }

// Update is called once per frame
void Update ()
    {
 }

    void OnMouseDrag()
    {
        Camera mainCam = Camera.main;

        //
        // 카메라 레이
        //
        Ray ray = mainCam.ScreenPointToRay(Input.mousePosition);

        //
        // 자신의 오브젝트로부터 카메라의 방향을 바라보는 무한 평면을 만든다.
        //
        Vector4 plane = new Vector4();

        // 평면의 방향은 카메라 정면의 반대다.
        Vector3 planeNormal = -mainCam.transform.forward;

        // normal값을 플레인에 대입
        plane.x = planeNormal.x;
        plane.y = planeNormal.y;
        plane.z = planeNormal.z;

        // 평면상의 위치로부터 원점까지의 방향벡터
        // 나의 위치도 평면상의 위치가 된다.
        // Vector3 dir = Vector3.zero - this.transform.position;
        Vector3 dir = -this.transform.position;

        // 원점까지의 방향벡터를 평면방향벡터에 투영한 거리가 평면의 d가 된다.
        plane.w = Vector3.Dot(planeNormal, dir);

        //
        // 평면과 레이의 교차점 얻기.
        //

        Vector3 normal = new Vector3(plane.x, plane.y, plane.z);

        // 한 벡터가 정규화되어있고 다른 벡터는 아니라면, normal을 origin에 투영한 거리가 된다.
        // 원점에서 카메라까지의 거리.
        float dot1 = Vector3.Dot(normal, ray.origin);
        //
        float dot2 = Vector3.Dot(normal, ray.direction);

        // 광선의 방향과 평면의 법선벡터의 각도차가 90 == 평행한다는 얘기..
        if (dot2 == 0.0f) return;

        // 광선의 시작점에서 평면까지의 최단거리
        float dist = dot1 + plane.w;

        // 광선의 시작점에서 광선의 방향으로 평면까지의 거리
        // dot2 = -dist / t;
        // dot2 * t = -dist;
        // t = -dist / dot2;
        float t = -dist / dot2;

        // 광선 방향 뒤에 플레인이 있다.
        if (t < 0.0f)
        {
            return;
        }

        // 충돌 위치
        Vector3 pos = ray.origin + (ray.direction * t);

        // 이동
        this.transform.position = pos;
    }

    }

암기

- characterController 와 rigidBody를 같이쓰면 안된다.

- 무한평면 공식. xyz(평면의 바라보는 방향.),w(원점과의 거리)

- Collision col;

- 충돌 지점 : ContactPoint[] conPoints = col.contacts;

- 충돌 지점의 법선(자기자신 중심) : conPoints[i].normal;

- 충돌 지점의 월드위치: conPoints[i].point;

- 부하를 줄이는 법 : 물리처리나 충돌체크등에서 레이어를 적극 활용하자.