Unity C#

WaitForSeconds в Unity C# — задержка и примеры использования

WaitForSeconds в Unity — это способ приостановить выполнение корутины на заданное количество секунд. Внутри корутины пишется yield return new WaitForSeconds(2f), и выполнение кода после этой строки возобновляется через 2 секунды игрового времени. Простейший таймер без переменных, таймеров в Update и лишних плагинов.

Сигнатура и синтаксис

Класс из пространства имён UnityEngine, наследуется от YieldInstruction:

Параметр Тип Описание
seconds float Время ожидания в секундах (масштабированное игровое время)
  • Возвращает: объект WaitForSeconds — используется только через yield return
  • Работает: исключительно внутри корутин (IEnumerator)
  • Время: масштабируется через Time.timeScale

Базовый пример

Запускаем корутину, ждём 5 секунд, выводим сообщение:

using UnityEngine;
using System.Collections;

public class DelayExample : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(WaitAndLog());
    }

    IEnumerator WaitAndLog()
    {
        Debug.Log("Начало, время: " + Time.time);

        // Приостанавливаем корутину на 5 секунд
        yield return new WaitForSeconds(5f);

        Debug.Log("Прошло 5 секунд, время: " + Time.time);
    }
}

Здесь Start() сам может быть корутиной — тогда корутина запускается автоматически без StartCoroutine:

IEnumerator Start()
{
    Debug.Log("Ждём 3 секунды...");
    yield return new WaitForSeconds(3f);
    Debug.Log("Готово");
}

Пример: пауза между ударами перезарядки

Практическая задача — перезарядка оружия. Игрок стреляет, и следующий выстрел возможен только через 1.5 секунды:

using UnityEngine;
using System.Collections;

public class Weapon : MonoBehaviour
{
    public float fireRate = 1.5f;
    private bool canShoot = true;

    void Update()
    {
        if (Input.GetMouseButtonDown(0) && canShoot)
        {
            StartCoroutine(Shoot());
        }
    }

    IEnumerator Shoot()
    {
        canShoot = false;
        Debug.Log("Выстрел!");

        // Пауза, после которой можно стрелять снова
        yield return new WaitForSeconds(fireRate);

        canShoot = true;
        Debug.Log("Перезарядка завершена");
    }
}

Обратите внимание: сам Update остаётся обычным методом. Корутина запускается из него через StartCoroutine, и пауза действует только на неё — игра продолжает работать.

Пример: цикл с задержкой

Частая задача — повторять действие с интервалом. В цикле for yield работает как обычно:

IEnumerator SpawnEnemies(int count, float interval)
{
    for (int i = 0; i != count; i++)
    {
        Debug.Log("Враг " + (i + 1) + " из " + count);

        // Пауза между итерациями; на последней итерации
        // тоже ждём — если это не нужно, добавьте условие
        if (i != count - 1)
            yield return new WaitForSeconds(interval);
    }

    Debug.Log("Все враги заспавнены");
}

Запуск: StartCoroutine(SpawnEnemies(5, 2f)) — пять сообщений с интервалом 2 секунды.

Пример: остановка задержки через StopCoroutine

Запущенную корутину с WaitForSeconds можно остановить. Есть два способа — по ссылке на корутину и по имени метода:

using UnityEngine;
using System.Collections;

public class StopExample : MonoBehaviour
{
    private Coroutine reloadRoutine;

    void Start()
    {
        // Сохраняем ссылку — так корутину можно остановить
        reloadRoutine = StartCoroutine(Reload());
    }

    void Update()
    {
        // Отмена по ссылке (самый надёжный способ)
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            StopCoroutine(reloadRoutine);
            Debug.Log("Перезарядка отменена");
        }

        // Отмена по имени метода — корутину ищут по строке
        if (Input.GetKeyDown(KeyCode.Space))
        {
            StopCoroutine("Reload");
        }
    }

    IEnumerator Reload()
    {
        Debug.Log("Начали перезарядку...");
        yield return new WaitForSeconds(3f);
        Debug.Log("Перезарядка завершена — эту строку мы не увидим после отмены");
    }
}

Подробнее о работе с корутинами читайте в статье StopCoroutine в Unity C#.

Ловушка: Time.timeScale

WaitForSeconds отсчитывает масштабированное время. Реальная задержка равна указанному значению, делённому на Time.timeScale. Это вызывает две типичные ошибки:

  • Пауза игры. Если в момент паузы вы ставите Time.timeScale = 0, любые WaitForSeconds «зависают» навсегда — игровое время остановилось, и корутина не возобновится.
  • Ускорение. При Time.timeScale = 2f (ускорение вдвое) задержка в 2 секунды сработает через 1 реальную секунду.

Именно поэтому в меню паузы WaitForSeconds не подходит: кнопки должны работать, даже когда время остановлено. Для реального времени есть отдельный класс:

using UnityEngine;
using System.Collections;

public class PauseSafeTimer : MonoBehaviour
{
    IEnumerator ShowMenuDelay()
    {
        // Работает даже при Time.timeScale = 0
        yield return new WaitForSecondsRealtime(1f);
        Debug.Log("Меню можно открывать");
    }
}

Короткое правило: таймеры геймплея (перезарядка, спавн, баффы) — WaitForSeconds; таймеры интерфейса (пауза, загрузка, задержка подсказки) — WaitForSecondsRealtime.

WaitForSeconds и Invoke — что выбрать

В MonoBehaviour есть встроенный Invoke("ИмяМетода", время). Он короче, но жёстче:

Свойство WaitForSeconds + корутина Invoke
Динамическое время Да — значение можно вычислить в runtime Нет — задаётся только при вызове
Отмена одного вызова Через StopCoroutine CancelInvoke("ИмяМетода") — отменяет все вызовы метода
Повтор Ручной цикл с условием InvokeRepeating — готов
Параметры Любые — метод корутины принимает аргументы Только метод без параметров
Читаемость последовательностей Высокая — код читается сверху вниз Низкая — цепочка вызовов теряется

Практический вывод: одна отложенная команда — можно Invoke; всё, что содержит несколько шагов, ожидание в цикле или условную логику — корутина с WaitForSeconds.

Частые ошибки

1. Вызов вне корутины

Код вроде void Start() { yield return new WaitForSeconds(1f); } не скомпилируется: yield допустим только в методе с возвращаемым типом IEnumerator. Ошибка компилятора: «Cannot yield in the body of a method with return type void».

2. Ожидание в Update

Update вызывается каждый кадр — там задержка бессмысленна. WaitForSeconds «подвешивает» только корутину, а не блокирует кадр. Если написать ожидание внутри Update, получится либо ошибка компиляции, либо новая корутина на каждый кадр.

3. Забытый StartCoroutine

Корутина, которую не запустили, не выполнится ни через 2 секунды, ни через 2 часа. Проверяйте, что в коде вызван StartCoroutine(ИмяМетода()).

4. Реальное время не совпадает с указанным

Это не баг. По документации Unity, если кадр долгий (например, синхронная загрузка блокирует главный поток), корутина возобновится не через t секунд после вызова, а через t секунд после конца такого кадра. Также возобновление происходит на первом кадре после истечения времени, а не мгновенно. На коротких задержках разница незаметна, на длинных — может добавить один лишний кадр.

Когда использовать WaitForSeconds, а когда нет

  • Подходит: перезарядка, задержки между ударами, спавн объектов, таймеры геймплея, последовательная анимация через код, паузы между строками диалога.
  • Не подходит: меню и пауза (используйте WaitForSecondsRealtime), точный счёт кадров (используйте WaitForEndOfFrame или WaitForFixedUpdate), блокировка выполнения в Update.

Связанные статьи: