Unity: вспышка на частоте с переменным отношением включения / выключения - PullRequest
0 голосов
/ 22 октября 2018

Я хочу иметь возможность прошивать вещи с определенной частотой.Для примера, скажем, 2 Гц.Я также хочу иметь возможность указать соотношение, где я могу отобразить объект, скажем, 2/3 цикла, и скрыть его для 1/3, поэтому соотношение будет 2: 1.Это дикая вспышка, поэтому мне нужно оставаться гибким в том, как я это делаю.Могут быть некоторые вспышки с соотношением 3: 5 и частотой 2 Гц, а некоторые другие - с частотой 4 Гц с соотношением 1: 1 и т. Д.

Кроме того, мне нужно иметь возможность мигать синхронно.Таким образом, если один объект уже мигает, и я начинаю мигать другой, они должны быть синхронизированы (или, скорее, их циклы должны быть синхронизированы, мигание может изменяться, поскольку соотношение может отличаться).Но если на одной и той же частоте они должны «включаться» одновременно, даже если их соотношения разные.Кроме того, все они должны включаться одновременно с самыми медленными включениями.

Мой текущий подход: у меня есть GameObject FlashCycle, который по существу в своем методе обновления вычисляет прогресс для 3 частот, которые у меня есть (2 Гц), 4 Гц и 8 Гц).

 float time = Time.time;
 twoHerzProgress = (time % twoHerzInSeconds) / twoHerzInSeconds;
 fourHerzProgress = (time % fourHerzInSeconds) / fourHerzInSeconds;
 eightHerzProgress = (time % eightHerzInSeconds) / eightHerzInSeconds;

Я пробовал разные time с, но это на самом деле не имело значения, поэтому давайте просто придерживаться этого, если вы не думаете, что это плохая идея!

Теперьвсякий раз, когда я хочу прошить объект, в его собственном 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.

К сожалению этоне мигает объекты с хорошим плавным и регулярным интервалом.Я измерил интервал 2 Гц и получил различия в соотношении до 48 мс, и, хотя, кажется, это не так уж и много, на экране это действительно имеет значение.

Итак, вопрос сводится к следующему: как мне быстро, регулярные вспышки, сохраняя при этом гибкость (соотношение и частота) и имеют синхронизированную вспышку?

Спасибо за помощь!

Ответы [ 2 ]

0 голосов
/ 22 октября 2018

Вы можете использовать Сопрограммы и 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 (выкл) с 8 Гц

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

При этой настройке сопрограмма работает «навсегда» в while(true) -цикле.Поэтому не забудьте перед запуском новой 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.Вы также не назвали бы значение размера cube.meters.Я бы порекомендовал переименовать его в flashRate.frequency.


Для того, чтобы обеспечить синхронизацию, вам каким-то образом понадобится доступ ко всем поведениям и сравнить их значения (так что я бы сказал, что некоторыеstatic List<YourBehavior>) и, например, в Coroutine дождитесь, пока все bools, например, не установятся в true, прежде чем продолжить с вашим собственным.Для этого вам понадобится дополнительный bool, поскольку вполне возможно, что 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);
    }
}

, поэтому вы также получаете в настоящее время отключенные компоненты и объекты

0 голосов
/ 22 октября 2018

У вас есть некоторые отличия, потому что вы делаете все в цикле Update () с условием <=.На более медленных / более быстрых машинах у вас будет больше / меньше различий, потому что длительность кадра никогда не будет равна вашей частоте. </p>

Попробуйте сделать все в коротине: единство сопрограммных документов

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