如何在不使用冗余代码的情况下使计时器每秒更改一个精灵?
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];
}
}
计时器结果:
我不太确定如何用短语表达问题,如果这令人困惑,我深表歉意。无论如何,对于上下文,我正在统一制作一种扫雷类型的游戏,而原始游戏的其中一个东西是计时器。 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];
}
}