1

我希望能够以一定的频率闪烁东西。例如,假设为 2Hz。我还希望能够指定一个比率,我可以在其中显示事物,比如说循环的 2/3,并将其隐藏 1/3,因此比例为 2:1。这是一堆疯狂的闪光,所以我需要保持灵活的方式。可能有一些以 3:5 的比率和 2Hz 的频率闪烁,而另一些以 4Hz 的比率以 1:1 的频率闪烁,等等。

另外,我需要能够同步闪烁。因此,如果一个对象已经在闪烁,而我开始闪烁另一个对象,则它们需要同步(或者更确切地说,它们的周期需要同步,闪烁可能会因比率不同而有所不同)。但如果在同一个频率下,它们需要同时“开启”,即使它们的比率不同。此外,它们都需要在最慢打开的同时打开。

我目前的方法:我有一个 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 的比率差异,虽然看起来并不多,但它确实在屏幕上产生了差异。

所以问题归结为:如何在保持灵活性(比率和频率方面)的同时获得快速、定期的闪光并拥有同步闪光?

谢谢你的帮助!

4

2 回答 2

3

你可以使用协程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);
    }
}

所以你可以用例如 8Hz 的频率来调用它

StartCoroutine(Flash(8.0f));

这实际上等于您设置的任何调用,onRatio = offRatio例如

StartCoroutine(Flash(8.0f, onRatio = 1, offRatio = 1));

StartCoroutine(Flash(8.0f, onRatio = 2, offRatio = 2));

....

或使用频率和比率,例如 1(on):2(off) with 8Hz

StartCoroutine(Flash(8.0f, onRatio = 1, offRatio = 2));

通过这种设置,协程在while(true)-loop 中“永远”运行。所以,不要忘记在你开始一个新的 Coroutine 之前,先用不同的参数停止所有的例程

 StopAllCoroutines();

现在,如果您想在 Update 方法中动态更改它,则必须在 roder 中添加一些控制标志和其他变量,以确保仅在发生更改时才调用新的 Coroutine:

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

笔记:

  1. 我不知道你的FlashRateInterval,但如果你出于某种原因需要使用它,你可以让它像

    public enum FlashRateInterval
    {
        AllwaysOn,
    
        twoHerz = 2,
        fourHerz = 4,
        eightHerz = 8
    }
    

    为了直接使用正确的值。

  2. 我称之为频率变量flashRate.herz。您也不会调用 size value cube.meters。我建议将其重命名为flashRate.frequency.


为了实现同步,您需要以某种方式访问​​所有行为并比较它们的值(所以我会说一些static List<YourBehavior>),然后在协程中等到所有布尔值都设置为真,然后再继续使用自己的值。为此,您将需要一个额外的布尔值,因为它可能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>()which 有点慢,您还可以执行类似的操作

public static List<YOUR_COMPONENT> Components = new List<YOUR_COMPONENT>();

private void Awake()
{
    if(!Components.Contains(this)){
        Components.Add(this);
    }
}

因此您还可以获得当前禁用的组件和对象

于 2018-10-22T14:30:42.430 回答
0

您有一些差异,因为您在 <= 条件的 Update() 循环中执行所有操作。在较慢/较快的机器上,您将有更多/更少的差异,因为帧的持续时间永远不会等于您的频率。

尝试在 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);
   }
}
于 2018-10-22T14:19:18.457 回答