统一:以可变 on/off 比率的频率闪烁
Unity: flash at frequency with variable on/off ratio
我希望能够以特定频率闪烁东西。例如,假设 2Hz。我还希望能够指定一个比率,我可以在其中显示 2/3 的周期,并隐藏 1/3,因此比率将为 2:1。这是一堆疯狂的闪光,所以我需要在我做的方式上保持灵活性。可能有一些频率为 3:5 且频率为 2Hz 的闪烁,还有一些频率为 4Hz 且频率为 1:1 的闪烁,依此类推。
此外,我需要能够同步闪光。因此,如果一个对象已经在闪烁,而我开始闪烁另一个对象,则它们需要同步(或者更确切地说,它们的周期需要同步,闪烁可能会因比率不同而有所不同)。但是如果是同一个频率,他们需要同时"turn on",即使他们的比例不同。此外,它们都需要同时打开,最慢的打开。
我目前的方法:我有一个 GameObject FlashCycle,本质上是在它的更新方法中计算我拥有的 3 个频率(2Hz、4Hz 和 8Hz)的进度。
float time = Time.time;
twoHerzProgress = (time % twoHerzInSeconds) / twoHerzInSeconds;
fourHerzProgress = (time % fourHerzInSeconds) / fourHerzInSeconds;
eightHerzProgress = (time % eightHerzInSeconds) / eightHerzInSeconds;
我尝试了不同的 time
s,但这并不重要,所以如果您认为它不是一个坏主意,我们就坚持使用那个吧!
现在,每当我想闪烁一个对象时,在它自己的 Update()
我这样做:
switch (flashRate.herz)
{
case FlashRateInterval.twoHerz:
show = flashCycle.oneHerzProgress <= onTimePercentage;
case FlashRateInterval.fourHerz:
show =flashCycle.twoHerzProgress <= onTimePercentage;
case FlashRateInterval.eightHerz:
show =flashCycle.fourHerzProgress <= onTimePercentage;
default:
show =true;
}
然后继续并在 show == true
.
时显示对象
不幸的是,这不会以非常平滑和规则的间隔闪烁对象。我测量了2Hz的间隔,得到了高达48ms的比率差异,虽然看起来不大,但在屏幕上确实有所不同。
所以问题归结为:如何在保持灵活性(比率和频率方面)的同时获得快速、规则的闪光并获得同步闪光?
感谢您的帮助!
您有一些不同之处,因为您在 Update() 循环中使用 <= 条件执行所有操作。在 slower/faster 机器上你会有 more/less 差异,因为帧的持续时间永远不会等于你的频率。
尝试在 Corotine 中完成所有操作:unity coroutine docs
//bad code below but i think its more understandable like this
IEnumerator Flash()
{
while(true)
{
BlinkOn();
Sync();//sync here another cicle if you want to sync when on starts
yield return new WaitForSeconds(yourDuration);// yourDuration*multiplier/something+0.5f....ecc
BlinkOff()
Sync();//sync here another cicle if you want to sync when of starts
yield return new WaitForSeconds(yourDuration);
}
}
您可以使用 Coroutines and WaitForSeconds
来实现
// onRatio and offRatio are "optional" parameters
// If not provided, they will simply have their default value 1
IEnumerator Flash(float frequency ,float onRatio = 1, float offRatio = 1)
{
float cycleDuration = 1.0f / frequency;
float onDuration = (onRatio/ (onRatio + offRatio)) * cycleDuration;
float offDuration = (offRatio/ (onRatio + offRatio)) * cycleDuration;
while(true)
{
show = true;
yield return new WatForSeconds(onDuration);
show = false;
yield return new WatForSeconds(offDuration);
}
}
所以你可以用一个频率来调用它,例如8赫兹
StartCoroutine(Flash(8.0f));
这实际上等于您设置 onRatio = offRatio
的任何调用,例如
StartCoroutine(Flash(8.0f, onRatio = 1, offRatio = 1));
StartCoroutine(Flash(8.0f, onRatio = 2, offRatio = 2));
....
或频率和比率,例如1(开):2(关)8Hz
StartCoroutine(Flash(8.0f, onRatio = 1, offRatio = 2));
使用此设置,协程在 while(true)
循环中运行 "forever"。因此,不要忘记在启动具有不同参数的新协程之前首先使用
停止所有例程
StopAllCoroutines();
现在,如果您想在 Update 方法中动态更改它,则必须在 roder 中添加一些控制标志和其他变量,以确保仅在发生更改时才调用新的协程:
FlashRateInterval currentInterval;
float currentOnRatio = -1;
float currentOffRatio = -1;
void Update()
{
// if nothing changed do nothing
if(flashRate.herz == currentInterval
//todo && Mathf.Approximately(<yourOnRatio>, currentOnRatio)
//todo && Mathf.Approximately(<yourOffRatio>, currentOffRatio)
) return;
StopAllCoroutines();
currentInterval = flashRate.herz;
//todo currentOnRatio = <yourOnRatio>;
//todo currentOffRatio = <yourOffRatio>;
switch (flashRate.herz)
{
case FlashRateInterval.twoHerz:
StartCoroutine(2.0f);
//todo StartCoroutine(2.0f, onRatio = <yournRatio>, offRatio = <yourOffRatio>);
case FlashRateInterval.fourHerz:
StartCoroutine(4.0f);
//todo StartCoroutine(4.0f, onRatio = <yournRatio>, offRatio = <yourOffRatio>);
case FlashRateInterval.eightHerz:
StartCoroutine(8.0f);
//todo StartCoroutine(8.0f, onRatio = <yournRatio>, offRatio = <yourOffRatio>);
default:
show =true;
}
}
备注:
我不知道你的 FlashRateInterval
但如果你出于某种原因需要使用它,你可以像
public enum FlashRateInterval
{
AllwaysOn,
twoHerz = 2,
fourHerz = 4,
eightHerz = 8
}
为了直接使用正确的值。
我会调用一个频率变量flashRate.herz
。您也不会调用大小值 cube.meters
。我建议将其重命名为 flashRate.frequency
.
为了实现同步,您需要以某种方式访问所有行为并比较它们的值(所以我会说一些 static List<YourBehavior>
),而不是例如在 Coroutine 中等到所有 bools 都是例如在继续你自己的之前设置为真。为此,您需要一个额外的布尔值,因为 show
可能在一个组件上永久为真。
public bool isBlinking;
IEnumerator Flash(float frequency ,float onRatio = 1, float offRatio = 1)
{
//todo: You'll have to set this false when not blinking -> in Update
isBlinking = true;
float cycleDuration = 1.0f / frequency;
float onDuration = (onRatio/ (onRatio + offRatio)) * cycleDuration;
float offDuration = (offRatio/ (onRatio + offRatio)) * cycleDuration;
// SYNC AT START
show = false;
// wait until all show get false
foreach(var component in FindObjectsOfType<YOUR_COMPONENT>())
{
// skip checking this component
if(component == this) continue;
// if the component is not running a coroutine skip
if(!component.isBlinking) continue;
// Now wait until show gets false
while(component.show)
{
// WaitUntilEndOfFrame makes it possible
// for us to check the value again already before
// the next frame
yield return new WaitForEndOfFrame;
}
}
// => this line is reached when all show are false
// Now lets just do the same but this time wating for true
// wait until all show get false
foreach(var component in FindObjectsOfType<YOUR_COMPONENT>())
{
// skip checking this component
if(component == this) continue;
// if the component is not running a coroutine skip
if(!component.isBlinking) continue;
// Now wait until show gets false
while(!component.show)
{
// WaitUntilEndOfFrame makes it possible
// for us to check the value again already before
// the next frame
yield return new WaitForEndOfFrame;
}
}
// this line is reached when all show are getting true again => begin of loop
while(true)
{
.........
除了使用有点慢的 FindObjectsOfType<YOUR_COMPONENT>()
,您还可以使用
public static List<YOUR_COMPONENT> Components = new List<YOUR_COMPONENT>();
private void Awake()
{
if(!Components.Contains(this)){
Components.Add(this);
}
}
因此您还可以获得当前禁用的组件和对象
我希望能够以特定频率闪烁东西。例如,假设 2Hz。我还希望能够指定一个比率,我可以在其中显示 2/3 的周期,并隐藏 1/3,因此比率将为 2:1。这是一堆疯狂的闪光,所以我需要在我做的方式上保持灵活性。可能有一些频率为 3:5 且频率为 2Hz 的闪烁,还有一些频率为 4Hz 且频率为 1:1 的闪烁,依此类推。
此外,我需要能够同步闪光。因此,如果一个对象已经在闪烁,而我开始闪烁另一个对象,则它们需要同步(或者更确切地说,它们的周期需要同步,闪烁可能会因比率不同而有所不同)。但是如果是同一个频率,他们需要同时"turn on",即使他们的比例不同。此外,它们都需要同时打开,最慢的打开。
我目前的方法:我有一个 GameObject FlashCycle,本质上是在它的更新方法中计算我拥有的 3 个频率(2Hz、4Hz 和 8Hz)的进度。
float time = Time.time;
twoHerzProgress = (time % twoHerzInSeconds) / twoHerzInSeconds;
fourHerzProgress = (time % fourHerzInSeconds) / fourHerzInSeconds;
eightHerzProgress = (time % eightHerzInSeconds) / eightHerzInSeconds;
我尝试了不同的 time
s,但这并不重要,所以如果您认为它不是一个坏主意,我们就坚持使用那个吧!
现在,每当我想闪烁一个对象时,在它自己的 Update()
我这样做:
switch (flashRate.herz)
{
case FlashRateInterval.twoHerz:
show = flashCycle.oneHerzProgress <= onTimePercentage;
case FlashRateInterval.fourHerz:
show =flashCycle.twoHerzProgress <= onTimePercentage;
case FlashRateInterval.eightHerz:
show =flashCycle.fourHerzProgress <= onTimePercentage;
default:
show =true;
}
然后继续并在 show == true
.
不幸的是,这不会以非常平滑和规则的间隔闪烁对象。我测量了2Hz的间隔,得到了高达48ms的比率差异,虽然看起来不大,但在屏幕上确实有所不同。
所以问题归结为:如何在保持灵活性(比率和频率方面)的同时获得快速、规则的闪光并获得同步闪光?
感谢您的帮助!
您有一些不同之处,因为您在 Update() 循环中使用 <= 条件执行所有操作。在 slower/faster 机器上你会有 more/less 差异,因为帧的持续时间永远不会等于你的频率。
尝试在 Corotine 中完成所有操作:unity coroutine docs
//bad code below but i think its more understandable like this
IEnumerator Flash()
{
while(true)
{
BlinkOn();
Sync();//sync here another cicle if you want to sync when on starts
yield return new WaitForSeconds(yourDuration);// yourDuration*multiplier/something+0.5f....ecc
BlinkOff()
Sync();//sync here another cicle if you want to sync when of starts
yield return new WaitForSeconds(yourDuration);
}
}
您可以使用 Coroutines and WaitForSeconds
来实现
// onRatio and offRatio are "optional" parameters
// If not provided, they will simply have their default value 1
IEnumerator Flash(float frequency ,float onRatio = 1, float offRatio = 1)
{
float cycleDuration = 1.0f / frequency;
float onDuration = (onRatio/ (onRatio + offRatio)) * cycleDuration;
float offDuration = (offRatio/ (onRatio + offRatio)) * cycleDuration;
while(true)
{
show = true;
yield return new WatForSeconds(onDuration);
show = false;
yield return new WatForSeconds(offDuration);
}
}
所以你可以用一个频率来调用它,例如8赫兹
StartCoroutine(Flash(8.0f));
这实际上等于您设置 onRatio = offRatio
的任何调用,例如
StartCoroutine(Flash(8.0f, onRatio = 1, offRatio = 1));
StartCoroutine(Flash(8.0f, onRatio = 2, offRatio = 2));
....
或频率和比率,例如1(开):2(关)8Hz
StartCoroutine(Flash(8.0f, onRatio = 1, offRatio = 2));
使用此设置,协程在 while(true)
循环中运行 "forever"。因此,不要忘记在启动具有不同参数的新协程之前首先使用
StopAllCoroutines();
现在,如果您想在 Update 方法中动态更改它,则必须在 roder 中添加一些控制标志和其他变量,以确保仅在发生更改时才调用新的协程:
FlashRateInterval currentInterval;
float currentOnRatio = -1;
float currentOffRatio = -1;
void Update()
{
// if nothing changed do nothing
if(flashRate.herz == currentInterval
//todo && Mathf.Approximately(<yourOnRatio>, currentOnRatio)
//todo && Mathf.Approximately(<yourOffRatio>, currentOffRatio)
) return;
StopAllCoroutines();
currentInterval = flashRate.herz;
//todo currentOnRatio = <yourOnRatio>;
//todo currentOffRatio = <yourOffRatio>;
switch (flashRate.herz)
{
case FlashRateInterval.twoHerz:
StartCoroutine(2.0f);
//todo StartCoroutine(2.0f, onRatio = <yournRatio>, offRatio = <yourOffRatio>);
case FlashRateInterval.fourHerz:
StartCoroutine(4.0f);
//todo StartCoroutine(4.0f, onRatio = <yournRatio>, offRatio = <yourOffRatio>);
case FlashRateInterval.eightHerz:
StartCoroutine(8.0f);
//todo StartCoroutine(8.0f, onRatio = <yournRatio>, offRatio = <yourOffRatio>);
default:
show =true;
}
}
备注:
我不知道你的
FlashRateInterval
但如果你出于某种原因需要使用它,你可以像public enum FlashRateInterval { AllwaysOn, twoHerz = 2, fourHerz = 4, eightHerz = 8 }
为了直接使用正确的值。
我会调用一个频率变量
flashRate.herz
。您也不会调用大小值cube.meters
。我建议将其重命名为flashRate.frequency
.
为了实现同步,您需要以某种方式访问所有行为并比较它们的值(所以我会说一些 static List<YourBehavior>
),而不是例如在 Coroutine 中等到所有 bools 都是例如在继续你自己的之前设置为真。为此,您需要一个额外的布尔值,因为 show
可能在一个组件上永久为真。
public bool isBlinking;
IEnumerator Flash(float frequency ,float onRatio = 1, float offRatio = 1)
{
//todo: You'll have to set this false when not blinking -> in Update
isBlinking = true;
float cycleDuration = 1.0f / frequency;
float onDuration = (onRatio/ (onRatio + offRatio)) * cycleDuration;
float offDuration = (offRatio/ (onRatio + offRatio)) * cycleDuration;
// SYNC AT START
show = false;
// wait until all show get false
foreach(var component in FindObjectsOfType<YOUR_COMPONENT>())
{
// skip checking this component
if(component == this) continue;
// if the component is not running a coroutine skip
if(!component.isBlinking) continue;
// Now wait until show gets false
while(component.show)
{
// WaitUntilEndOfFrame makes it possible
// for us to check the value again already before
// the next frame
yield return new WaitForEndOfFrame;
}
}
// => this line is reached when all show are false
// Now lets just do the same but this time wating for true
// wait until all show get false
foreach(var component in FindObjectsOfType<YOUR_COMPONENT>())
{
// skip checking this component
if(component == this) continue;
// if the component is not running a coroutine skip
if(!component.isBlinking) continue;
// Now wait until show gets false
while(!component.show)
{
// WaitUntilEndOfFrame makes it possible
// for us to check the value again already before
// the next frame
yield return new WaitForEndOfFrame;
}
}
// this line is reached when all show are getting true again => begin of loop
while(true)
{
.........
除了使用有点慢的 FindObjectsOfType<YOUR_COMPONENT>()
,您还可以使用
public static List<YOUR_COMPONENT> Components = new List<YOUR_COMPONENT>();
private void Awake()
{
if(!Components.Contains(this)){
Components.Add(this);
}
}
因此您还可以获得当前禁用的组件和对象