В статье речь идет о Ebiten 1.10.

Ebiten — это хорошо продуманная библиотека для создания 2D-игр, написанная Хадзиме Хошем на языке Go. С ее помощью созданы движки ряда мобильных и десктопных игр, как например зарелиженная в Apple Store Bear's Restaurant, или OpenDiablo2 — реализации Diablo 2 с открытым исходным кодом на Go. В этой статье я предлагаю вам познакомиться с несколькими фундаментальными концепциями видеоигр и их реализацией в Ebiten.

Анимация

Ebiten рендерит анимацию из нескольких отдельных неподвижных изображений (кадров), что, конечно, не является инновационным подходом в мире видеоигр. Набор кадров, в свою очередь, группируется в более крупное изображение — так называемый “текстурный атлас”, или “спрайтшит”. Вот пример спрайтшита, представленный на сайте Ebiten:

Данное изображение загружается в память и с применением достаточно простой математики рендерится кадр за кадром. В предыдущем примере размер каждого кадра равен 32 пикселям. Рендеринг каждого кадра анимации — это по сути просто процесс перемещения координаты X на 32 пикселя. Вот как выглядят его первые шаги:

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

screen.DrawImage(
   // draw a sub image of the sprite from the coordinates
   // x=0 to x=32 and y=32 to y=64
   runnerImg.SubImage(image.Rect(0, 32, 32, 64)).(*ebiten.Image),
   // variable declared somewhere before
   // that defines the position on the screen
   op,
)

Хадзиме Хош, создатель Ebiten, также разработал инструмент “file2byteslice”, который преобразует любое изображение в строку, позволяя встроить любой файл в Go. Он легко интегрируется инструментами Go с помощью аннотации go:generate, автоматически выгружая изображение в файл Go. Вот небольшой пример:

//go:generate file2byteslice 
  -package=img 
  -input=./images/sprite.png 
  -output=./images/sprite.go -var=Runner_png

Перед загрузкой в Ebiten и рендерингом в игре изображение должно быть декодировано. Далее мы рассмотрим способ рендеринга больших фоновых изображений, состоящих из повторяющихся элементов.

Тайловый фон

При рендеринге фонового пейзажа (бэкграунда) используется та же техника - большой атлас делится на множество небольших изображений, называемых “тайлами”. Пример текстурного атласа, представленный на сайте:

Этот атлас можно разделить на тайлы размером по 16 пикселей. Вот набор тайлов, созданный с помощью программы Tiled:

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

Порядковый номер для синих цветов - 303, что означает, что они находятся в 4-м столбце (303 делим по модулю на количество тайлов в строке, т.е. 303%25 = 3) и в 13-й строке (303 делим на количество тайлов в строке и смотрим на целую часть, т.е. 303/25 = 12).

Теперь мы можем построить карту с массивом индексов:

Отрисовка изображений по этой карте даст нам следующее:

Однако на предыдущем изображении отсутствует фон. Теперь нам нужно построить аналогичный массив, отвечающий за фон. Вот результат:

Получив готовые слои нам просто нужно наложить оба слоя, чтобы получить окончательный результат:

Код этого примера доступен на сайте Ebiten.

После формирования изображения, задача Ebiten заключается в обработке обновления экрана и отправке сформированной инструкции в видеокарту.

Обновление экрана (update)

Ebiten представляет собой абстракцию для драйверов, используемых iOS, которая в свою очередь использует Metal, и Android, которая использует OpenGL ES, что несомненно упрощает разработку. Она также позволяет вам определять функцию, которая обновляет экран и отрисовывает все ваши изменения - update. Однако из соображений повышения производительности перед отправкой драйверу библиотека будет упаковывать исходники вместе и аккумулировать изменения в буфере:

Буфер способен объединять инструкции отрисовки, чтобы уменьшить количество обращений к графическому процессору. На предыдущем рисунке три инструкции могут быть объединены в одну:

Это оптимизация очень важна, поскольку может значительно снизить накладные расходы при отправке инструкций. Вот как изменилась производительность после объединения инструкций:

При последовательной отправке инструкций без оптимизации, производительность сильно снижается:

Ebiten также обеспечивает контроль над обновлением на экране, что также помогает повысить производительность.

Управление TPS

По умолчанию Ebiten работает со скоростью 60 тактов в секунду. Однако это можно легко изменить с помощью API, предоставляемого Ebiten, а именно — метода ebiten.SetMaxTPS(). С помощью этого можно снизить нагрузку на железо. Вот та же простая программа только уже с 25 TPS:

TPS (ticks per second) — количество тактов в секунду, не путайте с FPS (frames per second) — количество кадров в секунду. Различия между ними хорошо описаны Хадзиме Хошем:

Кадры (фреймы) это обновление графики, что зависит от частоты обновления дисплея пользователя. Таким образом значение FPS может быть выставлено на 60, 70, 120 и так далее. Это число в принципе неконтролируемо. Что Ebiten может, так это просто включить или выключить vsync. Если vsync выключен, Ebiten пытается обновлять графику как можно чаще тогда значение FPS может достигать даже 1000 (ну или около того).

Такт представляет собой обновление, относящийся больше к логике. TPS говорит нам, сколько раз в секунду вызывается функция обновления (update). По умолчанию установлено значение равное 60. Разработчик игры может установить значение TPS с помощью функции SetMaxTPS. Если установлен UncappedTPS, Ebiten будет пытаться максимально часто вызывать функцию обновления.

Ebiten предлагает еще одну оптимизацию для рендеринга, когда окно работает в фоновом режиме. При потере фокуса игра будет находиться в режиме сна до тех пор, пока фокус не вернется. Если вдаваться в подробности, она засыпает на 1/60 секунды и снова проверяет фокус. Взгляните на пример сэкономленных ресурсов:

Эту оптимизацию при желании можно отключить — игра может быть замедлена по тактам самой системой, на которой она запущена. Например, игра, которая запускается в браузере, имеет ограничения при запуске в фоновых вкладках как в Firefox, так и в Chrome или любых других браузерах.

Ebiten открыт для спонсорства на Github; смело вносите свой вклад, если хотите увидеть больше игр, написанных на Go.


Материал подготовлен в рамках курса «Golang Developer. Professional». Если вам интересно узнать подробнее о формате обучения и программе, познакомиться с преподавателем курса — приглашаем на день открытых дверей онлайн. Регистрация здесь.

Комментарии (3)


  1. sedyh
    09.11.2021 18:51

    Приятно видеть, что библиотека становится популярной. Правда, оригинальная статья уже сильно устарела.


  1. sergeymolchanovsky
    09.11.2021 21:26
    -6

    Вообще так делать игры в наше время - это полное уродство. Есть же GameMaker и Unity.


    1. GospodinKolhoznik
      09.11.2021 22:29
      +6

      Для такой 2д игры, как в статье, написать код, который будет отрисовывать локацию и анимацию персонажей это меньше 1% всех трудозатрат. И что же, из за какого то 1% придется ограничить себя только юнити?