The Lost Knowledge. DevLog#1
Привет!
Я уже несколько лет, с переменным успехом, занимаюсь разработкой игры. Теперь, когда где-то на горизонте виднеется ее релиз, мне захотелось рассказать о ней публике. Сделать это я решил в серии постов. Каждый из которых будет разбит на несколько частей.
Всю свою сознательную жизнь я так или иначе был связан с компьютерными играми, начиная с 90-х годов прошлого столетия. В основном как потребитель, но в какой-то момент созидательная натура дала о себе знать и в голове начала мелькать мысль о создании своей игры. Сначала я реализовывал ее, начав водить кампании по ДНД (Подземелья и драконы), но потом и этого стало мало.
Предыстория. Часть 1.
Программировать или рисовать я не умел, а о профессии гейм-дизайнера вообще не знал. Придумывать игровые механики, это что, работа? Бред какой-то... Несколько лет назад, с началом пандемии, обстоятельства сложились так, что у меня появилась неплохая сумма денег и много свободного времени. Таким образом я клюнул на рекламу одного образовательного портала и приобрел курс обучения по направлению «Создание игр». Целью было научиться программировать и влиться в среду разработки для дальнейшего заработка в сфере мобильных игр (они даже гарантировали устройство на работу) и параллельного создания своего «Magnum Opus». По итогу обещанный год обучения растянулся на два с половиной, подписанный контракт был переписан так, что работа уже не гарантировалась, а деньги на жизнь у меня кончились. Совмещать работу и учебу не получилось, так что по факту я не доучился, но знаний там и тут нахватался.
Вследствие пробела в знаниях, отсутствия практического опыта и портфолио, найти работу в разработке у меня так и не получилось. Однако я не отчаялся и практиковался на сколько позволяло время. Обычно я брал старую игру времен Сеги или Денди и реализовывал какие-то определенные механики.
В то же время мы с товарищами плотно подсели на одну настольно-карточную игру. Играли в нее раз за разом, а интерес все не иссякал, разнообразие казалось бесконечным. Единственное, что портило процесс, это то, что за всеми механиками следить приходилось самостоятельно. По сути ты играл в игру, но играл и за игру, хоть и просто выполняя предписанные правилами последовательности действий. Сначала просто практикуясь в своих навыках я начал реализовывать эту игру в компьютерном варианте и через какое-то время решил, что смогу сделать её полностью. В последнее время стало модно переносить настольные игры в компьютерный вариант, взять в пример ту же Game of Thrones или Root, даже массивный GloomHaven понемногу переносят, но естественно для этого нужна лицензия, которую просто так не дадут случайному человеку, без портфолио и наглядного опыта. Тогда было принято решение о создании своей игры.
Я джва года делаю игру. Суть такова…
Как можно увидеть в заголовке, игра называется The Lost Knowledge.
Сложно определить жанр игры, в которой намешано так много и нет аналога на компьютерном рынке.
Я бы сказал, что это кооперативная колодостроительная карточная РПГ в постапокалиптическом фэнтези мире.
Начало игры
Первым делом, вам следует выбрать кампанию для игры. На релизе кампания планируется одна, но потом будут добавлены еще. Каждая рассказывает свой сюжет в рамках одного мира. Кампании разделены на сценарии, которые проходят игроки, справляясь с трудностями и принимая различные решения влияющие на дальнейшее прохождение, либо не справляясь и тогда решения будут приняты за них.
Выбор персонажа
После выбора кампании, каждый из игроков выбирает своего уникального персонажа, которым будет играть в ее рамках.
Уникальность персонажа включает в себя его предысторию, базовые характеристики, расовую способность, личный эффект "Провидения", личную карту силы, личную карту слабости, а также один из пяти игровых классов, которые влияют на проверки характеристик и на сбор колоды.
Один из наших героев
Сбор колоды
После выбора персонажа, вам следует собрать колоду. Базовое количество карт в колоде - 30, но у некоторых персонажей оно может отличаться. Вам следует собрать колоду, которая будет синергировать с другими игроками, и вы заранее должны понимать, чем вы будете заниматься в игре.
А чем собственно в ней заниматься?
Я уже говорил что кампании разбиты на сценарии? Вот вам еще один уровень — все сценарии разбиты на акты! Акты представляют собой задания для продвижения по сюжету: победить противника, найти какую-то локацию, обыскать локации, найти в них необходимые знания и многое другое. Однако задачи ограничены по времени и лучше вам поторопиться, иначе задача будет провалена и сюжет будет направляться в неблагоприятное для игроков русло. В итоге, принятые вами решения, выполненные и невыполненные задания будут откладывать свой отпечаток в течении всей кампании приводя вас к одному из множества эпилогов.
Еще один из игровых персонажей
Что ждет вас во время приключения?
Мешать выполнять задачи вам будут два основных фактора — это случайные события и противники. Прилетевший с крыши кирпич или бандит на вашем пути не всегда готовы к переговорам (но иногда готовы). Хоть в игре и пять классов с отличающимся геймплеем, но роли в команде всего две, а именно: защитник, готовый убрать противника с вашего пути или заслонить вас от кирпича своей чугунной головой и аналитик, способный найти всю требуемую информацию и связать ее воедино. Никто не говорит, что вы не можете выполнять обе роли одновременно, но вы слышали поговорку про двух зайцев?
Закончили сценарий
Закончили вы сценарий или все пошло не по плану и вы вылетели из окна, скорее всего вы заработали какое-то количество опыта во время своих приключений. Самое время его потратить! Между сценариями вы можете улучшать характеристики своего персонажа, либо заменить свои слабые карты на более сильные, высокого уровня. Главное делать это с умом, ведь очки опыта очень ограничены и достаются немалой кровью, а потом можно уже и в следующий сценарий. В следующий раз вы обязательно приземлитесь на ноги.
Заключение
Здесь хотелось бы написать благодарность всем, кто дочитал до этого момента, особенно если вы не пропустили раздел об игре.
Сейчас игра создается в команде из трех человек:
Программист/гейм дизайнер
Нарративный дизайнер/гейм дизайнер
Художник/визуальный дизайнер
На данный момент проделана огромная работа, до завершения которой осталась еще уйма задач, но я более чем уверен, что мы доведем игру до релиза. Постараюсь держать в курсе и выкладывать хотя бы один пост в месяц, сначала с воспоминаниями о проделанной работе, а потом о ходе разработки.
Если найдутся люди, готовые в небольшой степени консультировать по реализации некоторых задач на C#, вы сохраните мне время, чем очень поможете, спасибо.
Все кто узнал какая карточная игра лежит в основе разработки и чем мы вдохновлялись — вы крутые. Добавьте один токен со древним знаком в свой мешок хаоса.
Сам написал, сам поиграл: Как я написал 2D-игру для Android полностью с нуля, весом менее 1мб?
Многие программисты так или иначе имеют тягу и интерес к разработке игр. Немалое количество спецов было замечено за написанием маленьких и миленьких игрушек, которые были разработаны за короткое время «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, чтобы не пропускать новые статьи каждую неделю!
Но тут я даже чутка навру - на этой неделе вас ждёт сразу две статьи :) Следующая - в четверг, прошлую неделю я отдыхал и работал.
Раздача к дню программиста четырех книг по программированию на сайте fanatical.com
Цена: $39.99
Mathematics for Game Programming and Computer Graphics
Цена: $33.99
Game Development with Blender and Godot
Цена: $31.99
Functional Programming in Go
Product
Цена : $31.99
Fundamentals for Self-Taught Programmers
Product details
Скидка 20 процентов на ассеты или обучающие материалы по геймдеву
Команды raytrace и tracepath
Теперь tracepath(id) возвращает угол, который можно использовать для прицеливания и перемещения к противнику (раньше так работал raytrace(id)).
raytrace(id) же теперь возвращает расстояние до противника. Таким образом можно реализовывать довольно комплексные паттерны перемещения в функциях, вирусах и демонах.