Как выполнить несколько раз программу, не останавливая ее? - PullRequest
0 голосов
/ 06 июня 2019

У меня есть массив игровых объектов, которые являются источниками света, я пытаюсь увеличить и уменьшить размер диапазона точечного источника света с течением времени, проблема в том, что источники света несколько раз не уменьшаются со временем, они просто мгновенно переходят в диапазон 0.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class MainMenu : MonoBehaviour
{
    public GameObject[] stars;

    private void Start()
    {
        StartCoroutine(ChooseStar());
    }

    public void PlayGame()
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
    }

    public void QuitGame()
    {
        Application.Quit();
    }


    IEnumerator IncreaseRadius(GameObject star, float duration)
    {
        Debug.Log("Increasing: "+star.name + " radius: " + star.GetComponent<Light>().range);
        float counter = 0;

        while (counter < duration)
        {
            counter += Time.deltaTime;
            star.GetComponent<Light>().range = counter;
            yield return null;
        }
        StartCoroutine(DecreaseRadius(star));
    }

    IEnumerator DecreaseRadius(GameObject star)
    {
        Debug.Log("Decreasing: "+star.name+" radius: "+ star.GetComponent<Light>().range);
        float counter = star.GetComponent<Light>().range;

        while (star.GetComponent<Light>().range >= 0f)
        {
            counter -= Time.deltaTime;
            star.GetComponent<Light>().range = counter;
            yield return null;
        }
        star.GetComponent<Light>().range = 0f;

    }

    IEnumerator ChooseStar()
    {
        float duration = Random.Range(3, 8);
        float waitTime = 2f;

        GameObject choosenStar = stars[Random.Range(0, stars.Length)];

        if (choosenStar.GetComponent<Light>().range <= 0f)
        {
            StartCoroutine(IncreaseRadius(stars[Random.Range(0, stars.Length)], duration));
        }
        else
        {
            waitTime = 0f;
        }

        yield return new WaitForSeconds(waitTime);
        StartCoroutine(ChooseStar());

    }
}

ожидаемый результат должен быть такой:

1 - выбрать случайную звезду из массива игровых объектов 2 - проверьте, увеличивается ли дальность действия звезды, если да, начните поиск заново, если нет, увеличение не начнется. 3 - свет начинает увеличиваться до продолжительности, затем вызвать функцию уменьшения 4 - звезда начинает уменьшаться, когда функция выходит за пределы диапазона сброса на 0

1 Ответ

2 голосов
/ 07 июня 2019

Чтобы ответить на вопрос в целом: вы можете просто поставить

while (true)
{
   ... 

   yield return ...
}

вокруг вашего кода. Пока вы yield где-то внутри, это совершенно верно для сопрограмм.


Я полагаю, что вы получите одновременные сопрограммы, потому что вы не ждете, пока IncreaseRadius завершит выбор следующей случайной звезды ... которая может быть такой же, как и раньше.

if (chosenStar.range <= 0f)
{
    StartCoroutine(IncreaseRadius(stars[Random.Range(0, stars.Length)], duration));
}
else
{
    waitTime = 0f;
}

также вы снова делаете Random.Range, хотя вы уже выбрали другую звезду раньше, это было задумано?


Сначала вообще вместо использования GetComponent<Light> снова и снова, а довольно просто:

public Light[] stars;

ссылаются на объект точно так же, как и раньше, но теперь вы напрямую имеете дело с Light ссылками вместо GameObject.

Тогда вы знаете, что

float duration = Random.Range(3, 8);

фактически возвращает случайные полные int значения между 3 и 7. Если вы предпочитаете, чтобы значения float были также между 3 и 8, включите, например, например, 3.253453f тогда вам лучше использовать

var duration = Random.Range(3.0f, 8.0f);

Решение 1 - Только одна звезда за раз

В качестве простой альтернативы вы всегда можете анимировать только одну звезду за раз. Вы можете достичь этого путем yield return другого IEnumerator. Это заставляет другого IEnumerator исполняться и в то же время ожидает его завершения. Что-то вроде

public Light[] stars;

private void Start()
{
    StartCoroutine(ChooseStar());
}

private IEnumerator IncreaseRadius(Light star, float duration)
{
    Debug.Log("Increasing: " + star.name + " radius: " + star.range);
    float counter = 0;

    while (counter < duration)
    {
        counter += Time.deltaTime;
        star.range = counter;
        yield return null;
    }

    // again do the decreasing and at the same time wait for it to finish
    yield return DecreaseRadius(star);
}

private static IEnumerator DecreaseRadius(Light star)
{
    Debug.Log("Decreasing: " + star.name + " radius: " + star.range);
    var counter = star.range;

    while (star.range >= 0f)
    {
        counter -= Time.deltaTime;
        star.range = counter;
        yield return null;
    }
    star.range = 0f;
}

IEnumerator ChooseStar()
{
    // Looks scary but is totally fine in Coroutines as long as you yield somewhere
    // instead of starting a new Coroutine simple continue the one you already have
    while (true)
    {
        var duration = Random.Range(3.0f, 8.0f);

        var choosenStar = stars[Random.Range(0, stars.Length)];

        // This starts the Increase routine on that star
        // and at the same time waits for it to finish!
        //
        // since we also wait until DecreaseRadius is done this means 
        // at any time only exactly 1 star is animated at the same time
        yield return IncreaseRadius(choosenStar, duration);
    }
}

Решение 2 - Фильтр случайных чисел

В качестве альтернативы, как вы хотите , чтобы разрешить параллельную анимацию звезд, я бы просто отфильтровал Список доступных звезд (тех, которые в данный момент не анимированы) для получения случайного диапазона. Что-то вроде

public Light[] stars;

// Use a list for dynamically adding and removing items
private List<Light> availableStars = new List<Light>();

private void Start()
{
    // initialize the available list
    // copy the references from stars
    availableStars.AddRange(stars);

    StartCoroutine(ChooseStar());
}

private IEnumerator IncreaseRadius(Light star, float duration)
{
    Debug.Log("Increasing: " + star.name + " radius: " + star.range);

    // As soon as you start animating this star
    // remove it from the list of availables
    availableStars.Remove(star);

    float counter = 0;

    while (counter < duration)
    {
        counter += Time.deltaTime;
        star.range = counter;
        yield return null;
    }

    // Decreasing and at the same time wait for it to finish
    yield return DecreaseRadius(star);

    // when finished add the star again to the availables
    availableStars.Add(star);
}

private static IEnumerator DecreaseRadius(Light star)
{
    Debug.Log("Decreasing: " + star.name + " radius: " + star.range);
    var counter = star.range;

    while (star.range >= 0f)
    {
        counter -= Time.deltaTime;
        star.range = counter;
        yield return null;
    }
    star.range = 0f;
}

IEnumerator ChooseStar()
{
    // Looks scary but is totally fine in Coroutines as long as you yield somewhere
    while (true)
    {
        var duration = Random.Range(3.0f, 8.0f);

        // in case that currently all stars are being animated
        // simply wait until the next one becomes available again
        yield return new WaitUntil(() => availableStars.Count > 0);

        // Pick a random star from the availables instead
        var chosenStar = availableStars[Random.Range(0, availableStars.Count)];

        // this check becomes then actually redundant
        //if (chosenStar.range <= 0f)
        //{
        StartCoroutine(IncreaseRadius(chosenStar, duration));
        yield return new WaitForSeconds(2f);
        //}
    }
}
...