지정 위치에서 광선을 발사하여 닿은 오브젝트를 판별하는 기술
유니티에서 뿐만 아니라 언리얼 등 다양하게 활용되고 있는 기술이다.
말로만 설명해서는 감이 잘 오지 않으니, 레이캐스트 실습을 통해 차근차근 알아보도록 하자.
다른 게임 오브젝트와 상호작용하여, 물체를 들어서 위치를 옮기는 실습을 해볼 것이다.
해당 교육자료에서 이 실습은 배포한 에셋을 통해 이루어지지만, 무단 배포 시 저작권에 위반될 우려가 있어, 에셋을 다운 받았다고 가정하고 진행합니다.
해당 Prefab을 씬에 배치, 기존의 Main Camera 삭제
바닥 생성 및 바닥의 위치를 ( 0, 0, 0 ) 으로 설정
FPSController의 위치를 바닥보다 조금 더 높게 올려준다 <= Y값 조절
플레이어와 상호작용 할 큐브를 하나 만들어서 바닥 위쪽에 배치
이 과정을 모두 완료하면 실습을 진행할 준비가 끝났다.
씬에 배치한 FPSController ( 이하 플레이어 ) 에 스크립트를 하나 컴포넌트로 추가한다.
: 이 스크립트를 통해 레이캐스트를 시전하고, 플레이어 앞의 사물을 인지하여 상호작용 할 수 있도록 할 것이다.
코드 작성
public class RayCast : MonoBehaviour
{
private Camera playerCam;
public float distance = 100f;
// Start is called before the first frame update
void Start()
{
playerCam = Camera.main;
}
// Update is called once per frame
void Update()
{
// 광선의 시작 시점
Vector3 rayOrigin = playerCam.ViewportToWorldPoint
(new Vector3(0.5f, 0.5f, 0f));
// 광선 방향
Vector3 rayDir = playerCam.transform.forward;
//레이캐스트 시작 조건 : 마우스 클릭
if(Input.GetMouseButtonDown(0))
{
if(Physics.Raycast(rayOrigin, rayDir, distance))
{
Debug.Log("레이캐스트 성공!");
}
}
}
}
1. playerCam = Camera.main
메인 카메라를 플레이어의 카메라로 할당하여, 게임 시작 시 플레이어가 보는 시점이 카메라와 일치하도록 설정.
이에 따라 1인칭 플레이어 시점을 구현한다.
2. rayOrigin
광선의 시작 시점을 설정.
ViewportToWorldPoint 함수는 Viewport(카메라/화면 상 오브젝트의 위치) 를 WolrdPoint(실제 인게임 속의 오브젝트 위치) 로 전환하는 함수로, new Vector3 를 통해 설정된 위치를 월드의 공간에서 인지할 수 있도록 한다.
=> 카메라 상 오브젝트 위치인 ( 0.5f, 0.5f, 0f )를 실제 인게임 속 위치로 전환하여 이 위치를 광선의 시작 지점으로 설정한다.
3. rayDir
광선의 방향을 정해주기 위한 변수로, 플레이어가 바라보는 방향으로 광선을 발사하기 위해 playerCam 의 위치 중 forward 방향으로 정하였다.
4. Physics.Raycast()
레이캐스트를 시전하는 함수로 시작 위치, 방향, 거리(어느 정도 길이의 광선인지) 를 인자로 받는다.
실행 결과
플레이어가 큐브, 바닥 등 오브젝트를 바라보고 마우스 좌클릭을 하면 디버그 로그가 출력되는 것을 보고, 레이캐스트가 정상적으로 작동하고 있다는 것을 알 수 있다.
하지만 광선이 유저 뿐만 아니라 개발자인 우리의 눈에도 보이지 않다 제대로 발사되고 있는지 알 수가 없다.
코드 수정
//Update 문에 추가
//광선 출력
Debug.DrawRay(rayOrigin, rayDir * 100, Color.green);
Debug.DrawRay() 함수를 추가하여 개발자에게는 광선이 보일 수 있도록 그려준다.
시작 위치, 방향 및 거리, 광선의 색을 인자로 받는다
+ Debug. 으로 시작되는 변수/함수들은 모두 디버깅 용, 즉 개발자의 편의를 위해서 만들어지는 코드들이다. 실제 플레이 시 유저의 눈에는 보이지 않으니 프로젝트 패키징 이후를 걱정하지 않아도 된다.
실행 결과
정상적으로 광선의 길이와 방향이 보이게 되었다.
이제 이 광선을 통해 우리가 원하는 오브젝트만 판별할 수 있도록 해야한다.
현재 상태로는 모든 오브젝트, 즉 필드에 배치된 큐브와 바닥 모두 디버그 로그가 출력된다.
우리가 원하는 것은 바닥을 제외하고 '큐브만' 판별하는 것이다.
이러한 작동을 위해 '레이어' 기능을 사용할 것이다.
Inspector 창에서 설정
사용자 설정 레이어 (User Layer) 에 이름을 'CanRaycast' 로 설정하고 Cube의 레이어를 방금 만든 레이어로 지정해준다.
그리고 해당 레이어만 감지할 수 있도록 코드를 변경할 것이다.
코드 수정
//RayCast 클래스에 전역 변수로서 추가
//큐브만을 판별하기 위한 변수
public LayerMask whatIsTarget;
//whatIsTarget 을 매개변수로 더 추가
if(Physics.Raycast(rayOrigin, rayDir, distance, whatIsTarget))
{
Debug.Log("레이캐스트 성공!");
}
Inspector 창
실행 결과
이렇게 우리가 원하는 오브젝트, 큐브만을 판별해낼 수 있게 되었다.
원하는 오브젝트를 판별할 수 있게 됐으니, 이제 그 오브젝트의 정보를 바꿀 수 있어야 한다.
먼저 닿은 오브젝트의 정보를 가져와 보자.
코드 수정
//레이캐스트 시작 조건 : 마우스 클릭
if(Input.GetMouseButtonDown(0))
{
//레이캐스트 광선에 감지된 오브젝트의 정보를 hit 변수에 할당
RaycastHit hit;
if(Physics.Raycast(rayOrigin, rayDir, out hit, distance, whatIsTarget))
{
Debug.Log("레이캐스트 성공!");
Debug.Log(hit.collider.gameObject.name);
}
}
실행 결과
레이캐스트 성공 시, 큐브의 이름을 반환하게 되었다.
이제 어느 오브젝트가 닿았는지 알 수 있게 되었으니, 그 오브젝트의 정보를 수정해보도록 하자.
코드 추가
if(Physics.Raycast(rayOrigin, rayDir, out hit, distance, whatIsTarget))
{
Debug.Log("레이캐스트 성공!");
Debug.Log(hit.collider.gameObject.name);
//광선에 닿은 오브젝트 할당
GameObject hitTarget = hit.collider.gameObject;
//오브젝트 정보 수정 ( 색을 빨간색으로 변경 )
hitTarget.GetComponent<Renderer>().material.color = Color.red;
}
실행 결과
큐브의 색이 바뀌는 것을 확인하면서, 개발자만이 아닌 유저도 큐브가 상호작용이 되었다는 것을 확인할 수 있게 되었다.
이어서, 큐브의 위치 정보까지 바꿀 수 있도록 해보자.
최종적으로 원하는 것은 유저가 큐브를 들어서, 원하는 곳에 내려놓게 하는 것이다.
이 때, 우리는 1. 큐브의 정보 와 2. 유저와 큐브와의 거리 를 알아야 한다.
코드 추가
//전역변수로 선언
//오브젝트의 정보 수정을 위한 변수
private Transform moveTarget;
private float targetDistance;
//코드 수정
if(Input.GetMouseButtonDown(0))
{
//레이캐스트 광선에 감지된 오브젝트의 정보를 hit 변수에 할당
RaycastHit hit;
if(Physics.Raycast(rayOrigin, rayDir, out hit, distance, whatIsTarget))
{
Debug.Log("레이캐스트 성공!");
Debug.Log(hit.collider.gameObject.name);
//광선에 닿은 오브젝트 할당
GameObject hitTarget = hit.collider.gameObject;
//오브젝트 정보 수정 ( 색을 빨간색으로 변경 )
hitTarget.GetComponent<Renderer>().material.color = Color.red;
moveTarget = hitTarget.transform;
targetDistance = hit.distance;
}
}
if(moveTarget != null)
{
moveTarget.position = rayOrigin + rayDir * targetDistance;
}
if(Input.GetMouseButtonUp(0))
{
if(moveTarget != null)
{
moveTarget.GetComponent<Renderer>().material.color = Color.white;
}
//변수에 할당된 오브젝트의 정보 초기화
//다른 오브젝트를 레이캐스트 시 정상적으로 작동할 수 있도록
moveTarget = null;
}
moveTarget 변수와 targetDistance 변수의 활용에 집중하면서 코드를 작성해보자.
코드 설명
1. moveTarget
레이캐스트 시, 판별된 오브젝트를 이 변수에 할당하여, 오브젝트 전체를 관리할 수 있게 한다
2. targetDistance
타겟(판별된 오브젝트) 과 플레이어의 거리를 측정하여, 오브젝트를 들고 옮길 때 처음 들었던 거리를 유지할 수 있도록 한다.
3. if(moveTarget =! null)
moveTarget의 정보가 null이 아닐 때란, 곧 레이캐스트를 성공했을 때를 나타내는 것으로 이 경우 해당 오브젝트의 위치를 플레이어와의 처음 간격을 유지하며 플레이어의 시점에 따라 변화시키고자 하였다.
4. if(Input.GetMouseButtonUp(0))
GetMouseButtonUp은 마우스 버튼을 뗐을 때 ( <=> 반대로 눌렀을 때를 판별하려면 GetMouseButtonDown )
이 조건문을 통해 마우스 버튼을 누르고 있는 동안 레이캐스트를 하여 오브젝트의 위치를 옮기고, 버튼을 뗐을 때 정보를 초기화하여 오브젝트를 해당 위치에 고정한다.
4-1. if(moveTarget != null)
마우스 버튼을 뗐을 때 moveTarget에 오브젝트의 정보가 들어있다면 ( = 직전에 레이캐스트를 통해 오브젝트의 위치를 옮겼다면) 오브젝트를 내려놓을 때 본래의 색인 White로 다시 바꿔준다.
: 더 정확하게 하는 방법은 처음 레이캐스트 성공 시, Cube의 초기 색을 별도의 변수에 저장해놓고, Cube를 내려놓을 때 그 변수의 값을 다시 대입해주는 것이 옳다.
실행 결과
처음 의도했던 대로 정상적으로 작동한다.
이렇게 레이캐스트를 통해 오브젝트와 상호작용하는 실습을 해낼 수 있다.
지정 위치에서 광선을 발사하여 닿은 오브젝트를 판별하는 기술
눈에 보이지 않는 광선을 원하는 방향으로 발사 => '콜라이더(Collider)' 감지
GameObject.hitTarget = hit.collider.gameObject;
와 같은 코드에서 collider로 검사한 이유가 이 이유이다.
레이캐스트는 오브젝트의 콜라이더를 판별하여 동작한다.
따라서 정확한 물리 정보를 요구할 때 매우 유용하게 활용할 수 있다.
정확한 물리 정보?
오브젝트 정보를 가져오는 방식에는 레이캐스트 외에도
OnCollisionEnter() 등, 객체 자체를 받아오는 방식도 존재한다.
하지만 이러한 방법은 유니티 에디터 자체의 프레임, 혹은 메세지 전달을 트리거로 하기 때문에 정보가 정확하지 않은 경우가 발생할 수 있다.
1. 총알 발사
플레이어가 총알을 발사해 닿은 몬스터의 체력을 깎을 때,
총알 자체에 매우 짧은 길이의 레이캐스트를 설정하여 몬스터의 정보를 가져올 수 있다.
2. 벽 뒤의 오브젝트 판별
실습 때 활용했던 'User Layer' 를 사용,
벽에는 판별하는 레이어를 두지 않고 그 뒤의 오브젝트에 원하는 레이어를 설정한 후
레이캐스트에 원하는 레이어만 판별할 수 있도록 작성하면 다른 오브젝트에 가려진 그 너머의 오브젝트를 판별할 수 있다.
그외 등등
#13. 다형성 (0) | 2024.08.03 |
---|---|
#12. 상속 심화 (0) | 2024.08.02 |
#10. 코루틴(Coroutine) (0) | 2024.07.31 |
#9. 싱글톤(Singleton) (0) | 2024.07.31 |
#8. 리스트(List) (0) | 2024.07.30 |