Search Unity

Question Snap to child element in ScrollRect content

Discussion in 'Scripting' started by MidniteOil, Jul 14, 2021.

  1. MidniteOil

    MidniteOil

    Joined:
    Sep 25, 2019
    Posts:
    345
    Greetings,

    I have a "stage selection" screen in my game which will allow the players to select which stage/level they'd like to play.

    Each stage element consists of a prefab with a sprite, title, Scene name, unlocked (boolean), etc).
    They are added to the content panel of a ScrollRect with the Elastic movement type.

    I have prev/next navigation buttons underneath the scroll rect which can be used to navigate through the child elements.
    upload_2021-7-13_20-32-26.png
    I added a SnapToChild() method to my StageSelection component to try to bring the specified child element into view.

    The annoying side-effect is that when the first or last child is selected in this way, it positions it in the center of the content panel and then the elasticity slides it back over.

    Also, when navigating between 2 children that are already in view, it's moving the selected child into the center which is a bit jarring.

    Here's the code:
    Code (CSharp):
    1.     private void LateUpdate()
    2.     {
    3.         UpdateNavigationButtons();
    4.     }
    5.  
    6.     private void UpdateNavigationButtons()
    7.     {
    8.         _prevButton.interactable = _selectedStage > 0;
    9.         _nextButton.interactable = _selectedStage < _stageManager.HighestUnlockedStage;
    10.     }
    11.  
    12.     public void SelectNextStage()
    13.     {
    14.         if (_selectedStage < _stageManager.HighestUnlockedStage)
    15.         {
    16.             _selectedStage++;
    17.             SelectStage();
    18.         }
    19.     }
    20.  
    21.     public void SelectPreviousPage()
    22.     {
    23.         if (_selectedStage > 0)
    24.         {
    25.             _selectedStage--;
    26.             SelectStage();
    27.         }
    28.     }
    29.  
    30.     private void SelectStage()
    31.     {
    32.         Stage stage = _stages[_selectedStage];
    33.  
    34.         // Mark stage as selected
    35.         stage.SelectStage();
    36.  
    37.         // Scroll stage entry into view
    38.         SnapToChild(stage);
    39.     }
    40.  
    41.     private void SnapToChild(Stage stage)
    42.     {
    43.         Canvas.ForceUpdateCanvases();
    44.         _contentPanel.anchoredPosition =
    45.             (Vector2)_scrollRect.transform.InverseTransformPoint(_contentPanel.position) -
    46.             (Vector2)_scrollRect.transform.InverseTransformPoint(stage.transform.position);
    47.     }
    Also, here is a link to a short video demonstrating the problem:

    Hopefully someone can help me with my math to fix this.

    Thanks!
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    The approach I usually take for these is to ONLY move the focus under cursor control, NOT do the scrolling.

    Then over in Update() I go "Hm, is this one within the viewable area? Do nothing"

    OR... "It's offscreen, bring it into view"

    And I usually bring it in quite aggressively once it is fully offscreen.

    I find that whole method less jarring because no scrolling happens if my cursor remains fully visible.
     
  3. MidniteOil

    MidniteOil

    Joined:
    Sep 25, 2019
    Posts:
    345
    To clarify, when you say "under cursor control" does that mean dragging with the mouse (or your finger on a touch screen)?

    i.e. Am I correct that you would dispense with the navigation buttons? In my use case when a player completes a stage I will display the stage selection screen to allow them to pick which stage they want to play next, with the "next" one (the one after the one they just completed) being selected/highlighted by default. I need that element to be in view. The buttons just give them another way to navigate through the list of stages.

     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    Yes. Often on touch screen you do the scrolling yourself though... so not 1-to-1 mapping really with cursor keys.

    Yes or no... it's odd to have nav left/right cursor buttons if dragging works, but you could do it.
     
    MidniteOil likes this.
  5. MidniteOil

    MidniteOil

    Joined:
    Sep 25, 2019
    Posts:
    345
    Thanks again.

    So navigation buttons aside, I still need to know how to bring an offscreen child into view programmatically, preferably just enough to be fully visible.

    I've been looking at the debug values in the inspector and I'm having trouble deducing how to tell if an element is within the viewable area.

    That's what I need help with at this point.

     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    Over in the Unity UI Extensions repo there's some stuff to do this:

    https://bitbucket.org/UnityUIExtensions/unity-ui-extensions/wiki/Home

    I ended up learning from that codebase (I think mostly from ScrollPositionController.cs??) and then making my own.

    My approach was this:

    - make left and right edge-of-screen anchors (empty GameObjects in the UI hierarchy)
    - make an observer script on the scroll rect:
    ----> tell the observer "This guy has focus"
    ----> the observer then studies that object's position and adjusts it each frame until it is between the left / right

    How you do that, what "easing" function you use, what acceleration and smoothing you use will profoundly affect the feel of it, so be prepared to tinker until it feels good for you.
     
    MidniteOil likes this.
  7. MidniteOil

    MidniteOil

    Joined:
    Sep 25, 2019
    Posts:
    345
    Thanks for all your help. I ended up removing the navigation buttons (simpler is better in this case I think).
    I'll hopefully have an update of my game to push to the Apple app store, Google Play and itch.io soon.
    I just want to add a brief tutorial first. Some kind testers provided some constructive feedback and some of them said it wasn't immediately clear that the game was an "angry birds" clone and they didn't know what to do.

     
    Kurt-Dekker likes this.
  8. unity_6C82258DF4F967069335

    unity_6C82258DF4F967069335

    Joined:
    Sep 23, 2022
    Posts:
    11
    I use lean tween for animating the sliding effect.

    Code (csharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.UI;
    6.  
    7. public class SnapHorizontalScroll : MonoBehaviour
    8. {
    9.     public ScrollRect scrollRect;
    10.     public Button plusButton;
    11.     public Button minusButton;
    12.     private int currentCard = 0;
    13.  
    14.     private void Start()
    15.     {
    16.         currentCard = 0;
    17.         plusButton.onClick.AddListener(MoveRight);
    18.         minusButton.onClick.AddListener(MoveLeft);
    19.      
    20.         plusButton.interactable = currentCard < scrollRect.content.childCount - 1;
    21.         minusButton.interactable = currentCard > 0;
    22.     }
    23.  
    24.     public void MoveRight()
    25.     {
    26.         if (currentCard >= scrollRect.content.childCount - 1)
    27.         {
    28.             //plusButton.interactable = false;
    29.             return;
    30.         }
    31.         currentCard++;
    32.         LeanTween.value(scrollRect.gameObject, (float val) => { scrollRect.horizontalNormalizedPosition = val; }, scrollRect.horizontalNormalizedPosition, (float)currentCard / (scrollRect.content.childCount - 1), 0.5f);
    33.         plusButton.interactable = currentCard < scrollRect.content.childCount - 1;
    34.         minusButton.interactable = currentCard > 0;
    35.     }
    36.  
    37.     public void MoveLeft()
    38.     {
    39.         if (currentCard <= 0)
    40.         {
    41.             //minusButton.interactable = false;
    42.             return;
    43.         }
    44.         currentCard--;
    45.         LeanTween.value(scrollRect.gameObject, (float val) => { scrollRect.horizontalNormalizedPosition = val; }, scrollRect.horizontalNormalizedPosition, (float)currentCard / (scrollRect.content.childCount - 1), 0.5f);
    46.         plusButton.interactable = currentCard < scrollRect.content.childCount - 1;
    47.         minusButton.interactable = currentCard > 0;
    48.     }
    49. }
    50.  
     
    Last edited: Feb 2, 2023
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
  10. unity_6C82258DF4F967069335

    unity_6C82258DF4F967069335

    Joined:
    Sep 23, 2022
    Posts:
    11
  11. unity_6C82258DF4F967069335

    unity_6C82258DF4F967069335

    Joined:
    Sep 23, 2022
    Posts:
    11
    In this I have tried to do something more to animate like Carousel Scrolling effect. But I am unsuccessful till now. Or can do like the card which is not inside the view will have 20 degrees of rotation on y-axis. It will look good
    Can anyone help me?
     
  12. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    Let's stop necro-posting to old threads. Make your own new post, it's FREE!

    When you post, keep this in mind:

    How to report your problem productively in the Unity3D forums:

    http://plbm.com/?p=220

    This is the bare minimum of information to report:

    - what you want
    - what you tried
    - what you expected to happen
    - what actually happened, especially any errors you see
    - links to documentation you used to cross-check your work (CRITICAL!!!)

    If you post a code snippet, ALWAYS USE CODE TAGS:

    How to use code tags: https://forum.unity.com/threads/using-code-tags-properly.143875/