Геймдев, про который мы забыли: как работали 2D-игры на кнопочных телефонах нулевых
Друзья! А вы помните, какими были мобильные игры в 2000-х годах? Помните, как разработчики умудрялись уместить целые миры в устройство с небольшим дисплеем, аппаратной клавиатурой, весьма слабым железом и парой сотен килобайт памяти? Но задумывались ли вы, как в своё время работали эти сами игры «под капотом»? В сегодняшней статье-ретроспективе предлагаю вспомнить мобильный геймдев нулевых и узнать, как же работали 2D Java-игры, какие API были доступны и что из себя представлял средний телефон тех лет! Интересно? Тогда добро пожаловать под кат!
❯ Предисловие
Пожалуй, геймдев всегда был одной из самых интересных сфер программирования. Множество нестандартных задач, возможность применить профильные математические и алгоритмические познания, а также огромный простор для архитектов в продумывании архитектуры будущей игры, ведь, например, в больших студиях комплексные и большие игры обычно очень сложные и нередко тянут за собой целый пласт легаси-кода и наработок чуть ли не из 90-х.
Но помимо десктопных и консольных игр, существуют и мобильные игры, которые в последние 5 лет вплотную приблизились к уровню AAA на консолях (привет порту GRID, AC Mirage, RE4 на мобилки). А ведь ещё 15-20 лет назад мы играли на кнопочных телефонах с небольшими дисплейчиками, которые в свое время подарили нам множество эмоций и кайфа от прохождения этих самых игр, несмотря на простенькую графику, не особо комплексный геймплей и относительно простой левел-дизайн. Продвинутые мобильные геймеры играли уже на Symbian-смартфонах и WinMobile-коммуникаторах (да, в какой-то момент времени, устройства на WM были весьма перспективными), но чаще всего — на Java-телефонах Nokia, Sony Ericsson, Siemens и, конечно-же, Samsung с LG!
По правде сказать, игры на смартфонах — тема отдельная, например на Symbian был полноценный телефон-игровая консоль Nokia N-Gage, о которой я писал отдельный материал, а о разработке игры под Windows Mobile я относительно недавно написал отдельную статью. У смартфонов обычно было несколько больше ресурсов: шустрее процессор, значительно больше памяти доступной игре, а также возможность запуска нативного кода, но и игры для них было разрабатывать значительно сложнее.
Зато о том, как работали игры на Java-телефонах информации практически нет и этот недостаток нужно исправлять, ведь это была одна из первых попыток унифицировать формат приложений на телефонах вне зависимости от архитектуры их процессоров и ОС на борту. Недавно я писал о том как работали 3D-игры на Java-телефонах, но там затрагивалась только 3D-часть без 2D, звука, обработки ввода и иных модулей, без которых игра не может работать!
❯ Каким был телефон?
В середине 2000-х годов, обычно телефон представлял из себя девайс в корпусе моноблок/раскладушка/слайдер и «флип» с весьма большим цветным дисплеем, одним/двумя (привет Motorola E398) динамиками и несколькими аппаратными кнопками. В зависимости от ценового сегмента устройства, обычно менялся корпус, разрешение и размер дисплея, а также материалы, из которого был изготовлен девайс. При этом у многих больших вендоров были собственные программные платформы — у Nokia это был S40, у Sony Ericsson своя, у Samsung и LG тоже свои.
В среднем, характеристики телефонов были следующими:
Процессор: ARMv4/ARMv5 на частоте ~100-200МГц. Есть исключения — Siemens E-Gold работал на базе архитектуры C166, а платформа Motorola работала на 66МГц (что и объясняет небольшую тормознутось).
ОЗУ: ~8Мб SDRAM. Эта память распределялась под все нужды системы, в том числе и обработку GSM, Java и пользовательский интерфейс. Java-приложениям было доступно ~1Мб ОЗУ.
Постоянная память: в среднем ~10-30Мб, плюс возможность расширения памяти за счет MicroSD или MS Pro Duo (Sony Ericsson).
Казалось бы, не густо. На самом деле вполне достаточно, учитывая все ограничения телефонов тех лет. Но почему именно Java?
Ещё в начале нулевых, когда прогресс развития телефонов шёл семимильными шагами, перед разработчиками телефонов встал вопрос, какой формат для программ выбрать, дабы привлечь как можно больше разработчиков на рынок мобильных приложений. Очевидно, что нативные программы на C/C++ точно не подойдут (разные архитектуры, большие отличия в платформах), поэтому нужна была виртуальная машина с собственным байткодом. Вариантов было несколько: Mophun, некая корейская виртуальная машина (точного названия, увы, не помню и инфы очень мало) и, конечно-же, Java с JVM. Со временем именно J2ME стала стандартом благодаря оптимальной скорости работы, хорошему и простому API и низкому порогу входа.
❯ Какие API существовали?
Несмотря на то, что игры под кнопочные телефоны писались на Java, набор API и поддерживаемых пакетов отличался от обычной JVM на ПК, которую использует, например, Minecraft. Всего существует три профиля — J2SE (Android и ПК), J2EE (серверы и энтерпрайз) и J2ME (встраиваемая электроника и телефоны). Однако сам по себе J2ME делится ещё на два стандарта — CLDC/CDC (набор поддерживаемых фишек языком — например, ранние телефоны не поддерживали float) и MIDP (набор поддерживаемых телефоном фишек — работа с дисплеем, проигрывание звуков, доступ в сеть и обработка ввода — всё это часть MIDP). За всё время существования было две версии MIDP — 1.0, которая была весьма ограничена в возможностях (например, нельзя было развернуть игру на весь экран) и использовалась с 2001 по ~2003 год и MIDP 2.0, которая использовалась вплоть до кончины J2ME.
Теоретически, появление J2ME должно было стандартизировать игры на телефонах разных производителей… но был нюанс — ведь функционал телефонов рос как на дрожжах, разрешение дисплеев тоже, у телефонов появлялась собственная память и файловая система, возможность подключения к интернету и Bluetooth и появился целых ворох API…
Несмотря на то, что игры по большей части были одинаковыми (или почти одинаковыми) на всех кнопочных телефонах, тем не менее набор поддерживаемых API каждым устройством значительно отличался. Вероятно, вы помните как многие игры подразделялись не только на версии для разных разрешений дисплея, но и на версии для каждого производителя отдельно: Nokia, SE, Samsung и т. п. Для реализации каких-то особых фишек (например, быстрая отрисовка изображений с регулируемой прозрачностью) требовалось использовать пакеты, неподдерживаемые в базовом профиле MIDP. И подобные пакеты делились на два типа — JSR и Vendor-specific пакеты.
JSR — это расширения-спецификации (то есть просто описание классов без какого либо кода), которые вносились в специальную базу Java community process и формально стандартизировались среди всех нормальных производителей телефонов. Среди таких JSR есть и поддержка 3D-графики (JSR184 — M3G, JSR239 — OpenGLES Bindings for J2ME), и доступа к файловой системе устройства (JSR75), и возможность использования Bluetooth для реализации мультиплеера (JSR82). Говоря простыми словами, это опциональные «фишки», которые могли быть доступны на каких-то телефонах, а на каких-то не поддерживались и соответственно игры, которые их используют, в большинстве случаев просто вылетают с ошибкой (однако особенно «умные» игры используют рефлексию и определяют поддерживается ли та или иная функция с помощью метода Class.forName).
Vendor-specific пакеты обеспечивали очень крутой функционал, характерный не просто одному производителю телефонов, а зачастую даже одной линейке телефонов на определенной платформе. На SE такие пакеты практически не использовались (кроме, конечно, Mascot Capsule), а вот на Nokia постоянно (Nokia UI, Nokia S40 API), позволяя на изначально «слабеньких» s40-телефонах рисовать в буфер дисплея напрямую, а также отрисовывать треугольники, рисовать полупрозрачные картинки и выполнять некоторые другие операции, недоступные на других телефонах. У Samsung, же, например, в свое время была поддержка MMF-звуков в мобильных играх, что в начале и середине 2000х годов было просто нереально крутым, даже несмотря на другие ограничения корейских телефонов.
❯ Графика
Возможности по отрисовке графики на кнопочных телефонах были не сказать что сильно широкие, но тем не менее позволяли легко реализовать графику уровня SNES или даже PlayStation 1. Например, в отличии от современных смартфонов, мы не могли использовать шейдеры, умножить спрайт на цвет (дабы придать ему другой оттенок) и даже использовать аффинные трансформации (поворот, скейлинг) — исключительно полупрозрачные спрайты даже без возможности плавно «растворить» спрайт путем изменения его альфы! Поэтому многие разработчики шли на «хак» и предварительно рисовали в редакторе 8-16 положений одного спрайтов с разным углом поворота, дабы потом выбрать нужный в зависимости от физического угла поворота в градусах!
Для графики использовался пакет javax.microedition.lcdui, в котором были классы для построения нативного интерфейса (выглядело так себе на большинстве телефонов), а также механизм фреймов (Form, Canvas).
Для игр же предлагался Canvas и GameCanvas, которые позволяли развернуть поверхность для рисования на весь экран и предлагали инстанс объекта Graphics, который сразу предоставлял механизм двойной буферизации! В свою очередь, Graphics предоставлял методы для отрисовки спрайтов (Image и drawRGB для «сырых» картинок не в нативном-формате, может быть медленно), примитивов (линии, прямоугольники, овалы), текста и… всё! Например, картинку можно было нарисовать вот так:
getGraphics().drawImage(img, 0, 0, Graphics.LEFT | Graphics.TOP);
При этом с шрифтами вопрос был отдельный: у каждого устройства был свой набор поддерживаемых шрифтов и свои фишки, о которых клиентская программа даже могла и незнать: например поздние телефоны поддерживали сглаживание шрифтов (что дико лагало на устройствах типа Nokia Asha), но что самое забавное — шрифты не могли быть произвольного размера, лишь 3х типов (один из них — моноширинный) и 3х размеров (маленький, средний, большой). Немудрено, что многие вендоры реализовывали свои рендереры битмапных шрифтов, которые точно будут нужного разработчику размера.
Но откуда же грузить картинки? Для этого, в Java был использован встроенный механизм открытия ресурсов из JAR: никакого кэша, никаких OBB, все нужные данные сразу в пакете с игрой. Да, это накладывало некоторые ограничения: например на телефонах Samsung долгое время было ограничение ~250Кб на приложение, зато было просто и портативно. Выглядело это вот так:
InputStreamReader reader = new InputStreamReader(getClass().getResourceAsStream('/img.png");
Или в случае картинок так:
Image image = Image.createImage("/img.png");
Всё очень легко и понятно, согласитесь?
❯ А звук?
Помните диалог «включить звук» при запуске почти каждой игры? Конечно же помимо графической части, в каждой игре должен быть и звук! И с его реализацией были свои нюансы: ведь в MIDP 1.0 звук поддерживался только с помощью Vendor-specific API (то есть его вообще могло и не быть, зато на телефонах Samsung поддерживался MMF, что, как я уже и говорил раннее, было очень круто).
MIDP 2.0 уже стандартизировал нормальный протокол для общения с мультимедийной подсистемой устройства с помощью пакета javax.microedition.media, в котором было три класса: Player (собственно, сам звук или музыка), PlayerListener (прослушиватель событий от плеера) и Control для управления различными параметрами воспроизведения (громкость, тональность и, вероятно, прочие расширения от производителей типа эквалайзера).
Конечно-же набор поддерживаемых форматов был невелик, но почти все устройства хотя-бы поддерживали wav (для коротких эффектов) и midi (для музыки), на ранних телефонах ни о каком mp3 и речи не шло (именно в Java-приложениях). При этом на некоторых телефонах, насколько мне известно, не было возможности воспроизводить одновременно звуки и музыку из-за отсутствия программного или аппаратного микшера. Интерфейс для воспроизведения звуков был один: мы создаём Player с помощью метода createPlayer, которому передаём адрес нужного ресурса и проигрываем его. Это мог быть как и трек на удаленном сервере (стриминг поддерживался не везде), так и в ресурсах программы:
InputStream is = getClass().getResourceAsStream("/music.wav");
Player player = Manager.createPlayer(is, "audio/x-wav");player.prefetch();
player.start();
Так почему-же в большинстве игр на телефонах тех лет были midi-мелодии вместо wav? Всё дело в размере и ресурсах: во первых, midi-мелодия на пару минут может весит пару десятков килобайт. Помните «бумер.mid», «europa.mid» и другие известные тогда файлы? Эти треки весили совсем немного благодаря тому, что в отличии от оцифрованных сэмплов (т.е аналоговых данных с микрофона), вес которых зависит от разрешения, наличие стерео и частоты дискретизации, midi оперировали лишь наборами инструментов: что где и когда нужно проиграть. Во вторых, в Java-телефонах был ограниченный объем памяти, а heap мог быть менее 1 мегабайта, поэтому загрузка даже небольшого wav-файла могло быть крайне проблематичным на таком устройстве. Поэтому выкручивались как могли!
Но в целом, аудио-возможности были хорошими. Java-игры славились весьма неплохим звуковым сопровождением для уровня телефонов, явно не хуже GBA.
❯ Мультиплеер! Давай про мультиплеер!
Вероятно многие читатели помнят, что локальный мультиплеер в Java-играх был зачастую Must-have: возможность игры с друзьями по «локалке» собирала все лавочки и подоконники в школах на переменах в жёстких баталиях на бипланах, или, например, в матчах CS для Java!
И для реализации мультиплеера у Java было довольно немало возможностей: в первую очередь, это наличие полноценных TCP-сокетов и Http-подключений с помощью класса Connection. Да, были некоторые ограничения (например на Nokia нельзя было установить TCP-соединение на порт 80 в обход встроенного клиента Http), но тем не менее даже через GPRS можно было создать с кем-то матч и попробовать поиграть, а чуть позже, к 2009 году, в РФ уже появился +- стабильный 3G и можно было поиграть в игры с достаточно быстрым и стабильным интернетом! Но интернет был дорогой, да и смысл ради сессионного матча подключаться к интернету, когда есть Bluetooth?
Появление Bluetooth в телефонах значительно расширяло возможности телефонов в обмене информации на короткой дистанции. Конечно и до этого уже был ИК-порт, который позволял передавать файлы на относительно низкой скорости, но у него была не самая большая стабильность, да и далеко не все можно было успеть перекинуть за время школьной переменной (и не все давали свой телефон «на урок»). Появление OBEX и возможности передачи файлов друг-другу через беспроводной канал дало возможность скидывать музыку и игры прямо на уроке, что было очень круто и позволило некоторым школьникам с флэшкой или телефоном с большим объемом встроенной памяти даже торговать контентом и скидывать, например, эротику за пирожок или школьную пиццу (я застал когда она уже стоила около 10 рублей — весьма немало!). Особо красноречивые ребята умудрялись уболтать друзей себе скидывать весь контент, что был у них на телефонах и становились центром внимания с новым крутым треком — я и сам в некоторой степени таким был (у меня была флэшка на 2 гигабайта!).
Но помимо возможности обмена файлами, Bluetooth также поддерживал некоторые профили: например, подключение к наушникам или протокол L2CAP/RFCOMM для установки соединения клиент-сервер между устройствами, которое и использовалось в Java-играх. Именно оно позволяло сделать один телефон сервером (хостом), а другому — клиентом, который подключается к серверу и они инициируют сессию игры!
❯ Проблемы мультиплатформенности
На бумаге все было хорошо: Java-машина была стандартизированной, поддерживаемые профили тоже и по идее игры и программы должны без проблем запускаться на большинстве Java-телефонов. Но как-бы не так: проблемы с кроссплатформенностью имели место быть. Начиная от упомянутых выше Vendor-specific API и версиями MIDP, заканчивая… как это ни странно, разрешениями экрана.
Да, сейчас игры не зависят от разрешения дисплея благодаря возможности скейлинга картинок до любого размера. Таким образом достаточно заранее нарисовать спрайты для, например, FHD разрешения и просто скейлить их по размеру дисплея в меньшую или большую сторону. Никто не мешает и отдалять камеру в зависимости от разрешения дисплея, впрочем, это считается не очень хорошей практикой (и зависит от игры).
Во времена Java-телефонов, зависимость от разрешения дисплея была критичной и поэтому игры для «не того» разрешения либо выходили за экран, либо наоборот — выглядели слишком маленькими и игрались в небольшом окошке. Многие вероятно вспомнят как устанавливая игру для малого разрешения дисплея, можно было заметить как шлейфом уезжают спрайты за виртуальный экран и остаются на белом фоне…
Небольшие проблемы были и с обработкой ввода. И если резистивные тачскрины поддерживались еще в MIDP 2.0 с помощью обработки определенных событий, то с мультитачем (во времена Asha и поздних телефонов Samsung) было уже сложнее. Другой вопрос что даже коды кнопок почему-то не унифицировали, из-за чего возникало деление на Samsung, Sony Ericsson и Nokia: разработчики J2ME предполагали что смартфоны будут в разных форм-факторах и предоставили лишь механизм для унификации «игровых» кнопок. Таким образом, некоторые игры, собранные под телефоны конкретного производителя могли не реагировать на нажатие кнопок клавиатуры из-за отличающихся кодов клавиш.
❯ Заключение
Друзья! Вы, вероятно, думаете что если телефоны с поддержкой J2ME больше не производятся, значит и коммьюнити уже «всё»? Как-бы не так: после моих статей мне продолжают писать читатели и спрашивать детали реализации тех или иных техник или игровых механик! Да, энтузиастов мало, но они есть, как и у ретро-компьютеров: например, спектрума, или консолей типа NES… А значит наше дело будет жить и Java-телефоны с их играми останутся в наших сердцах, а Java-телефоны останутся на скрижалях истории! Берегите своих кнопочных красавцев и восстанавливайте по возможности, благо пока-что даже корпуса на популярные модели кнопочных телефонов найти относительно легко.
Надеюсь, сегодняшний материал вам был интересен, писал его специально так, чтобы было понятно даже тем читателям, которые не пишут код или знакомы с программированием поверхностно. Подписывайтесь на мой Telegram-канал, куда я публикую различные мысли и советы по ремонту и программированию под гаджеты прошлых лет, подсъемы с новых видосов и всегда актуальные ссылки на новые статьи!
Материал подготовлен при поддержке TimeWeb Cloud. Подписывайтесь на меня и @Timeweb.Cloud, дабы не пропускать новые статьи каждую неделю!
Ответ на пост «Ответ на пост Олды тут?»
Подержите мое пиво.
Академия Знаний. Книга 1. Глава 31. Двухсторонняя оборона. Часть 6
Система жизнеобеспечения уже заменяла мою кровь на тяжелую, предназначенную для планет с высокой гравитацией, без которой совершить аварийную посадку в аварийном режиме не получиться. Да. Инженера устроивший такой прием простым точно не назовешь. Но и бывалый воин так же не был простым человеком. Добравшись до центрального ручного управления поврежденного десантируемого посадочного модуля, капитан узнал об очередных неприятностях с двух сторон самой базы. С одной стороны его атаковало огромное количество разъяренных и крайне агрессивных пожирателей жизни. Добиться от космических паразитов такого эффекта обычно еще требовало приложить не малое количество усилий. А с другой стороны ему пришлось защищаться от многочисленной малоразмерной армией роботов. Вооружение которой было на пару порядков более совершенней чем у его собственной армии. Такой огневой мощи не должно было быть у обычных экзаменуемых.
Понравился рассказ, с тебя лайк и подписка в ТГК.
Для вас старались:
Нейронные сети:
Озвучено SaluteSpeech
Аниматор Genmo.AI
А так же, все тот же один человек:
Автор - ПавелС
Академия Знаний. Книга 1. Глава 31. Двухсторонняя оборона. Часть 5
Группа десантирования состояла из армии тяжелых планетарных роботов, так как только они могли пережить скоростную посадку. В которой сейчас выживал капитан корабля, так как его посадочные системы были повреждены. Но для бывалого капитана такие посадки были не в первой. Правда это не меняло того факта, что неплохо помятый до этого пират сейчас медленно прожаривался в защитном костюме. И хотя на самой планете не было плотных слоев атмосферы, поврежденная система посадки сейчас пыталась затормозить аварийное приземление, направляя реактивные струи двигателей в направления планеты, что создавало огненное облако, в котором медленно пропекался капитан. Но старый воин не только выжил, но и успешно спланировал свою посадку рядом со своим десантным кораблем. Все же боевой опыт ни когда не подводил. Аварийная посадка всегда требовала предварительной подготовки.
Понравился рассказ, с тебя лайк и подписка в ТГК.
Для вас старались:
Нейронные сети:
Озвучено SaluteSpeech
Аниматор Genmo.AI
А так же, все тот же один человек:
Автор - ПавелС
Внуки не получат наследство (
Пять причин поиграть за Тзинча в Total War: Warhammer III
Сжатое содержание статьи в формате короткого видео для вашего удобства.
1 причина - вы всю жизнь ждали сову из Хогварства. И до сих пор ждёте.
Тзинч - бог Хаоса, Повелитель Перемен, Архитектор Судеб и Владыка магии. И если вы любите выжигать вражеские армии манованием руки - эта фракция для вас. Часто враг кончается даже раньше, чем заканчиваются ваши ветра магии.
Тзинчитам доступно три школы магии - школа огня, металла и школа Тзинча. И если с первыми двумя всё понятно, то школа Тзинча по сути является так же разновидностью огненной магии с огненным же уроном. Заклинания преимущественно атакующие и наносящие урон.
Сова разносящая письма из местного Хогварста.
2 причина - вы фанат как достать соседа. Можно бесить врага даже не выходя из своего курятника (то есть со своей территории). За гримуары Тзинча (местную уникальную валюту, которую дают после сражений или генерируют некоторые постройки) Вы можете без проблем отравлять жизнь всем окружающим, например - устраивайте восстания зумеров у вражеских поселений, полностью останавливайте вражескую армию на целый ход. Снимите туман войны со всей территории выбранной фракции или даже примирите врагов или развязывайте турнир во славу Кхорна между союзниками.
Каждый найдёт себе пакость по вкусу.
Шалость удалась!
3 причина - ля ты крыса, да я крыса - только армии Тзинча владеют боевой телепортацией. У остальных фракций есть аналог телепортации - вроде зверинных троп, подземных путей и так далее. Но ни один из этих аналогов не позволяет с помощью них нападать на врага, только встать рядом, а последователи Бога Перемен - могут. Причём телепортировавшись на армию врага, вы застанете его в засаду. Чувствуете боевой потенциал? А он есть.
В начале телепортация стоит ветра магии, но так как вы фракция с мощным магическим уклоном, проблем с количеством маны у вас не будет. А технологиями можно сделать телепортацию и вообще бесплатной.
Скавены и тзинчиты спорят, кто из них большая крыса, фото в цвете.
4 причина - вы фанат народа Огня и желаете развязать войну. Большая часть войск, снарядов и заклинаний - имеют огненные или магические атаки (или и то и то сразу).
Вы заставите вспотеть от ужаса даже нежить и прыгать в воду нурглитов, у которых из-за регенерации и исцеления есть уязвимость к огню. Всякая астральная нечисть неуязвимая к обычному оружию для вас тоже не помеха.
А главное - эстетично красиво.
5 причина - вы латентный фанат протоссов - все войска тзинчитов дополнительно защищены магическим полем, которое тратится перед тем, как будет тратиться здоровье отряда. С помощью навыков и умений лордов - можно увеличить количество этого щита и скорость его восстановаления. Да, вы не ослышались - щит будет восстанавливаться, если отряд какое-то время не участвует в сражении. Отряды тзинчитов в основном хрупкие и щит открывает пространство для тактического манёвра - отводить и менять отряды с пробитым щитом на резервы у которых он уже восстановился и снова бросать их в бой. Но это в идеале, можно конечно этим и не заниматься.
Войска тзинчитов телепортируются, не перепутайте.
Тзинч набрал 146 процентов на выборах лучшего Бога Хаоса! Не наеб*шь не проживёшь.
Кандидат от народа. Он сам так сказал.
Как подготовить машину к долгой поездке
Взять с собой побольше вкусняшек, запасное колесо и знак аварийной остановки. А что сделать еще — посмотрите в нашем чек-листе. Бонусом — маршруты для отдыха, которые можно проехать даже в плохую погоду.
Штырлиц
Штырлиц не боялся высоты , он боялся сломать ноги...