Unity C#

WaitForEndOfFrame в Unity C# — практическое руководство

WaitForEndOfFrame в Unity приостанавливает корутину до самого конца кадра — после того, как отрисованы все камеры и интерфейс, но ещё до вывода кадра на экран. Именно в этой точке содержимое экрана полностью готово, и с ним можно работать: читать пиксели, кодировать в изображение, отправлять на сервер.

Сигнатура

  • Класс: UnityEngine.WaitForEndOfFrame, наследуется от YieldInstruction
  • Параметры: нет — объект создаётся пустым: new WaitForEndOfFrame()
  • Использование: только через yield return внутри корутины

Почему нельзя прочитать экран раньше

Порядок кадра в Unity: обновление логики → физика → отрисовка камер → интерфейс → вывод на экран. Если вызвать ReadPixels в Update или в начале корутины, буфер содержал бы кадр прошлого раза или пустоту — рисование ещё не произошло. WaitForEndOfFrame как раз и закрывает эту разницу.

Пример: скриншот и его загрузка на сервер

Классический сценарий из документации Unity:

using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

public class ScreenshotUploader : MonoBehaviour
{
    IEnumerator Start()
    {
        UploadPNG();
        yield return null;
    }

    IEnumerator UploadPNG()
    {
        // Ждём, пока все камеры и GUI отрисованы
        yield return new WaitForEndOfFrame();

        // Текстура размером с экран, формат RGB24
        int width = Screen.width;
        int height = Screen.height;
        Texture2D tex = new Texture2D(width, height, TextureFormat.RGB24, false);

        // Чтение содержимого экрана в текстуру
        tex.ReadPixels(new Rect(0, 0, width, height), 0, 0);
        tex.Apply();

        // Кодирование в PNG
        byte[] bytes = tex.EncodeToPNG();
        Destroy(tex);

        // Отправка на сервер
        WWWForm form = new WWWForm();
        form.AddField("frameCount", Time.frameCount.ToString());
        form.AddBinaryData("fileUpload", bytes);

        var request = UnityWebRequest.Post("http://localhost/upload", form);
        yield return request.SendWebRequest();

        if (request.result != UnityWebRequest.Result.Success)
            Debug.LogError(request.error);
        else
            Debug.Log("Скриншот загружен");
    }
}

Обратите внимание на порядок: сначала WaitForEndOfFrame, затем ReadPixels. Условие в if на request.result — актуальный синтаксис UnityWebRequest.

Пример: отрисовка поверх сцены через GL

Вторая область применения — код, который должен выполниться после всей отрисовки: например, показ альфа-канала экрана в режиме игры. Unity держит встроенный шейдер Hidden/Internal-Colored, который для этого подходит:

using System.Collections;
using UnityEngine;

public class AlphaViewer : MonoBehaviour
{
    Material mat;

    void CreateMaterial()
    {
        var shader = Shader.Find("Hidden/Internal-Colored");
        mat = new Material(shader);
        mat.hideFlags = HideFlags.HideAndDontSave;

        // Режим смешивания: показываем альфа-канал назначения
        mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.DstAlpha);
        mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);

        // Отключаем отсечение задних граней, запись и тест глубины
        mat.SetInt("_Cull", (int)UnityEngine.Rendering.CullMode.Off);
        mat.SetInt("_ZWrite", 0);
        mat.SetInt("_ZTest", (int)UnityEngine.Rendering.CompareFunction.Always);
    }

    IEnumerator Start()
    {
        CreateMaterial();

        while (true)
        {
            // Ждём полной отрисовки кадра вместе с интерфейсом
            yield return new WaitForEndOfFrame();

            GL.PushMatrix();
            GL.LoadOrtho();
            mat.SetPass(0);
            GL.Begin(GL.QUADS);
            GL.Vertex3(0, 0, 0);
            GL.Vertex3(1, 0, 0);
            GL.Vertex3(1, 1, 0);
            GL.Vertex3(0, 1, 0);
            GL.End();
            GL.PopMatrix();
        }
    }
}

Ограничения, о которых знает не каждый

Замерзание при переключении на Scene view

В редакторе Unity, если переключиться из Game view в Scene view, WaitForEndOfFrame «замерзает» — корутина возобновится только при возврате в Game view. На собранной игре этого нет, это чисто редакторское поведение.

Батч-режим редактора

В batch mode WaitForEndOfFrame не выполняется — даже если на скрипте стоят ExecuteInEditMode или ExecuteAlways. Это важно для автоматизации сборки скриншотов и рендеринга.

WaitForEndOfFrame или WaitForFixedUpdate

Частая путаница из-за похожих имён. Решающий вопрос — что нужно дождаться:

Инструкция Ждёт Типичная задача
WaitForEndOfFrame Конца кадра, после отрисовки камер и GUI Скриншот, ReadPixels, GL-оверлей
WaitForFixedUpdate Следующего фиксированного обновления физики Работа с Rigidbody после расчёта физики

Подробно про второй вариант — WaitForFixedUpdate в Unity C#.

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

1. ReadPixels без ожидания конца кадра

Самая частая. Симптом — чёрная или прозрачная текстура вместо скриншота. Лечится добавлением yield return new WaitForEndOfFrame() перед чтением.

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

Update — обычный метод, там yield невозможен. WaitForEndOfFrame работает только в корутине (IEnumerator).

3. Утечка текстуры

Texture2D, созданная под скриншот, не освобождается сама. После EncodeToPNG вызывайте Destroy(tex) — иначе каждая попытка добавит мегабайты в память.

4. Ожидание в Editor-корутине без Play mode

Вне режима Play WaitForEndOfFrame не сработает (за исключением Editor coroutines пакета). Для редакторских задач это ограничение нужно учитывать заранее.

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