ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Unity] UI Image의 Vertex를 드래그하여 다각형 그리기(feat. BaseMeshEffect)
    Unity 2023. 11. 22. 10:45

    프로젝트 진행 중 WorldCanvas 기준에서 UI의 이미지의 각 꼭짓점을 드래그하여 다각형을 그리는 기능이 필요했다.

    필요한 기능은 위 그림과 같이 꼭짓점을 드래그하여 각 정답 위치에 꼭짓점을 놓는 기능이었다.

    그래서 구글링..을 해본 결과, BaseMeshEffect라는 클래스가 있었다.

    링크는 아래에 걸어놓겠다.

     

    1. 하이어라키 및 앵커

    1. TargetImage는 위에 보이는 빨간 이미지를 뜻하며, 저 이미지의 Vertex를 가지고 놀 수 있도록 BaseMeshEffect 클래스를 상속받는 스크립트가 들어가야 한다.
    2. 각 꼭짓점을 움직이기 위해 필요한 하얀색 원 이미지(Point)는 TargetImage의 자식으로 들어가 있어야 한다. 그 이유는 RectTransformUtility.ScreenPointToLocalPointInRectangle()이라는 함수를 사용하기 위함이다.
    3. 정답 위치를 체크하기 위해 배치해놓은 CorrectPoint는 적절한 곳에 배치한다.
    4. CorrectPoint를 제외한 TargetImage의 Point들의 앵커는 Middle/Center이어야 한다. RectTransformUtility.ScreenPointToLocalPointInRectangle()에서 반환되는 Vector값과의 호환(?)을 위해서이다.
    5.  Vertex의 인덱스값은 그림에 보이는대로  왼쪽아래에서부터 0, 1, 2, 3이다. BaseMeshEffect에서 제공하는 Index라고 생각하면 편리하다.
    6. 이 기능은 Canvas의 RenderMode가 World Space이어야 한다. (물론, Overlay나 Camera도 가능하지만, 이 경우 각 Point의 앵커와 적용되는 Vector값을 적절히 변경해줘야 한다. 사실 제가 아직 안해봤다..)

    2. 스크립트

    public override void ModifyMesh(VertexHelper vh)
        {
            if (!IsActive())
            {
                return;
            }
            int vertCount = vh.currentVertCount;
            var vertex = new UIVertex();
            for (int i = 0; i < vertCount; i++)
            {
                vh.PopulateUIVertex(ref vertex, i);
    
                if (!isMouseDrag)
                {
                    return;
                }
                vertex.position = verticesVectors[i];
    
                vh.SetUIVertex(vertex, i);
            }
        }
    if (!isMouseDrag)
    {
        return;
    }
    vertex.position = verticesVectors[i];

    1. BaseMeshEffect를 상속하였을 때 필요한 Abstract(추상)이며, 아래 BaseMeshEffect 관련 링크의 글을 많이 참조하였다.(꾸벅)

    여기서 필자가 살짝 커스텀한 부분은 아래 부분이다. vertex의 포지션에, 드래그하며 반환된 마우스의 Vector값을 넣어주었다. BaseMeshEffect는 Image의 Mesh 자체를 가지고 장난치는(?) 거라고 생각하면 되겠다.

     

    -------------------------------------------------------------------------------------------------------

        // 마우스 드래그 체크
        private bool isMouseDrag;
    
        // ModifyMesh에 각 vertex의 vector값을 전달하기 위한 필드
        private List<Vector2> verticesVectors;
    
        // Canvas
        [SerializeField] private RectTransform targetCanvas;
    
        // 움직일 버튼이미지들
        [SerializeField] private List<RectTransform> verticesImages;
    
        // 정답 위치 이미지들
        [SerializeField] private List<RectTransform> correctVerticesImages;
    
        // 버튼이 정답 위치에 놓인 경우 컬러 변경
        private List<Color> pointColors; // 0: fail, 1: correct
        protected override void Start()
        {
            Initialize();
        }
    
        private void Initialize()
        {
            isMouseDrag = false;
    
            // ModifyMesh에 각 vertex의 vector값을 전달하기 위해 만들어진 리스트(Initialize에서는 처음 값을 넣어준다.) 
            verticesVectors = new()
            {
                verticesImages[0].anchoredPosition,
                verticesImages[1].anchoredPosition,
                verticesImages[2].anchoredPosition,
                verticesImages[3].anchoredPosition,
            };
            pointColors = new()
            {
                Color.green, Color.magenta
            };
    
            // 각 버튼들에 Event 추가(EventTrigger 사용)
            for (int i = 0; i < verticesImages.Count; i++)
            {
                // 클로저 방지용 Index (이와 관련된 내용은 글 아래 링크 참조)
                var closureIndex = i;
    
                // EventTrigger Clear
                if (verticesImages[closureIndex].GetComponent<EventTrigger>() != null)
                {
                    verticesImages[closureIndex].GetComponent<EventTrigger>().triggers.Clear();
                }
    
                // AddEventTrigger는 필자가 직접 만든 확장 메서드(글 아래 코드 참조)
    
                // BeginDrag(버튼을 처음 눌렀을 때 한 번 호출) 이벤트 추가
                verticesImages[closureIndex].AddEventTrigger(EventTriggerType.BeginDrag, () =>
                {
                    // 마우스 드래그 시작
                    isMouseDrag = true;
                });
    
                // Drag(버튼을 클릭한 채 움직일 때마다 호출) 이벤트 추가
                verticesImages[closureIndex].AddEventTrigger(EventTriggerType.Drag, () =>
                {
                    // WorldCanvas 상에서의 마우스 포지션을 반환함. vertices는 해당 오브젝트 이미지의 자식으로 있어야 하며, 앵커는 센터이어야 함.
                    // 이와 관련된 내용은 글 아래 링크 참조
                    RectTransformUtility.ScreenPointToLocalPointInRectangle(targetCanvas, Input.mousePosition, Camera.main, out var vector);
    
                    //버튼 Vector Update 함수
                    UpdateVertex(closureIndex, vector);
                });
    
                // EndDrag(드래그 중인 버튼을 뗐을 때 한 번 호출) 이벤트 추가
                verticesImages[closureIndex].AddEventTrigger(EventTriggerType.EndDrag, () =>
                {
                    // 마우스 드래그 종료
                    isMouseDrag = false;
    
                    // 두번째 파라미터에 들어간 Vector가 첫번째 파라미터에 들어간 RectTransform 위치에 포함되는지 체크 (이와 관련된 내용은 글 아래 링크 참조)
                    var contain = RectTransformUtility.RectangleContainsScreenPoint(correctVerticesImages[closureIndex], Input.mousePosition, Camera.main);
    
                    // 위 값이 true인 경우 버튼의 컬러 변경
                    correctVerticesImages[closureIndex].GetComponent<Image>().color = pointColors[contain ? 1 : 0];
                });
            }
        }
        static public void AddEventTrigger(this RectTransform UI, EventTriggerType triggerType, UnityAction action)
        {
            if (UI.gameObject.GetComponent<EventTrigger>() == null)
            {
                UI.gameObject.AddComponent<EventTrigger>();
            }
            EventTrigger.Entry entry = new();
            entry.eventID = triggerType;
            entry.callback.AddListener((evnetData) => action());
            UI.gameObject.GetComponent<EventTrigger>().triggers.Add(entry);
        }

    2. 드래그를 할 각 꼭짓점 Point에 Drag, BeginDrag, EndDrag 이벤트를 넣어주었다. 이 기능을 만들어보면서 RectTransformUtility이라는 클래스를 처음 접하였는데, 아주 유용한 기능이 많다. 링크는 아래에 있다.

    [SerializeField]는 약간 필자가 무의식적으로 많이 쓰는데 나쁜 건 아닐테니(?) 이쁘게 봐주셨으면 좋겠고, 확장메서드는 EventTrigger를 애용하는 자로서 하나쯤 만들어놓으면 편할 것 같아 거의 모든 프로젝트에 만들어놓았다.

     

    -------------------------------------------------------------------------------------------------------

        public void UpdateVertex(int index, Vector2 vector)
        {
            // 해당 index의 anchoredPosition 업데이트
            verticesImages[index].anchoredPosition = vector;
    
            // 해당 index의 anchoredPosition 저장
            verticesVectors[index] = vector;
    
            // PolygonMesh Update
            graphic.SetVerticesDirty();
        }

    3. 드래그 이벤트로 반환된 Vector값을 BaseMeshEffect의 graphic에 적용시키는 함수이다. verticesVectors에 해당 Vector값을 저장한 후 graphic.SetVerticesDirty()로 위 ModifyMesh를 실행하여 업데이트한다고 보면 되겠다.

     

    이렇게 준비를 완료하면 아래와 같이 다각형을 그리는 기능이 만들어진다.

     

     

    <개선점>

    1. WorldCanvas에서만 실행해보았던 점

    2. BaseMeshEffect에 대해 조금 더 공부를 해보아야 할 점

     

    <링크>

    클로저: https://doublsb.tistory.com/73

     

    C# Closure : for문의 변수를 람다식에서 참조하면 슬퍼지는 이유

    1 발생한 문제 오늘 개인 프로젝트를 작업하다가 슬픈 일을 겪었다. delegate를 사용해 Change_Tab 함수를 버튼에 추가하려고 했는데, 의도대로 되지 않았다. 아래쪽과 같은 코드를 사용해 프로그램을

    doublsb.tistory.com

    BaseMeshEffect 1: https://docs.unity3d.com/2018.2/Documentation/ScriptReference/UI.BaseMeshEffect.html

     

    Unity - Scripting API: BaseMeshEffect

    You've told us this page needs code samples. If you'd like to help us further, you could provide a code sample, or tell us about what kind of code sample you'd like to see: You've told us there are code samples on this page which don't work. If you know ho

    docs.unity3d.com

    BaseMeshEffect 2: https://gist.github.com/MattRix/579e43200875f9f1b3f9f08cfcbb026d

     

    Example of doing a vertex modifer for Unity UI stuff

    Example of doing a vertex modifer for Unity UI stuff - VertBend.cs

    gist.github.com

    RectTransformUtility: https://docs.unity3d.com/ScriptReference/RectTransformUtility.html

     

    Unity - Scripting API: RectTransformUtility

    Success! Thank you for helping us improve the quality of Unity Documentation. Although we cannot accept all submissions, we do read each suggested change from our users and will make updates where applicable. Close

    docs.unity3d.com

     

Designed by Tistory.