Как это сделано: мобильный кроссплатформенный движок
Для вас подготовил серию статей о мобильном геймдеве, основанную на полученном опыте и пройдённых граблях. В первой статье речь пойдёт о создании собственного кроссплатформенного движка для мобильных игр. По правде говоря не только мобильных, и не только игр.
А нужен ли свой движок вообще?
Каждый раз, когда очередной популярный движок становится бесплатным или открытым, я задаю себе этот вопрос. Давайте рассмотрим плюсы и минусы:
Плюсы
- Вы всегда знаете как работают ваши проекты внутри, а главное можете влиять на их работу как пожелаете. Вам не нужно штурмовать форумы разработчиков просьбами добавить нужный вам функционал или пофиксить старые баги.
- Вы сможете самостоятельно добавлять поддержку новых платформ и добавлять новые сторонние SDK.
- Вы сможете использовать любые удобные вам форматы хранения данных (графика, звуки, ресурсы).
- Вы не ограничены никакими лицензиями.
Минусы
- Все придётся писать самому, а так же вникать во все нюансы каждой платформы. Этот процесс займёт довольно много времени, поэтому просто так «ради интереса» этим заниматься не стоит. Другое дело, если вы занимаетесь геймдевом профессионально и планируете выпускать довольно много игр. Практика показывает, что большинство игровых студий рано или поздно создают свои SDK.
- Если вы делаете проекты на заказ, то не все заказчики рады вашим самописным решениям. Ведь возможно поддерживать проект придется совершенно другим людям.
- Во многих популярных движках есть встроенные визуальные 2D/3D редакторы. Об их удобстве можно долго спорить, но у вас изначально не будет и этого.
Конечно каждый для себя увидит свои плюсы и минусы. Мое дело предупредить. Поехали!
Из чего это сделано?
Мы говорим в первую очередь о разработке мобильных игр, поэтому основа будет однозначно на C++/OpenGL. Без вариантов! Однако без второстепенных языков тоже не обойтись. Давайте посмотрим что используется на каждой платформе:
Платформа | Основа | Обертка | Графика |
iOS | C++ | ObjectiveC или Swift | OpenGL |
Android | C++ (NDK) | Java | OpenGL |
WindowsPhone | C++ | C# | OpenGL через врапер или DirectX |
tvOS (AppleTV) | C++ | ObjectiveC или Swift | OpenGL |
OSX | C++ | ObjectiveC или Swift | OpenGL |
Linux | C++ | C++ | OpenGL |
Как видите C++ и OpenGL встречаются везде. На ObjectiveC/Java/C# придется написать только обертку для работы с системой девайса. Сам же код ваших проектов будет единый — на С++. На этой ноте скажем: «До свидания, мучительное портирование!».
OpenGL
Настоятельно рекомендую использовать OpenGL 2.0 и выше. Время OpenGL 1.1 давно прошло, а переход с 1.х на 2.х вы будете вспоминать в кошмарных снах. Однако не спешите использовать последнюю версию OpenGL не убедившись, что все целевые платформы его поддерживают. В большинстве случаев OpenGL 2.0 вполне хватает и поддерживают его все платформы.
С++
Та же ситуация и с С++11/14. Если уверены, что все компиляторы с ним дружат — супер. Мне же хватает C++98, так что при добавлении новой платформы —, а в планах есть поддержка консолей — я буду спокоен.
IDE
Xcode — для iOS, OSX, tvOS. Плагины через CocoaPods.
Android Studio — для Android. Плагины через Gradle.
Visual Studio — все что под Windows.
Структура движка
Прежде всего движок и проекты должны аккуратно и логично храниться на диске. В итоге я пришел к такой структуре:
- Engine (все что касается движка)
- Classes (.h, .cpp файлы движка)
- Modules (модули и сторонние SDK, которые нужны не во всех проектах)
- Рекламные SDK
- Аналитика
- Game Center
- Изображения
- Социальные сетки
- … и т.д.
- Platforms (специфические классы по платформам)
- Android
- iOS
- OSX
- tvOS
- … прочие платформы
- Тестовый проект
- iOS
- Проект.xcworkspace
- Icons (иконки приложения, *auto — сборщик проекта сам заполняет эти папки)
- Launch (картинки при старте приложения, *auto)
- Res (готовые ресурсы приложения, *auto)
- Pods
- … прочие файлы ios проекта, plist, build и т.д.
- Android, OSX, tvOS… такие же по смыслу папки под разные платформы и IDE. Для Android Studio своя структура проекта.
- Assets
- Icons (иконки приложения всех размеров)
- Launch (ланч-скрины всех размеров)
- Resources (оригиналы ресурсов проекта)
- General (основные ресурсы для всех платформ)
- Lang (шрифты и локализация)
- Fonts (папка с SDF шрифтами)
- Lang.xls (файл с переводами)
- Platform (ресурсы специфические для платформы)
- iOS
- Android
- … прочие платформы
- Shaders (шейдеры)
- Sounds (звуки)
- MP3 (для треков)
- OGG (для звуков)
- Textures (ресурсы по форматам текстур)
- ATI
- ETC
- PVRTC
- S3TC
- Source (.h, .cpp файлы самого проекта)
- Config (файл настроек проекта для сборщика)
- iOS
Сборщик проекта
Сборщик проекта отвечает за подготовку ресурсов, форматы и упаковку. А именно:
- Берет иконки (или даже одну иконку максимального размера 1024×1024) из Assets/Icons, делает остальные размеры (от 16×16 до 1024×1024) и копирует в папки по платформам [Platform]/Icons
- Так же поступает с экранами старта из Assets/Launch
- Берет ресурсы приложения из следующих папок:
- Resources/General
- Resources/Shaders
- Resources/Sounds/MP3, OGG
- Resources/Textures/[нужный формат текстур]
- Resources/Platform/[платформа]
- Resources/Lang/Fonts
Далее сборщик конвертирует ресурсы, шифрует, упаковывает и помещает в [Platform]/Res.
Самое важное тут — это конвертация файлов по расширению. Я использую такие конвертации:
- PNG и JPEG превращаются в WEBP. Общие настройки конвертации (например минимальное качество) можно вынести в настроки проекта Project/Config, а можно и указать прямо в названии файла.
Например image~q100.png будет сжата с параметром quality 100, а image~less.png будет сжата без потери качества.
Например к image~p1.png будет применен 1й пресет, который перевернет картинку зеркально и сохранит с качеством 90%.
- Текстовые файлы и шейдеры (.txt, .vs, .ps) шифруются нехитрым способом. Простая защита от любопытных.
- Файл локализации Resources/Lang/Lang.xls парсится по языкам, шифруется и упаковывается в бинарный формат.
- На лету создаются текстурные атласы. К примеру из папки с именем folder~atlas будут взяты все картинки и упакованы в единую картинку + сохранится файлик с координатами.
- Звуки конвертируются в OGG формат.
- 3D модели конвертируются во внутренний формат движка.
- Файлы с именем file.pack преобразуются из текстовых в бинарные. Это хорошо подходит для всевозможных конфигов игры, уровней и т.д.
При этом сборщик смотрит время изменения файла и конвертирует только измененные файлы, что заметно ускоряет его работу. Конкретно у меня сборщик написан на PHP. Возможно это не лучший выбор, но мне так было проще. К тому же потенциально его можно перенести на сервер для командной работы.
Форматы
Я бы рекомендовал использовать такие форматы:
WEBP для картинок. Вряд ли для кого-нибудь этот формат окажется новым. А для тех, кто слышит о нем впервые — webp может хранить картинку без потери качества как PNG, а так же с потерей — как JPEG, однако с заметно лучшим качеством, меньшем весом и с прозрачностью. Еще из плюсов — возможность скейла картинки на лету при чтении файла. Компилируется libwebp под все платформы без проблем.
OGG для звуков. Андроид нативно понимает OGG формат, а на iOS/OSX/tvOS я использую библиотеку Tremor (fixed-point version of the Ogg Vorbis) для раскодировки звуков в WAV и скармливанию их OpenAL. Попытки использовать OpenAL и на андроиде успехом не увенчались (звуки были с задержками).
Классы и модули
Разберем подробнее какие классы содержит движок и для чего нужны модули?
Правило «что выносить в модуль, а что в движок?» очень простое:
Следуя этому правилу, я распределил классы следующим образом:
Движок
- Работа с платформой. Тут происходит инициализация приложения, а так же передача внешних событий (пауза, тачскрин, кнопки) в основной класс движка.
- Основной класс. Тут крутится mainloop, обрабатываются входящие события (уже в универсальном виде независимом от платформы), происходит управление потокам и фоновыми задачами.
- Работа с 2D. Вывод картинок, атласов, постэффектов.
- Работа с 3D моделями. Загрузка моделей, рендеринг, управление шейдерами.
- Стандартные UI элементы. Окна, кнопки, скролинг, уведомления.
- Текстуры. Загрузка и выгрузка текстур. Сами декодеры находятся в модулях.
- Вывод текста, рендеринг SDF шрифтов.
- Математика. Всевозможные формулы, матрицы, кватернионы и т.д.
- Социалка. Отправка писем, стандартный шаринг, rate me. Сами же соц. cети вынесены в модули.
- Чтение/запись файлов.
- Работа с UTF8 строками.
- Работа с сетью.
- Музыка/звуки.
Модули
- Социалка
- Google Plus
- VK
- Replay Kit (запись экрана для iOS)
- JSON/XML
- Декодеры картинок
- WEBP
- JPEG
- PNG
- In-apps (внутренние платежи)
- Game Center, Google Play Services
- Crashlytics (отслеживать краши)
- Branch (глубокие ссылки)
- Аналитика
- Google Analytics
- Game Analitycs
- Flurry
- Реклама
- Appodeal
- Chartboost
- Fyber
- AdColony
- UnityAds
- Tapjoy
- Google Ads
- Heyzap
- AdToApp
В следующих статьях я подробнее остановлюсь на конкретных классах и модулях, с примерами и полезностями. Отдельное внимание хочу уделить рендерингу SDF шрифтов (Signed Distance Field) и шейдерам в игре из шапки.
Если какие-то отдельные вопросы вас заинтересовали — плз пишите в каментах, добавлю их в план статей.