Unity - How to scroll a scrollbar for a ScrollRect smoothly? - unity3d

I have a ScrollRect to which I add content to. When it reaches the point where the content is longer than the ScrollRect (ie when the ScrollBar's value changes from 0), I want the ScrollRect to scroll all the way down (I do this by tweening the ScrollBar's value until it reaches 0). However, my problem is that I can't figure out how to do this smoothly over time.
Here's my code snippet:
public void Update() {
if (scrollbar.size < 1 || scrollbar.value > 0) {
LeanTween.value(scrollbar.value, 0, duration).setOnUpdate((float val) => {
if (scrollbar.value == 0) {
LeanTween.cancel(this.gameObject);
} else {
scrollbar.value = val / scrollAdjustment;
}
});
}
}
I tried using "Time.deltaTime" and "Time.time" in place of duration and it did not seem to matter. Here's a gif of what happens:
(In this example, I used "duration" that had the value of 5 (the idea was to have the transition take 5 seconds) and "scrollAdjustment" was 50 but it did not seem to matter what I set either of these values to.
You can see it instantly snaps to the bottom. I'd like this to be a smooth transition. Any help is appreciated!
My settings:
Then here is me scrolling with my mouse wheel while the autoscroll feature is turned off (because I'm using Rewired, I am intercepting an input called "ZoomIn" and "ZoomOut" and add "0.01f * scrollSpeed" (where scrollSpeed is 15 in this case):

You shouldn't need to add any additional code to make the scroll bar function as the way you want it to. Try deactivating your script and mess with your scroll bar. Make sure you have your Number Of Steps On Your Scrollbar Component as 0 to make it smooth. If it's still not working, send some screenshots of your scroll gameobject to see if the rect is the problem or something else.

Related

Unity: add different scroll speeds in UI Scroll Rect

In Unity, I created a UI Scroll Rect Object. This object has two children, a Button Handler with different Buttons, and a Background.
Using it, both the Buttons and the Background are scrolling at the same speed. I want the Buttons to scroll faster than the Background, thus creating an effect of depth to the scrolling.
I can not find any options in the Objects or the Scroll Rect for this. Any ideas?
This will require a tiny bit of scripting. The best way in my opinion would involve following steps:
a) for each button, add a component that remembers its start position.
b) grab a scrollrect instance in parent
c) using it to grab an instance of ScrollBar
d) scrollbars have onValueChanged(float) callbacks, you can bind to, to know when the scroll position changes (to avoid doing checks in Update)
e) every time a scrollbar has moved (This will work regardless of whether the user used the scrollbar, or used another mean of scrolling, as the scrollrect will move the scrollbar, which will fire the event anyways), you get a callback with current scrollbar position which will be equal to normalized scrollrect position (0.1)
f) use that value to offset your localposition (something value*parallaxVector2), this should give you a nice cheap depth effect
Here's an example implementation
public class ScrolleRectDepthButton : MonoBehaviour
{
RectTransform content; // we'll grab it for size reference
RectTransform myRect;
public float parallaxAmount = 0.05f;
public Vector2 startPosition;
void Start()
{
myRect = GetComponent<RectTransform>();
startPosition = myRect.anchoredPosition;
ScrollRect scrollRect = GetComponentInParent<ScrollRect>();
content = scrollRect.content;
Scrollbar scrollbar = scrollRect.verticalScrollbar;
scrollbar.onValueChanged.AddListener(OnScrollbarMoved);
}
void OnScrollbarMoved(float f)
{
myRect.anchoredPosition = startPosition - (1 - f) * parallaxAmount * content.rect.height * Vector2.up;
}
}

In Unity, how to detect if window is being resized and if window has stopped resizing

I wanted my UI to not resize when user is still resizing the game (holding click in the window border) and only when the user has released the mouse the resize event will trigger.
I have tried to achieve it on Unity but so far I only able to detect windows size change, which my script checked every 0.5 second and if detected change it will resize the UI. But of course resizing everything caused a heavy lag, so resizing every 0.5 second is not a good option but resizing every 1 second is not a good idea either because 1 second is considered too long.
The question might be too broad but I have specified the problem as small as possible, how do I detect if user is still resizing the window? And how do I detect if user has stopped resizing the window (stop holding click at window border)?
You can't tell when someone stops dragging a window, unless you want to code a low level solution for ever desktop environment and every operating system.
Here's what worked for me with any MonoBehavior class, using the OnRectTransformDimensionsChange event:
public class StackOverflow : MonoBehaviour
{
private const float TimeBetweenScreenChangeCalculations = 0.5f;
private float _lastScreenChangeCalculationTime = 0;
private void Awake()
{
_lastScreenChangeCalculationTime = Time.time;
}
private void OnRectTransformDimensionsChange()
{
if (Time.time - _lastScreenChangeCalculationTime < TimeBetweenScreenChangeCalculations)
return;
_lastScreenChangeCalculationTime = Time.time;
Debug.Log($"Window dimensions changed to {Screen.width}x{Screen.height}");
}
}
I have some good news - sort of.
When the user resizes the window on Mac or PC,
Unity will AUTOMATICALLY re-layout everything.
BUT in fact ONLY when the user is "finished" resizing the Mac/PC window.
I believe that is what the OP is asking for - so the good news, what the OP is asking for is quite automatic.
However. A huge problem in Unity is Unity does not smoothly resize elements as the user is dragging the mouse to expand the Mac/PC window.
I have never found a solution to that problem. (A poor solution often mentioned is to check the size of the window every frame; that seems to be about the only approach.)
Again interestingly, what the OP mentions
" ..and if window has stopped resizing .."
is automatically done in Unity; in fact do nothing to achieve that.
I needed something like this for re generating a line chart, but as it has too many elements, it would be heavy to do it on every update, so I came up with this, which for me worked well:
public class ToRunOnResize : MonoBehaviour
{
private float screenWidth;
private bool screenStartedResizing = false;
private int updateCounter = 0;
private int numberOfUpdatesToRunXFunction = 15; // The number of frames you want your function to run after, (usually 60 per second, so 15 would be .25 seconds)
void Start()
{
screenWidth = Screen.width; // Identifies the screen width
}
private void Update()
{
if (Screen.width != screenWidth) // This will be run and repeated while you resize your screen
{
updateCounter = 0; // This will set 0 on every update, so the counter is reset until you release the resizing.
screenStartedResizing = true; // This lets the application know when to start counting the # of updates after you stopped resizing.
screenWidth = Screen.width;
}
if (screenStartedResizing)
{
updateCounter += 1; // This will count the updates until it gets to the numberOfUpdatesToRunXFunction
}
if (updateCounter == numberOfUpdatesToRunXFunction && screenStartedResizing)
{ // Finally we make the counter stop and run the code, in my case I use it for re-rendering a line chart.
screenStartedResizing = false;
// my re-rendering code...
// my re-rendering code...
}
}
}

How to slide a panel from right to left using bunifuTransition in c#

I have bunifutransition1 that slides my mainpanel from left to right upon clicking showbutton. (It shows the hidden mainpanel.)
What I want is, when I click closebutton, the mainpanel will slide from right to left (to hide the mainpanel again). It seems that bunifuTransition does not have an animation that reverses the animation of VertSlide or HorizSlide.
What should I do to slide my mainpanel from right to left to hide it again on my form?
I was having the exact same issue but upon reading your question the answer finally became prevalent in my mind. The solution here is to stop using BunifuTranisition altogether and go for the good ol' for loops and the other mods, puns intended.
int originalWidth = panel.width;
int menuClicksIndex = 0;
private void beginTransition()
{
if (menuClickIndex % 2 == 0)
{
//This executes on the first click
for(int i = originalWidth-1; i>=0; i--)
{
// Loops from original width to 0
panel.Width = i;
}
}
else
{
for (int i = 0; i <= originalWidth; i++)
{
panel.Width = i;
}
}
menuClickIndex++;
}
This works for me but it glitches on the way back from left to right. So a mixed version with BunifuTransitions for the opener and the for loop for the closer would be the ideal solution here.
UPDATE 1: It seems as if while changing the width of the panel from 0 to say, 350, the content inside the panel doesn't render until the height is set to the max, but while decreasing the height from 350 to 0, the content is already rendered and so it seems smooth to close but cluttery to open, hence probably explaining why BunifuTransition is unable to do that as well.
Solution Here.
Just go bunifu transition properties
Open or dragdown DefaultAnimation. Find this option in menu ("Side Coeff") It Show value of
X and Y {X=1, Y=0} . You just change this value {X=-1, Y=0}.
Then Start your project and check. Your slider sliding left to right. :)
Keep enjoy.
Regards,
Haris Ali
Use this command: bunifuTransition1.HideSync(guna2Panel1); before any code on event button click!

Unity 5: how to zoom and translate an object from a grid layout to the center of the screen

I'm trying to create a scroll grid view in which every cell object is tapable.
When a cell object is tapped I want to scale and traslate it to the center of the screen and render it above other cells.
I was able to make it tapable and scale it in its position. Now I want to move the cell object to the center of the screen and render it above other cells.
I've tried many solutions but none of them works.
This is my hierarchy:
This is the grid in normal state:
This is the grid when a cell was tapped:
I'm populating the grid from a C# script dynamically.
void Populate()
{
GameObject cardContainerInstance, cardInstance;
foreach (var c in cardsCollection.GetAll())
{
if (c.IsOwned)
{
cardContainerInstance = Instantiate(cardContainer, transform);
cardInstance = cardContainerInstance.transform.Find("Card").gameObject;
var cardManager = cardInstance.GetComponent<CardManager>();
cardManager.card = c;
cardManager.AddListener(this);
}
else
{
Instantiate(cardSlot, transform);
}
}
}
public void OnCardClick(GameObject cardObject, Card card)
{
Debug.Log("OnCardClick " + card.name);
if (openedCard != null) {
if (openedCard.Number == card.Number)
{
CloseCard(openedCardObject);
}
else
{
CloseCard(openedCardObject);
OpenCard(cardObject, card);
}
}
else
{
OpenCard(cardObject, card);
}
}
void OpenCard(GameObject cardObject, Card card)
{
//cardObject.GetComponent<Canvas>().sortingOrder = 1;
var animator = cardObject.GetComponent<Animator>();
animator.SetTrigger("Open");
openedCard = card;
openedCardObject = cardObject;
}
void CloseCard(GameObject cardObject)
{
var animator = cardObject.GetComponent<Animator>();
animator.SetTrigger("Close");
openedCard = null;
openedCardObject = null;
}
I can't figure out how to move the cell to the center and render it above others.
Note that all is animated using an animator attached to the object itself.
Could anyone help me please? Thank you very much!
EDIT: more details
All cell object have the following hierarchy:
where:
CardContainer is an empty object added to use animator on Card child object
Card is the object itself that has a script, a canvas renderer and an animator
StatsImage is the object that slide out when the card is tapped
Image is a calssic UIImage with Image script, Shadow script and canvas renderer
Other component are simple texts.
EDIT: fix in progress
Trying to apply this suggestions I was able to manage the rendering order (as you see on the image below) but it seems that prevent touch events to be detected on the game object.
I've added a GraphicsRaycaster too and now the bottom horizontal scroll view scrolls again but only if I click and drag a card.
Moreover, with the GraphicsRaycaster, the main grid card still are not clickable and it's possible to open the card only if it is behind the bottom panel (if I click on the red spot in the image below the card behind the panel receives che click)
This is the CardContainer at runtime(note that I'm attaching new Canvas and GraphicsRaycaster on the CardContainer, which is the "root" element):
You didn't clarify whether you are using a sprite renderer or some other method but here is an answer for each.
Sprite renderer:
this the simple one. In each sprite renderer, there is a variable called "sortingOrder" in script and "Order in layer" in the inspector. sprite renderer with sorting Orders that are higher is rendered first. All you would need to do is call:
cardObject.GetComponent<SpriteRenderer>().sortingOrder = 1;
when you click the card, and
cardObject.GetComponent<SpriteRenderer>().sortingOrder = 0;
when you unclick it. I hope that makes sense!
Other Method:
this one is a bit harder and I would suggest that you switch to sprite renderers and it will be much easier and more stable down the road, but I can understand if you have already written a lot of scripts and don't want to go back and change them.
Anyway, all you will need to do Is create two layers: cardLower and cardUpper. then create a new camera and call it topCamera. now under the top camera object in the inspector, change the culling mask (it's near the top) and make sure cardUpper is selected. then change the Clear flags (first one) to "Don't Clear" finally change the depth to 0 (if that doesn't work change it to -2). Now objects in the cardUpper will always be rendered above everything else. You can change the layer through script with
cardObject.layer = "cardUpper"
or
cardObject.layer = "cardLower"
I hope that helps!
Ok, so its pretty simple. So you are going to want to add another canvas component to the game object, and check the override sorting to true. Then use
cardObject.GetComponent<Canvas>().sortingOrder = 1;
to place it in the front and
cardObject.GetComponent<Canvas>().sortingOrder = 0;
to put it in the back.
you are also going to need to put a GraphicsRaycaster on to each of the cardObjects
Ignore my other answer about sprite renderers, they are not needed here

Only scroll in one direction

I have a infinite vertical scrolling bar in unity and I want to suddenly limit the scrolling (in one direction only) when reaching a (variable) threshold.
public GameObject MyScrollRectContent;
public float limit = 300;
void Update () {
if(MyScrollRectContent.transform.localPosition.y >= limit){
//make it ONLY possible to scroll backwards or not beyond the limit and stop elasticity
}
Any ideas how to limit the infinite scrolling?
The simplest thing would be to use the existing ScrollRect. It allows scroll in a defined rectangle. You would have to simulate infinity by setting a really big size in one direction, or find a way to seamlessly reset the position if the user goes too far (difficult to do without seeing the jump but might be possible depending on your content).
If this is not an acceptable solution:
To limit the scrolling, you can just set the position to the limit if the user goes too high:
void Update ()
{
if(MyScrollRectContent.transform.localPosition.y >= limit)
{
var currentPos = MyScrollRectContent.transform.localPosition;
MyScrollRectContent.transform.localPosition = new Vector3(currentPos.x, limit, currentPos.z);
}
}
Now if on top of that you want elasticity, it is a bit more tricky: Somewhere, your script must currently set MyScrollRectContent.transform.localPosition. Instead of doing that, set a targetYPosition. You can imagine this as a point that moves, on which the scrollrect is linked by an elastic. This targetYPosition is contrained by your limit. Then in your update you make the ScrollRectContent follow.
public void OnUserInput(float y) // I don't know how you actually do this. this is an example
{
_targetYposition = y;
if(_targetYposition > limit)
{
_targetYPosition = limit;
}
}
private void Update()
{
var currentPos = MyScrollRectContent.transform.localPosition;
var y =
Vector3.Lerp(currentPos.y,
_targetYPosition, Time.deltaTime*_speed);
MyScrollRectContent.transform.localPosition = new Vector3(currentPos.x, y, currentPos.z);
}
Note that this is a simple example aiming at hinting you in the right direction. You'll have to adapt it to your code. The elasticity will likely not look as you want either, so you might want to modify the Update function to create other effects