유니티에서 충돌을 표현하는 방법은 주로 2가지이다.
1. Rigidbody Component와 Collider Component의 조합을 이용한 충돌
2. RayCast를 이용한 충돌
Rigidbody&Collider Component가 물리연산을 통해서 실제 부딛치는걸 충돌이라고 한다면, RayCast는 가상을 일직선을 긋고서 충돌하는 물체가 있는지 확인하는 것이다.
유니티는 이 직선을 Ray라는 오브젝트로 제공하며, 레이는 항상 뷰 내의 한 점에 부합하므로 Camera 클래스의 ScreenPointToRay, ViewportPointToRay 함수를 통해서 스크린의 한 점을 입력받아 Ray로 반환해준다.
ScreenPointToRay는 스크린 좌표계로 픽셀 좌표를 요구하는 반면에 ViewportPointToRay는 Viewport 좌표계로 0~1까지로 정규화된 좌표값을 요구한다.
RayCast의 파라미터는 몇가지 안된다. origin, direction, maxDistance, layerMask로 Ray가 origin, direction을 담고있기 때문에 생략할 수 있다.
maxDistance는 따로 인자를 넘기지 않으면 무한으로 가며, layerMask는 Layer를 선택안할 시 모든 Layer에 영향을 준다.
물론 origin과 direction의 값을 그냥 넘겨줄 수도 있다.
Layer는 Inspector 창에서 설정할 수 있으며, 최대 32개의 Layer를 만들 수 있다. (0~31)
이는 BitMask로 연산이되는데 int형으로 사용되는 것 같다.
일부 유니티에서 예약한 레이어가 있고, 만약 5,6 Layer를 동시에 RayCast의 인자로 넘긴다면 (1<<5)|(1<<6) 을 인자로 넘겨서 5,6 Layer만 성립하게 할 수 있다.
private float mSpeed = 10;
void Update()
{
const float distance = 20;
Vector3 vec = transform.TransformDirection(Vector3.forward);
RaycastHit raycastInfo;
if(Physics.Raycast(transform.position + Vector3.up, vec, out raycastInfo, distance))
Debug.Log(raycastInfo.collider.gameObject.name);
Debug.DrawRay(transform.position + Vector3.up, vec * distance, Color.red);
}
RayCastHit 클래스는 부딪칠 경우의 정보를 담는 클래스이다.
vec변수는 모델좌표계의 앞을 월드 좌표계로 변경하여 레이캐스트를 쏘는 것이다.
Debug.DrawRay는 디버깅 상에서만 보이도록 선을 쏘는 것이다.
Physics.RayCast는 vec로 방향만 입력받는 반면에, Debug.DrawRay는 vec의 방향뿐만 아니라 크기도 영향을 받는다.
먼저 마우스 클릭에 반응하게끔 바닥에 대한 Layer를 만들어주자.
보면 User Layer 6번에 바닥에 대한 클릭이 되게끔 만들 것이다.
https://docs.unity3d.com/kr/530/ScriptReference/Input.GetMouseButtonDown.html
Button 클릭에 대한 Input 메서드
위에 유니티 공식문서에서 보면 Input.GetMouseButtonDown 메서드를 통해서 마우스 입력을 받을 수 있다. 인자로 0이면 Left Click, 1이면 Right Click, 2이면 마우스 중간 클릭이다.
바닥으로 쓸 GameObject에 Layer 설정을 해준다.
그리고 InputManager 스크립트를 수정하겠다.
public class InputManager
{
public event Action mKeyboard;
public event Action mMouse;
public void OnUpdate()
{
if(mKeyboard != null)
mKeyboard.Invoke();
if (mMouse != null)
mMouse.Invoke();
}
}
마우스 입력이벤트에 대한 이벤트를 추가해주고,
Player 스크립트를 수정해주겠다.
public class Player : MonoBehaviour
{
void Start()
{
Manager.Instance.mInputManager.mKeyboard += OnKeyEvnet;
Manager.Instance.mInputManager.mMouse += OnMouseEvnet;
}
private float mSpeed = 10;
void OnKeyEvnet()
{
// 생략
}
void OnMouseEvnet()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit rayInfo;
if(Physics.Raycast(ray, out rayInfo,50, (1 << 6))) {
mFinDest = false;
mPosition = rayInfo.point;
}
}
}
private bool mFinDest = true;
private Vector3 mPosition;
void Update()
{
if (!mFinDest)
{
Vector3 vec = mPosition - transform.position;
if (vec.magnitude <= 0.001)
{
mFinDest = true;
}
else
{
if (vec.magnitude - Time.deltaTime * mSpeed >= 0)
transform.position += vec.normalized * Time.deltaTime * mSpeed;
else
transform.position += vec.normalized * vec.magnitude;
transform.LookAt(mPosition);
}
}
}
}
Camera.main.ScreenPointToRay는 현재 메인 카메라의 Screen의 픽셀 위치 값을 받아 ray로 변환해주는 함수이다.
1<<6은 Plane Layer만 영향 받게끔 설정하는 것이다.
참고로 Unity에서 MainCamera는 태그를 통해서 관리된다. 만약 MainCamera말고 다른 Tag로 변경 시 오류가 발생한다.
if(vec.magnitude - Time.deltaTime * mSpeed >= 0)
를 사용한 이유는 원래 목적지를 지나서 도착한 위치에서 다시 원래 목적지로 가려고 돌아가려는 현상으로 인해서 와리가리가 발생하기 때문이다.