스터디

유니티 확장 UI - Scroll-Snap

장꼬미 2021. 7. 20. 00:04

유니티 기본 UI외에 가장 많이 만들어서 사용하는 두번째 기능은 아마 누가 뭐라고 해도 Scroll-Snap 기능일 것이다.

 

게임의 페이지 전환이나 배너 넘기기, 인벤토리 넘기기등 모바일 게임 초창기부터 등장한 UI 기능으로 지금까지도 널리 , 그리고 적극적으로 사용되고 있는 Scroll-Snap은 더이상 직접 구현할 필요가 없어 졌다.

 

찾아보니 무료로 AssetStore에 올라와 있었기 때문이다.

 

따라서 코드를 전체를 올릴 수도 있겠지만 그건 도리가 아니고 주요한 필드멤버와 메소드만 추출해서 살펴보자.

 

	public MovementAxis movementAxis = MovementAxis.Horizontal; // 가로/세로 지정
        public bool automaticallyLayout = true; // 자식 패널의 사이즈를 제어한다.
        public int startingPanel = 0; //시작할 패널을 선택한다. 
        public Button previousButton = null; // 앞패널로 이동하는 퍼튼
        public Button nextButton = null; // 뒤 버튼
        public GameObject pagination = null; // 현재 위치를 보여주는 아이콘
        public SnapTarget snapTarget = SnapTarget.Next; //선택할 타겟의 종류를 바꿈.

주요 필드 멤버 들이다.

 

   private void Setup()
        {
            if (NumberOfPanels == 0) return;

            // ScrollRect
            if (movementType == MovementType.Fixed)
            {
                scrollRect.horizontal = (movementAxis == MovementAxis.Horizontal);
                scrollRect.vertical = (movementAxis == MovementAxis.Vertical);
            }
            else
            {
                scrollRect.horizontal = scrollRect.vertical = true;
            } // 스크롤 방향을 필드 변수에 따라 설정한다

            // Panels
            size = (sizeControl == SizeControl.Manual) ? size : new Vector2(GetComponent<RectTransform>().rect.width, GetComponent<RectTransform>().rect.height);

            Panels = new GameObject[NumberOfPanels];
            PanelsRT = new RectTransform[NumberOfPanels];
            for (int i = 0; i < NumberOfPanels; i++) // 자식 패널들을 사이즈 세팅하고 정리한다.
            {
                Panels[i] = Content.GetChild(i).gameObject;
                PanelsRT[i] = Panels[i].GetComponent<RectTransform>();

                if (movementType == MovementType.Fixed && automaticallyLayout)
                {
                    PanelsRT[i].anchorMin = new Vector2(movementAxis == MovementAxis.Horizontal ? 0f : 0.5f, movementAxis == MovementAxis.Vertical ? 0f : 0.5f);
                    PanelsRT[i].anchorMax = new Vector2(movementAxis == MovementAxis.Horizontal ? 0f : 0.5f, movementAxis == MovementAxis.Vertical ? 0f : 0.5f);

                    float x = (rightMargin + leftMargin) / 2f - leftMargin;
                    float y = (topMargin + bottomMargin) / 2f - bottomMargin;
                    Vector2 marginOffset = new Vector2(x / size.x, y / size.y);
                    PanelsRT[i].pivot = new Vector2(0.5f, 0.5f) + marginOffset;
                    PanelsRT[i].sizeDelta = size - new Vector2(leftMargin + rightMargin, topMargin + bottomMargin);

                    float panelPosX = (movementAxis == MovementAxis.Horizontal) ? i * (automaticLayoutSpacing + 1f) * size.x + (size.x / 2f) : 0f;
                    float panelPosY = (movementAxis == MovementAxis.Vertical) ? i * (automaticLayoutSpacing + 1f) * size.y + (size.y / 2f) : 0f;
                    PanelsRT[i].anchoredPosition = new Vector2(panelPosX, panelPosY);
                }
            }

            // Content
            if (movementType == MovementType.Fixed)
            {
                // Automatic Layout
                if (automaticallyLayout)
                {
                    Content.anchorMin = new Vector2(movementAxis == MovementAxis.Horizontal ? 0f : 0.5f, movementAxis == MovementAxis.Vertical ? 0f : 0.5f);
                    Content.anchorMax = new Vector2(movementAxis == MovementAxis.Horizontal ? 0f : 0.5f, movementAxis == MovementAxis.Vertical ? 0f : 0.5f);
                    Content.pivot = new Vector2(movementAxis == MovementAxis.Horizontal ? 0f : 0.5f, movementAxis == MovementAxis.Vertical ? 0f : 0.5f);

                    Vector2 min = PanelsRT[0].anchoredPosition;
                    Vector2 max = PanelsRT[NumberOfPanels - 1].anchoredPosition;

                    float contentWidth = (movementAxis == MovementAxis.Horizontal) ? (NumberOfPanels * (automaticLayoutSpacing + 1f) * size.x) - (size.x * automaticLayoutSpacing) : size.x;
                    float contentHeight = (movementAxis == MovementAxis.Vertical) ? (NumberOfPanels * (automaticLayoutSpacing + 1f) * size.y) - (size.y * automaticLayoutSpacing) : size.y;
                    Content.sizeDelta = new Vector2(contentWidth, contentHeight);
                }

                // Infinite Scrolling
                if (infinitelyScroll)
                {
                    scrollRect.movementType = ScrollRect.MovementType.Unrestricted;
                    contentLength = (movementAxis == MovementAxis.Horizontal) ? (Content.rect.width + size.x * infiniteScrollingEndSpacing) : Content.rect.height + size.y * infiniteScrollingEndSpacing;

                    OnInfiniteScrolling(true);
                }

                // Occlusion Culling
                if (useOcclusionCulling)
                {
                    OnOcclusionCulling(true);
                }
            }
            else
            {
                automaticallyLayout = infinitelyScroll = useOcclusionCulling = false;
            }

            // Starting Panel // 시작 패널을 세팅한다
            float xOffset = (movementAxis == MovementAxis.Horizontal || movementType == MovementType.Free) ? Viewport.rect.width / 2f : 0f;
            float yOffset = (movementAxis == MovementAxis.Vertical || movementType == MovementType.Free) ? Viewport.rect.height / 2f : 0f;
            Vector2 offset = new Vector2(xOffset, yOffset);
            previousContentAnchoredPosition = Content.anchoredPosition = -PanelsRT[startingPanel].anchoredPosition + offset;
            CurrentPanel = TargetPanel = NearestPanel = startingPanel;

            // Previous Button
            if (previousButton != null)
            {
                previousButton.onClick.RemoveAllListeners();
                previousButton.onClick.AddListener(GoToPreviousPanel);
            }

            // Next Button
            if (nextButton != null)
            {
                nextButton.onClick.RemoveAllListeners();
                nextButton.onClick.AddListener(GoToNextPanel);
            }

            // Pagination
            if (pagination != null)
            {
                Toggles = pagination.GetComponentsInChildren<Toggle>();
                for (int i = 0; i < Toggles.Length; i++)
                {
                    if (Toggles[i] != null)
                    {
                        Toggles[i].isOn = (i == startingPanel);
                        Toggles[i].interactable = (i != TargetPanel);
                        int panelNum = i;

                        Toggles[i].onValueChanged.RemoveAllListeners();
                        Toggles[i].onValueChanged.AddListener(delegate
                        {
                            if (Toggles[panelNum].isOn && toggleNavigation)
                            {
                                GoToPanel(panelNum);
                            }
                        });
                    }
                }
            }
        }

 

메인 세팅하는 메소드이다.

 

몇몇 세팅하는 부분만 잘 사용해도 Scroll_snap기능을 쉽게 사용할 수 있다.

 

 

기본적인 세팅은 인스펙터에서 할 수 있다.

 

쉽고 간단하게 스크롤 스냅을 써보자.