How do I make timer change a sprite every second without using code that's redundant?

Viewed 148

I'm not entirely sure how to phrase question so sorry if this is confusing. Anyways for context I'm making a sort of minesweeper type of game in unity and one of the things the original game had was a timer. Here it is. I want to copy that sort of thing, and while I do have code that works, it's honestly kind of redundant here's what I have .

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Timer : MonoBehaviour
{
    public float timer = 0;
    public bool isStop = false;
    public Image scoreCount;
    public Sprite[] numbersprite;
    // Start is called before the first frame update
    void Start()
    {
        timer = 0;
        isStop = true;
    }

Ignore all the stuff on the top.

    // Update is called once per frame
    void Update()
    {
        if(!isStop)
        {
            timer += Time.deltaTime;
            if(timer >= 1f)
            {
                scoreCount.sprite = numbersprite[1];
            }
            if(timer >= 2f)
            {
                scoreCount.sprite = numbersprite[2];
            }
            if(timer >= 3f)
            {
                scoreCount.sprite = numbersprite[3];
            }
            if(timer >= 4f)
            {
                scoreCount.sprite = numbersprite[4];
            }
            if(timer >= 5f)
            {
                scoreCount.sprite = numbersprite[5];
            }
            if(timer >= 6f)
            {
                scoreCount.sprite = numbersprite[6];
            }
        }
    }
}

What I want is to make it so that it both displays a specific sprite after a certain amount of time has based but also not have to resort to using any of this. Is there any way I can make this work? If you want some more information I can give you that.

4 Answers

I'm assuming you have 10 sprites for you numbers 0-9.

numberSprite[0] would hold the sprite for "0", numberSprite[1] would hole "1", etc.

Let's say the timer is at 319.8f seconds on the back end. You would want 3 sprites to display: 3, 1, 9.

To do this, you'll need to break your timer value and sprite into the hundredths, tenths, and seconds individually. You could do this:

    int timerInt = (int)Mathf.floor(timer); //Get the int value of the timer

    int hundredth = (timerInt/100) % 10; // (319/100) => 3 ... (3 % 10) => 3
    scoreCountHundredths.sprite = numberSprite[hundredth];

    int tenth = (timerInt /10) % 10; //(319/10) => 31 ... (31 % 10) => 1
    scoreCountTenths.sprite = numberSprite[tenth];

    int second = timerInt % 10; // (319 % 10) => 9
    scoreCountSeconds.sprite = numberSprite[second];

With the above code, your timer should correctly update to any number between 000-999 requiring only 10 sprites uploaded. Additionally, it will automatically loop if your timer goes above 999 due to the modulo (%) logic.

Warning. Coroutines or InvokeRepeating may be a trap here: Coroutines can be used to track the time between updating the sprites, but you'll likely be wanting to tie this display directly to the in-game time. relying on coroutines to update the sprite de-couples the in-game timer from the display, as they do not have built-in catchup behaviour. If your frames are slightly delayed or lag at all, you run the risk of the time running slower when using coroutines or InvokeRepeating.

Coroutines are perfect for this. Try this code here:

public Image image;
public Sprite[] sprites;
private bool isStop = true;

private void Start()
{
    isStop = false;
    StartCoroutine(Timer());
}

private IEnumerator Timer()
{
    while (!isStop)
    {
        for (int i = 0; i < sprites.Length; i++)
        {
            if (isStop) break;
            image.sprite = sprites[i];
            yield return new WaitForSeconds(1f);
        }
    }
}

You can convert float to int

int spriteIndex = (int)Math.Round(timer);

And used spriteIndex as index to array sprites. Or... if you need used different time interval for every sprite, you can make special struct for this animation. For example:

[Serializable]
public struct SpriteFrame
{
    public Sprite FrameSprite;
    public float TimeToShow;
}

public class SpriteAnimationComponent : MonoBehaviour
{
    public Image ScoreCount; 
    public List<SpriteFrame> Frames = new List<SpriteFrame>();
    private int _currentFrame = 0;
    private float _currentPlayAnimationTime = 0f;

    private bool IsPlay => _currentFrame < Frames.Count;

    public void Start()
    {
        UpdateFrame();
    }

    public void Update()
    {
        if(!IsPlay)
            return;

        _currentPlayAnimationTime += Time.deltaTime;
        if(NeedShowNextFrame())
            ShowNextFrame();
    }

    private bool NeedShowNextFrame()
        => Frames[_currentFrame].TimeToShow < _currentPlayAnimationTime;

    private void ShowNextFrame()
    {
        _currentPlayAnimationTime -= Frames[_currentFrame].TimeToShow;
        _currentFrame++;
        if(IsPlay)
        {
            UpdateFrame();
        }
    }

    private void UpdateFrame()
    {
        ScoreCount.sprite = Frames[_currentFrame].FrameSprite;
    }
}

You need used SerializableAttribute ([Serializable]) on SpriteFrame for show struct in Unity Inspector. In current code animation show once, but you can make it loop. For loop animation just add _currentFrame %= Frames.Count after _currentFrame++

This code is the ultimate solution to the problem and can support sprite indefinitely. It is also fully optimized. Just put the sprites 0 to 9 in the first list and the images in the second list respectively.

public Sprite[] spriteNumbers = new Sprite[10]; // fill with numbers

public List<Image> spriteFieds; // set Images Based on a unit of tens of hundreds
public void Start() => InvokeRepeating(nameof(SyncTimer), 0f, 1f);
public void SyncTimer()
{
    for (var i = 0; i < spriteFieds.Count; i++)
    {
        var index = (int) (Time.time / Mathf.Pow(10, i) % 10);
        spriteFieds[i].sprite = spriteNumbers[index];
    }
}

How to make Stop Timer?

Here I created a standalone timer field and you can stop the step timer by pressing the Space key. You can also reset the timer with the R key, for example.

public Sprite[] spriteNumbers = new Sprite[10]; // fill with numbers

public List<Image> spriteFieds; // set timer Fields

public bool isStop;
public float timer;
public void Update()
{
    if (Input.GetKeyDown(KeyCode.Space)) isStop = !isStop;

    if (Input.GetKeyDown(KeyCode.R))
    {
        timer = 0;
        SyncTimer();
    }
    if (!isStop)
    {
        timer += Time.deltaTime;
        SyncTimer();
    }
}
public void SyncTimer()
{
    for (var i = 0; i < spriteFieds.Count; i++)
    {
        var index = (int) (timer / Mathf.Pow(10, i) % 10);
        spriteFieds[i].sprite = spriteNumbers[index];
    }
}

The Timer Result:

enter image description here

Related