28.11.2013

Движок игры Survive Alone завершен



За последний месяц движок разрабатываемой игры Survive Alone претерпел значительные оптимизации и доработки. Подробности разработки и несколько скриншотов под катом.



Итак, начнём по порядку.

Первое, что я был вынужден сделать - переписать движок на использование игровых объектов независимо от основных классов (Activity и SurfaceView). Да, в игре изначально всё было в куче и было очень сложно добавлять новый функционал. Сразу не подумал об этом, пришлось переписывать почти весь код, зато теперь всё "по полочкам" и стало гораздо удобнее добавлять новые объекты в игру.

Второе, что я сделал - оптимизировал игровой поток. Он работал так же, как и во всех моих предшествующих проектах, по схеме "каждый фрейм - новый поток", т.е. имеется Runnable с кодом фрейма и Handler, которой запускает каждый следующий фрейм, когда отрисуется текущий с задержкой в 1000/FPS миллисекунд. Довольно простой и эффективный с точки зрения многопоточности метод. Каждый новый фрейм мог выполняться на любом ядре процессора, что с одной стороны хорошо, а с другой стороны может вызвать подергивания и провалы вплоть до 15 FPS. Это не круто и портит впечатление от игры. К тому же постоянный вызов методов создания и удаления потока снижали число FPS. Было решено переписать основной игровой поток на схему "все фреймы - один единственный дополнительный поток" (что более грамотно с точки зрения игростроения).

Всё получилось не сразу. Типовой код, коего очень много в интернете, например здесь или здесь был усовершенствован и переписан под игру. Теперь главный игровой поток держит 40 FPS стабильно и без провалов, а если не держит (на слабых девайсах), то автоматически проглатывает нужное число циклов отрисовки и рисует сразу несколько обработанных фреймов, что ускоряет игру при низких значениях FPS в соответствующее пропущенным фреймам число раз, т.е. если устройство держит только 15 FPS, то поток ускорит игру в 40/15≈3 раза, т.е. будет 15×3=45 фиктивных FPS. Думаю, что я изобрел велосипед (ибо похожая схема уже существует и работает во многих проектах), но именно мой "велосипед" лучше всего удовлетворяет поставленной задаче, к тому же он достаточно простой в реализации.

Всё хорошо, но иногда поток нарывается на исключения при отрисовке или просчете нового фрейма и падает, что, конечно же, тянет за собой падение всего приложения. Долго же я танцевал с бубном, перехватывая исключения, пытаясь угодить сразу двум требованиям:
  1. При незначительной ошибке поток не должен обращать на неё внимания и отрисовать фрейм ещё раз;
  2. При фатальной ошибке поток должен безопасно завершить свою работу и выдать сообщение об ошибке на экран пользователю в виде всплывающего окна.
И тут мне в голову взбрела идея: а что, если перехватывать все исключения и вести подсчет пропущенных фреймов, и в случае, когда число подряд идущих ошибок превысит, скажем, 300, он выдаст сообщение о том, что что-то не так? Так и сделал. Работает так, как я хотел, и теперь мелкие сбои пропускаются потоком, а при грубой ошибке вылетает "безопасное" сообщение об ошибке, после чего приложение закрывается.

Объясню почему меня не устраивает стандартное сообщение об ошибке при падении приложения. Дело в том, что после краха приложения, вызванного не основным потоком, само приложение закрывается, а дополнительный поток странным образом не всегда выгружается из памяти, хотя этот поток не фоновый. Это не позволяет запустить такой же поток при рестарте игры, и игра снова вылетает, и так до тех пор, пока сборщик мусора не очистит из оперативной памяти зависший поток. Так что в данной ситуации мой способ работоспособнее, хотя не исключаю тот факт, что можно было сделать по-другому/более правильно.

С потоками всё. Теперь передо мной встала другая задача: как сделать систему освещения в игре? Игра наделена временем, оно идёт постоянно, наступает ночь, становится темно, и на небе сверкают звезды. Романтика, да и только. Но с горящим в темноте костром было бы ещё приятнее. Костер я сделал, но вот темнота ночью создавалась следующим образом: рисовались звезды, затем фон, затем все объекты, а потом обычная заливка темно синим цветом с режимом смешивания DARKEN (Темнее). Как же сделать так, чтобы свет от костра был реалистичным, и пространство вокруг него становилось видимым лучше, чем всё вокруг?

На помощь пришёл способ, собранный по кусочкам из разных источников:
  1. Создаем новый Bitmap и его Canvas величиной с экран;
  2. Заливаем созданный холст темно синим цветом;
  3. Рисуем все маски освещенности с режимом смешивания DST_OUT;
  4. Рисуем полученный Bitmap с режимом смешивания DARKEN и прозрачностью в зависимости от времени суток на основном холсте.
Способ работал, но появилась другая проблема: звезды на небе становились синими, как и всё неосвещенное вокруг пространство. Попробуем рисовать маску звезд с режимом DST_OUT на холсте нашего ночного битмапа, но получается, что маска рисуется поверх всех объектов игры, в том числе и фона, что находится перед звездами. Получается, что фон и всё остальное просвечивается звездным небом. Не круто. Думаем ещё и придумываем такое решение:
  1. Создаем новый Bitmap и его Canvas величиной с экран;
  2. Заливаем созданный холст темно синим цветом;
  3. Рисуем маску звездного неба с режимом смешивания DST_OUT;
  4. Рисуем маску фона, находящегося перед звездным небом с режимом SRC_IN и фильтром на один цвет - темно синий;
  5. Рисуем маски освещенности с режимом смешивания DST_OUT;
  6. Рисуем полученный Bitmap с режимом смешивания DARKEN и прозрачностью в зависимости от времени суток на основном холсте.
Сегодня, в результате долгих экспериментов, этот способ заработал, и я радовался как ребенок, когда увидел звезды правильного цвета, отсутствие просвечивания ими фона, темноту ночи и свет от костра на одном экране без ошибок:

Сейчас движок игры полностью готов, осталось наполнить его ресурсами и функционалом. На это уйдет значительное время, но я всё же постараюсь снять ролик и выпустить демку к Новому 2014 Году.

На этом всё. Спасибо, что дочитали до конца.
И напоследок несколько скриншотов:

Комментариев нет:

Отправить комментарий