드래그로 오브젝트 Y축 회전하기 (Drag to Rotate Object in Y-axis)
드래그로 오브젝트 위, 아래로 움직이기 (Drag Object in Y-Axis)
드래그로 오브젝트 움직이기 (Move GameObject with Drag)
드래그로 땅 위의 오브젝트 움직이기 (Drag and Move on the Ground)
드래그로 평면 위의 오브젝트 움직이기 (Drag and Move on the Plane)
드래그로 블럭 옆으로 한 칸 움직이기 (Drag GameObject Snapped to a Grid)
이전 글에서는 땅 위에서 닮음을 이용해 오브젝트를 움직였다.
이번에는 오브젝트를 평면 위에 붙여서 움직이도록 해보자.
최종 결과를 미리보면 아래와 같다.
먼저 기본 이동 아이디어는 드래그로 땅 위의 오브젝트를 움직이는 것과 같다.
카메라에서 쏜 Raycast가 object의 hit point를 움직인다.
따라서 OnMouseDown, OnMouseUp은 그대로 사용한다.
RaycastHit hit;
GameObject objectHitPosition;
void OnMouseDown()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast(ray, out hit))
{
objectHitPosition = new GameObject("Empty");
objectHitPosition.transform.position = hit.point;
this.transform.SetParent(objectHitPosition.transform);
}
}
void OnMouseUp()
{
this.transform.parent = null;
Destroy(objectHitPosition);
}
이제 왼쪽의 오브젝트를 드래그로 오른쪽으로 옮긴다고 가정해보자.
카메라에서 Raycast를 쏴서 맞은 오브젝트의 위치를 옆으로 오른쪽으로 옮겼다.
(실제로는 매우 작은 간격으로 움직인다.)
즉, objectHitPosition의 위치가 ○(이동 전)에서 ○(이동 후)가 되도록 해야 한다.
objectHitPosition은 아래의 검은색 평면에서 움직인다고 볼 수 있다.
시점을 바꿔보면 아래와 같은 영역이 된다.
오브젝트를 향해 Raycast를 쏠 때, OnMouseDrag에서는 평면의 hit point를 구해야 한다.
Plane에는 Layer를 "Plane"으로 설정해두고, LayerMask를 이용해서 평면의 hit point를 구한다.
따라서 OnMouseDrag에 Raycast + LayerMask를 이용해 평면을 얻는다.
RaycastHit hitLayerMask;
void OnMouseDrag()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Debug.DrawRay(ray.origin, ray.direction * 100.0f, Color.red);
int layer = 1 << LayerMask.NameToLayer("Plane");
if(Physics.Raycast(ray, out hitLayerMask, Mathf.Infinity, layer))
{
/* 이동 처리 */
}
}
다시 그림으로 돌아가보자.
검은색 평면의 노멀 벡터는 Plane의 노멀 벡터와 같다. 따라서 hitLayerMask.transform.up 가 된다.
그리고 이 평면이 지나는 점은 오브젝트의 두께, 여기에서는 yHeight를 알면 구할 수 있다.
(오브젝트의 크기는 미리 알고 있어야 한다.)
이 평면의 한 점은 hitLayerMask.point + hitLayerMask.collider.transform.up * yHeight 가 된다.
여기서는 간단히 큐브를 이용하였으므로 yHeight는 localScale의 y값이 된다.
float yHeight;
void Start()
{
yHeight = this.transform.localScale.y;
}
이제 오브젝트를 옮길 위치 objectHitPosition이 어디로 이동해야 하는지 알아야 한다.
현재 알 수 있는 정보는
objectHitPosition이 지나는 평면의 노멀벡터 - hitLayerMask.transform.up
objectHitPosition이 지나는 평면의 점 - hitLayerMask.point + hitLayerMask.collider.transform.up * yHeight
Raycast의 시작점 (A) - Camera.main.transform.position
Raycast의 끝점 (B) - hitLayerMask.point
구해야하는 점 - objectHitPosition.transform.position
이 문제는 결국 평면과 직선의 접점 좌표 구하기 문제와 같다.
A와 B가 이어졌을 때 검은색 평면을 지나는 점을 찾기 때문이다.
Vector3 getContactPoint(Vector3 normal, Vector3 planeDot, Vector3 A, Vector3 B)
{
Vector3 nAB = (B - A).normalized;
return A + nAB * Vector3.Dot(normal, planeDot - A) / Vector3.Dot(normal, nAB);
}
따라서 OnMouseDrag는 아래와 같다.
void OnMouseDrag()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Debug.DrawRay(ray.origin, ray.direction * 100.0f, Color.red);
int layer = 1 << LayerMask.NameToLayer("Plane");
if(Physics.Raycast(ray, out hitLayerMask, Mathf.Infinity, layer))
{
Vector3 normal = hitLayerMask.transform.up;
Vector3 planeDot = hitLayerMask.point + hitLayerMask.collider.transform.up * yHeight;
Vector3 A = Camera.main.transform.position;
Vector3 B = hitLayerMask.point;
objectHitPosition.transform.position = getContactPoint(normal, planeDot, A, B);
}
}
여기까지 적용하면 아래와 같은 결과가 나온다.
오브젝트의 방향이 바뀌지 않았기 때문에 문제가 발생하였다.
this.transform.rotation = Quaternion.LookRotation(hitLayerMask.collider.transform.forward);
코드를 추가하여 transform의 방향을 hitLayerMask의 forward와 일치시켜주면 된다.
if(Physics.Raycast(ray, out hitLayerMask, Mathf.Infinity, layer))
{
Vector3 normal = hitLayerMask.transform.up;
Vector3 planeDot = hitLayerMask.point + hitLayerMask.collider.transform.up * yHeight;
Vector3 A = Camera.main.transform.position;
Vector3 B = hitLayerMask.point;
this.transform.rotation = Quaternion.LookRotation(hitLayerMask.collider.transform.forward);
objectHitPosition.transform.position = getContactPoint(normal, planeDot, A, B);
}
OnDrawGizmos를 추가하여 오브젝트의 방향을 표시해두면 정상적으로 회전되는 것을 알 수 있다.
private void OnDrawGizmos()
{
Gizmos.color = Color.blue;
Gizmos.DrawRay(this.transform.position, this.transform.localRotation * Vector3.up * 3.0f);
}
위의 코드를 모두 적용하면 처음에 본 결과를 얻을 수 있다.
최종 코드는 아래와 같다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(BoxCollider))]
public class MoveOnThePlane : MonoBehaviour
{
RaycastHit hit, hitLayerMask;
GameObject objectHitPosition;
float yHeight;
private void OnDrawGizmos()
{
Gizmos.color = Color.blue;
Gizmos.DrawRay(this.transform.position, this.transform.localRotation * Vector3.up * 3.0f);
}
Vector3 getContactPoint(Vector3 normal, Vector3 planeDot, Vector3 A, Vector3 B)
{
Vector3 nAB = (B - A).normalized;
return A + nAB * Vector3.Dot(normal, planeDot - A) / Vector3.Dot(normal, nAB);
}
void OnMouseDown()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast(ray, out hit))
{
objectHitPosition = new GameObject("Empty");
objectHitPosition.transform.position = hit.point;
this.transform.SetParent(objectHitPosition.transform);
}
}
void OnMouseUp()
{
this.transform.parent = null;
Destroy(objectHitPosition);
}
void OnMouseDrag()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Debug.DrawRay(ray.origin, ray.direction * 100.0f, Color.red);
int layer = 1 << LayerMask.NameToLayer("Plane");
if(Physics.Raycast(ray, out hitLayerMask, Mathf.Infinity, layer))
{
Vector3 normal = hitLayerMask.transform.up;
Vector3 planeDot = hitLayerMask.point + hitLayerMask.collider.transform.up * yHeight;
Vector3 A = Camera.main.transform.position;
Vector3 B = hitLayerMask.point;
this.transform.rotation
= Quaternion.LookRotation(hitLayerMask.collider.transform.forward);
objectHitPosition.transform.position = getContactPoint(normal, planeDot, A, B);
}
}
void Start()
{
yHeight = this.transform.localScale.y;
}
}
Unity Plus:
Unity Pro:
Unity 프리미엄 학습:
'개발 > Unity' 카테고리의 다른 글
유니티 Plane, 평면의 길이 (0) | 2022.05.14 |
---|---|
유니티 - 간단한 투명 Material 만들기 (1) | 2022.05.14 |
유니티 - 사각형 안에 있는 점 판단하기 (How to Check If a Point Is Inside a Rectangle) (0) | 2022.05.13 |
유니티 - 평면과 직선의 접점 좌표 구하기 (Intersection of a Line and a Plane) (0) | 2022.05.12 |
유니티 - 평면과 점 사이의 최단거리 구하기 (How to Get Shortest Distance between the Plane and the Point) (0) | 2022.05.12 |
댓글