Skip to content

Поверхности

Rushan Mukhutdinov edited this page Mar 3, 2025 · 2 revisions

Оригинальная статья.

Поверхность приложения

В обычных событиях отрисовки GameMaker не отрисовывает напрямую на экран, а вместо этого рисует на поверхности, называемой поверхностью приложения.

Эта поверхность — по сути чистый «холст», который можно изменять перед выводом на экран по мере необходимости, и в большинстве случаев GameMaker делает это автоматически (хотя вы можете управлять ею самостоятельно в коде для шейдеров, масштабирования и других задач — подробности приведены ниже).

Пользовательские поверхности

Помимо поверхности приложения, вы можете создавать собственные поверхности и использовать их для реализации ярких или тонких специальных эффектов в игре.

Например, с помощью поверхностей можно «захватывать» экземпляры объектов, которые затем уничтожаются, создавая эффект декали: спрайт объекта остаётся на поверхности, как будто он всё ещё существует. Это позволяет добиться графических эффектов (обломки, кровь и т.д.) без значительной нагрузки на обработку.

Также поверхности можно применять в качестве текстур для дальнейшей обработки, создавать спрайты «на лету» или сложные наложения. По сути, возможности их использования безграничны!

Использование поверхностей

Основное применение поверхности выглядит следующим образом:

  • сначала создаём переменную для хранения поверхности, например, в событии создания:

    surf = -1;
  • в событии отрисовки (или любом другом событии) до того, как захотите нарисовать что-либо на поверхности, проверьте, существует ли она, и если нет — создайте её:

    if (!surface_exists(surf))
    {
        surf = surface_create(960, 540);
    }
  • если поверхность автоматически удаляется из памяти в нужный момент, этот код возмёт ситуацию в свои руки и воссоздаст её;

  • затем, когда будет необходимо отрисовать что-либо, установите поверхность как цель отрисовки (вместо дисплея), например, в событии отрисовки:

    surface_set_target(surf);
  • далее выполните отрисовку нужных объектов и произведите необходимые манипуляции. Рекомендуется очистить поверхность перед началом отрисовки:

    draw_clear_alpha(c_black, 0);
    draw_sprite(spr_icon, 0, 48, 48);
  • после завершения отрисовки на поверхности сбросьте цель отрисовки, чтобы последующая отрисовка происходила на экране:

    surface_reset_target();
  • в конце отобразите поверхность (или используйте её в шейдере, или любым иным требуемым способом):

    draw_surface(surf, 0, 0);
  • когда поверхность больше не нужна, освободите её из памяти:

    surface_free(surf);

Правила работы с поверхностями

Обычные поверхности достаточно просты в использовании, однако есть несколько основных правил, которые необходимо соблюдать:

  • во-первых, следует понимать, что поверхности (за исключением поверхности приложения) являются летучими. Это означает, что если устройство или окно теряют фокус или сворачиваются (например, при переключении окон с помощью Alt + Tab в Windows или при потере фокуса приложения на Android из-за входящего звонка), поверхность может быть уничтожена. Это происходит, поскольку она хранится только в памяти текстур (VRAM) и может быть перезаписана, когда системе потребуется эта память для других задач. Поэтому обязательно реализуйте резервный код, обычно с использованием функции surface_exists();

Примечание: это, похоже, не происходит со спрайтами или другими визуальными элементами (хотя, на самом деле, происходит!), так как они тоже хранятся в обычной памяти (ОЗУ), и когда их удаляют из памяти текстур (VRAM), они мгновенно восстанавливаются из обычной памяти, когда игра снова получает фокус.

  • во-вторых, имейте в виду, что поверхности могут занимать значительный объём VRAM, поэтому старайтесь делать их как можно меньше. Старайтесь не превышать размер вида или окна отображения;

  • в-третьих, создавайте поверхности только в событии отрисовки. Если создать поверхность в событии создания экземпляра, она может получить тот же индекс, что и application_surface, что приведёт к множеству проблем и путанице: вы можете думать, что используете свою поверхность, а на самом деле используете текущую цель рендеринга. Также старайтесь рисовать на поверхности исключительно в событии отрисовки, поскольку GameMaker оптимизирует этот процесс, и все функции отрисовки (включая очистку поверхности при её создании) должны вызываться именно в этом событии. Рисование на поверхности вне события отрисовки возможно и иногда необходимо для определённых эффектов, но этого стоит избегать;

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

    if (view_current == 0)
    {
        surface_set_target(surf);
        with (obj_Effect)
        {
            var _vx = camera_get_view_x(view_camera[1]);
            var _vy = camera_get_view_y(view_camera[1]);
            draw_sprite(sprite_index, image_index, x - _vx, y - _vy);
        }
        surface_reset_target();
    }
    else
    {
        draw_surface(surf, 0, 0);
    }
  • наконец, обратите внимание, что при рисовании на поверхность учитываются как цвет, так и альфа-компонента (прозрачность) каждого пикселя — как самой поверхности, так и наносимых объектов. Это может приводить к неожиданным результатам (например, рисование спрайта с прозрачностью 0,5 на поверхности с прозрачностью 0 даст итоговую прозрачность 0,25). Причины этого описаны в разделе «Руководство по использованию режимов смешивания».

Примечание: это правило не относится к поверхности приложения, только к пользовательским поверхностям.

Ещё один момент: если вам необходимо отрисовать всё содержимое экрана (включая тайлы, фоны и т. д.) на поверхность, можно использовать саму поверхность приложения (подробнее см. ниже) или назначить поверхность определённому порту просмотра, используя переменную view_surface_id[от 0 до 7] — тогда всё, что отображается в этом порте просмотра, будет нарисовано на соответствующей поверхности.

Буфер глубины и трафаретный буфер

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

Подробную информацию смотрите в разделе «Буфер глубины и трафаретный буфер».

Справочник функций

Ниже приведён список функций для работы с поверхностями (эти функции предназначены для создания и управления поверхностями; для их отрисовки на экране используйте специальные функции отрисовки, перечислённые далее):

Ниже приведены функции для отрисовки поверхностей:

Наконец, имеются две функции для сохранения и извлечения поверхностей в буферах:

Последовательность событий для поверхности приложения

Как уже упоминалось, GameMaker не отрисовывает большинство объектов напрямую на экран, а выводит их на поверхность приложения. Это по сути поверхность, аналогичная тем, которые вы можете создать самостоятельно с помощью функций работы с поверхностями, и соответственно её можно изменять, рисовать на ней, использовать в шейдерах и т. д. Практически всё, что можно сделать с пользовательской поверхностью, применимо и к поверхности приложения.

Примечание: единственное, что нельзя сделать с поверхностью приложения — освободить её из памяти. Она всегда существует, хотя её идентификатор может меняться.

При запуске игры данная поверхность создаётся при первом вызове события отрисовки в каждой новой комнате, то есть до этого момента ничего не отображается. Однако вы можете получить позицию поверхности приложения и изменить её размер в событии создания или любом другом событии, при этом не получая никаких ошибок, и использованные значения будут применяться при создании поверхности. Фактическая последовательность событий для создания и отрисовки поверхности приложения выглядит следующим образом:

  • событие перед отрисовкой;
  • —> создаётся поверхность приложения (если она ещё не существует) и устанавливается цель отрисовки;
  • для каждого видимого порта просмотра (или, если порты просмотра не активны, один раз):
    • событие начала отрисовки;
    • событие отрисовки;
    • событие конца отрисовки;
    • —> здесь сбрасывается цель отрисовки поверхности приложения;
  • событие после отрисовки;
  • —> по умолчанию поверхность приложения отрисовывается в буфер экрана (вы можете отключить это с помощью функции application_surface_draw_enable());
  • событие начала отрисовки слоя интерфейса;
  • событие отрисовки слоя интерфейса;
  • событие конца отрисовки слоя интерфейса.

Использование этой поверхности позволяет создавать потрясающие переходы с использованием шейдеров, оборачивать экран вокруг 3D-форм или просто масштабировать игру с низким разрешением до любого экрана… Возможности безграничны!

Чтобы получить доступ к этой поверхности, используйте встроенную глобальную переменную application_surface, о которой поясняется на следующей странице:

Также имеются функции, предназначенные только для работы с поверхностью приложения: