2
\$\begingroup\$

I'm working on a timeline UI element in Unity, that looks like this: enter image description here

I generate the bottom ticks by instantiating a number of times over the length of the parent RectTransform, and offsetting them on both sides inside the rectangle. This snippet is the general idea of how the ticks are instantiated:


// Take the 4 corners of the RectTransform.
// Think of the vertices of the Rectangle being spaced clockwise
// from BottomLeft (i.e. [0]) to BottomRight (i.e.[3])

float length = vertices[3].x - vertices[0].x;

// Because I want to inset the ticks by an amount
// I subtract the offset twice to keep my length correct
length = length - offset * 2;

// The number of ticks is an integer that is the count of a list.
// In order to make sure the spacing is even and stays within the boundaries
// of the timeline, we can identify the spacing between the ticks as follows:
// Visually represented below as well
float lengthBetweenTicks =  length / (ticks.count - 1)

The formula here for a is: a = 20 / 16 - 1 Length formula visualized

Anyway, the ticks are then created at the position:

Instantiate(tick, new Vector3(vertices[0].x + offset
    + lengthBetweenTicks * index, vertices[0].y, 
    vertices[0].z),Quaternion.Identiy, transform);

This all is well and good, and the ticks sit where I want them to. This is all to say that the formula for spacing the ticks is reliant on an initial offset, and a distance between them calculated as the desired length divided by the number of ticks minus one.

Backstory Over

The next thing I would like to implement is to have a slider go to each tick as time passes, or a button is pressed skipping to the next tick. I don't care about tweening the value or anything pretty, I just need it to jump to the next position. I have tried using an Image component and a Mask component, but had issues and started using a Slider component with almost perfect success. The problem is that the slider seems to drift as it goes over the timeline:

enter image description here

The code I have use to adjust the slider is as follows:

float minimum = offset;
float maximum = length;
float increment = length / ticks.count + offset;

// Update slider value by increment
// For testing I just add the increment on Spacebar
// But it should be more like this
slider.value = increment * tickIndex;

What would be the correct formula for the slider value to hit the center of a given tick, regardless of the index?

Importantly, the value of a Slider is not a 0-1 range like Unity's Mask component, so that makes life a bit easier by not having to convert position back to percentages.

EDIT: Further testing has revealed to me the distance that the slider drifts changes on the screen resolution. I no longer think the formula is the problem, but I don't have a solution yet.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Could you provide a minimal project with this problem? \$\endgroup\$
    – Serg
    Commented Sep 1, 2021 at 9:52

1 Answer 1

3
\$\begingroup\$

This solution sets the dots the same way as the rectFill of the slider with the anchors and should be reactive to the size of the slider. This script is currently placed on the slider. It is independent of the max slider value and the component displayed is the default slider with the slider knob deleted.

//offset in pixel
public float offset = 150f;
public int ticks = 3;
//your cicle icon
public GameObject tick;
public int tickPosition = 2;
//this is the fillArea of the slider
public Transform dotParent;

void Start() {
    float totalWidth = GetComponent<RectTransform>().rect.width;
    float offSetAmount = offset / totalWidth;
    float anchorDistance = (1f - 2 * offSetAmount) / (ticks - 1);
    for (int index = 0; index < ticks; index++) {
        GameObject spawnTick = Instantiate(tick, dotParent);
        spawnTick.GetComponent<RectTransform>().anchorMin = new Vector2(anchorDistance * index + offSetAmount, 0);
        spawnTick.GetComponent<RectTransform>().anchorMax = new Vector2(anchorDistance * index + offSetAmount, 0);
    }
    SetSpinner(tickPosition);
}

public void SetSpinner(int index) {
    float totalWidth = GetComponent<RectTransform>().rect.width;
    float maxValue = GetComponent<Slider>().maxValue;
    float offSetAmount = maxValue * offset/totalWidth;
    float lengthBetweenTicks = (maxValue - offSetAmount * 2) / (ticks - 1);
    GetComponent<Slider>().value = index * lengthBetweenTicks + offSetAmount;
}

enter image description here

\$\endgroup\$
3
  • \$\begingroup\$ This looks great! One thing I guess I will add just for future readers that tripped me up is that the tick transform needs to be at 0,0 as the position is relative to it's world position. Thanks for the help! \$\endgroup\$
    – Rug
    Commented Sep 2, 2021 at 23:30
  • 1
    \$\begingroup\$ @Rug Prefabs should be always at 0,0,0 for a lot of reasons. In this case I added a slight y-offset to the prefab so they sit at the correct distance to the bar, but I will adjust the answer to address it. \$\endgroup\$
    – Zibelas
    Commented Sep 3, 2021 at 7:53
  • \$\begingroup\$ Absolutely true - i didn't mean to imply it changed the validity of your answer, just wanted to comment on it to make sure any future readers were reminded, just in case. \$\endgroup\$
    – Rug
    Commented Sep 3, 2021 at 23:38

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .