Здесь статья о том, как мы с друзьями писали игру. Мы ее дописали и продаем, но денег она нам не приносит. Однако, нам было очень интересно и весело ее делать, и я решил поделиться своими воспоминаниями. В статье будет минимум технических подробностей, код я выкладывать не буду, так как учиться на нем бессмысленно. Это код любителей, а не профессионалов, там ошибка на ошибке. Никто из нас не имеет АйТи образования и никто профессионально никогда не занимался программированием. Я иногда буду выкладывать технические подробности, так как без этого никуда. Прошу читателей также не постить коментарии о том, какие мы лохи, так как это обидно читать. Хоть и правда.
Другие части статьи
Часть 1
Часть 3
Глава 3. Юрский период
До решения проблемы с памятью я додумался быстро. Правда. Количество уникальных моделей на уровне просто ограничил 5ю для врагов и 6 для наших уродцев. «Клонов» могло быть очень много, но памяти они занимали копейки, так как самое тяжелое — модель и текстура, — хранилось в единственном экземпляре для всех «клонов». Конечно, уродцев клонировать было нельзя, так как игрок мог решить отправить на миссию совершенно разные модельки, так что я просто ограничил отряд 6ю бойцами. В принципе, отряд из 6 хорошо вооруженных уродцев должен одолеть любого врага, размышлял я, а врагов будет много одного вида, еще много – второго, немного третьего, чуть-чуть – четвертого, и босс пятого. Еще я уменьшил большинство текстур до 128х128 или 256х256 (ну, уменьшали Димон и Федор Михалыч, я тут руководил), и стал хранить многие компоненты вершин как short или char, вместо float. Этого хватило, чтобы «игра» запустилась на устройстве. Гонять уродцев по экрану пальцем было весело, к тому же, они могли ходить сквозь стены. Обходить препятствия они не могли. Хуже того, я понятия не имел, как это сделать. Понемногу стали появляться модели игровой обстановки, и Федор Михалыч все сильнее и сильнее намекал мне, что пора бы запилить редактор уровней.
Где-то за 2 недели я сделал ТЕКСТОВЫЙ редактор уровня. Работал он из командной строки. При запуске спрашивал размерность поля. Потом выводил выбор типа:
1. Указать текстуру квадрата 2. Добавить врага 3. Добавить объект 4. Добавить точку входа отряда Ваш выбор?2 Введите тип врага (1-15):3 Введите координату Х:5 Введите координату Z:5 Враг добален. Что делаем дальше? 1. Указать текстуру квадрата 2. Добавить врага 3. Добавить объект 4. Добавить точку входа отряда Ваш выбор?
И далее в таком стиле.
«Ужасно удобно», — думал я: «Полный контроль над разработкой, только консоль, только хардкор.»
Федор Михалыч так не думал. Именно тогда мне понадобился мой старый код от нашей ММОРПГ под Windows. На его базе я и создал новый редактор. Да, наш новый редактор уровней использовал для отрисовки OpenGL. Но на создание графического редактора ушло довольно много времени.
Это последняя версия редактора, где есть все. Таким он стал далеко не сразу.
Первоначально задуманные 6 месяцев давно прошли, а у нас был только редактор уровней, текстуры пола, 1 модель уродца и несколько моделек обстановки в игре. Врагов не было. Оружие было впаяно в уродцев (да, это потом аукнется ого-го!), и, так как у нас пока была только одна модель, оружие было тоже только одно. Хуже того, карту по экрану двигать было нельзя, так как координаты, куда тапнул пользователь, переводились из экранных в мировые по «таблице соответствия». Таблица представляла из себя картинку самого игрового поля в игровой же перспективе, только красный канал в ней содержал мировую Х координату той ячейки, которую отображал, а синий – мировую Z координату. Таким образом, когда случался тап, проверялся пиксель с координатами тапа в этой карте, и синий и красный компоненты этого пикселя возвращали мировую координату.
Я считал эту систему распознования тапов верхом совершенства. Федор Михалыч так не считал. Он говорил, что вся карта на экран не уместится – будет слишком все мелко. Карту надо двигать.
— Не проблема, — сказал я, и разрешил пользователю перемещать карту по экрану «скачками». Так, чтобы после перемещения карта прыгала на сетку с координатами-цветами со смещением, соответствующим перемещению карты по экрану, но выравниваясь по квадратам сетки. Таким образом, можно было двигать карту на целое количество игровых клеток вдоль любой из 2х мировых осей (высоты в игре не было).
Федор Михалыч, скрепя сердце, согласился пока оставить так, однако ворчал, что карту нельзя вращать. А я ему говорил, что, если он хочет вращать, надо переписывать движок, и тогда мы возвращаемся в начало, а прошло уже и так 6 месяцев, а до конца игры еще, хоть и не очень, но далеко. Федор Михалыч неудовлетворенно молчал и делал уровни. Мне кажется, в итоге, уровни – это лучшее, что есть в этой игре. Правда.
Но уровни получались все сложнее, объектов на них было все больше, и все это начинало жутко тормозить. Причем, профайлер показывал, что какого-то одного узкого места в коде не было, тормозило все вообще. Из плюсов, я прикрутил А*, и уродцы теперь могли нормально ходить, только неясно было, как движку понять, можно пройти через объект (дверной проем, например), или нет (стена, например). И Федор Михалыч игнорировал мой восторг по поводу А*, но все больше и больше жаловался на скорость движка.
— Скорость упала ниже 10 кадров в секунду на новейшем айфоне 5, — говорил он, хотя его айфон 5 давно перестал быть новейшим. – А на втором айпаде просто слайд шоу.
Тогда я решил ввести карты видимости. Работало это следующим образом. Создавалась матрица размерностью с игровое поле. Каждый раз, когда кто-то из уродцев двигался, матрица пересчитывалась так, чтобы единицами были заполнены все ячейки в радиусе 15 клеток от каждого из уродцев, остальное – нулями. Потом в список рендеринга заносились только те клетки карты и объекты, которые обладали координатами, помеченными единицами в матрице. Остальное не рисовалось. Туман войны! Это помогло, но не кардинально. То есть, кардинально, скорость возросла до 20 кадров, но, если вне тумана было много объектов, все равно все было грустно.
Отдельно хочется написать о том, как генерировалась карта тумана. Вся карта помечалась, как находящаяся в тумане, затем от каждого из уродцев трассировалось 360 лучей, образующих полный круг вокруг уродца, радиусом в длину «видимости» уродца. Естественно, до первого пересечения с препятствием. Каждая клетка, по которой проходил луч, помечалась как видимая. То есть, по факту, примитивная модель рейтрейсинга из Вольфа.
Но 360 лучей было явно недостаточно.
— У тебя какая-то хрень получилась, — говорил мне Федор Михалыч, показывая, что стены, расположенные вдоль взгляда игрока, но немного в стороне, были сами частично невидимы, частично видимы, а частично «взгляд» проходил сквозь них.
— Свет – это волна, — отвечал я. – Дифракцию знаешь?
Но количество лучей до 720 я тогда увеличил.
Темно-серые квадраты с серым червяком — это туман войны. Не путать с такими же, но посветлее, это — бетон!
Именно тогда Федору Михалычу на глаза попался проект двигателя dEngine, который я решил изучить с целью пополнения знаний.
Это был код с другой планеты.
Первое, что меня ввергло в шок: под iOS можно писать на Си. Как я был рад. Я уже почти полюбил идиотский синтаксис Objective C, но осознание того, что есть люди, которые пишут по-человечески, не давало мне спокойно кушать. И из-за этого я много пил. И тут – такое.
Я сказал Федору Михалычу, что мне нужен месяц, и меня не беспокоить. Я буду творить. И сел переписывать код на Си.
С нуля.
Это очень помогло, потому что велосипедов и костылей в старом коде за более, чем полгода накопилось порядком. Я не стал их тащить в новый. Среди прочих, я избавился от костыля в виде карт преобразования координат тапа через текстуру, и углубился в изучение вопроса детально. Написал функцию обратного преобразования координат. Карту теперь можно было вращать, масштабировать, двигать как угодно, и все равно нажатия распознавались верно.
Я был рад. Федор Михалыч был рад.
Для облегчения отрисовки уровня, я решил сделать пол уровня состоящим из одного прямоугольника, а не из клеток-квадратиков, как раньше. Так как раньше каждая клетка была своей моделькой со своей текстурой, это нововведение очень сильно подняло производительность игры (да, раньше было несколько сотен переключений текстур на кадр только для отрисовки пола, и несколько сотен вызовов функуий отрисовки для него же), но теперь для каждого уровня нужна была своя текстура пола, полностью готовая и содержащая все клетки вместе. Я добавил функционал для создания такой текстуры в редактор. Туман войны надо было оставить, но делал я теперь его, передавая «карту видимости» уродцев в виде текстуры в шейдер. Плюсом пришла фильтрация. Теперь туман войны не наступал резко, а имел градиент. Было приятно. Ничего в тумане войны мы по-прежнему не рисовали.
Новый туман войны. И текстуры пола уже новые.
Пересмотрели мы и структуру файлов, хранивших информацию об уровнях. Теперь каждый уровень содержал карты ходимости и видимости. В карте ходимости отмечались клетки, через которые нельзя было пройти. В карте видимости – через которые нельзя было видеть, они служили для рассчета тумана войны. Мы договорились, что, если через клетку нельзя видеть, то сквозь нее нельзя и стрелять. А если можно видеть, но нельзя пройти, то стрелять через нее можно, но точность попаданий снижается. Да, попадания мы считали по вероятностной модели. У каждого оружия была точность, определяющая вероятность попадания выстрела в цель, и количество выстрелов в очереди. Кроме того, вероятность повышалась с ростом звания уродца, либо с помощью тренировки навыка снайпера.
Заодно я добавил систему клонов для объектов обстановки уровня, чтобы не грузить дубликаты, так же, как ранее сделал это для врагов. Значительной экономии памяти это не принесло, однако скорость рендеринга возросла из-за меньшего количества переключений текстур, так как все объекты одного типа всегда отрисовывались подряд.
Но прошло несколько больше времени, чем я планировал. Примерно через год от начала написания игры мы вернулись на то место, где были полгода назад, но теперь код работал быстро, стабильно упираясь в 60 ФПС, лимит айфона, и выдавал значительно лучшую картинку. А у меня намечался ребенок. И все, с кем я разговаривал по поводу игры, говорили, что надо обязательно успеть завершить ее до появления ребенка, иначе – все.
Глава 4. Каменный век.
Я не сильно опасался за прогресс проекта в с связи с появлением ребенка. Да, я понимал, что времени будет сильно меньше, но у Федора Михалыча к тому времени уже был маленький ребенок, и, тем не менее, он справлялся. Я думал, что и я справлюсь. В целом, примерно так и вышло, хотя прогресс все же пошел медленнее. Федор Михалыч делал уровни и заказывал Димону модели для них, Димон работал с фрилансерами и пытался математически вычислить баланс сил в игре. Федор все делал эмпирически. Я не делал практически ничего.
Мне тогда казалось, что я очень устал от игры. Прошло примерно 2 года с момента начала разработки. У меня на руках был новорожденный ребенок и вполне работоспособные пол-игры. Да, мы уже были близки к финалу «боевки». Мы назвали ее «акшнгейм». Это где игрок сражается с террористами с помощью отряда уродцев. Но была еще и вторая часть игры, названая нами «тактикгейм», это время на базе, между миссиями. Там надо управлять разведкой, чтобы отыскивать базы террористов, управлять отрядом, лечить раненых бойцов, тренировать здоровых, строить, покупать и продавать оружие. Этого всего не было. Не было и системы очков, не было системы опыта, не было прогресса уродцев.
— Тупо штамповать уровни становится слишком тупо. Надо браться за тактикгейм, — сказал мне Федор Михалыч.
Я упирался. Я устал. Тогда Федор Михалыч сказал мне, что он займется полировкой акшнгейм и идеями для тактики, Димон займется артом для тактики, а я немного могу отдохнуть. Так и решили.
Тем временем, ремейк икс-кома для компа уже давно вышел, и мы даже в него наигрались вдоволь, и на горизонте маячил релиз для айос. Федор Михалыч говорил, что на нашу игру после выхода икс-кома для айос никто не обратит внимания никогда. И я сел за тактикгейм.
Структуру тактикгейма я набросал быстро. По моему видению, с которым согласились остальные, альтер-эго игрока должен был стоять посередине небольшой комнаты. Спереди, сзади, слева и справа от него должны были быть двери, ведущие в другие комнаты. Вид должен был быть «из глаз», как в 3Д-шутерах. Свайпом игрок должен был «крутиться», «переключая внимание» с одной комнаты на другую, а тапом – заходить в ту комнату, на которую он смотрел. В этих 4х комнатах он мог, соответственно, управлять разведкой, отрядом, вооружением и отправлять отряд на миссии.
Дали фрилансерам задание создать модель базы, а я пока нарисовал задники в пэинте и использовал их вместо реальных моделей.
Одна из «комнат» на базе, полностью сделанная мной.
Прогресс шел относительно весело, да и фрилансеры справились с базой относительно быстро, и вскоре функционал был готов. Выглядело все довольно коряво, особенно, модели уродцев-солдат и уродцев-работников базы. Модели уродцев-солдат, впрочем, были сами по себе вполне ничего для акшнгейма, где они рисовались маленькими. Но когда они отрисовывались на весь экран, да еще и на айпаде, был полный провал.
К тому же, они жрали очень много памяти.
У нас было 3 базовых модели уродцев, каждый с 3 разными текстурами головы. Еще было 3 разных варианта текстур камуфляжа (2 камуфляжа и 1 вариант брони). Также, было 4 варианта оружия. Я уже говорил ранее, что, для упрощения анимации, мы решили «вжарить» оружие в модель уродца. То есть, для одного и того же уродца существовало 4 разных модели, по одной для каждого оружия. Итого, для отображения отряда на базе, в памяти надо было хранить 12 моделей и 36 текстур.
В памяти 4го (и 4S) айфонов это не умещалось никак. Особенно, вместе с моделью базы. Было большое желание дропнуть поддержку «четверок». Но это сделать было сложно, так как, даже несмотря на скорый выход 6ки (да, еще была проблема с реальным разрешением 6 плюс! OpenGL оперировал в плюсе реальным разрешением экрана, 1920х1080, а тачскрин выдавал виртуальные координаты, из сетки 2208х1242. И одно надо было преобразовывать в другое, чтобы правильно распознавать координаты тача, предварительно определив, что работаем в такой конфигурации), 4S вполне себе могли работать с последней iOS, и я не представлял, как в аппсторе запретить пользователям четверок покупать нашу игру.
Федор Михалыч предложил вместо моделек на базе использовать их пререндеренные картинки. Это почти помогло. Окончательно помог еще и даунсайз всех текстур базы. Теперь все умещалось в памяти 4ки. И даже относительно неплохо выглядело на маленьком экране 4го айфона. Но на айпаде, или даже на айфоне 6 Плюс, это было катастрофой.
Окончательным решением стало определение объема памяти устройства при загрузке и исполнение соответствующей ветки кода с загрузкой текстур соответствующего качества.
Поймите, когда я называю модельки наших солдат уродцами, я не шучу, ведь формат md2 оперирует 8 битами на координату, что дает разрешение 256х256х256 точек, причем важно не столько пространственное разрешение самой модели, сколько разрешение текстурных привязок. Модель рисовалась на экране высотой в 1,5 тыс пикселей, и, даже если она занимала половину высоты экрана, она была 500 пикселей в высоту. Непопадания в текстуру тут были очень даже видны.
Не скажу, что картинки уродцев были сильно страшнее самих моделек уродцев, но сами модельки уродцев были анимированы, что частично сглаживало негативный опыт.
Однако, игра работала. Да, она выглядела непритязательно, да, были трудноуловимые баги, а баланс был очень кривой. И да, уровней было мало и они были на тот момент все очень разные, некоторые – нагруженные и большие, такие, что бой мог легко продлиться час, а другие проходились моментально. Но игра работала.
Кстати, немного слов об уровнях. В игре, как в уважающем себя клоне икс-кома, было два типа миссий. Те, что влияли на прогресс мы обозвали сюжетными. Эти миссии игроку выпадали, когда его разведка находила базу террористов. По прохождении последней такой миссии, игра должна была закончиться. Второй тип миссий выпадал игроку время от времени, случайно. Такие миссии мы называли «рандомными». Это были теракты, на которые игрок должен был реагировать: спасать заложников или обезвреживать бомбы, чтобы зарабатывать деньги и опыт.
Так вот, с сюжетными миссиями все было более менее понятно, у нас как-никак был сценарий, в котором игрок постепенно через террористов выходил на Гринворлд (нет, не Гринпис, а Гринворлд) и на пингвинов, а в итоге – на пингвина-мутанта, супербосса игры. А вот с рандомными было непонятно. Их нужно было много, так как не особо хотелось заставлять игрока переигрывать одни и те же рандомные уровни снова и снова. Кроме того, в комнате, из которой игрок стартовал на миссию, как в любом уважающем себя клоне икс-кома, миссии были видны на карте (да, не на глобусе, а на карте, это было наше ноу-хау, очень яркое отличие от оригинала!), и надо было сделать какую-никакую привязку содержимого уровня с картой, чтобы при нажатии на миссию на северном полюсе не загрузился уровень с пустыней. Дело было нехитрое, но муторное, благо, занимался в основном всем Федор Михалыч, порой требуя от Димона новых моделей для уровней, и от меня добавить их в редактор и саму игру (за «мастер-билд» по-прежнему отвечал я). А я был свободен, так как, по общему мнению, уровни, сделанные мной, отличались особой отстойностью. Хотя 1 или 2 в итоге в игру попали. Возможно, поэтому она и не принесла денег.
От нечего делать я начал тюнинговать код. Освещения у нас в игре не было. Точнее, оно было плоское. Я решил это поправить. В моделях, понятное дело, нормалей не было. Это не беда, я же читал Ламота. Я прикрутил направленное освещение, но статическое, оно просчитывалось при загрузке модельки. Стало лучше. Бонусом пришло «сглаживание» модельки, так как нормали для каждой вершины считались как среднее значение нормалей всех треугольников, которым принадлежала эта вершина, а шейдер уже делал плавное изменение освещения треугольника от одной вершины к другой (тонирование Гуро).
Потом я решил прикрутить тени. Это очень долго не получалось сделать на устройстве. Дело в том, что в симуляторе все работало здорово, а в устройстве тени то появлялись, то пропадали, то были полосатыми. В итоге я плюнул на тени на стенах, и рисовал их только на полу, сделав для него шейдер с тенью, а для всех остальных объектов я просто игнорировал карту теней. Тени получились немного кастрированными, но они были. Правда, адаптировать размер зоны, в которой просчитывались тени, к размеру видимой области у меня не получилось, поэтому карта теней всегда рассчитывалась для всего игрового поля. В результате, несмотря на огромный размер карты в 2048х2048 пикселей (16 МБ в памяти!), при максимальном приближении тени выглядели очень пикселизировано (но выглядели!), а в версии для 4го айфона тени пришлось и вовсе отключить, так как для карты теней просто не хватало памяти.
Так выглядела игра на новых устройствах
А так, на айфоне 4, 4S и айпаде 2 и мини:
Примерно тогда же, увидев, что разница между картинкой, рисуемой игрой на устройствах с 512 МБ памяти (а кроме айфона 4, 4S это еще и айпад 2 и айпад мини) и более современных устройствах была довольно заметной, мы решили выводить для пользователей четверок и старых айпадов оповещение, что устройство у них, мягко говоря, отстойное, а мы ни в чем не виноваты.
Заключительная часть повествования находится в стадии шлифовки/полировки, и я постараюсь ее выложить до конца недели.
Источник