Предисловие
В прошлой статье, я упомянул GameDev, который мотивирует людей заняться программированием, поэтому решил написать статью о создании простой игры на движке Defold.
Почему Defold?
Есть много 2D движков для создания игр, часть из которых будут проще для понимания новичкам. Однако считаю, что Defold хоть и функциональный, простой для понимания. У него интуитивно понятный интерфейс и хорошо организованная структура проектов. Основным языком программирования движка является Lua, который тоже прост в освоении. Так же, Defold умеет быстро создавать приложения для Windows, Linux, Android, iOS и HTML5. Последняя функция позволяет разработчикам делать большие и красочные 2D игры для web.
Игра
Для ознакомления с Defold, я решил описать создание простой игры, состоящую из двух объектов - астероида и космического корабля. В игре одна механика, как можно дольше не сталкиваться с астероидами. Для понимания механики желательно поиграть в игру по ссылке выше.
Почему для статьи выбрана настолько простая игра? Банально, чтобы не перегружать лишней информацией новичков. Опытный разработчик сможет быстрее понять, как устроен Defold. Для новичка чем больше механик и элементов, тем сложнее для понимания статья. В любом случае, игру можно использовать, как примитивную заготовку, для будущих проектов. Игра и 10% не использует функционала движка. На ее примере легко объяснить принципы работы с Defold.
Начало работы
Для начала скачайте Defold с официального сайта. Движок распространяется по лицензии Open Source, поэтому бесплатный. Defold не имеет установщика, скачайте архив, разархивируйте в любую папку и запустите. После чего, скачайте ассеты, которые в нашем случае состоят из двух картинок. Ссылки на исходные изображения:
Однако, изображение астероида нужно уменьшить до размера 230х175. Это нужно для корректного отображения размеров астероида в игре.
Если сложно поменять размеры сторонними программами, то возьмите готовые изображения в папке assets на GitHub.
После того, как загрузите ассеты на ПК, запустите Defold.
Откройте вкладку NEW PROJECT и выберите Empty Project. Ниже, в поле Title введите название проекта, после чего нажмите Create New Project и подождите пока создастся новый проект.
После создания проекта вы увидите рабочую область.
Потом, в папке main создайте файл main.collection. Он будет основной коллекцией, где будут размещены объекты и компоненты игры.
Создайте папку assets в корне проекта, куда поместить изображения, через файловый проводник.
На этом моменте первичные приготовления закончены и можно дальше продолжить работу.
Структура проектов Defold
Defold обладает четкой структурой организации проектов. Основной элемент движка - это Game object. Своеобразный контейнер содержащий в себе компоненты. Component - это компоненты (да тавтология) игрового движка, т.е. спрайты, звуки, скрипты и т.д.
В нашей игре два Game object - это корабль и астероид. В каждом из них содержится ряд компонентов. Например, для корабля и астероида написаны скрипты, которые определяют поведение в игре. Спрайты, которые отрисовываются на экране. Коллизии, по которым определяются столкновения. Компонентов в движке много и каждый предназначен для определенных функции.
Еще, Game object создают для вспомогательных компонентов игры. Например, в игре Game object с именем other, где хранится label лучшее время игры и механизм фабрики, необходимой для спавна астероидов.
Game object объединяются в Collection - альтернативой игровых сцен в других движках. Которые хранят много игровых объектов.
Еще стоит упомянуть, что есть элементы движка, которые не помещаются в Collection или Game Object, например атласы (Atlas), хранящие изображения, вспомогательные Lua скрипты и т.д.
Работа с графикой и коллизиями
В Defold работа с текстурами происходит через Atlas. (еще есть Title source, но сейчас его рассматривать не буду). Дело в том, что движок не берет изображения напрямую из файловой системы, а создает из них специальные файлы атласы, в которых они хранятся специальным способом.
Давайте создадим атлас в нашей игре.
Теперь поместим в атлас наши изображения. Нажмите ПКМ в Outline и нажмите на Add Image, после чего выберите ваши изображения.
Все, атлас создан. Теперь необходимо создать небольшую структуру проекта. Для этого создайте папки asteroid и ship.
В каждой папке создайте файл Game object.
Вернитесь в main.collection и в Outline добавьте Game object, которые только что создали.
Зайдите в файл ship.go. Добавьте в Outline компоненты Sprite, Label и Collision.
Теперь нужно настроить каждый компонент. Начнем с настройки Sprite. Для этого нажмите на компонент в Outline.
- Переименуйте Id объекта на ship_sprite.
- Выберите atlas в поле Image.
- В поле Default Animation выберите ship.
Так, данному спрайту будет присвоено изображение с именем ship, хранящееся в атласе. После чего, корабль появится на рабочей области.
Далее необходимо настроить компонент Label.
- Переименуйте Id объекта на now_time
- В поле Text введите: second: 0
При помощи инструментов перемещения, поворота и изменения размера, переместите label, как показано на скриншоте.
Теперь пришло время создать коллизию для корабля.
Выберите компонент collisionobject и выберите поле Type на Trigger.
Создайте Shape. Лично я создал несколько шэйпов и разместил, как показано на скриншоте. Их тоже можно двигать, поворачивать и изменять размер соответствующими инструментами.
В итоге, должна получится такая структура компонентов для корабля.
Проделанные операции с объектом корабля, повторите для объекта астероида. Должно получится как на скриншоте.
Опять же, шэйпы раставляйте на свое усмотрение. Лично я решил, что лучше всего для коллизии астероида подойдет два круга.
Разместите объекты в main.collection, как на скриншоте. Чтобы переместить объект, выберите его в Outline и переместите соответствующим инструментом.
Размещать объекты в данном случае можно на глаз. Главное, чтобы астероид был за пределами экрана и не мелькал на игровом экране. Для понимания, где находится область экрана, я обвел ее на этом скриншоте.
Для определения размеров экрана, границ и положения объектов, нужно определить, где точка отсчета. В Defold точка отсчета находится слева снизу на пересечении осей. На скриншоте, в левом и нижнем краях находятся линейки по высоте и ширине. Они отсчитывают пиксели. Точка с координатами (0, 0) и есть точка отсчета.Увеличение пикселей происходит слева-направо, снизу-вверх. Если поместить объект за пределами границ экрана, он не будет виден в игре. Левую и нижнюю границу определить легко - это нули. Правая и верхняя - это размеры игрового экрана. Они прописываются в настройках проекта, которые хранятся в файле game.project, в категории Display.
В идеале, положение объектов нужно рассчитывать, но иногда приходится пользоваться магическими цифрами.
Лично я разместил объекты по следующим координатам.
Обратите внимание на поле Scale. Поскольку текстура корабля большая, уменьшите ее через это поле. Это второй способ уменьшения изображений.
Графика и коллизия объектов игры готовы. Можно приступать к программированию.
Программирование
Для начала, создайте файлы скриптов в папках asteroid и ship c одноименными названиями.
Создайте еще один скрипт в папке asteroid, с названием asteroid_factory. Он будет отвечать за спавн астероидов через механику движка - factory. В итоге, должна получится такая структура.
Добавьте скрипты в объекты asteroid.go, ship.go и main.collection.
Сначала, добавьте скрипты в asteroid.go и ship.go. Обратите внимание, что скрипт asteroid_factory в объект asteroid.go добавлять не нужно, потому что он описывает появление астероидов и если его добавить в объект астероида, то при спавне скрипт будет постоянно дублироваться, поэтому в asteroid.go должен быть только скрипт, управляющий его поведением.
Создайте вспомогательный Game object, но не отдельным файлом, а прямо в коллекции.
Здесь же создайте компонент factory.
- Поле Id переименовать в asteroid_factory.
- Поле Prototype выбрать asteroid.go.
Теперь фабрика будет создавать объекты на основе указанного Game object.
Добавьте скрипт asteroid_factory в other, как добавляли скрипты в ship и asteroid.
В other добавьте label, содержащее лучшее время. Она добавляется на игровой экран по аналогии с label текущего времени, в объекте ship.
В label лучшего времени поменяйте следующие поля:
- Поле Id переименовать в best_time
- В поле Text написать best second: 0
Расположение label лучшего времени подбирается на глаз, подбивая координаты позиции, округляя десятки. Проще, разместите label, как больше нравится. Лично я ее расположил, как показано на скриншоте.
В итоге, должна получится такая структура.
Теперь напишите скрипты и оживите игру. В статье я не буду описывать каждую строчку кода, потому что сделал это в скриптах. Однако, опишу основные принципы работы скриптов.
Если вы сейчас откроете любой скрипт, то вы уведите следующее.
Скрипт будет содержать в себе 6 системных функций с краткими описаниями. Давайте расскажу, что делает каждая из этих функций.
function init(self) - отвечает за инициализацию объекта. Если на человеческом языке, то срабатывает только один раз, когда объект создается. Обычно в ней объявляются свойства или переменные, выполняются простые вычисления или функции.
function final(self) - срабатывает, когда объект удаляется из игры. Например, в нашей игре когда астероид за пределами
игрового экрана то удаляется, чтобы не занимать оперативную память компьютера. В этой функции можно, например реализовать счетчик который бы считал сколько астероидов пролетело и было удалено за всю игру.
function update(self, dt) - срабатывает каждый кадр игры. Если игра идет в 60 fps, то функция выполнится 60 раз в секунду, поэтому думайте о оптимизации, когда пишите здесь код. В основном, здесь описывается логика перемещения и изменения объектов.
function on_message(self, message_id, message, sender) - нужна для приема сообщений от других объектов и для обработки столкновения коллизий.
function on_input(self, action_id, action) - отвечает за отслеживание нажатие клавиш и кнопок.
function on_reload(self) - используется для функции движка Hot Reload.
В игре потребуются только функции: init, update, on_input, on_message.
Теперь, давайте перейдем непосредственно к коду.
Ship
Этот скрипт отвечает за поведение корабля - управление курсором мыши и обработка столкновений с астероидами. Если коротко описать принцип работы скрипта, то он обрабатывает нажатие ЛКМ в функции on_input, меняя состояние self.moving и запоминая координаты курсора в self.actions. После чего, в функции update, в зависимости от состояния self.moving перемещает корабль по координатам курсора мыши при помощи функции go.animate() или оставляет его на месте (если ЛКМ не нажата).
Так же, в этом скрипте реализован подсчет текущего времени и лучшего времени. Счетчики хранятся в свойствах self.now_time и self.best_time. В функции update к счетчикам прибавляется +1, однако счетчик лучшего времени увеличивается на +1, только если текущее время больше
счетчика лучшего времени. В функции on_message() отслеживаем столкновение корабля с другими объектами и если столкновение произошло, то счетчик текущего времени обнуляется.
Исходный код.
Asteroid
Этот скрипт отвечает за поведение астероидов в игре. Поскольку астероиды только летят вниз и исчезают, залетев за пределы игрового экрана, то и скрипт у них небольшой. В скрипте 2 функции init и update. В первой функции, создается одно свойство, где хранится положение астероида. Во второй, астероид перемещается с заданной скоростью и удаляется если покинул игровой экран.
В этом скрипте думаю будет два непонятных момента новичкам:
- Первая строчка go.property("speed", 1000). Эта команда задает компоненту скрипта параметр. Если сделать скорость параметром а не свойством, то ее можно изменять средствами движка. Если нажать в Outline на скрипт, то увидите изменяемый параметр скорости.
- Второй непонятный момент связан с переменной dt. Не буду подробно рассказывать как переменная работает. Если коротко, она исправляет проблемы с перемещением объектов. Например, часто в играх попадается баг, когда персонаж движется наискосок быстрее, чем в вертикальном или горизонтальном направлениях. Много подобных проблем исправляет dt.
Исходный код.
Asteroid_factory
Этот скрипт отвечает за спавн астероидов и тоже небольшой по размерам. Состоит из двух функций init и update. Вначале получаем размеры игрового экрана. В init объявляем свойства интервала и позиции спавна. В update создаем позиции спавна для каждого нового астероида. Координаты спавна задаются следующим образом:
Координата x задается случайным образом (функция random) в диапазоне от 100 до ширина экрана плюс 100. Цифра 100 - это отступы от левого и правого краев экрана, чтобы астероиды не появлялись за их пределами. Почему 100? На самом деле это магическое число и было найдено методом тыка, в поисках более приятной глазу визуализации.
Координата y, в отличии от координаты x для каждого нового астероида постоянна, а именно высота игрового экрана плюс 200. Расстояние выше высоты экрана на 200 пикселей нужно для более красивого появления астероидов. Почему 200? Текстура астероида имеет размер в высоту - 175 пикселей. Я округлил это число до целых сотых ради более приятной визуализации, чтобы астероиды не появлялись на глазах игрока.
После создания позиции для астероида, в скрипте прописан его спавн по этой позиции, используя компонент factory.
Исходный код.
На этом моменте работа с проектом завершена, теперь осталось его скомпилировать.
Компиляция
Скомпилировать игру в Defold довольно просто. В меню движка нужо выбрать пункт Project и нажать на интересующий вас Build. В нашем случае - это Build HTML5.
После нажатия на этот пункт, игра скомпилируется и запустится в вашем браузере по умолчанию.
Чтобы разместить игру на сайте, нужно создать приложение. Выберите Project -> Bundle -> HTML5 Application. После чего, в появившимся окне выбрать все, как показано на скриншоте ниже. Потом указать папку, где сохраняться файлы игры. Далее эти файлы можно разместить на вашем сайте.
Послесловие
В заключении, хочу предупредить о одной особенности движка. Если запустить проект в качестве exe приложения, то астероиды будут двигаться очень быстро. Это связано с особенностями построения HTML5 бандлов. Поэтому, если создавать игру под определенную платформу, то изначально тестируйте игру на ней, чтобы избежать проблем в будущем.
На этом моменте статью можно закончить. Надеюсь она оказалась вам полезной.