Ботинки-скороходы начали продаваться в США Moonwalkers — это девайс, который надевается поверх вашей обуви и позволяет разгоняться до 24 км/ч. Препятствия они проходят легко, а на ноге сидят комфортно. Покупатели уже говорят, что с ними ходьба ощущается, как бег.
После подробного материала с разбором и тестами N14 Pro, компания Ninkear предложила заслать мне их следующую модель - A15 Plus, которая построена на базе проверенной временем платформы Ryzen 5700U. Пожалуй, если сейчас зайти на любой онлайн-маркетплейс, можно найти сотни различных предложений ноутбуков на базе "красной" платформы и даже среди них модели от Ninkear есть чем выделится! Стоит ли брать A15 Plus за 60.000 рублей? Узнаем в сегодняшней обзорной статье!
Распаковка
Пожалуй, не могу не отметить свою благодарность компании Ninkear за подаренный ультрабук N14 Pro: девайс во многом мне помогает, с учётом того, что мы собираемся с вами реализовать проект автомобильной тематики (который в свою очередь требует частого нахождения на улице), иметь подобный портативный гаджет как минимум удобно.
После прошлого обзора ребята из Ninkear сказали, что мой стиль написания обзоров им понравился и в особенности то, что я уделяю внимание такому важному аспекту для многих разработчиков, как скорость clean-сборки комплексных проектов с десятками тысяч строк кода. Поэтому когда мне предложили протестировать другой их лэптоп на базе "красной" платформы - я согласился :)
Девайс пришёл ко мне в хорошо запечатанной коробке, внутри которой находилось ещё две небольшие коробочки: первая с блоком питания, проводной мышкой (в N14 Pro была беспроводная), а также фирменным ковриком. Ультрабук хорошо запакован, в целом, не вызывает сомнений, что он доедет нормально и служба доставки его не разобьёт.
Дизайном лэптоп напоминает, в первую очередь, конечно-же технику от всем известной яблочной компании. Пожалуй, это какой-то тренд последних лет, но назвать его плохим язык не повернется - ультрабук очень тонкий, почти полностью металлический и в целом отдаёт некоторой премиальностью. Корпус очень сильно напоминает оный у N14 Pro, однако наша сегодняшняя модель значительно тоньше - что можно записать как в плюсы (очень компактный), так и в минусы (нужно быть осторожнее, если устройство необходимо будет закинуть в рюкзак или в машину).
Открывая лэптоп, мы лицезреем здоровенный тачпад с поддержкой мультитача (т.е возможностью делать операции а-ля зум) и весьма удобную клавиатуру, которая, кстати, весьма похожа на оную в N14 Pro. Судя по всему, Ninkear не стали изобретать велосипед и использовали одну из generic-клавиатур, которые можно без проблем найти в Китае и заказать по парт-номеру. Небольшой, но всё же плюсик к ремонтопригодности! Кроме того, в клавиатуре установлена светодиодная регулируемая подсветка для работы в темное время суток. Мелочь, а приятно.
Лишь один мелкий нюанс может расстроить некоторых читателей - это совмещенная кнопка включения с общей клавиатурой. Я лично тоже немного скучаю по этим тактильным отдельным кнопочкам, но увы, тренды диктуют свои правила!
В остальном же, девайс не вызывает никаких нареканий с точки зрения внешнего вида и в целом, весьма стоек к царапкам и прочим вещам, характерным для устройств, которые активно используются в портативном режиме. Давайте же узнаем, что у девайса "под капотом"!
Производительность
Как уже было оговорено ранее, ультрабук построен на базе достаточно актуальной и проверенной платформы Ryzen 7 5700U. В отличии от N14 Pro, который построен на базе платформы от Intel, A15 Plus отличается значительно более высокой производительностью и в первую очередь - в 3D графике: сложно поспорить с тем, что GPU семейства Radeon значительно шустрее Iris XE в наше время. Давайте же ознакомимся с подробными спецификациями A15 Plus:
Процессор: AMD Ryzen 7 5700U с 8-ядрами/16-потоками и частотой до 4.3ГГц на базе архитектуры Zen 2, выпущенный по 7нм тех. процессу. Процессор вышел в 2021 году и получил 8 мегабайт L3 кэша (общего), 512Кб L2 кэша (на каждое ядро отдельно), а также имеет весьма "холодный" нрав в виде TDP 15W.
GPU: Radeon 512SP на базе архитектуры GCN 5.1 (Vega II), который работает на частоте до 1.750МГц и использует некоторую часть ОЗУ в качестве VRAM. Ускоритель поддерживает DX12, OGL 4.6, Vulkan 1.3 и SM 6.7. Практически все самые последние стандарты!
ОЗУ и SSD: 32Гб DDR4 на частоте 3200МГц. SSD - NVMe на 1Тб.
Аккумулятор: 57Вт/ч. Производитель обещает около 10 часов работы от аккумулятора в обычном режиме (понятное дело, что если процессор будет молотить весь аптайм на 100% - то аккумулятор сядет быстрее).
Дисплей: 15.6" матрица с разрешением 1920x1080, выполненная по технологии IPS с яркостью 400Нит. Её качество вполне на уровне, хотя для кого-то яркость может показаться маловатой.
Интерфейсы: 2x USB 3.0, MicroSD, 3.5мм джек для наушников, а также HDMI и полноценный Type-C (как передача данных, так и зарядка), а также Wi-Fi 2.4GHz/5.0GHz.
ОС: Windows 11 Home.
Цена: 63.000 рублей на момент написания статьи или 675$!
По моему субъективному мнению, характеристики весьма и весьма неплохие для такого прайса! Я помню лэптопы за те-же деньги в 2012-2013 году (те же Samsung'и RV серии), которые предлагали совсем уж базовые характеристики за эти же деньги. А сейчас, вот, можно купить весьма бодрый лэптоп не только для офиса и серфинга в сети, но и более серьезных рабочих задач!
В отличии от N14 Pro, у A15 Plus практически нет настроек в UEFI. По словам Ninkear, почти во всех своих лэптопах они даже дебаг-режим не прячут в релизных версиях прошивок UEFI, что даёт доступ к UART'у. Стоит ли говорить о том, что если девайс пишет логи в UART, диагностика нерабочего устройства значительно облегчается?
Давайте же перейдем к бенчмаркам. Тут всё по классике: CPU-Z, GPU-Z, Furmark, а также AIDA64. Начинаем с CPU-Z, который выдаёт более подробную информацию о нашем процессоре. Бенчмарк набирает 510.9 попугаев в однопоточном режиме и 3596.9 попугаев в многопоточном - вполне неплохо, это уровень.
GPU-Z подтверждает нашу информацию о GPU, а Furmark 2 выдаёт ~12 FPS на бублике в FHD (это относительно нормальный результат для встройки, FurMark - очень тяжелый бенчмарк). Ну, игровой потенциал мы протестируем в отдельном разделе нашего обзора!
В AIDA64 я тестировал ЦПУ используя два бенчмарка: CPU Queen для подсчета производительности АЛУ, а также FPU Julia для теста сопроцессора чисел с плавающей точкой. Результат очень и очень достойный, на уровне десктопного i7-7800K.
В повседневных задачах девайс проявляет себя замечательно: нет никаких проблем при серфинге страниц, благодаря 32Гб шустренькой DDR4 памяти девайс может держать параллельно открытыми пару браузеров с несколькими вкладками и параллельно запущенный видеоролик в YouTube, а также какой-либо рабочий софт. Конечно-же девайс подойдет и для офиса, или, например, диагностики автомобилей (относительно современных).
Если вы любитель GNU/Linux, то с этим есть некоторые проблемы - конкретно на A15 Plus, последний релиз Ubuntu работал с косяками: в самой системе залипали кнопки клавиатуры на 1-2 секунды (скорее всего, баг "вялого"). С драйверами на Radeon сейчас всё хорошо (учитывая что AMD охотно делится спеками и документацией на старые видеочипы), система подхватывает почти все устройства (я не проверял управление охлаждением).
Давайте же перейдем к профильным тестам ультрабука, ведь статья позиционируется как обзор именно для разработчиков!
Для разработчика
Как и в случае с обзором на N14 Pro, мы будем с вами проводить тесты в нескольких кейсах: компиляция Android-проектов и работа в Android-Studio, компиляция .NET проектов и использование VS Community, а также сборка здоровенных С++ проектов с сотнями тысяч строк исходного кода! В качестве изюминки мы замерим время компиляции самого свежего ядра Linux для нашего устройства!
Начинаем с Android Studio, который работает очень шустро, несмотря на общую прожорливость IDE от Google и JetBrains. Никаких неудобств при разработке не возникает, редактор лейаутов и кода работает нормально. Время сборки проекта моего клиента YouTube для Android 2.2 без учёта Gradle refresh - 9 секунд.
Переходим к .NET. Здесь я использую VS2022 Community Edition, а собираю свой самопальный шутер на собственном 3D-движке. Игра небольшая - всего около 3х тысяч строк кода, которые на Ryzen 5700U собираются менее чем за секунду! Очень-очень шустро! IDE работает быстро и оптимизирована хорошо.
Давайте соберем большой и крутой движок Urho3D из исходного кода в рамках теста компиляции C++. Опять же, компилятор - VC2022, а время сборки - всего 55 секунд. Это в два раза быстрее, чем время сборки проекта на N14 Pro (скорее всего сказывается значительно более шустрый SSD и Ryzen, который хорошо дружит с распараллеленными компиляторами). Intellisense работает моментально.
Изначально, я хотел протестировать время компиляции ядра Linux, но увы. Хотя, конечно, можете меня пожурить в комментариях за то, что настоящий линуксоид собирает ядро и без этих ваших XTerm, прямо в обычном текстовом терминале!
Играем
Пришло самое время поиграть! Я установил несколько игр, правда стоит всё же учитывать, что это интегрированная графика и смысла запускать условный киберпанк на ней нет. Поэтому я прошёлся по нескольким любимым мной играм прошлых лет, которые, тем не менее, все еще могут нагрузить относительно бюджетные и среднебюджетные лэптопы! Пожалуйста, учтите что все тесты проводились в портативном режиме для честности.
Начинаем с Black Mesa. Игра выдаёт нестабильные 30 FPS при средних настройках графики в FHD. Несмотря на то, что BM работает на Source, ребята из Crowbar Collective форкнули одну из веток движка и значительно переписали рендерер, из-за чего игра способна нагрузить многие шустрые десктопные карточки прошлых лет.
А вот с Fallout 4 ситуация сложнее. На средних настройках в FHD и отключенным антиальясингом, игра едва ли тянет в 15 кадров при нахождении в густонаселенных локациях с, предположительном, большим овердравом.
GTA 5 ноутбук тянет в относительно стабильные кинематографичные 24-30 FPS на "стандартных" настройках графики. Не так хорошо, как хотелось бы, но в целом нормально.
Под капотом
Давайте разберем девайс и узнаем, что у него внутри! Как я уже говорил в обзоре на N14 Pro, лэптопы Ninkear очень легко разбираются: достаточно лишь открутить винты и расщелкнуть клипсы поддона. После этого, перед нами предстает плата устройства.
Очень приятно то, что аккумулятор можно отключить без полной разборки устройства - это полезно, если вам нужно надолго убрать ноутбук на полку и вы не хотите увести АКБ в глубокий разряд.
Система охлаждения выполнена в виде одного большого кулера, от которого идут две теплотрубки: одна на процессор, другая на хаб.
Аккумулятор представляет из себя несколько последовательно соединенных банок с общей емкостью 5Ач:
В остальном, ультрабук скомпонован весьма грамотно и его легко обслуживать. Многие читатели смогут без проблем его разобрать и прочистить от пыли и засорений. Никаких разборок через клавиатуру!
Заключение
Вот такой обзор на A15 Plus у нас с вами получился. Как вы считаете, девайс достаточно шустрый? Достоин покупки? Лично по моему мнению - да, ультрабук имеет хороший баланс цена/качество и предлагает хорошую производительность за небольшие деньги. При этом его легко обслуживать.
Плюсы устройства:
Шустрый "кукурузен": Ультрабук замечательно подойдет для работы и выполнения большинства задач. Начиная от офиса, заканчивая сборкой тяжелых проектов с комплексными системами сборки.
Хороший дисплей: FHD IPS матрица - сбалансированный выбор, который позволяет не особо грузить не самую шуструю встройку, снизить конечную устройства и при этом порадовать пользователя хорошей цветопередачей и отличными углами обзора.
Большой объём ОЗУ: Благодаря 32гб ОЗУ, есть возможность запустить множество программ одновременно. Но в этом есть и минус - память одноканальная, хоть и шустрая.
Бесшумность: Ультрабук не слышно от слова совсем. Он крайне тихий даже при 100% нагрузке и не сказать что горячий.
Хороший корпус: Конструктивно девайс не вызывает никаких нареканий. Ничего не скрипит, не люфтит, всё выполнено на достойном уровне. Металлические материалы корпуса оставляют приятное впечатление от девайса.
Минусы:
Звук: Не очень хорошее качество звука из динамиков устройства. Не сказать, что оно совсем плохое, но N14 Pro в этом плане показывает себя лучше. В любом случае, проблема решается подключением внешних колонок, благо в A15 Plus оставили 3.5мм джек.
Не работает текущая версия Ubuntu из коробки: Ну, тут некоторые читатели скажут мол я не трушный, трушный пропатчил бы вялого под эту платформу и заставил нормально работать клавиатуру. Но в конце концов, большинство читателей этого обзора ожидают нормальной работы из коробки.
Низкая производительность GPU: Здесь сложно упрекнуть Ninkear, но пока что ситуация на рынке встроек не особо меняется: что в нулевых на IGP можно было поиграть лишь в релизы 5-10 летней давности, что сейчас. Но в целом, почему-бы не перепройти классику? :)
Подойдет ли вам A15 Plus?
Офисная работа и серфинг: Легко! Ноутбук справится с задачами серфинга и офисного использования без каких либо проблем.
Разработка: Благодаря шустрому процессору 5700U, девайс быстро собирает большие проекты в 16 потоков и предоставляет приятный экспиренс в IDE даже с множеством открытых больших файлов.
Игры: Если вы любитель игр из начала-серединых 2010х годов, почему-б и нет?
Спасибо компании Ninkear за предоставленный девайс! Приобрести девайс можно на официальном сайте компании с быстрой доставкой по России, или на WB.
Моддинг-сцена с разработкой и портированием кастомных прошивок для Android-устройств существует вот уже более 10 лет. В основном, энтузиасты пытаются проапгрейдить свои устройства путем портирования более свежих версий Android, чем предлагает производитель девайса. Чего уж говорить, если Galaxy S III, которому уже 12 лет стукнуло, получил неофициальный апгрейд до Android 14. Порой мне в голову приходят различные, весьма странные моддерские мысли: например, почему бы не портировать на старенький смартфон… ещё более старую версию Android, дабы посмотреть «что будет». Казалось бы «портировал и портировал», но в процессе работы я столкнулся с множеством интересных нюансов и особенностей работы Android, о которых хотел бы рассказать и вам — моим читателям! Сегодняшняя статья будет в классическом «научпоп»-стиле без кода, зато с подробными объяснениями одной из техник портирования Android-прошивок путем патчинга скриптов для конфигурации системы и подмены Board-specific библиотек, дабы система «увидела» всё необходимое железо! Интересно? Тогда жду вас под катом!
❯ Мотивация
У меня, как и у многих моих читателей, одной из первых версией Android в жизни была 2.x. Наверное, я уже никогда не смогу забыть первые впечатления от использования своего новенького, пусть и бюджетного и слабого Android-смартфона после простеньких китайских кнопочников. Эти ощущения были прекрасными: вот я разблокирую смартфон, потянув «замочек» вправо, свайпаю рабочие столы и тапаю на значок приложения браузера, выполненный в стиле скевоморфизма, загружаю полноценную страницу Википедии через GPRS-сеть (мой первый смартфон не имел 3G) и плавно скроллю страницу, не забывая смахнуть шторку вниз и проверить статус уведомлений в пока ещё совсем простенькой панели нотификаций… Это были по настоящему ламповые впечатления, которые не смог превзойти ни один современный девайс: ни AOSP, ни MIUI, ни OneUI.
Моим первым смартфоном была китайская реплика Samsung Galaxy S III Mini, купленная в самом начале 2013 года. Возможно, кто-то из вас помнит, как подобные дешевые смартфоны и планшеты «Sumsanc» можно было купить на рыночных развалах, в метро и прочих местах, где допускается торговать несертифицированными гаджетами. Даже с учётом накрутки, эти смартфоны стоили всего 2 000 рублей, что было просто «подарком» для цены абсолютно нового гаджета. Девайс был крайне простым для начала 2013 года и имел следующие характеристики:
Процессор: Spreadtrum SC6820. Одно ядро Cortex-A5 на частоте до 1ГГц, Mali400 MP в качестве GPU. Чипсет был крайне высоко-интегрированным для своих лет: в одном корпусе располагалось ARM-ядро, GPU, контроллер питания, GPS, множество периферии (например, DAC), а также Baseband-часть GSM-радиотракта. BT/Wi-Fi реализовывались в отдельном комбочипе разработки RDA.
Память: 256Мб DDR1 ОЗУ/256Мб NAND-памяти в одном чипе eMCP от Hynix. Предположительно, эти чипы остались на складах ещё со времен первых Android-смартфонов, но очень быстро потеряли актуальность и их, вероятно, отдавали «за бесценок» что позволило ещё сильнее снизить цену производства таких смартфонов.
Дисплей: безоговорочно, TFT, обычно с разрешением не выше 480x320, что для 3" дисплея было нормальным, но для 5" — уже несколько маловато. Тем не менее, сами дисплеи были нормальными и глаза от них не «вытекали». Тачскрин обычно ёмкостной, на 2 касания.
Android: 2.2, на некоторых похожих моделях встречался 2.3.
Аккумулятор: ~1.500мАч, не больше. По форм-фактору напоминает BP-4L, без проблем подходит от многих S60 смартфонов Nokia тех лет.
Не густо, да? Уже в апреле того же года вышел Galaxy S4 с Snapdragon 600, 2Гб ОЗУ и 32Гб встроенной памяти, а мы тут с одноядерным чипсетом и 256Мб ОЗУ сидим. Но мне, будучи школяром, это было за счастье — чего я на нём только не делал, и на PHP какие-то WAP-сайты динамические пытался писать и на FTP заливать, и даже ADT Bundle скачал, дабы попытаться что-то своё запилить под собственный смартфон! В общем, я был счастлив, несмотря на лаги девайса. Именно того девайса у меня уже давным-давно не осталось… но память я всё ещё храню и стараюсь дать новый дом таким китайчикам, которые в большинстве своем оказались на свалке истории в новом мире современных смартфонов!
Но на самом деле, смартфоны 10+ летней давности могут быть интересны и своим форм-фактором: в современном мире едва ли можно найти хоть какие-то телефоны с полноценной QWERTY-клавиатурой (исключение — смартфоны UniHertz, которые стоят недешево) и уж тем более, боковые слайдеры. Поэтому мой интерес к подобным девайсам очень легко объяснить!
Однако, порой мне самому хочется снова пережить эти эмоции и ещё раз походить с подобным девайсом «на каждый день», даже когда на Android 2.2 особо никакие сервисы уже не работают. Отчасти, я решаю свои проблемы сам и пишу клиенты нужных мне сервисов, если они действительно нужны, дабы рано или поздно всё таки вдохнуть новую жизнь в «старенькие» девайсы. И казалось бы, это можно списать на синдром утёнка и банальную ностальгию, но мои ощущения «ламповости» отнюдь не мимолетны и всё равно меня тянет именно на те смартфоны, с тем самым интерфейсом, которые я когда-то увидел впервые!
Пожалуй, сказать что я решил портировать старый Android на отнюдь не новый смартфон «просто так» было бы ложью. Я всё ещё верю в то, что смогу в одиночку хотя бы частично вдохнуть новую жизнь в эти девайсы и позволить им работать с современными сервисами, дабы они могли приносить пользу не только мне, но и другим людям, которые намеренно занимаются дауншифтингом или вынуждены сидеть на девайсах с старыми версиями Android! Сегодняшним нашим подопытным станет один из представителей подобных noname-смартфонов тех лет, реплика Galaxy S III Mini на том самом железе, на котором работал мой первый смартфон. Однако с завода на нём стоял Android 2.3 — слишком свежая, по моему мнению, версия системы, которую я конечно-же захотел откатить до Android 2.2!
Задача облегчалась тем, что смартфоны на этом чипсете с Android 2.2 уже выходили, что позволило мне портировать прошивку путем несложных патчей скриптов инициализации и копирования Platform-specific файлов, дабы завести все необходимые для смартфона модули. А поскольку о таком простом способе портирования свежих и старых прошивок знают далеко не все мои читатели — я решил написать об этом отдельный подробный материал! Давайте же перейдём к практической части нашей статьи.
❯ Первые шаги
Перед тем, как начать портирование системы, нам необходимо разбираться в том, как вообще происходит процесс загрузки Android и какие процессы при этом загружаются. Вкратце, описать весь процесс загрузки можно так:
Загрузчик: при включении смартфона, первичный загрузчик BootROM, аппаратно-прошитый в чипсет ещё на этапе изготовления чипа, инициализирует некоторую периферию, загружает вторичный загрузчик из NAND (коим может быть SPL — Second Program Loader, занимающийся инициализацией контроллера DDR и UART) и передаёт ему управление. Вторичный загрузчик в свою очередь передаёт управление U-Boot — в задачи которого входит также инициализация периферии, обработка устройств постоянной памяти (например, NAND или контроллер SD), загрузка ядра Linux и конфигурация самого процесса загрузки. U-Boot можно считать эдакой альтернативной UEFI/BIOS в мире не-x86 устройств. В смартфонах на базе чипов MediaTek и Qualcomm, роль U-Boot выполняет LK — маленькая ОС, в задачи которой входит инициализация периферии и передача управления ядру Linux с помощью программы aboot.
Ядро Linux: после загрузки образа ядра с initrd (небольшая файловая система, которая загружается сразу в память и содержит в себе скрипты для конфигурации всего остального) и передачи управления ядру, Linux начинает выполнение программы с PID 0 — /init, в задачи которой входит выполнение скриптов инициализации userspace-окружения системы в init.rc. При этом смартфон уже фактически готов к работе — в одной из своих статей я показывал, как можно приостановить загрузку Android и выполнять свой код, используя все ресурсы смартфона для своих целей.
zygote и app_process: помимо запуска необходимых для работы смартфона служб, динамической загрузки драйверов (с помощью insmod) и определения режима загрузки (например, если телефон подключили выключенным к зарядке — необходимо показать анимацию этой самой зарядки), init.rc запускает две программы, одна из которых необходима для функционирования системы. Первая — это bootanimation, которая проигрывает анимацию включения смартфона и app_process, который в одном из режимов работы превращается в zygote — самый важный процесс для работы Android, который предварительно при старте системы загружает системный Java-байткод, отвечающий за отрисовку интерфейса, проигрывание звука и т. п. из framework.jar и другие системные ресурсы (например темы и изображения), а затем при запуске каждого приложения просто клонирует сам себя (с уже загруженными ресурсами) и начинает выполнение байткода любого запущенного Android-приложения или службы.
Каждое запущенное приложение или служба — это отдельный app_process, в том числе и лаунчер, и Google-сервисы и клиент любого мессенджера.
Всё выглядит просто и логично, не так ли? Подытожив, можно сказать что для того, чтобы система минимально стартовала, нам необходима подходящее ядро для нашего устройства, рабочий init.rc и адекватно запускающийся init.rc. Кроме того, Android зависит от некоторых платформо-специфичных библиотек: в основном, они находятся в /lib/hardware и без них система может не запуститься или что-то может не работать. Особенно осторожно надо подходить к libhardware.so.
Как я уже сказал выше, прошивку мы будем портировать от другого смартфона на том-же чипсете и что забавно — такую же реплику, просто более-раннюю! «Из коробки», мой смартфон работает на Android 2.3, значительно более стабильной, чем изначальный порт 2.2 на эту платформу. Отличий 2.3 от 2.2 достаточно: например, на 2.2 совсем иной цвет шторки, по умолчанию стоит Light-тема, нельзя закрывать уведомления смахиванием и в целом система несколько отличается внешне. Для работы нам нужно будет два образа прошивки: ту, которую будем портировать и та, которая стоковая. Прошивки в смартфонах на платформе Spreadtrum распространяются в формате pac, однако нет никаких проблем подменить образ раздела в ResearchDownload — фирменной утилите для прошивки смартфонов на этом чипсете.
Я решил взять прошивку от FeiTeng N9300 Mini, родная для моего смартфона — M-Horse 9500 Mini. В случае моего девайса, разметка и список разделов между устройствами никак не отличалась, поэтому изначально я напрямую прошил раздел system.img, дабы посмотреть что будет с устройство. Не забывайте, что ядро и init.rc хранится в образе boot.img — поэтому прошивка раздела system безопасна!
❯ Первый запуск
После прошивки чужого раздела system, смартфон стартовал… однако работал несколько странно: во первых, у нас не было сети, во вторых не работал тачскрин (при родном то ядре), а в третьих, Android ни в какую не видел аккумулятор, вися на 0% и моментально отключаясь, если смартфон не стоит на зарядке, а при попытке воткнуть кабель — смартфон показывал индикацию зарядки, но потребление было на нуле.
Поскольку тачскрина у нас нет, root доступ через adb придется включать «ручками» — для этого нам необходимо перепаковать наш родной раздел boot. Для распаковки и запаковки образов, я пользуюсь MtkImgTool — весьма удобная «кухня» для работы. Вытаскиваем boot.img из pac, закидываем в Unpack/Image/ и распаковываем с помощью Boot -> Unpack -> boot.img
В Unpack/boot/ramdisk/default.prop нам необходимо изменить ro.debuggable на 1, а ro.secure на 0. Это даст возможность отлаживать устройство даже если Android фактически не загрузился.
Теперь у нас есть root-консоль устройства, даже если смартфон висит на заставке. Прошиваем обратно образ, пишем adb shell в консоли и смотрим, что же тут не так… Вообще, драйвер тачскрина обычно статически слинкован с ядром, но в случае устройств Spreadtrum — они вынесены в динамические модули ko, которые можно найти в папке /lib/modules/, либо /sps/. Давайте глянем init.sp6820a.init.3rdparty.rc, который отвечает за специфичную для этой модели смартфона инициализацию.
Ага, видим insmod gt868.ko? Это команда загрузки драйвера тачскрина, в нашем случае — это вышеупомянутый GT868. Иногда встречаются другие модели тачскринов, но главное отличие прошивки 2.2 от 2.3 — разные названия папок с драйверами и некоторые службы. Достаём из родного образа драйвер gt868.ko, используя всё тот-же MtkImgTools, распаковывая его как обычный ext2 раздел:
И наслаждаемся тем, что у нас теперь появился тачскрин! Android сам подхватил новое устройство ввода, поскольку драйвер тачскрина — обычное устройство в /dev/input/. Чтобы драйвер грузился при загрузке, его достаточно добавить в init.sp6820a.3rdparty.rc, предварительно закинув в раздел /system/. Перед этим, раздел нужно перемонтировать для возможности записи:
После модификации rc-скрипта, нужно обратно запаковать boot.img с помощью MtkImgTools и прошить его с помощью ResearchDownload — тачскрин будет работать даже после перезагрузки!
❯ Поднимаем зарядку и сеть
Переходим к отсутствию связи с аккумулятором и нулевым потреблением АКБ. Здесь мне пришлось несколько покопаться и почитать логи ядра с помощью команды dmesg. Я обратил внимание на то, что некая служба пишет что-то об аккумуляторе, но разобраться было несложно: в папке /system/bin я нашёл программу charge, которая, очевидно, отвечает за настройку КП для старта зарядки. Что она точно делает — мне неизвестно, возможно корректирует какие-то значения в sysfs, возможно с помощью ioctl общается с драйвером КП и даёт разрешение на старт зарядки и обновление информации в sysfs. В любом случае, после замены /system/bin/vcharged на оный из родной прошивки, зарядка заработала.
Для этого мы снова перемонтируем /system/ в режим записи и копируем vcharged, не забыв вернуть обратно необходимые права:
Перезагружаем устройство и… зарядка с индикацией появилась!
Вроде всё работает на первый взгляд: и звук, и вибро, и Wi-Fi с Bluetooth… однако сети-то нет! Девайс не определял наличие SIM, а вместо IMEI у нас был null/null:
Чтобы её поднять, нам необходимо разобраться в том, как работает подсистема взаимодействия с радиомодулем в Android, которая называется ril — Radio Interface Library. RIL предоставляет API для системы, дабы оперировать не напрямую AT-командами (которые могут быть проприетарными, а на некоторых чипсетах, как, например, Qualcomm вообще отсутствовать), а удобным набором функций — например о запросе статуса радиомодуля, начале звонка, поиска сети и т. п. RIL состоит из сервиса rild в /system/bin/ и библиотеки libril.so, которую можно найти в папке /system/lib/. При запуске системы, TelephonyManager открывает сокет с rild и опрашивает его состояние. Именно из TelephonyManager система берет информацию о силе сигнала, название оператора, IMEI и другие данные.
Путем ковыряния в dmesg я понял, что система флудит из-за невозможности запустить проприетарный сервис Spreadtrum — sprd_monitor. При попытке позвонить в 112, смартфон бесконечно пытается включить радиомодуль. Я ковырялся в UI-части исходного кода Android, дабы понять логику работы, но проблема крылась как раз в упомянутых выше службах sprd_monitor. Берём их из /system/bin/ оригинальной прошивки, закидываем их в устройство, не забыв установить права и отправляем систему в ребут:
Ошибки в dmesg пропали, IMEI появился, но устройство до сих пор не хочет никуда звонить и просто висит на экране звонка. В настройках смартфон говорит о том, что уровень сигнала недоступен, а значит, радиомодуль до сих пор не работает :(
Но и мы так просто не сдаемся! Поковыляв по файловой системе, в директории /system/opl/telephony/bin/ я нашел скрипт, отвечающий за инициализацию радиотракта, который вызывает родной 3rdparty.rc! Запускаем sh-скрипт и обнаруживаем, что сеть появилась и девайс дозвонился в 112, а также увидел SIM-карту!
Теперь всё полностью работает :) Дабы радиотракт запускался при старте устройства, я перенес часть инита из boot.img от прошивки, которую мы портировали. Для кого-то, казалось бы, это всё достаточно сложно и долго. Но у меня ушел всего один день на полную отладку и запуск такой кастомной прошивки на своем устройстве! Можно сказать, это самый базовый и краткий экскурс в такое нелегкое дело, как моддинг Android-устройств.
Но мы ведь это всё не просто так делали! Давайте глянем, как будет работать такой девайс на Android 2.2 в 2024 году — спустя 14 лет после выхода системы. Всё ли так плохо, как кажется?
❯ Знакомимся с девайсом
Думаю, многие читатели вспомнят этот ламповый интерфейс, обои с одуванчиком и лаунчеры а-ля TouchWiz на тех смартфонах, где интерфейс Samsung был не предусмотрен. А эти «бульк»… их сложно забыть!
Конечно, изначально может показаться, что устройство плохо подходит для выполнения современных задач: браузер не способен загрузить большинство страниц, а из альтернатив есть только Opera Mini, где вообще нет динамического контента, а официальные клиенты ВК, WhatsApp и YouTube уже давно не работают. Опечаленный читатель может подумать, что девайс, как и многие его ровесники уже давно превратились в звонилки…
Но это отнюдь не так! Ведь как я уже говорил, я стараюсь своими силами вдохнуть в подобные девайсы новую жизнь, реализуя на них клиенты нужных мне сервисов сам! Да, пусть примитивно и корявенько, далеко не ынтырпрайз-уровень, но эти приложения выполняют свои функции и что, немаловажно, весят очень мало (до 100Кб) и работают крайне шустро! Клиент ВКшечки просто летает, несмотря на то, что фактически реализован только мессенджер с нотификациями и музыка.
Пожалуй, многие читатели удивятся — но на таких девайсах есть YouTube! Мой самопальный клиент не поддерживает стриминг из сети (да и многие девайсы объективно не потянут), поэтому предварительно загружает видео на MicroSD-флэшку и затем уже их воспроизводит. Как приятный бонус — видео потом можно посмотреть в любой момент в галерее.
Я помню насколько было лампово слушать музыку с таких девайсов. И если претензии к основному динамику не очень актуальны, то к качеству звука в наушниках были придирки — звук был громкий, но ему не хватало низких частот, из-за чего он звучал несколько плоско, хотя мне и этого хватало — ведь я слушал музыку в наушниках по 200-300 рублей с рынка! Я всё ещё помню те времена, как качал mp3-треки по 2-3 мегабайта через 2G-интернет… слушаешь один трек — как раз загрузится другой и так по кругу наполнял свою фонотеку. Эх времена то какие были! Тем не менее, для некоторых базовых мультимедийных возможностей девайс подходит и сейчас, например в машину в качестве BT-хоста с музыкой. А ещё на таких девайсах порой клёво скачать какой-нибудь Temple Run образца 2011 года и вспомнить самое начало смартфонного гейминга тех лет… ведь далеко не все игры того времени запускаются на свежих версиях Android!
❯ Заключение
В остальном же, подобные девайсы отнюдь не бесперспективны! Несмотря на совсем не новое железо, они всё ещё могут выполнять многие задачи, стоит лишь снова запилить необходимые приложения для них! Мессенджеры, соц. сети, музыкальные сервисы и даже просмотр видео — всё это доступно даже для таких, казалось бы, «устаревших» девайсов, когда есть запал энтузиазма и жгучее желание походить именно с этим конкретным устройством как с основным!
Для кого-то это просто проявление синдрома утенка или картинки «вот кому-то делать не.»… ну а для меня — это крайне интересное, захватывающее и кайфовое времяпровождение: начиная от аппаратного ковыряния с такими девайсами и копания исходников ядер/драйверов, заканчивая написанием оптимизированных клиентских приложений, которые весят не 100-200Мб, а 100-200Кб :)
Друзья, если у вас есть подобные китайчики и вы не разделяете желания пытаться вдохнуть в них жизнь, но выбрасывать их жалко — можете задонатить их мне :) Как сами видите — девайсы попадают в хорошие руки. Из недавнего — я взял нерабочую, утопленную китайскую копию 14 Pro Max из под СЦ в качестве основного смартфона. Также у меня есть канал в Telegram, куда я выкладываю бэкстейджи статей, различные заметки о ремонте, моддинге, программировании и реверс-инжиниринге и свои мысли. Кому интересно — залетайте!
Понравилась ли вам статья? Какими были ваши первые Android-смартфоны? Пишите в комментариях, будет интересно почитать!
Пожалуй, многие из вас помнят, какими были мобильные игры до и после выхода первого iPhone. В начале 2000-х годов, ещё до появления яблочного смартфона, игры для телефонов в основном были весьма интересными, но тем не менее, достаточно простенькими с точки зрения графики и реализации в целом. После запуска AppStore в 2008 году, на iPhone начали выходить самые разные красочные, невиданные раннее по уровню детализации и проработке 2D и 3D игры. Но появление таких игр — отнюдь не заслуга Apple, а относительной малоизвестной компании PowerVR (подразделение Imagination Tech), которая смогла разработать на базе видеочипа Dreamcast и внедрить один из первых действительно массовых мобильных 3D-ускорителей, имя которому — PowerVR MBX! Сейчас мы с вами привыкли, что почти любой дешевый смартфон может отрисовывать графику уровня PS3 в 1080p, а то и выше, но когда-то даже уровень PS2 был роскошью… Сегодня мы с вами: узнаем предысторию появления аппаратно-ускоренной 3D-графики на телефонах, рассмотрим такую фирменную фишку PowerVR, как тайловый рендеринг, а в практической части статьи нам поможет легендарный КПК Dell Axim X51v с MBX на борту, под который мы напишем 3D-игру «про жигули» с нуля! Интересно? Тогда добро пожаловать под кат!
❯ Мобильная 3D-графика. Начало
Пожалуй, 3D-графика на мобильных устройствах начала развиваться ещё с самого начала 2000-х годов. К тому моменту, как мобильные телефоны научились запускать сторонние Java-приложения, практически сразу же появился прибыльный рынок мобильных игр. Ещё до появления поддержки jar-приложений, люди ставили рекорды в «Змейке» на телефонах Nokia, таскали ящики в «Строителе» на Siemens и играли в другие предустановленные игры на девайсах других брендов, поэтому было очевидно, что игры на мобильных телефонах рано или поздно смогут занять немалую часть сегмента портативных игровых устройств.
Именно появление J2ME дало тот самый толчок для развития мобильного гейминга. Производители телефонов активно развивали и дорабатывали мобильную платформу, добавляя в неё различные API-расширения — например, активацию приложений через СМС и доступ в WAP-интернет. Сама платформа J2ME была достаточно простой для изучения и имела низкий порог вхождения не только для людей, имевших какой-то опыт программирования, но даже для совсем новичков, которые никогда не писали код и тем более игр! Благодаря этому, появились сотни игр, многие из которых до сих пор помнят и любят: это и легендарный «мячик» Bounce, и «зайчик с морковками» Bobby Carrot, и весьма крутой Gish, а также множество различных платформеров по известным фильмам и «большим» играм!
В 2003 году появился Nokia N-Gage — первый массовый телефон, ориентированный именно на мобильный гейминг, который поддерживал не только Java-игры, но и собственные Symbian-игры с достаточно крутой 3D-графикой! Примерно в том же 2003 году, для платформы Java вышло сразу два API-расширения, которые добавляли поддержку симпатичной 3D-графики даже в самые простенькие и бюджетные телефоны: Mobile 3D Graphics (M3G, была почти везде) и Mascot Capsule (эта платформа была только на Sony Ericsson и Motorola). Именно благодаря этим API, мы с вами увидели такие легендарные игры, как V-Rally, Galaxy on Fire, Deep3D и многие другие! Но тем не менее, эти API были относительно медленными из-за программной растеризации на процессоре без отдельного 3D-ускорителя и весьма ограниченными в функционале. Ближайший пример по функционалу — уровень софтрендера первой кваки на первом Pentium! Кстати, про 3D на мобильных телефонах я писал отдельную статью, там в практической части мы пишем 3D-бродилку для Sony Ericsson!
Но помимо кнопочных телефонов, существовал сегмент High-end мультимедийных устройств, которые предоставляли гораздо больший функционал и производительность за немалые деньги. И речь, конечно же, о КПК! Девайсы, работавшие на базе шустрых процессоров Intel PXA и Samsung S3C с Windows Mobile на борту были заметно более перспективными для игр… но как-то не задалось из-за отсутствия нормальных каналов для распространения. Но тем не менее, Intel (иронично, но один из самых больших производителей ARM-чипсетов для КПК в те годы), которая уже занималась развитием десктопной графики GMA и PowerVR активно работали в этой сфере и результатом стало появление видеоускорителя 2700G, который представлял из себя не только 3D GPU PowerVR MBX Lite, но и аппаратный декодер видео, позволявший смотреть видео в высоком качестве! MBX Lite позволял запустить даже Quake 3 в 640x480 (!), пусть и в 10-15 FPS… Ещё за 5 лет до этого, далеко не все десктопные видеокарты могли выдать больше 30 FPS в 800x600!
Конечно в 2004 году уже вышел PSP, выставивший новую планку уровня 3D-графики для портативного гейминга, однако для смартфонов и КПК, уровень графики, разрешение и производительность 3D-игр на MBX Lite был просто немыслимым! Одним из самых легендарных и популярных устройств с 2700G, которое вы можете приобрести достаточно дешево и сейчас, был КПК Dell Axim X51v, флагманская модель с VGA-дисплеем тех лет. Но нельзя сказать, что только PowerVR работала в этом направлении. Параллельно NVidia выпустили GoForce, крайне редко попадающийся в «полноценном» виде (NVidia предлагала дешевле лицензировать только видео-декодер с отключением 3D-части, как это было в Toshiba Portege G900) и ATI Imageon, который чаще всего можно встретить в виде Adreno на ранних Android-чипсетах Qualcomm (Adreno — анаграмма Radeon :)).
Тем не менее, решение PowerVR было действительно массовым: компания не предлагала отдельный чип (что обычно было дороже), как конкуренты, а лицензировала другим компаниям уже готовые IP-ядра, которые производители чипов могли синтезировать и использовать в своих собственных чипсетах, или, сопроцессорах, как в случае с 2700G. Благодаря этому, MBX появился в чипсете TI OMAP 2430, использовавшийся в легендарных Nokia N93i и Nokia N95, Samsung INNOV8, Asus Lamborghini, Nokia E90 и некоторых других. Кроме того, PowerVR MBX использовался в процессоре Samsung S5L8900, судя по всему, разработанный для iPhone 2G и 3G! Благодаря этому, его можно считать одним из первых массовых 3D GPU в телефонах!
Одна из игр для iPhone 2G и N95 — Assasins Creed
И Asphalt 5!
Весьма симпатично, согласитесь?
❯ Под капотом
Но MBX, конечно же, не появился «из ниоткуда» и был основан на более ранних разработках компании Imagination Tech, а именно GPU из полноценной домашней консоли SEGA Dreamcast — PowerVR CLX2, который в свою очередь был основан на ранних десктопных GPU PowerVR из середины-конца 90-х годов. Основная фишка PowerVR была в использовании так называемой техники отложного тайлового рендеринга (TBDR), которая, в отличии от классической растеризации и сортировки с помощью Z-буфера (или ручной сортировки треугольников) всех примитивов «в лоб» (методика, используемая в PSP, PS2 и большинстве видеокарт 2000-х годов), сначала ждёт от программы списка всех рисуемых треугольников в кадре, разбивает весь экран на тайлы (небольшие прямоугольные области), которые содержат в себе информацию о пересекающихся треугольниках, а затем процессом, несколько схожим с рейтрейсингом, определяет, какой из пикселей треугольника ближе всего находится к камере наблюдателя. Таким образом, мы избавляемся от необходимости сортировки геометрии с помощью Z-буфера (который сам по себе занимает достаточно много, по меркам тех лет, памяти и страдает от проблем точности и Z-fighting'а), а также такой метод позволяет реализовать более дешевый альфа-блендинг без ручной сортировки полупрозрачных примитивов и имеет ещё одну приятную фишку — «бесплатный» Occlusion Query, который можно использовать для реализации продвинутых техник отсечения невидимой глазу геометрии.
Производительность PowerVR MBX была весьма достойной для своих лет: при частоте работы в 200МГц, видеочип обеспечивал филлрейт в 100Мп, обрабатывал до 1млн треугольников в секунду. Нативным графическим API MBX был OpenGL ES 1.1 — специальная урезанная версия OpenGL для встраиваемых устройств, из которой выбросили все ненужное и которая заточена не только под floating-point, но и под fixed-point арифметику. В остальном, особо никаких отличий для программиста по сравнению с обычными GPU не было, можно было без проблем портировать уже существующие приложения для десктопого OpenGL для мобильные девайсы, чем и пользовались энтузиасты при портировании Quake 3 на Nokia E90, КПК и другие девайсы. Также, PowerVR MBX поддерживал D3DM — графический API Windows Mobile, о котором мы поговорим позднее.
Однако PowerVR MBX был GPU с фиксированным конвейером (FFP), а не программируемым, как принято в современных 3D-ускорителях. Что-же такое программируемый и фиксированный конвейер? Давайте разберемся:
Фиксированный конвейер: для того, чтобы задать визуальную составляющую рисуемой геометрии, программист оперирует набором заранее определенных при проектировании видеочипа параметров, которые позволяют управлять внешним видом растеризуемых примитивов. Например, для реализации света, программист задает параметры каждого из 8 источников света влияющих на рисуемый объект. Если программисту необходимо наложить несколько текстур за один проход (например, для реализации плавных переходов текстур на ландшафте или нанесения карты отражений на модель), он оперировал комбайнерами, которые позволяли задавать для каждого сэмплера параметры наложения. Такой подход использовался на десктопных GPU эпохи до GeForce 3 (т. е. примерно до 2000 года), до PS3 на Sony PlayStation (Xbox сразу вышел с GeForce 3) и до PSP включительно на портативках. Очевидно, что такой подход сильно ограничивает программиста в том, как будет выглядеть его игра на той или иной видеокарте.
Программируемый конвейер: в программируемом подходе, для управления визуальной составляющей программист пишет небольшие программы для видеокарты, называемые шейдерами. Всего есть два базовых (в современных GPU их больше) этапа программируемого конвейера: первый из них — вершинный шейдер, отвечающий за трансформацию геометрии (перевод из мировой системы координат в экранную) и, например, анимацию. Трансформированные вершины отправляются в следующий этап конвейера — растеризацию, где выполняется уже пиксельный шейдер, который определяет цвет пикселя (или более корректно — фрагмента в терминологии 3D графики) — т.е например, окрас объекта в определенной цвет, текстуру (или несколько текстур), рассчитывает попиксельное освещение, накладывает тени и т. д. Кроме того, такой подход позволяет реализовать сложные техники типа Ambient Occlusion, SSR, а также пост-эффекты (например блюр/блум, правда эти два можно «сэмулировать» и на FFP при определенной сноровке).
К 2007 году, Khronos выпустили спецификацию второй версии OpenGL ES, которая добавляла в мобильные устройства поддержку программируемого конвейера и шейдеров. Таким образом, мобильные GPU всё ближе приближались к уровню консолей и могли выдавать вполне годную графику, близкую к консолям. Даже была когда-то такая консоль, как Zeebo, которая работала на базе смартфонного чипсета Qualcomm с графикой ATI Imageon (!). PowerVR уже в 2009 выпустила серию SGX, которая также использовалась в iPhone, iPad, многих Android-смартфонах и планшетах, а также PS Vita!
Modern Combat 3 на iPad
Но статья с пересказом фишек PowerVR MBX была бы не особо интересной без практической части с написанием 3D-игры под этот GPU с нуля! Поэтому предлагаю посмотреть на нашего сегодняшнего гостя, легендарный флагманский КПК Dell Axim X51v из далекого 2005 года! Для тех лет, это настоящий «жир»:
Его мне подарил мой читатель Сергей с Хабра, за что ему огромное спасибо! Девайс был в полной комплектации, даже с флэшкой и усиленной АКБ, которая до сих пор неплохо держит заряд, однако у него не работал тачскрин. Если вам интересен только процесс программирования игры, а не аппаратного ремонта, то листайте ниже сразу до следующего абзаца :)
❯ Практическая часть: ремонтируем КПК
По факту, девайс полностью работал, однако в некоторые моменты времени не откликался на кнопки и тачскрин, и по всем симптомам это напоминало дребезг кнопок. При этом тачскрин сам по себе реагировал нормально во всех местах, что, фактически, исключало вероятность его поломки (хотя резистивные тач-панели сами по себе не особо надежные, в отличии от емкостных тачскринов). Дело было вот в чём: во многих КПК тех лет был отдельный аппаратный переключатель блокировки клавиатуры и тачскрина, который можно было использовать при просмотре фильмов. Однако на моем девайсе он был слишком разболтанным…
Разбирается КПК несложно: выкручиваем 4 винта и снимаем переднюю часть корпуса. На всякий случай я прочистил грязь между тачем и верхней частью корпуса — она тоже бывает влияет на ложные нажатия и чувствительность тачскрина:
А вот и виновник наших проблем: рычажок переключателя был отломан, но все еще находится в положении «разблокирован». Даже если в выжать в упор — он все равно не работал. Ну что ж, фен в руки, сдуваем переключатель и ставим вот такую перемычку (на фото флюс ещё не отмыт):
Включаем девайс и смотрим — теперь всё работает! Вот такой простой и быстрый ремонт Axim'а. КПК мне сразу очень понравился, я и ранее знал о его легендарности, но теперь узнал и о том, что он очень круто спроектирован и собран! Кстати, есть смысл сразу сдуть концевой выключатель, который прижимает задняя крышка и заменить на перемычку. GPU не очень хорошо работает на кастомных прошивок, на которую прошиты многие Axim X51v. Поэтому есть смысл прошить сток: качаем прошивку (Файл отката), закидываем на SD-карту и ребутим девайс нажатием клавиш Wi-Fi + включение + Reset. После этого, девайс пойдет прошиваться.
Теперь девайс чистый, как с завода! Можно приступить к написанию небольшой демки-игрушки, которая сможет продемонстрировать нам перспективы нашего КПК в 3D!
❯ Практическая часть: подготовка
Изначально, в практической части статьи должна была участвовать не менее легендарная Nokia N95. Однако вот незадача: несмотря на то, что под Symbian сохранился SDK (который работает нормально только под Windows XP), на устройствах с системой старше 9.x необходимо взламывать installserver, дабы иметь возможность ставить хоумбрю программы (к которым относится и наша игра) и отладчик TRK.
И хотя свой девайс я пропатчил, дебаггер нормально поднять мне так и не удалось. Я смог проинициализировать контекст GLES, запилить примитивный рендерер с загрузкой ассетов из памяти устройства но потом решил перевести проект на WinMobile… Проблем с разработкой под Symbian много: если приложение крашится — то оно просто закрывается, без сообщений и логов. Добавьте к этому то, что в Symbian вообще нет исключений и не всегда можно записать ошибки в лог и отладка превращается в ужас. Ситуацию исправляет Qt, который работает на N95, но в котором нет поддержки GLES (по крайней мере, в виде обычного QOpenGL, хотя возможность юзать API системы из Qt есть и дебаггер там работает нормально, так что не всё потеряно). Если вы когда-то что-то пилили под Symbian, особенно в Carbide — пишите свой опыт в комментариях, интересно почитать :)
WinMobile не менее интересен тем, что в нём поддерживается сразу два графических API: классический OpenGLES в профиле Common Lite (только fixed-point арифметика) и мобильная версия Direct3D — D3DM.dll, которая предоставляет API очень похожее на DX9, но без поддержки шейдеров. Что не менее приятно — есть официальные биндинги от Microsoft к D3DM в .NET Compact Framework, что позволяет легко писать 3D-игры под WM на C#/VB.NET. Поскольку WinMobile — достаточно открытая для пользователя система, хватит лишь накатить VS2005/2008 на машину с WinXP/WinVista/Win7/Win8 и сразу начать разрабатывать под неё приложения, никаких проблем с отладкой и запуском приложений тут нет. На Win10/Win11 совместимость с WM5 поломали :(
Создаём приложение для смарт-устройств, выбираем в качестве целевой платформы WM5-устройство (эмулятор будет слишком медленным для наших целей, он даже для 2D-игр не подойдет) и, наконец-то, приступаем к написанию игры!
Что же за игра у нас будет? Я решил сделать эдакое 3D-переосмысление популярного в прошлом бесконечного раннера из «тетриса», где мы едем на машинке F1 и обгоняем другие машины, стараясь в них не врезаться. Основной целью является набрать как можно больше очков. Подобные игры достаточно популярны на мобильных девайсах и сейчас: вспомнить хотя-бы Highway Traffic, однако мой вариант будет весьма колоритным: ведь в моей демке мы будем кататься на ТАЗе 21099 и уворачиваться от гнилых «вторых гольфов». Ну а почему бы и нет, я просто очень люблю старые гнилые жигули и это не первый мой проект про машины этого производителя :)
❯ Практическая часть: «движок»
Как и у настоящей машины, у каждой игры должен быть собственный движок! Однако в случае конкретно нашей игры, это скорее небольшой фреймворк, который предоставляет ровно тот функционал, который нужен игре без каких либо излишеств. Необходимо изначально распланировать требования для будущего фреймворка, дабы написание игры не скатилось в процесс, известный в узких кругах как «движкописание» :)
Рендерер: с графической точки зрения, фреймворк должен реализовывать весьма небольшой функционал. Загружать геометрию и текстуры из файлов в специально-подготовленном формате, реализовывать концепцию камеры, отрисовывать статическую геометрию, а также спрайты и текст, реализовывать примитивную систему материалов, которая позволяет наносить на геометрию текстуры, красить их в определенный цвет и управлять повершинным освещением, а также наносить на геометрию отражения с помощью специально подготовленных enviornment-текстур. Кроме того, рендерер должен уметь рисовать симпатичное анимированное небо в виде полусферы.
Звук: воспроизведение wav-звуков и музыки из файлов. Да и всё пожалуй — что ещё нужно от звуковой подсистемы? :) Стерео ведь нет, поэтому и 3D-звук не нужен.
Ввод: обработка нажатий на тачскрин и аппаратные кнопки устройства, маппинг кейкодов в виртуальный «геймпад». GUI-подсистему тоже частично можно отнести именно сюда!
Физика: AABB и Sphere vs Sphere столкновения. Никакого полноценного солвера тут и не нужно :)
Начинаем, пожалуй, с реализации рендерера. Сначала нам необходимо создать окно и контекст D3DM. Процесс практически идентичен D3D8 и D3D9: передаём информацию о нужном адаптере (видеочипе) и заполняем структуру PresentationParameters, однако есть важные нюансы: аппаратный FSAA лучше всего отключить (MultisampleQuality), а также передавайте точный размер окна, в которое собираетесь рендерить изображение, иначе система начнёт софтварно (!) скейлить рендертаргет до размера окна каждый кадр, что, как сами понимаете, крайне медленно.
Из форматов Depth-Stencil форматов поддерживается D16, D24S8 и D32. Желательно использовать D16 (несмотря на тайловую архитектуру, насколько мне известно, в MBX все равно есть fallback до классического рендеринга при некоторых условиях). Практически на всех КПК и коммуникаторах использовался 16-битный цвет, т.е RGB565, но можно указать Unknown — тогда GAPI подцепит тот формат пикселя, что используется в остальной системе.
Переходим сразу же к рисованию геометрии! Для начала рендеринга, нам необходимо подготовить состояние контекста: посчитать и установить матрицы вида (т. е. камеры) и проекции для трансформации геометрии, задать рендерстейты (список состояний, например нужно ли рисовать модельку с освещением, или нет), очистить экран и Z-буфер и установить параметры фильтрации текстур. Перспективная коррекция текстур — достаточно тяжелая операция и использовать её стоит лишь при необходимости:
public void EndScene() { device.EndScene(); device.Present();
System.Threading.Thread.Sleep(16); }
Чтобы какую-то модельку нарисовать, нам нужно сначала её загрузить! Для возможности напрямую прочитать треугольники из файла и сразу записать их в вершинный буфер, я написал небольшой конвертер из формата SMD (GoldSrc) в собственный, очень простой и легковесный формат, который состоит из позиции вершины и её текстурных координат:
foreach (SmdTriangle triangle in mesh.Triangles) { for (int i = 0; i < 3; i++) { writer.Write(FloatToFixedPoint(triangle.Verts[i].Position.X)); writer.Write(FloatToFixedPoint(triangle.Verts[i].Position.Y)); writer.Write(FloatToFixedPoint(triangle.Verts[i].Position.Z));
Обратите внимание, PowerVR MBX оперирует fixed-point арифметикой! D3DM, конечно, может автоматически преобразовывать float-координаты вершин в числа с фиксированной точкой, вот только реализовано это криво и косо: драйвер будет конвертировать все вершины в fixed-point каждый вызов отрисовки, вместо того, чтобы один раз преобразовать их после Unlock'а вершинного буфера. Теперь представьте, насколько это тормозно для хоть сколь-либо комплексной модели :)
При этом загрузчик модели при таком подходе будет очень простым и будет работать шустро даже на таком слабеньком железе:
public Model(string debugName, Stream strm) { BinaryReader reader = new BinaryReader(strm);
int hdr = reader.ReadInt32(); int numVerts = reader.ReadInt32(); int vertSize = 20;
Переходим к текстурам. Грузить напрямую png/jpg на КПК слишком долго, поэтому их я тоже перегоняю в собственный примитивный формат, который состоит из описания ширины/высоты, а также формата текстуры и собственно, самих пикселей. На данный момент поддерживаются только RGB565 текстуры — с ними MBX работает лучше всего:
public sealedclass TextureConverter { publicconstint Header = 0x1234;
Загрузчик тоже получился примитивным и шустрым донельзя, пусть и без какой либо компрессии. PowerVR MBX поддерживает собственный формат компрессии — PVRTC:
BinaryReader reader = new BinaryReader(strm);
int hdr = reader.ReadInt32(); int fmt = reader.ReadInt32();
Handle = new Texture(Engine.Current.Graphics.device, Width, Height, 1, Usage.Lockable, Format.R5G6B5, Pool.VideoMemory);
int pitch; GraphicsStream gs = Handle.LockRectangle(0, LockFlags.None, out pitch); gs.Write(data, 0, data.Length); Handle.UnlockRectangle(0);
strm.Close();
Переходим, наконец, к фактическому рисованию модели! Для этого мы строим мировую матрицу для трансформации нашей модели, а также задаем вершинный буфер для, собственно, вершинного конвейера и посылаем видеочипу команду отрисовки. ZBufferWriteEnable нужен для отрисовки геометрии без записи в Z-буфер, что можно использовать, например, для реализации скайбоксов:
public void DrawModel(Model model, Transform transform, Material material) { Matrix matrix = Matrix.RotationY(transform.Rotation.Y * MathUtils.DegToRad) * Matrix.Translation(transform.Position); device.SetTransform(TransformType.World, matrix);
void Start() { model = Model.FromFile("model.mdl");
mat = new Material(); mat.Diffuse = Texture2D.FromFile("test.tex"); }
void Update() { t = new Transform(); t.Position.Z = 150; t.Rotation.Y += 0.1f;
graphics.DrawModel(model, t, mat); }
Результат: у нас есть крутящийся кубик или любая другая произвольная 3D-модель!
Переходим к обработке ввода. Тут ничего сложного нет, ловим события KeyUp/KeyDown формы и назначаем виртуальным кнопкам их состояние.
public Input(Form parentForm) { keyState = newbool[(int)GamepadKey.Count];
parentForm.KeyPreview = true;
parentForm.KeyDown += new KeyEventHandler(OnKeyDown); parentForm.KeyUp += new KeyEventHandler(OnKeyUp); }
private GamepadKey ResolveKeyCode(Keys key) { GamepadKey k = GamepadKey.Count;
switch (key) { case Keys.Left: k = GamepadKey.Left; break; case Keys.Right: k = GamepadKey.Right; break; case Keys.Up: k = GamepadKey.Up; break; case Keys.Down: k = GamepadKey.Down; break; case Keys.Return: k = GamepadKey.OK; break; }
Теперь мы сможем управлять нашей машинкой в игре (которой пока ещё нет). Самая-самая основа для реализации игры подобного плана у нас есть, пора переходить к геймплею!
Она полностью поглощает звук изнутри и снаружи. Девайс надо надеть на лицо и подключить к телефону. После манипуляций окружающие не смогут услышать то, что вы говорите, а собеседника не будет отвлекать шум вокруг вас.
Отлично сочетается с AirPods с включенным шумоподавлением.
Размер экрана — краеугольный камень мира современных смартфонов. Кто-то считает, что дисплеи должны становиться только больше, а рамки — меньше, кто-то любит «средние» дисплеи диагональю в 5+", ну а кто-то остаётся ярым поклонником и приверженцем компактных смартфонов с крошечными дисплейчиками. В наше время, купить новый смартфон с относительно небольшим дисплеем за приемлемые деньги почти нереально — самые бюджетные модели будут слишком тормозными для современного пользователя. Некоторое время назад, я купил себе бюджетный крошечный смартфон 2012 года выпуска — Samsung Galaxy Pocket, причём всего за 100 рублей. Конечно же мне захотелось довести его до ума — а доводить пришлось руками и навыками прожженного программера! Какой смартфон можно получить за 100 рублей? Читаем в статье!
Минутка предыстории
С самого появления смартфонов на рынке, весь мир шагал к тотальному увеличению дисплеев и уменьшению рамок. В какой-то момент, большие смартфоны даже получили отдельное название — падфоны или смартпэды. Такой ход событий было не трудно предугадать: ведь производители дисплейных матриц осваивали всё более и более высокие разрешения и предлагали больше вариантов производителям смартфонов.
Однако несмотря на всеобщее засилие больших «лопат», в мире всё ещё оставались поклонники маленьких и компактных телефонов, которыми очень удобно пользоваться одной рукой. Сейчас подобные устройства представляют только небольшие бренды, известные достаточно в узких кругах — в основном, их можно купить на маркетплейсах, в обычных салонах связи их не найти. Мне известно о нескольких подобных устройствах, которые сейчас присутствуют на рынке. Первый из них «закос» под iPhone — Soyes XS11:
Но тут уж, если честно, хочется назвать такой смартфон не просто компактным, а совсем малюсеньким. На нём вполне удобно выполнять задачи звонилки, но совсем неудобно набирать текст — поэтому под наши задачи, он не особо подходит. Кроме того, эти девайсы работают на базе бюджетного смартфонного железа 6-7 летней давности, поэтому их производительность будет достаточно невысокой по меркам современного пользователя. Конечно же есть и более серьёзные варианты — например, компания Unihertz (да, тот самый продолжатель идей BlackBerry) делает смартфоны Jelly 2: дисплей с диагональю 3", Helio P61 под капотом и Android 11 на борту. Вот только цена, мягко говоря, кусачая — 18 тысяч рублей на момент написания статьи. Это слишком дорого!
Но если душа прямо таки лежит к компактным смартфонам, почему бы не обратиться к рынку Б/У устройств и не присмотреть что-то из… прошлого десятилетия? А вариантов ведь реально много — тут и LG Optimus L3 (3.2"), и Samsung Galaxy Pocket Neo (2.8"), Samsung Galaxy Star (3"), Samsung Galaxy Fame (3.5"), Samsung Galaxy Young. Все перечисленные девайсы стоят реально копейки — можно купить живой вариант до 400-500 рублей!
Я решил взять себе целых два смартфона: Samsung Galaxy Mini и Samsung Galaxy Pocket первого поколения. Оба достались мне в одном лоте за 2.000 рублей (с 20 телефонами) и обошлись мне по сто рублей, причём оба смартфона были рабочими! Чуть позже я докупил отдельно Galaxy Star (250 рублей), Galaxy Fame (250 рублей) и Galaxy Pocket Neo (~400 рублей) для полноты коллекции — вышло совсем недорого. Итак, что за характеристики мы получаем в смартфоне за 100 рублей:
Android: 2.3 Gingerbread.
Чипсет: Broadcom BCM21553 с одним ядром Cortex-A5 на частоте 832мгц. Видеочип: VideoCore IV, он же использовался в Raspberry Pi.
ОЗУ: 256 мегабайт (предположительно — DDR1).
Встроенная память: 3 гигабайта + слот для SD.
Дисплей: 2.8", 240x320, емкостной тачскрин.
Сеть: Поддержка 2G/3G. Об LTE и речи не идёт.
Выглядит не особо густо, да? И разрешение весьма низкое — большинство софта не запустится, а о клиентах современных сервисов и мечтать не приходится… или приходится?
Конечно же шаловливым ручкам захотелось вернуть жизнь этому миниатюрному красавцу и я решил использовать его как второй смартфон — при этом с клиентом ВК и музыкой, которые я запилил сам.
Разработка под старые версии Android
На самом деле, разработка под старые версии Android не особо отличается от современных версий системы. Кое-где приходится костылить, велосипедить и юзать AppCompat для реализации современных фишек на старых версий системы, но, будем честным, подобного и в последних версиях Android достаточно.
Даже сейчас нет никакой проблемы скачать последнюю версию Android Studio, подключить смартфон с включенной отладкой и отлаживать приложение прямо на девайсе — logcat тоже есть. Единственный нюанс — поиск драйверов и ручное закрытие приложений в таскменеджере, если вы деплоите под Android 2.x (Android Studio не умеет сам закрывать приложение, чтобы переустановить пакет).
В целом, за всё время разработки под старые устройства, я пришёл к следующим выводам:
Поскольку большинство устройств имеет одно ядро, для плавности интерфейса нужно минимизировать любую работу в фоне.
Взаимодействие с современными веб-сервисами может быть осложнено из-за отсутствия поддержки TLS1.2 и устаревших сертификатов (проверка сертификатов легко обходится специальным костылем, а вот TLS — нет).
У Android до 3.0 вся отрисовка интерфейса программная и она опять же, будет сказываться на скорости работы фоновых служб. Чем менее интерфейс комплексный, тем лучше.
Пушей нет — да, вообще. Однако это ничуть не помешает нам сделать уведомления практически в реальном времени с помощью… очередного костыля!
Допиливаем ВК
Я уже писал клиент ВК в рамках одной из прошлых статей. Теперь нам нужно довести его до ума — подогнать под разрешение экрана и переработать интерфейс для большей удобности, а также добавить недостающие разделы — я тот ещё любитель полистать мемчики, сидя в автобусе.
Честно сказать, вся концепция интерфейса требовала полной переработки — боковое меню банально очень неудобно использовать на подобных устройствах из-за малых размеров каждой строчки. Поэтому я решил не изобретать велосипед, а обратился к дизайнерам Apple и первоисточнику: официальному клиенту ВК для iOS 6, родом из 2012 года!
Приложение для Android выглядело +- также в те годы. Видите вкладки с разделами снизу? Они то нам и нужны — это самый удобный способ навигации на таких смартфонах! Накидав макет в layout'е, я приступил к реализации:
Изначально мне хотелось, чтобы всё приложение было плавным и анимированным: для этого я обратился к фреймворку анимаций Android. Суть очень простая — это обычный интерполятор значений от a до b за определенный промежуток времени. При этом мы не можем анимировать произвольное свойство — только те, который уже реализованы в системе (переход, поворот, масштабирование, альфа-канал). Более наглядно это можно представить вот так:
Да, это всё анимация :) Получаем примерно такой результат:
Обратите внимание, что запуск большого количества анимаций будет вызывать перерисовку даже в том случае, если элемент не видно на экране — от чего у нас будут дикие тормоза! Осторожнее с этим.
После этого, я решил доработать раздел с музыкой: я все еще пользуюсь грязными хаками для получения доступа к API музыки, поскольку «левым» клиентам такой возможности не дают. Публично его расписывать не буду, поскольку это скорее всего нелегально, да и сами ребята из ВК об этом знают (но не думаю, что будут применять какие-то санкции по отношению к «маленьким» разработчикам) — но если нужно, пишите в личку, расскажу всю концепцию.
Во первых, мне хотелось добавить возможность скачивать треки на внутреннюю память/флэшку. А во вторых, мне хотелось добавить фоновое воспроизведение — до этого возможность свернуть приложение и послушать музыку уже была, однако Android мог в любой момент прибить окно с музыкой и оставить нас с носом, остаётся только реализация в виде foreground-сервиса:
В Android есть два типа служб: background (фоновые) и foreground (видимые пользователю). Первый тип служб система может прибить когда угодно — например мало памяти или экономия заряда АКБ. А вот второй тип служб система не прибивает практически никогда, поскольку они обозначают выполнение важной операции в фоне — например скачивание файла или обновление системы. Однако у них есть одно ограничение — они должны быть привязаны к собственному уведомлению, которое нельзя закрыть. В процессе реализации возникло еще пару проблем — Wakelock'и (механизм, предотвращающий уход девайса в «сон») и WiFiLock'и (тоже самое, но для WiFi).
Точно таким же способом я реализовал механизм уведомлений — как я уже говорил раньше, пушей на старых смартфонах нет вообще ни в каком виде, поэтому пришлось реализовывать свой механизм «обновления»: каждые 3-5 секунд запрашиваем список последних 5 диалогов с сервера и сравниваем с предыдущим результатом, если есть новые сообщения — создаём нотификацию (листинг слишком длинный - пришлось перезалить на pastebin):
После этого, я начал рутинную работу по реализации интерфейса для данных с сервера — паблики, друзья, профили, лента и.т.п. В некотором смысле, реализация лента весьма занимательна: вообще, для очень больших списков существуют т.н виртуализация ListView — это когда ListView отображает только видимый пользователю кусок датасета (набора данных — например, список записей на стене) и на старых версиях Android она доступна. Однако мне было интересно реализовать вариант, который потреблял бы минимальное количество ОЗУ и где я точно знал бы, когда пользователь видит тот или иной фрагмент приложения. Поэтому я реализовал… пагинацию свайпами! Вот так привет из нулевых!
Для этого я использовал GestureDetector — встроенный в систему класс для обнаружения простых жестов — свайпов и.т.п. ВК при запросе ленты отдаёт специальную метку для получения следующей страницы новостей (поскольку она может динамически меняться и нужно хранить её стейт), мы эти метки просто сохраняем и переключаемся по странницам новостей с помощью обычных свайпов вправо-влево:
Выглядит весьма забавно.
Юзабельно ли всё это на деле?
Давайте смотреть, может ли юзать такой смартфон в наши дни. Берём наш девайс в руки, логинимся и оцениваем его производительность «вхолостую».
Работает весьма шустренько, учитывая что это бюджетник 2012 года. Как насчет нашего самопального клиента ВК? Смотрим:
Работает весьма бодро. Не сказать что также плавно, как последний айфон, но и совсем плохим результат явно не назвать!
Смартфонный функционал у девайса тоже вполне ничего: 1-2 SIM (в зависимости от версии), нормальная синхронизация контактов с ПК (однако Kies вроде-бы не работает на Windows 10, но есть vcf):
Встроенный почтовый клиент продолжает работать без каких либо проблем. Однако настраивать некоторые почтовые сервисы нужно вручную и с помощью «паролей приложений» — напрямую залогинится возможности нет. В случае «покета», придется поставить стоковый клиент из Android 2.3 вручную. Мультимедийные возможности тоже радуют: встроенный плеер тачвиза мне всегда очень нравился. Есть и настройки эквалайзера.
Единственное, что откровенно подводит — браузер. Последним вариантом осталась Opera Mini 7 — она позволяет смотреть сайты, но не поддерживает динамический контент, только статику. Ну, зайти на википедию или почитать статью на Хабре хватит. Родной браузер уже не в состоянии что либо загрузить :(
Ну а в общем, производителньость смартфона весьма радует, согласитесь? Нельзя сказать, что он уж слишком тормозной — по крайней мере, современные ультрабюджетные смартфоны (до 4-5 тысяч рублей) зачастую показывают себя гораздо хуже чем и флагманы прошлых лет, и даже бюджетники!
Заключение
И всё таки, я считаю что мне удалось в каком-то смысле вдохнуть новую жизнь в старенький девайс. Если использовать подобный девайс как второй — на случай, если сел основной смартфон, то такой миниатюрный красаввчик может неождианно выручить даже в довольно сложной ситуации. Кроме того, эти смартфоны всеядны к аккумуляторам — достаточно подпаять + и — и они будут работать хоть от BL-4C.
Главная ценность Galaxy Pocket — в его компактных размерах. А поскольку по настоящему дешевых, маленьких и шустрых смартфонов становится всё меньше и меньше, то нам остаётся лишь продлять жизнь моделям прошлых лет! Есть ли в этом смысл и получил ли смартфон новую жизнь? Пишите в комментариях!
Клиент ВК можно сказать на 4pda. Там лежит самая последняя версия (для скачивания нужна регистрация на форуме). Если по каким-то причинам не хотите регистрироваться на форуме — я выложил актуальную версию в комментариях.
Многие программисты так или иначе имеют тягу и интерес к разработке игр. Немалое количество спецов было замечено за написанием маленьких и миленьких игрушек, которые были разработаны за короткое время «just for fun». Большинству разработчиков за счастье взять готовый игровой движок по типу Unity/UE и попытаться создать что-то своё с их помощью, особенно упорные изучают и пытаются что-то сделать в экзотических движках типа Godot/Urho, а совсем прожжённые ребята любят писать игрушки… с нуля. Таковым любителем писать все сам оказался и я. И в один день мне просто захотелось написать что-нибудь прикольное, мобильное и обязательно — двадэшное! В этой статье вы узнаете про: написание производительного 2D-рендерера с нуля на базе OpenGL ES, обработку «сырого» ввода в мобильных играх, организацию архитектуры и игровой логики и адаптация игры под любые устройства. Интересно? Тогда жду вас в статье!
❯ Как это работает?
Конечно же разработка собственных игр с нуля — это довольно веселое и увлекательное занятие само по себе. Ведь удовольствие получает не только пользователь, который играет в уже готовую игру, но и её разработчик в процессе реализации своего проекта. В геймдеве есть множество различных и интересных задач, в том числе — и для программиста.
Один из прошлых проектов — 3D шутэмап под… коммуникаторы с Windows Mobile без видеоускорителей! Игра отлично работала и на HTC Gene, и на QTek S110!
В больших студиях принято всю нагрузку распределять на целые команды разработчиков. Артовики занимаются графикой, звуковики — музыкой и звуковыми эффектами, геймдизайнеры — продумывают мир и геймплей будущей игры, а программисты — воплощают всё это в жизнь. Однако, за последние 20 лет появилось довольно большое количествобесплатныхинструментов, благодаря которым маленькие команды или даже разработчики-одиночки могут разрабатывать собственные игры сами!
Подобные инструменты включают в себя как довольно функциональныеконструкторы игр, которые обычно не требуют серьёзных навыков программирования и позволяют собирать игру из логических блоков, так и полноценных игровых движков на манер Unity или Unreal Engine, которые позволяют разработчикам писать игры и продумывать их архитектуру самим. Можно сказать что именно «благодаря» доступности подобных инструментов мы можем видеть текущую ситуацию на рынке мобильных игр, где балом правят очень простые и маленькие донатные игрушки, называемыегиперкежуалом.
Но у подобных инструментов есть несколько минусов, которые банально не позволяют их использовать в реализации некоторых проектов:
Большой вес приложения: При сборке, Unity и UE создают достаточно объёмные пакеты из-за большого количества зависимостей. Таким образом, даже пустой проект может спокойно весить 50-100 мегабайт.
Неоптимальная производительность: И у Unity, и у UE очень комплексные и сложные рендереры «под капотом». Если сейчас купить дешевый смартфон за 3-4 тысячи рублей и попытаться на него накатить какой-нибудь 3 в ряд, то нас ждут либо вылеты, либо дикие тормоза.
Лично я для себя приметил ещё один минус — невозможность деплоить игры на устройства с старыми версиями Android, но это, опять же, моя личная хотелка.
Поэтому когда мне в голову пришла мысль сделать игрушку, я решил написать её с нуля — не используя никаких готовых движков, а реализовав всё сам — и игровую логику, и сам «движок» (правильнее сказать фреймворк). Не сказать, что в этом есть что-то очень сложное — в геймдеве есть отдельная каста «отшельников», которые называют себя «движкописателями» и пишут либо движки, либо игры — правда, не всегда хотя-бы одна игра доходит до релиза.
❯ Определяемся с задачами
Перед тем, как садится и пилить игрушку, нужно сразу же определится с целями и поставить перед собой задачи — какой стек технологий мы будет использовать, как будем организовать игровую логику, на каких устройствах игра должна работать и.т.п. Я прикинул и решил реализовать что-то совсем несложное, но при этом достаточно динамичное и забавное… 2D-шутер с видом сверху!
Игра будет написана полностью на Java — родном языке для Android-приложений. Пустые пакеты без зависимостей весят всего около 20 килобайт — что только нам на руку! Ни AppCompat, ни какие либо ещё библиотеки мы использовать не будем — нам нужен минимальный размер из возможных!
Итак, что должно быть в нашей игре:
Основная суть: Вид сверху, человечком по центру экрана можно управлять и стрелять во вражин. Цель заключается в том, чтобы набрать как можно больше очков перед тем, как игрока загрызут. За каждого поверженного врага начисляются баксы, за которые можно купить новые пушки!
Оружие: Несколько видов вооружения, в том числе пистолеты, дробовики, автоматы и даже пулеметы! Всё оружие можно купить в внутриигровом магазине за валюту, которую игрок заработал во время игры
Враги: Два типа врагов — обычный зомби и «шустрик». Враги спавнятся в заранее предусмотренных точках и начинают идти (или бежать) в сторону игрока с целью побить его.
Уровни: Можно сказать, простые декорации — на момент написания статьи без какого либо интерактива.
Поскольку игра пишется с нуля, необходимо сразу продумать необходимые для реализации модули:
Графика: Аппаратно-ускоренный рендерер полупрозрачных 2D-спрайтов с возможность аффинных трансформаций (поворот/масштаб/искривление и.т.п). На мобильных устройствах нужно поддерживать число DIP'ов (вызовов отрисовки) как можно ниже — для этого используется техника батчинга. Сам рендерер работает на базе OpenGLES 1.1 — т.е чистый FFP.
Ввод: Обработка тачскрина и геймпадов. Оба способа ввода очень легко реализовать на Android — для тачскрина нам достаточно повесить onTouchListener на окно нашей игры, а для обработки кнопок — ловить события onKeyListener и сопоставлять коды кнопок с кнопками нашего виртуального геймпада.
Звук: Воспроизведение как «маленьких» звуков, которые можно загрузить целиком в память (выстрелы, звуки шагов и… т.п), так и музыки/эмбиента, которые нужно стримить из физического носителя. Тут практически всю работу делает за нас сам Android, для звуков есть класс — SoundPool (который, тем не менее, не умеет сообщать о статусе проигрывания звука), для музыки — MediaPlayer. Есть возможность проигрывать PCM-сэмплы напрямую, чем я и воспользовался изначально, но с ним есть проблемы.
«Физика»: Я не зря взял этот пункт в кавычки :) По сути, вся физика у нас — это один метод для определения AABB (пересечения прямоугольник с прямоугольником). Всё, ни о какой настоящей физике и речи не идет :)
Поэтому, с учетом требований описанных выше, наша игра будет работать практически на любых смартфонах/планшетах/тв-приставках кроме китайских смартфонов на базе чипсета MT6516 без GPU из 2010-2011 годов. На всех остальных устройствах, включая самый первый Android-смартфон, игра должна работать без проблем. А вот и парк устройств, на которых мы будем тестировать нашу игру:
С целями определились, самое время переходить к практической реализации игры! По сути, её разработка заняла у меня около дву-трех дней — это с учетом написания фреймворка. Но и сама игра совсем несложная :)
❯ Рендерер
Начинаем, мы конечно же, с инициализации контекста GLES и продумывания архитектуры нашего будущего фреймворка. Я всегда ставил рендерер на первое место, поскольку реализация остальных модулей не особо сложная и их можно дописать прямо в процессе разработки игры.
По сути, в современном мире, 2D — это частный случай 3D, когда рисуются всё те же примитивы в виде треугольников, но вместо перспективной матрицы, используется ортографическая матрица определенных размеров. Во времена актуальности DirectDraw (середина-конец 90х) и Java-телефонов, графику обычно не делали адаптивной, из-за чего при смене разрешения, игровое поле могло растягиваться на всю площадь дисплея. Сейчас же, когда разброс разрешений стал колоссальным, чаще всего можно встретить два подхода к организацию проекции:
Установка ортографической матрицы в фиксированные размеры: Если координатная система уже была завязана на пиксели, или по какой-то причине хочется использовать именно её, то можно просто завязать игру на определенном разрешении (например, 480x320, или 480x800). Растеризатор формально не оперирует с пикселями — у него есть нормализованные координаты -1..1 (где -1 — начало экрана, 0 — середина, 1 — конец, это называется clip-space), а матрица проекции как раз и переводит координаты геометрии в camera-space координатах в clip-space — т.е в нашем случае, автоматически подгоняет размеры спрайтов из желаемого нами размера в физический. Обратите внимание, физические движки обычно рассчитаны на работу в метрических координатных системах. Попытки задавать ускорения в пикселях вызывают рывки и баги.
Перевод координатной системы с пиксельной на метрическую/абстрактную: Сейчас этот способ используется чаще всего, поскольку именно его используют самые популярные движки и фреймворки. Если говорить совсем просто — то мы задаем координаты объектов и их размеры не относительно пикселей, а относительно размеров этих объектов в метрах, или ещё какой-либо абстрактной системы координат. Этот подход близок к обычной 3D-графике и имеет свои плюшки: например, можно выпустить HD-пак для вашей игры и заменить все спрайты на варианты с более высоким разрешением, не переделывая половину игры.
Для совсем простых игр я выбираю обычно первый подход. Самое время реализовать главный метод всего рендерера — рисование спрайтов. В моём случае, спрайты не были упакованы в атласы (одна текстура, содержащая в себе целую анимацию или ещё что-то в этом духе), поэтому и возможность выборки тайла из текстуры я реализовывать не стал. В остальном, всё стандартно:
Всё более чем понятно — преобразуем координаты спрайта из world-space в camera-space, отсекаем спрайт, если он находится за пределами экрана, задаем стейты для GAPI (на данный момент, их всего два), заполняем вершинный буфер геометрией и рисуем на экран. Никакого смысла использовать VBO здесь нет, а на nio-буфферы можно получить прямой указатель без лишних копирований, так что никаких проблем с производительностью не будет. Обратите внимание — вершинный буфер выделяется заранее — аллокации каждый дравколл нам не нужны и вредны.
Обратите внимание на вызовы ByteBuffer.order — это важно, по умолчанию, Java создаёт все буферы в BIG_ENDIAN, в то время как большинство Android-устройств — LITTLE_ENDIAN, из-за этого можно запросто накосячить и долго думать «а почему у меня буферы заполнены правильно, но геометрии на экране нет!?».
В процессе разработки игры, при отрисовке относительно небольшой карты с большим количеством тайлов, количество вызовов отрисовки возросло аж до 600, из-за чего FPS в игре очень сильно просел. Связано это с тем, что на старых мобильных GPU каждый вызов отрисовки означал пересылку состояния сцены видеочипу, из-за чего мы получали лаги. Фиксится это довольно просто: реализацией батчинга — специальной техники, которая «сшивает» большое количество спрайтов с одной текстурой в один и позволяет отрисовать хоть 1000, хоть 100000 спрайтов в один проход! Есть два вида батчинга, статический — когда объекты «сшиваются» при загрузке карты/в процессе компиляции игры (привет Unity) и динамический — когда объекты сшиваются прямо на лету (тоже привет Unity). На более современных мобильных GPU с поддержкой GLES 3.0 есть также инстансинг — схожая технология, но реализуемая прямо на GPU. Суть её в том, что мы передаём в шейдер параметры объектов, которые мы хотим отрисовать (матрицу, настройки материала и.т.п) и просим видеочип отрисовать одну и ту же геометрию, допустим, 15 раз. Каждая итерация отрисовки геометрии будет увеличивать счетчик gl_InstanceID на один, благодаря чему мы сможем расставить все модельки на свои места! Но тут уж справедливости ради стоит сказать, что в D3D10+ можно вообще стейты передавать на видеокарту «пачками», что здорово снижает оверхед одного вызова отрисовки.
Для загрузки спрайтов используется встроенный в Android декодер изображений. Он умеет работать в нескольких режимах (ARGB/RGB565 и.т.п), декодировать кучу форматов — в том числе и jpeg, что положительно скажется на финальном размере игры.
На этом реализация рендерера закончена. Да, все вот так просто :) Переходим к двум остальным модулям — звук и ввод.
❯ Звук и ввод
Как я уже говорил, звук я решитл реализовать на базе уже существующей звуковой подсистемы Android. Ничего сложного в её реализацир нет, можно сказать, нам остаётся лишь написать обёртку, необходимую для работы. Изначально я написал собственный загрузчик wav-файлов и хотел использовать AudioTrack — класс для воспрозизведения PCM-звука напрямую, но мне не понравилось, что в нём нет разделения на источники звука и буферы, из-за чего каждый источник вынужден заниматься копированием PCM-потока в новый и новый буфер…
Полная реализация звукового потока выглядит так. И да, с SoundPool нет возможности получить позицию проигрывания звука или узнать, когда проигрывание закончилось. Увы.
Да будет звук! Ну и про ввод не забываем (листинг получился слишком длинный, а на Пикабу нет тега для кода - так что как-то так):
Сама реализация джойстика крайне простая — запоминаем координаты, куда пользователь поставил палец и затем считаем дистанцию положения пальца относительно центральной точки, параллельно нормализововая их относительно максимальной дистанции:
Кроме того, я добавил вспомогательный метод для вызова диалога ввода текста — это для таблицы рекордов и прочих фишек, которые требуют ввода текста пользователем. Ну не будем же мы сами клавиатуру костылить!
Основа для игры есть, теперь переходим к её реализации!
❯ Пишем игру
Писать игру я начал с создания первого уровня и реализации загрузчика уровней. В качестве редактора, я выбрал популярный и широко-известный TileEd — удобный редактор с возможностью экспорта карт в несколько разных форматов. Я лично выбрал Json, поскольку в Android уже есть удобный пакет для работы с этим форматом данных.
Карта делится на 3 базовые понятия: тайлы — фон, с изображением травы/асфальта/земли и.т.п, пропы — статичные объекты по типу деревьев и кустов и сущности — объекты, участвующие в игровом процессе, т.е игрок, зомби и летящие пули. Система сущностей реализована в виде абстрактного базового класса, который реализовывает логику апдейтов, просчитывает Forward-вектор и выполняет другие необходимые задачи:
После этого, я приступил к реализации игрока, оружия и механики стрельбы. В целом, практически всю логику игрока можно описать в виде одного метода update: обрабатываем ввод и ходьбу с джойстика, поворачиваем игрока в сторону пальца на экране и пока зажата какая-либо область на экране мы ходим и стреляем:
Ну и не забываем про реализацию зомби. Она тоже очень простая: есть базовый класс Zombie, от которого наследуются все монстры и который реализует несколько необходимых методов — повернуться в сторону игрока, идти вперед и конечно же атака!
❯ Что у нас есть на данный момент?
Честно сказать, статья итак уже получилась слишком длинной. Я очень хотел написать игру, о разработке которой можно было бы рассказать в рамках одной не особо большой статьи, но с моим стилем написания текстов так сделать не выйдет. Придется разбивать на части! Однако, некоторый прогресс уже есть и мы можем даже поиграть в игру на текущем ее этапе!
Как мы видим, игра (а пока что — proof of concept) работает довольно неплохо на всех устройствах, которые были выбраны для тестирования. Однако это ещё не всё — предстоит добавить конечную цель игры (набор очков), магазин стволов и разные типы мобов. Благо, это всё реализовать уже совсем несложно :)
❯ Заключение
Написать небольшую игрушку с нуля в одиночку вполне реально. Разработка достаточно больших проектов конечно же требует довольно больших человекочасов, однако реализовать что-то своё, маленькое может и самому!
Пишите своё мнение в комментариях. Если вам вдруг интересна тематика самопальной разработки игр, то постараюсь выпускать подобные статьи почаще!
Статья подготовлена при поддержке компании TimeWeb Cloud. Подписывайтесь на меня и @Timeweb.Cloud, чтобы не пропускать новые статьи каждую неделю! Но тут я даже чутка навру - на этой неделе вас ждёт сразу две статьи :) Следующая - в четверг, прошлую неделю я отдыхал и работал.