如何延迟 Unity 和 C# 中的方法?
How to delay a method in Unity and C#?
我叫 Laurenz,我的问题是如何使用 C# 在 Unity 中延迟我的精灵的颜色变化。
现在我有一个随机生成器,它根据数字选择颜色,但每一帧都会发生这种情况。所以现在真正的挑战是如何延迟它以减少它的变化。
public class colorchange : MonoBehaviour
{
public int color;
public bool stop = true;
void Start()
{
}
void Update()
{
Debug.Log("Hello");
color = Random.Range(1, 5);
if (color == 2)
{
gameObject.GetComponent<SpriteRenderer>().color = Color.blue;
}
if (color == 3)
{
gameObject.GetComponent<SpriteRenderer>().color = Color.red;
}
if (color == 4)
{
gameObject.GetComponent<SpriteRenderer>().color = Color.yellow;
}
}
}
您可以使用计时器和Time.deltaTime:
public class colorchange : MonoBehaviour
{
public int color;
public bool stop = true;
public float delay;
private float timer;
void Start()
{
timer = delay;
}
void Update()
{
timer -= Time.deltaTime;
if (timer <= 0) {
timer = delay;
Debug.Log("Hello");
color = Random.Range(1, 5);
if (color == 2)
{
gameObject.GetComponent<SpriteRenderer>().color = Color.blue;
}
if (color == 3)
{
gameObject.GetComponent<SpriteRenderer>().color = Color.red;
}
if (color == 4)
{
gameObject.GetComponent<SpriteRenderer>().color = Color.yellow;
}
}
}
}
您可以将代码放入 Coroutine 的循环中,每隔几秒迭代一次:
public class colorchange : MonoBehaviour
{
public int color;
public float delaySeconds = 1f;
IEnumerator changeColorCoroutine;
SpriteRenderer mySprite;
public bool doChangeColor;
void Start()
{
// cache result of expensive GetComponent call
mySprite = GetComponent<SpriteRenderer>();
// initialize flag
doChangeColor = true;
// create coroutine
changeColorCoroutine = ChangeColor();
// start coroutine
StartCoroutine(changeColorCoroutine);
}
void OnMouseDown()
{
// toggle doChangeColor
doChangeColor = !doChangeColor;
}
IEnumerator ChangeColor()
{
WaitUntil waitForFlag = new WaitUntil( () => doChangeColor);
while (true)
{
yield return waitForFlag;
Debug.Log("Hello");
color = Random.Range(1, 5);
// switch for neater code
switch (color)
{
case 2:
mySprite.color = Color.blue;
break;
case 3:
mySprite.color = Color.red;
break;
case 4:
mySprite.color = Color.yellow;
break;
}
yield return new WaitForSeconds(delaySeconds);
}
}
}
你可以用一个简单的协同程序来处理这个问题。如果您不知道如何操作,请查看 Unity 官方 API 文档中的 link ( https://docs.unity3d.com/ScriptReference/WaitForSeconds.html )。
如果不想使用协程,可以使用Unity的InvokeRepating方法。创建一个用于更改颜色的新函数,并每隔 x 秒调用一次。
这也是 InvokeRepating 方法的 link https://docs.unity3d.com/ScriptReference/MonoBehaviour.InvokeRepeating.html
ps:在这种情况下,使用 Switch 确定颜色整数比对每个颜色代码使用 if 和 else if 更有效。它更高效,更容易扩大规模。
有多种方法可以满足您的需求。
计时器
您可以使用计时器每隔 x 时间更改一次颜色。
至少有两种方法可以做到这一点:
1
public class colorchange : MonoBehaviour
{
public int color;
public bool stop = true;
private SpriteRenderer _mySpriteRenderer;
private float _timeBetweenChanges = 0.5F;
private float _lastChange = 0f;
void Start()
{
_mySpriteRenderer = GetComponent<SpriteRenderer>();
}
void Update()
{
if (Time.time - _lastChange >= _timeBetweenChanges)
{
_lastChange = Time.time;
color = Random.Range(1, 5);
if (color == 2)
{
_mySpriteRenderer.color = Color.blue;
}
if (color == 3)
{
_mySpriteRenderer.color = Color.red;
}
if (color == 4)
{
_mySpriteRenderer.color = Color.yellow;
}
}
}
}
注意: 您可以缓存您的组件 SpriteRenderer 以避免每次调用 GetComponent
。 See here
2
public class colorchange : MonoBehaviour
{
public int color;
public bool stop = true;
private SpriteRenderer _mySpriteRenderer;
private float _timeBetweenChanges = 0.5F;
void Start()
{
_mySpriteRenderer = GetComponent<SpriteRenderer>();
InvokeRepeating("ChangeColor", 0F, _timeBetweenChanges);
}
void Update()
{
// You don't need Update here, you can safly remove it unless you need it for anything else
}
void ChangeColor()
{
color = Random.Range(1, 5);
if (color == 2)
{
_mySpriteRenderer.color = Color.blue;
}
if (color == 3)
{
_mySpriteRenderer.color = Color.red;
}
if (color == 4)
{
_mySpriteRenderer.color = Color.yellow;
}
}
}
随机
你可以用一个RNG来决定你是否要改变这一帧的颜色。这意味着您无法定义每次 sec / min / in general
更改颜色的次数。
public class colorchange : MonoBehaviour
{
public int color;
public bool stop = true;
private SpriteRenderer _mySpriteRenderer;
private float _randomChance = 0.2F;
void Start()
{
_mySpriteRenderer = GetComponent<SpriteRenderer>();
}
void Update()
{
if (Random.Range(0F, 1F) < _randomChance)
{
color = Random.Range(1, 5);
if (color == 2)
{
_mySpriteRenderer.color = Color.blue;
}
if (color == 3)
{
_mySpriteRenderer.color = Color.red;
}
if (color == 4)
{
_mySpriteRenderer.color = Color.yellow;
}
}
}
}
这里有 20 个
or a would be to use InvokeRepeating
and stop it using CancelInvoke
的另一种选择。在像您这样的用例中,我发现这种方式更易于实施和控制:
public float Interval = 1;
[SerializeField] private SpriteRenderer spriteRenderer;
private void Awake()
{
// DO THIS ONLY ONCE
if(!spriteRenderer) spriteRenderer = GetComponent<SpriteRenderer>();
}
// Automatically called when this component or GameObject gets enabled
private void OnEnable ()
{
// Start invoking every Interval seconds
InvokeRepeating(nameof(ChangeColor), Interval, Interval);
}
// Automatically called when this component or GameObject gets disabled
private void OnDisable()
{
// Stop the repeated invoking
CancelInvoke();
}
private void ChangeColor()
{
Debug.Log("Hello");
color = Random.Range(1, 5);
// You should also use a switch case here
switch(color)
{
case 2:
spriteRenderer.color = Color.blue;
break;
case 3:
spriteRenderer.color = Color.red;
break;
case 4:
spriteRenderer.color = Color.yellow;
break;
}
}
然后您可以通过启用和禁用此组件来简单地启用和禁用颜色更改。
或者简单地将相应的代码行从 OnEnable
和 OnDisable
移动到相应的 public 方法,如 public void StartColorChange
和 public void StopColorChange
.
这可以通过多种方式完成。在向您展示具体内容之前,这是我将使用的所有示例代码通用的基本结构:
public class ColorChanger : MonoBehaviour {
//Avoid Find and GetComponent methods in performance-critical contexts like Update and FixedUpdate
//Store the value once in the beginning. This is called 'caching'
public SpriteRenderer _renderer;
//Don't hard-code stuff like this
public Color[] _colors;
public float _colorChangeInterval = 0.5f;
//Convenience property to access _renderer.color
public Color Color {
get => _renderer.color;
set => _renderer.color = value;
}
private void Start() {
//Attempts to find the SpriteRenderer in the object if it wasn't set in the inspector
if (!_renderer)
_renderer = GetComponent<SpriteRenderer>();
}
//This piece of code does a specific thing, so it's best to put it in a method
public void ChangeColor() {
if (_colors.Length < 1)
Debug.LogError($"You forgot to set {nameof(_colors)} in the Inspector. Shame! Shame!");
Color = _colors[Random.Range(0, _colors.Length - 1)];
}
}
在我看来,以下是一些主要内容,按照直观程度排序:
计时器模式:
有两种口味。
1) 可以是经过时间的累加器(如下面的代码),或者相反,从间隔递减到零:
private float _elapsed;
private void Update() {
_elapsed += Time.deltaTime;
if (_elapsed < _colorChangeInterval)
return;
ChangeColor();
_elapsed %= _colorChangeInterval;
}
或 2) 可以是从上次开始或直到下一次(如下所示)的时间戳检查触发器,时间戳:
//Replaces _elapsed
private float _timestamp;
private void Start() {
//...
_timestamp = Time.time; //Initial timestamp
}
private void Update() {
if (Time.time < _timestamp + _colorChangeInterval)
return;
ChangeColor();
_timestamp = Time.time;
}
协程和 WaitForSeconds:
当您需要延迟或统一序列代码时,这是推荐的程序。
注意unity还提供了其他类型的等待方法,如WaitWhile
、WaitUntil
等...
//Since unlike code in Update, coroutines need to be started and stopped, we start it when the script is enabled
private void OnEnable() {
StartCoroutine(ChangeColorContinuously());
}
//This is automatically stopped by unity when the script is disabled
private IEnumerator ChangeColorContinuously() {
while (true) {
yield return new WaitForSeconds(_colorChangeInterval);
ChangeColor();
}
}
不要执行异步等待!
好吧,它可以完成,但是它有很多陷阱并且非常不 推荐给初学者。
而且无论如何它都无意取代协程。
不要执行 InvokeRepeating!
这是一种依赖魔法字符串和反射的方法。对于示例代码的快速简单设置很有用,但如果可能的话(由于上述方法,这是可能的)应该像生产代码中的瘟疫一样避免。
我叫 Laurenz,我的问题是如何使用 C# 在 Unity 中延迟我的精灵的颜色变化。
现在我有一个随机生成器,它根据数字选择颜色,但每一帧都会发生这种情况。所以现在真正的挑战是如何延迟它以减少它的变化。
public class colorchange : MonoBehaviour
{
public int color;
public bool stop = true;
void Start()
{
}
void Update()
{
Debug.Log("Hello");
color = Random.Range(1, 5);
if (color == 2)
{
gameObject.GetComponent<SpriteRenderer>().color = Color.blue;
}
if (color == 3)
{
gameObject.GetComponent<SpriteRenderer>().color = Color.red;
}
if (color == 4)
{
gameObject.GetComponent<SpriteRenderer>().color = Color.yellow;
}
}
}
您可以使用计时器和Time.deltaTime:
public class colorchange : MonoBehaviour
{
public int color;
public bool stop = true;
public float delay;
private float timer;
void Start()
{
timer = delay;
}
void Update()
{
timer -= Time.deltaTime;
if (timer <= 0) {
timer = delay;
Debug.Log("Hello");
color = Random.Range(1, 5);
if (color == 2)
{
gameObject.GetComponent<SpriteRenderer>().color = Color.blue;
}
if (color == 3)
{
gameObject.GetComponent<SpriteRenderer>().color = Color.red;
}
if (color == 4)
{
gameObject.GetComponent<SpriteRenderer>().color = Color.yellow;
}
}
}
}
您可以将代码放入 Coroutine 的循环中,每隔几秒迭代一次:
public class colorchange : MonoBehaviour
{
public int color;
public float delaySeconds = 1f;
IEnumerator changeColorCoroutine;
SpriteRenderer mySprite;
public bool doChangeColor;
void Start()
{
// cache result of expensive GetComponent call
mySprite = GetComponent<SpriteRenderer>();
// initialize flag
doChangeColor = true;
// create coroutine
changeColorCoroutine = ChangeColor();
// start coroutine
StartCoroutine(changeColorCoroutine);
}
void OnMouseDown()
{
// toggle doChangeColor
doChangeColor = !doChangeColor;
}
IEnumerator ChangeColor()
{
WaitUntil waitForFlag = new WaitUntil( () => doChangeColor);
while (true)
{
yield return waitForFlag;
Debug.Log("Hello");
color = Random.Range(1, 5);
// switch for neater code
switch (color)
{
case 2:
mySprite.color = Color.blue;
break;
case 3:
mySprite.color = Color.red;
break;
case 4:
mySprite.color = Color.yellow;
break;
}
yield return new WaitForSeconds(delaySeconds);
}
}
}
你可以用一个简单的协同程序来处理这个问题。如果您不知道如何操作,请查看 Unity 官方 API 文档中的 link ( https://docs.unity3d.com/ScriptReference/WaitForSeconds.html )。
如果不想使用协程,可以使用Unity的InvokeRepating方法。创建一个用于更改颜色的新函数,并每隔 x 秒调用一次。 这也是 InvokeRepating 方法的 link https://docs.unity3d.com/ScriptReference/MonoBehaviour.InvokeRepeating.html
ps:在这种情况下,使用 Switch 确定颜色整数比对每个颜色代码使用 if 和 else if 更有效。它更高效,更容易扩大规模。
有多种方法可以满足您的需求。
计时器
您可以使用计时器每隔 x 时间更改一次颜色。
至少有两种方法可以做到这一点:
1
public class colorchange : MonoBehaviour
{
public int color;
public bool stop = true;
private SpriteRenderer _mySpriteRenderer;
private float _timeBetweenChanges = 0.5F;
private float _lastChange = 0f;
void Start()
{
_mySpriteRenderer = GetComponent<SpriteRenderer>();
}
void Update()
{
if (Time.time - _lastChange >= _timeBetweenChanges)
{
_lastChange = Time.time;
color = Random.Range(1, 5);
if (color == 2)
{
_mySpriteRenderer.color = Color.blue;
}
if (color == 3)
{
_mySpriteRenderer.color = Color.red;
}
if (color == 4)
{
_mySpriteRenderer.color = Color.yellow;
}
}
}
}
注意: 您可以缓存您的组件 SpriteRenderer 以避免每次调用 GetComponent
。 See here
2
public class colorchange : MonoBehaviour
{
public int color;
public bool stop = true;
private SpriteRenderer _mySpriteRenderer;
private float _timeBetweenChanges = 0.5F;
void Start()
{
_mySpriteRenderer = GetComponent<SpriteRenderer>();
InvokeRepeating("ChangeColor", 0F, _timeBetweenChanges);
}
void Update()
{
// You don't need Update here, you can safly remove it unless you need it for anything else
}
void ChangeColor()
{
color = Random.Range(1, 5);
if (color == 2)
{
_mySpriteRenderer.color = Color.blue;
}
if (color == 3)
{
_mySpriteRenderer.color = Color.red;
}
if (color == 4)
{
_mySpriteRenderer.color = Color.yellow;
}
}
}
随机
你可以用一个RNG来决定你是否要改变这一帧的颜色。这意味着您无法定义每次 sec / min / in general
更改颜色的次数。
public class colorchange : MonoBehaviour
{
public int color;
public bool stop = true;
private SpriteRenderer _mySpriteRenderer;
private float _randomChance = 0.2F;
void Start()
{
_mySpriteRenderer = GetComponent<SpriteRenderer>();
}
void Update()
{
if (Random.Range(0F, 1F) < _randomChance)
{
color = Random.Range(1, 5);
if (color == 2)
{
_mySpriteRenderer.color = Color.blue;
}
if (color == 3)
{
_mySpriteRenderer.color = Color.red;
}
if (color == 4)
{
_mySpriteRenderer.color = Color.yellow;
}
}
}
}
这里有 20 个
InvokeRepeating
and stop it using CancelInvoke
的另一种选择。在像您这样的用例中,我发现这种方式更易于实施和控制:
public float Interval = 1;
[SerializeField] private SpriteRenderer spriteRenderer;
private void Awake()
{
// DO THIS ONLY ONCE
if(!spriteRenderer) spriteRenderer = GetComponent<SpriteRenderer>();
}
// Automatically called when this component or GameObject gets enabled
private void OnEnable ()
{
// Start invoking every Interval seconds
InvokeRepeating(nameof(ChangeColor), Interval, Interval);
}
// Automatically called when this component or GameObject gets disabled
private void OnDisable()
{
// Stop the repeated invoking
CancelInvoke();
}
private void ChangeColor()
{
Debug.Log("Hello");
color = Random.Range(1, 5);
// You should also use a switch case here
switch(color)
{
case 2:
spriteRenderer.color = Color.blue;
break;
case 3:
spriteRenderer.color = Color.red;
break;
case 4:
spriteRenderer.color = Color.yellow;
break;
}
}
然后您可以通过启用和禁用此组件来简单地启用和禁用颜色更改。
或者简单地将相应的代码行从 OnEnable
和 OnDisable
移动到相应的 public 方法,如 public void StartColorChange
和 public void StopColorChange
.
这可以通过多种方式完成。在向您展示具体内容之前,这是我将使用的所有示例代码通用的基本结构:
public class ColorChanger : MonoBehaviour {
//Avoid Find and GetComponent methods in performance-critical contexts like Update and FixedUpdate
//Store the value once in the beginning. This is called 'caching'
public SpriteRenderer _renderer;
//Don't hard-code stuff like this
public Color[] _colors;
public float _colorChangeInterval = 0.5f;
//Convenience property to access _renderer.color
public Color Color {
get => _renderer.color;
set => _renderer.color = value;
}
private void Start() {
//Attempts to find the SpriteRenderer in the object if it wasn't set in the inspector
if (!_renderer)
_renderer = GetComponent<SpriteRenderer>();
}
//This piece of code does a specific thing, so it's best to put it in a method
public void ChangeColor() {
if (_colors.Length < 1)
Debug.LogError($"You forgot to set {nameof(_colors)} in the Inspector. Shame! Shame!");
Color = _colors[Random.Range(0, _colors.Length - 1)];
}
}
在我看来,以下是一些主要内容,按照直观程度排序:
计时器模式:
有两种口味。
1) 可以是经过时间的累加器(如下面的代码),或者相反,从间隔递减到零:
private float _elapsed;
private void Update() {
_elapsed += Time.deltaTime;
if (_elapsed < _colorChangeInterval)
return;
ChangeColor();
_elapsed %= _colorChangeInterval;
}
或 2) 可以是从上次开始或直到下一次(如下所示)的时间戳检查触发器,时间戳:
//Replaces _elapsed
private float _timestamp;
private void Start() {
//...
_timestamp = Time.time; //Initial timestamp
}
private void Update() {
if (Time.time < _timestamp + _colorChangeInterval)
return;
ChangeColor();
_timestamp = Time.time;
}
协程和 WaitForSeconds:
当您需要延迟或统一序列代码时,这是推荐的程序。
注意unity还提供了其他类型的等待方法,如WaitWhile
、WaitUntil
等...
//Since unlike code in Update, coroutines need to be started and stopped, we start it when the script is enabled
private void OnEnable() {
StartCoroutine(ChangeColorContinuously());
}
//This is automatically stopped by unity when the script is disabled
private IEnumerator ChangeColorContinuously() {
while (true) {
yield return new WaitForSeconds(_colorChangeInterval);
ChangeColor();
}
}
不要执行异步等待!
好吧,它可以完成,但是它有很多陷阱并且非常不 推荐给初学者。
而且无论如何它都无意取代协程。
不要执行 InvokeRepeating!
这是一种依赖魔法字符串和反射的方法。对于示例代码的快速简单设置很有用,但如果可能的话(由于上述方法,这是可能的)应该像生产代码中的瘟疫一样避免。