본문 바로가기
개발/Unity

유니티 - 드래그로 땅 위의 오브젝트 움직이기 (Drag and Move on the Ground)

by 피로물든딸기 2022. 4. 11.
반응형

Unity 전체 링크

 

드래그로 오브젝트 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)


참고 - 드래그로 오브젝트 움직이기 (Move GameObject with Drag)

 

땅(Ground, Stage)위에 있는 오브젝트를 드래그로 움직여보자.

원하는 오브젝트에 콜라이더와 DragAndMove.cs 스크립트를 추가한다.

 

LayerMask를 이용하기 때문에 Stage의 Layer를 "Stage"로 만들어 추가한다.

Layer는 Add Layer를 이용해서 원하는 이름을 추가하면 된다.

 

이제 아래의 코드를 작성한다.

 

마우스 드래그를 이용해 오브젝트를 옮기므로 OnMouseDrag에서 ray를 발사한다.

integer 변수 layerMask에 NameToLayer를 bit masking하면 특정 Layer만 검출할 수 있다.

검출된 위치는 RaycastHit의 point에 Vector3로 담겨있다.

땅 위를 움직이므로 높이는 이전 값(tranform.position.y)을 저장해서 높이를 유지한다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DragAndMove : MonoBehaviour
{
    RaycastHit hitLayerMask;

    private void OnMouseDrag()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        Debug.DrawRay(ray.origin, ray.direction * 1000, Color.green);

        int layerMask = 1 << LayerMask.NameToLayer("Stage"); /* 특정 layer 검출 */
        if (Physics.Raycast(ray, out hitLayerMask, Mathf.Infinity, layerMask))
        {
            float y = this.transform.position.y; /* 높이 저장 */
            this.transform.position = new Vector3 (hitLayerMask.point.x, y, hitLayerMask.point.z);            
        }
    }
}

 

결과는 썩 괜찮다.

 

하지만 다음과 같은 문제가 있다.

오브젝트를 클릭하는 위치마다 최초로 한 번은 갑작스럽게 오브젝트가 움직인다.

 

이유는 this.transform.position이 갑자기 RaycastHit의 point로 바뀌었기 때문이다.


Vector3 distance를 추가하여 RaycastHit의 point와 오브젝트의 position의 차이를 기억해둔다.

그리고 그 차이만큼을 유지하면 튕기는 현상을 방지할 수 있다.

distance는 Start에서 초기화 하고, Drag가 종료되는 순간, 즉 OnMouseUp에서 초기화한다.

그리고 OnMouseDrag의 최초(distance == Vector3.zero인 순간)에 값을 기억한 후,

position에 point만큼 더하여 계산한다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DragAndMove : MonoBehaviour
{
    RaycastHit hitLayerMask;
    Vector3 distance;

    private void OnMouseUp()
    {
        distance = Vector3.zero;
    }

    private void OnMouseDrag()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        Debug.DrawRay(ray.origin, ray.direction * 1000, Color.green);

        int layerMask = 1 << LayerMask.NameToLayer("Stage");
        if (Physics.Raycast(ray, out hitLayerMask, Mathf.Infinity, layerMask))
        {
            if (distance == Vector3.zero) distance = this.transform.position - hitLayerMask.point;

            this.transform.position = hitLayerMask.point + distance;
        }
    }

    private void Start()
    {
        distance = Vector3.zero;
    }
}

 

오브젝트를 클릭해도 확 튀는 현상은 없어졌다.

하지만 물체를 움직일 때, 최초 클릭된 마우스의 pivot이 어긋나는 것을 알 수 있다.

 

위의 영상 마지막을 보면 이미 마우스가 오브젝트를 벗어나게 된다.

 

이유는 최초의 distance가 변경되지 않고 계속 고정이기 때문이다.


안타깝게도 이 문제를 해결하는 것은 꽤 까다롭다.

 

distance를 OnMouseDrag를 호출할 때마다 변경해버리면, 오브젝트는 움직이지 않는다.

Drag에 의해 point가 변해도, distance도 같이 변하기 때문에 오브젝트의 위치는 그대로이기 때문이다.

this.transform.position = hitLayerMask.point + distance;

 

따라서 카메라의 위치 (x1, y1, z1)에서 LayerMask(Layer = Stage)의 hit point (x2, y2, z2)의 위치에서

오브젝트가 맞은 위치(objectHitPosition)의 높이가 고정이 되도록 한다.

 

objectHitPosition은 아래와 같이 카메라에서 나온 ray에서 최초로 맞게 되는 position을 저장할 빈 오브젝트다.

저 높이가 h라면,

카메라의 위치 (x1, y1, z1)와 Ground Hit Point (x2, y2, z2) 사이에서 높이가 h가 되는 점을 찾으면 된다.

 

그림을 그리면 아래와 같다.

닮음을 이용하여 x3의 값을 구해보면, (x3 - x1) : (x2 - x3) = (H - h) : h 가 된다.

→ (x2 - x3) * (H - h) = (x3 - x1) * h

→ x2 * H - x2 * h - x3 * H + x3 * h = x3 * h - x1 *h

→ x3 = (x2 * H - x2 * h + x1 * h) / H

 

따라서 높이가 h가 되는 Ray가 되는 Vector의 (x3, y3, z3)는 아래와 같다.

    float x3 = (x2 * H - x2 * h + x1 * h) / H;
    float y3 = (y2 * H - y2 * h + y1 * h) / H;
    float z3 = (z2 * H - z2 * h + z1 * h) / H;

이전 코드에서는 오브젝트의 물체를 변경시켰다.

this.transform.position = hitLayerMask.point + distance;

 

하지만 위의 식은 objectHitPosition의 위치를 변경시켜야 한다.

따라서 오브젝트를 클릭해서 objectHitPosition을 만들고, 현재 오브젝트의 부모로 만든다.

(부모로 만들어야 objectHitPosition의 위치를 변경해야 오브젝트도 따라 움직인다.)

    GameObject objectHitPostion; // 오브젝트 hit point 저장
    RaycastHit hitRay /* 오브젝트 체크 */, hitLayerMask /* Stage 체크 */;

    private void OnMouseDown()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        if(Physics.Raycast(ray, out hitRay))
        {
            objectHitPostion = new GameObject("HitPosition");
            objectHitPostion.transform.position = hitRay.point;
            this.transform.SetParent(objectHitPostion.transform);
        }
    }

빈 오브젝트는 new GameObject(string)으로 만들면 된다.

 

반대로, 이동이 완료한 경우(마우스를 놓은 경우)에는 빈 오브젝트가 더 이상 필요 없으므로 제거한다.

오브젝트의 부모를 null로 변경한 후, 빈 오브젝트는 Destroy 한다.

    private void OnMouseUp()
    {
        this.transform.parent = null;
        Destroy(objectHitPostion);
    }

 

정상적으로 작동한다면, 클릭을 할 경우 HitPosition이라는 빈 게임오브젝트가 생성되고 부모로 설정되고,

클릭을 해제하면 원래대로 돌아간다.

onMouseDown <-> onMouseUp

 

이제 위의 식을 그대로 적용해 드래그를 할 때, objectHitPosition의 위치를 변경하면 된다.

    private void OnMouseDrag()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        Debug.DrawRay(ray.origin, ray.direction * 1000, Color.green);

        int layerMask = 1 << LayerMask.NameToLayer("Stage");
        if(Physics.Raycast(ray, out hitLayerMask, Mathf.Infinity, layerMask))
        {
            float H = Camera.main.transform.position.y;
            float h = objectHitPostion.transform.position.y;

            float x1 = Camera.main.transform.position.x;
            float y1 = Camera.main.transform.position.y;
            float z1 = Camera.main.transform.position.z;

            float x2 = hitLayerMask.point.x;
            float y2 = hitLayerMask.point.y;
            float z2 = hitLayerMask.point.z;

            float x3 = (x2 * H - x2 * h + x1 * h) / H;
            float y3 = (y2 * H - y2 * h + y1 * h) / H;
            float z3 = (z2 * H - z2 * h + z1 * h) / H;

            objectHitPostion.transform.position = new Vector3(x3, y3, z3);
        }
    }

 

식을 벡터 연산으로 한번에 정의하면 아래와 같다.

    private void OnMouseDrag()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        Debug.DrawRay(ray.origin, ray.direction * 1000, Color.green);

        int layerMask = 1 << LayerMask.NameToLayer("Stage");
        if(Physics.Raycast(ray, out hitLayerMask, Mathf.Infinity, layerMask))
        {
            float H = Camera.main.transform.position.y;
            float h = objectHitPostion.transform.position.y;

            Vector3 newPos 
            	= (hitLayerMask.point * (H - h) + Camera.main.transform.position * h) / H;

            objectHitPostion.transform.position = newPos;
        }
    }

 

 

카메라가 어떤 방향이든 오브젝트가 마우스를 그대로 따라다니는 것을 알 수 있다.

(튕기는 현상이 없어졌고, 마우스가 오브젝트를 벗어나지도 않는다.)

 

최종 코드는 아래와 같다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DragAndMove : MonoBehaviour
{
    GameObject objectHitPostion;
    RaycastHit hitRay, hitLayerMask;

    private void OnMouseUp()
    {
        this.transform.parent = null;
        Destroy(objectHitPostion);
    }

    private void OnMouseDown()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        if(Physics.Raycast(ray, out hitRay))
        {
            objectHitPostion = new GameObject("HitPosition");
            objectHitPostion.transform.position = hitRay.point;
            this.transform.SetParent(objectHitPostion.transform);
        }
    }

    private void OnMouseDrag()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        Debug.DrawRay(ray.origin, ray.direction * 1000, Color.green);

        int layerMask = 1 << LayerMask.NameToLayer("Stage");
        if(Physics.Raycast(ray, out hitLayerMask, Mathf.Infinity, layerMask))
        {
            float H = Camera.main.transform.position.y;
            float h = objectHitPostion.transform.position.y;

            Vector3 newPos 
            	= (hitLayerMask.point * (H - h) + Camera.main.transform.position * h) / H;

            objectHitPostion.transform.position = newPos;
        }
    }
}

 

Unity Plus:

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Unity Pro:

 

Unity Pro

The complete solutions for professionals to create and operate.

unity.com

 

Unity 프리미엄 학습:

 

Unity Learn

Advance your Unity skills with live sessions and over 750 hours of on-demand learning content designed for creators at every skill level.

unity.com

반응형

댓글