如何在不使用冗余代码的情况下使计时器每秒更改一个精灵?

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

我不太确定如何用短语表达问题,如果这令人困惑,我深表歉意。无论如何,对于上下文,我正在统一制作一种扫雷类型的游戏,而原始游戏的其中一个东西是计时器。 Here it is。我想复制那种东西,虽然我确实有可用的代码,但老实说,这有点多余。

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;
    }

忽略顶部的所有内容。

    // 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];
            }
        }
    }
}

我想要的是让它在一定时间后显示特定的精灵,但也不必诉诸于使用任何一个。有什么办法可以使这项工作吗? 如果你想要更多信息,我可以给你。

我假设你有 10 个 sprite 数字 0-9。

numberSprite[0] 会保留“0”的精灵,numberSprite[1] 会打孔“1”,等等。

假设后端的计时器为 319.8f 秒。您可能希望显示 3 个精灵:3、1、9。

为此,您需要将计时器值和 sprite 分别分解为百分之一、十分之一和秒。你可以这样做:

    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];

使用上面的代码,您的计时器应该正确更新为 000-999 之间的任何数字,只需要上传 10 个精灵。此外,由于模数 (%) 逻辑,如果您的计时器超过 999,它会自动循环。

警告。协程或 InvokeRepeating 可能是这里的陷阱: 协程可用于跟踪更新精灵之间的时间,但您可能希望将此显示直接与 in-game 时间相关联。依靠协同程序从显示器更新精灵 de-couples 和 in-game 计时器,因为它们没有 built-in 追赶行为。如果您的帧稍微延迟或完全滞后,您 运行 使用协程或 InvokeRepeating 时 运行 时间变慢的风险。

协程非常适合这个。在此处尝试此代码:

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);
        }
    }
}

您可以将 float 转换为 int

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

并使用 spriteIndex 作为精灵数组的索引。 或者......如果你需要为每个精灵使用不同的时间间隔,你可以为这个动画制作特殊的结构。 例如:

[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;
    }
}

您需要在 SpriteFrame 上使用 SerializableAttribute ([Serializable]) 才能在 Unity Inspector 中显示结构。在当前代码中动画只显示一次,但你可以让它循环播放。对于循环动画只需在 _currentFrame++

之后添加 _currentFrame %= Frames.Count

这段代码是问题的最终解决方案,可以无限支持sprite。它也得到了全面优化。只需将精灵 0 到 9 分别放在第一个列表和图像在第二个列表中即可。

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];
    }
}

如何制作停止计时器?

我在这里创建了一个独立的计时器字段,您可以通过按 Space 键来停止步进计时器。例如,您也可以使用 R 键重置计时器。

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];
    }
}

计时器结果: