본문 바로가기
유니티 최고/유니티 AR

유니티(Unity) AR foundation 오브젝트 선택 / 이동 / 크기조절

by Lee_story_.. 2023. 3. 20.
728x90

오늘은 전 글에 이어서 이번엔 설치한 오브젝트를 선택하고 이동하는

 

아래의 영상을 참고했습니다!

전 글하고는 코드가 좀 달라졌습니다;;;

 

이번 코드까지 완성한다면!! 아래 처럼 만들어집니다!

 

자 이제 시작!

먼저 이동/회전/사이즈를 조정하기 위해서 어떤 오브젝트를 선택해야하는데 이부분부터 먼저 구현해보겠습니다.

 

생성할 큐브를 하나 만들어줍시다.

그리고 내부에 cube_sel라고 선택되면 초록색으로 표시되게끔 만들 큐브를 하나 더 만들어 줍시다.

(기존큐브보다 약간크게)

 

투명도를 위해 새로운 머터리얼을 하나 만들어 주고

color > A부분을 조정해서 투명하게 만들어 줍시다!

 

이제 선택되면 조정이 가능하게 만들어 줄 코드를 만들어 봅시다!

 

 

 

가장 먼저 터치를 하면 그 터치가 유효한지를 판별할 코드!

private static ARRaycastManager raycastMgr;
    private static List<ARRaycastHit> hits = new List<ARRaycastHit>();

    static TouchUtility()
    {
        raycastMgr = GameObject.FindObjectOfType<ARRaycastManager>();
    }

    public static bool Raycast(Vector2 screenPosition, out Pose pose)//레이케스트!
    {
        if (raycastMgr.Raycast(screenPosition, hits, TrackableType.All)) // 모든부분에 대해서 검사
        {
            pose = hits[0].pose;
            return true;
        }
        else
        {
            pose = Pose.identity;
            return false;
        }
    } // 충돌 확인

무엇가 충돌했는지

public static bool TryGetInputPosition(out Vector2 position) // 터치중인지 판별하는부분
    {
        position = Vector2.zero;

        if (Input.touchCount == 0)
        {
            return false;
        }

        position = Input.GetTouch(0).position;

        if (Input.GetTouch(0).phase != TouchPhase.Began)
        {
            return false;
        }

        return true;
    } //

 

 

화면에 터치를 했는지 확인하는 함수 2개를 선언해주고

 

 

 

다음! 이번엔 선택되었을때 작동하는 코드! (이 코드는 위에서 만든 cube_sel에 적용시킬것)

이부분에서 회전 이동 사이즈 조절 등등의 기능들을 넣어줄것입니다!

 

 

 

가장먼저 선택을 초기화 해줍시다.

private void Awake()
    {
        cubeSelected.SetActive(false);
    }

 

 

선택되었는지 IsSelected함수를 통해 받아오고

 public bool IsSelected
    {
        get => SelectedObject == this;

    }

    private static select selectedObject;
    public static select SelectedObject
    {
        
        get => selectedObject;
        set
        {
            if(selectedObject == value)
            {
                return;
            }

            if(selectedObject != null)
            {
                selectedObject.cubeSelected.SetActive(false);
            }

            selectedObject = value;

            if (value != null)
            {
                value.cubeSelected.SetActive(true);
            }
        }
    }

아까 설정한 cube_sel을 활성화/비활성화 부분을 만들어 줍시다.

 

 

다음으로 기능을 하나씩 추가해줍시다.

이 부분은 드래그를 통해 큐브를 이동하기 위한 코드입니다.

public void onPointerDrag(BaseEventData bed)  // Click _ drag >>> 좀 바꾸기
    {
        if (IsSelected)
        {
            PointerEventData ped = (PointerEventData)bed;

            if(TouchUtility.Raycast(ped.position,out Pose hitPose))
            {
                transform.position=hitPose.position;
            }
        }
    }

원리는 모바일 화면 터치시 그 위치에서부터 레이케스트를 출력하여 바닥면과 닿는 부분에 큐브를 이동하는 방식입니다.

 

 

 

이번엔 사이즈 조정을  위한 코드입니다.

손가락 2개의 위치를 받아 비율만큼 size를 증가시켜주는 방법을 사용했습니다.

public void sizeUpdate(Touch touchZero, Touch touchOne)
    {
        if(touchZero.phase==TouchPhase.Ended || touchZero.phase==TouchPhase.Canceled||
            touchOne.phase==TouchPhase.Ended || touchOne.phase == TouchPhase.Canceled)
        {
            return; // 동작끝!
        }
        
        if(touchZero.phase == TouchPhase.Began || touchOne.phase == TouchPhase.Began)// 터치 시작
        {
            initialDistance = Vector2.Distance(touchZero.position, touchOne.position);
            initialScale = transform.localScale;
        }
        else//터치한채로 이동
        {
            var currentDistance = Vector2.Distance(touchZero.position, touchOne.position);

            if (Mathf.Approximately(initialDistance, 0)) // 미세한 움직임은 그냥 넘겨주기;;
            {
                return;
            }

            var factor =currentDistance/initialDistance;
            transform.localScale = initialScale * factor;
        }

    }

 

마지막으로 회전관련 코드입니다.

public void base_set(Slider slider)
    {
        rotateSlider = slider;

        rotateSlider.minValue = rotateMin;
        rotateSlider.maxValue = rotateMax;

        rotateSlider.onValueChanged.AddListener(rotateSliderUpdate);
    }


    public void base_rotate()
    {
        rotateSlider.value = (transform.rotation.y + 360) % 360;
    }


    void rotateSliderUpdate(float value)//회전 동기화
    {
        if (IsSelected)
        {
            print("value");
            transform.localEulerAngles = new Vector3(transform.rotation.x, value, transform.rotation.z);
        }
    }

회전은 슬라이더를 설치하여 슬라이더 value값에 따라 크기를 조절해주었습니다.

 

 

위의 것들을 적용시켜주기위해 아래처럼 설치할 오브젝트에 select를 추가해주고

이번엔 터치시에 작동할 CreateOnplane 코드를 보겠습니다.

 

변수가 좀 많....

[SerializeField]
    private GameObject Prefab;  // 생성할 프리팹
    private Vector2 touch_position; // 터치위치

    [SerializeField]
    private Camera arCamera; // 카메라 

    [SerializeField]
    private LayerMask placedObjectLayerMask; // 선택한 프리팹의 레이어마스크?


    private Vector2 touchPosition; // 터치위치
    private Ray ray; 
    private RaycastHit hit;

    public bool selct_mode=false;

    [SerializeField]
    private GameObject Select_item; // 선택된 오브젝트

    [SerializeField]
    private Slider rotateSlider; // 회전 슬라이더

 

 

이제 update 함수로 모든 부분에 대해서 확인을 해줍시다.

void Update()
    {
        if(!TouchUtility.TryGetInputPosition(out touch_position) || selct_mode) // 터치 안함...
        {
            if (Input.touchCount == 2) // zoom 기능
            {
                select.SelectedObject.sizeUpdate(Input.GetTouch(0), Input.GetTouch(1));
            }

            return;
        }

        ray=arCamera.ScreenPointToRay(touch_position); 
        if(Physics.Raycast(ray,out hit, Mathf.Infinity, placedObjectLayerMask)) /// 오브젝트 선택시!
        {
            select.SelectedObject = hit.transform.GetComponentInChildren<select>();

            Select_item.SetActive(true); // ui on
            select.SelectedObject.base_rotate();//회전 슬라이더 
            selct_mode =true;
            
            return;
        }

        select.SelectedObject = null;

        if(TouchUtility.Raycast(touch_position,out Pose hitpose)) // 프리팹 스폰
        {
            GameObject temp= Instantiate(Prefab, hitpose.position, hitpose.rotation);
            temp.GetComponent<select>().base_set(rotateSlider);
            
        }
    }

위에서 확인하는 부분은 

1. 터치를 하는가 >> 한다면 2곳에서 터치가 되는가

2. 오브젝트를 선택했는가

3. 빈 평면을 선택했는가 입니다!

이렇게 모든 조건에 대해서 걸러줍시다.

 

 

아래처럼 간단하게 ui도 만들어주고 

 

마지막으로 아래처럼 세팅해줍시다.

 

 

 

여기서끝!

 

글을 쓰다보니... 너무길어진 느낌이네요 

 

코드도 너무길고 복잡... 빼먹은 부분도 있을수도 있어요..

이해가 안되는 부분이나 틀린부분이 있다면 댓글로 알려주세요!

 

코드 

더보기

createOnplane

using System.Collections;
using System.Collections.Generic;
using System.Net.Sockets;
using UnityEngine;
using UnityEngine.PlayerLoop;
using UnityEngine.UI;
public class CreateOnplane : MonoBehaviour
{
    [SerializeField]
    private GameObject Prefab;
    private Vector2 touch_position;

    [SerializeField]
    private Camera arCamera;

    [SerializeField]
    private LayerMask placedObjectLayerMask;


    private Vector2 touchPosition;
    private Ray ray;
    private RaycastHit hit;

    public bool selct_mode=false;

    [SerializeField]
    private GameObject Select_ui;

    [SerializeField]
    private Slider rotateSlider;


    private GameObject Select_item;
    //private Slider scaleSlider;


    // Start is called before the first frame update
    void Start()
    {
        
    }


    // Update is called once per frame
    void Update()
    {
        if(!TouchUtility.TryGetInputPosition(out touch_position) || selct_mode) // 터치 안함...
        {
            if (Input.touchCount == 2) // zoom 기능
            {
                select.SelectedObject.sizeUpdate(Input.GetTouch(0), Input.GetTouch(1));
            }

            return;
        }

        ray=arCamera.ScreenPointToRay(touch_position); 
        if(Physics.Raycast(ray,out hit, Mathf.Infinity, placedObjectLayerMask)) /// 오브젝트 선택시!
        {
            select.SelectedObject = hit.transform.GetComponentInChildren<select>();
            Select_item=hit.transform.gameObject;
            Select_ui.SetActive(true); // ui on
            select.SelectedObject.base_rotate();//회전 슬라이더 
            selct_mode =true;
            
            return;
        }

        select.SelectedObject = null;

        if(TouchUtility.Raycast(touch_position,out Pose hitpose)) // 프리팹 스폰
        {
            GameObject temp= Instantiate(Prefab, hitpose.position, hitpose.rotation);
            temp.GetComponent<select>().base_set(rotateSlider);
            
        }
    }


    public void on_off(bool check)
    {
        if (check) // 변경 ㅇㅇ  >> 고민.....   >> DB랑 연동... 휴대폰 터지겠다
        {
            selct_mode = false;
            select.SelectedObject = null;
            Select_ui.SetActive(false);
        }
        else
        {
            selct_mode = false;

            Select_ui.SetActive(false);
            Destroy(Select_item);
            select.SelectedObject = null;

        }
    }
}

select

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class select : MonoBehaviour
{
    [SerializeField]
    private GameObject cubeSelected; // 선택시

    private Slider rotateSlider;
    private Slider scaleSlider;

    public float rotateMin; // 회전관련
    public float rotateMax;


    private float initialDistance; // 크기관련
    private Vector3 initialScale;

    public bool IsSelected
    {
        get => SelectedObject == this;

    }

    private static select selectedObject;
    public static select SelectedObject
    {
        
        get => selectedObject;
        set
        {
            if(selectedObject == value)
            {
                return;
            }

            if(selectedObject != null)
            {
                selectedObject.cubeSelected.SetActive(false);
            }

            selectedObject = value;

            if (value != null)
            {
                value.cubeSelected.SetActive(true);
            }
        }
    }

    private void Awake()
    {
        cubeSelected.SetActive(false);
    }


    public void base_set(Slider slider)
    {
        rotateSlider = slider;

        rotateSlider.minValue = rotateMin;
        rotateSlider.maxValue = rotateMax;

        rotateSlider.onValueChanged.AddListener(rotateSliderUpdate);
    }


    public void base_rotate()
    {
        rotateSlider.value = (transform.rotation.y + 360) % 360;
    }


    void rotateSliderUpdate(float value)//회전 동기화
    {
        if (IsSelected)
        {
            print("value");
            transform.localEulerAngles = new Vector3(transform.rotation.x, value, transform.rotation.z);
        }
    }


    public void sizeUpdate(Touch touchZero, Touch touchOne)
    {
        if(touchZero.phase==TouchPhase.Ended || touchZero.phase==TouchPhase.Canceled||
            touchOne.phase==TouchPhase.Ended || touchOne.phase == TouchPhase.Canceled)
        {
            return; // 동작끝!
        }
        
        if(touchZero.phase == TouchPhase.Began || touchOne.phase == TouchPhase.Began)// 터치 시작
        {
            initialDistance = Vector2.Distance(touchZero.position, touchOne.position);
            initialScale = transform.localScale;
        }
        else//터치한채로 이동
        {
            var currentDistance = Vector2.Distance(touchZero.position, touchOne.position);

            if (Mathf.Approximately(initialDistance, 0)) // 미세한 움직임은 그냥 넘겨주기;;
            {
                return;
            }

            var factor =currentDistance/initialDistance;
            transform.localScale = initialScale * factor;
        }

    }


    public void onPointerDrag(BaseEventData bed)  // Click _ drag >>> 좀 바꾸기
    {
        if (IsSelected)
        {
            PointerEventData ped = (PointerEventData)bed;

            if(TouchUtility.Raycast(ped.position,out Pose hitPose))
            {
                transform.position=hitPose.position;
            }
        }
    }


    public void drag_x(BaseEventData bed)
    {
        if (IsSelected)
        {
            PointerEventData ped = (PointerEventData)bed;

            transform.position = new Vector3(transform.position.x, transform.position.y, transform.position.z);

        }
    }

    // slider로 조정 size하고 rotation
    //public 


    // Start is called before the first frame update
    void Start()
    {
        
    }

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

 

touchUtility

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;

public class TouchUtility : MonoBehaviour
{
    private static ARRaycastManager raycastMgr;
    private static List<ARRaycastHit> hits = new List<ARRaycastHit>();

    static TouchUtility()
    {
        raycastMgr = GameObject.FindObjectOfType<ARRaycastManager>();
    }

    public static bool Raycast(Vector2 screenPosition, out Pose pose)
    {
        if (raycastMgr.Raycast(screenPosition, hits, TrackableType.All))
        {
            pose = hits[0].pose;
            return true;
        }
        else
        {
            pose = Pose.identity;
            return false;
        }
    } // 충돌 확인


    public static bool TryGetInputPosition(out Vector2 position) // 터치중인지 판별하는부분
    {
        position = Vector2.zero;

        if (Input.touchCount == 0)
        {
            return false;
        }

        position = Input.GetTouch(0).position;

        if (Input.GetTouch(0).phase != TouchPhase.Began)
        {
            return false;
        }

        return true;
    } // 


    // Start is called before the first frame update
    void Start()
    {
        
    }

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

 

 

 

 

 

틀린점이 있다면 댓 달아주세요!

 

 

 

 

댓글