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.
Связанные статьи: