Pull to refresh

Как написать игру на Monogame, не привлекая внимания санитаров. Часть 0, вступительная

Reading time6 min
Views11K

1. Введение

Данной статьей я открываю серию дневников разработки уже третьей реинкарнации моей будущей игры KARC. Первая версия дошла до минимально играбельного вида и выложена в открытый доступ. Однако, мне не понравилось, как я спроектировал структуру приложения. Поэтому, ошибочно полагая, что я понял, как правильно спроектировать архитектуру приложения, я сделал с нуля вторую версию. В ней мне удалось прогрузить сцену с машинкой игрока, которая реагирует на клавиатуру. Однако, развивать эту версию дальше я не посчитал целесообразным, потому что, увлекшись, накрутил слишком много всего и, самое главное, неправильно. В результате в коде стало очень трудно ориентироваться. Так бы и остался проект в заморозке на неопределенный срок, как и моя мечта делать игры, если бы я в очередной раз не решил попытаться понять архитектурные паттерны, в частности, MVP, и у меня это, внезапно не получилось бы. И так мне MVP понравился, что я понял, как хочу переписать архитектуру KARC. В этот раз я решил фиксировать все свои шаги на бумаге, а потом выложить в широкий доступ. Дело в том, что я делаю игру на фреймворке Monogame, но в сети я не нашел внятного руководства, как сделать на нем что-нибудь законченное. Есть много роликов и статей о том, как сделать какие-то отдельные элементы (например, какой метод подгружает спрайт, но не как лучше это осуществить, если их у вас много и под каждый игровой объект их несколько) или мелкие игры, которые тянут в лучшем случае на технодемку, но не создать какую-либо законченную игру целиком (кое-что на самом деле нашел, и ссылки в последующих статьях будут, но в целом мне не понравилось). Поэтому, возможно, описание моего пути кому-нибудь пригодится.

2. Материалы и методы

Игра KARC будет представлять из себя простейшую аркаду с видом сверху, где машинка игрока двигается по прямой дороге, уворачиваясь от других машинок. Игра будет разрабатываться на языке C# с использованием фреймворка Monogame.

Почему Monogame? Я очень люблю язык C# и разрабатывать игру хочу на нем. Однако, сколько раз ни подступался к Unity, он неизменно вызывал у меня отторжение – Unity является полноценным универсальным движком, и моему мозгу всегда тяжело было охватить его устройство. А, если я не понимаю от начала и до конца, как что-то работает хотя бы в общих чертах, то мне крайне некомфортно этим пользоваться. Я копировал на Unity несколько проектов, которые находил в уроках в сети, но понимания не было, и я забрасывал. Когда-нибудь вернусь просто потому, что интересно, но не потому, что нужно - для воплощения моих скромных геймдевелоперских аппетитов Monogame пока хватает с лихвой.

Однако, я забегаю вперед. Так бы моя детская мечта разрабатывать свои собственные игрушки и осталась нереализованной, но как-то на Метаните я наткнулся на руководство по Monogame, впервые узнав о его существовании. Прочитав первые несколько уроков, я понял, что это именно то, что мне нужно. Дело в том, что Monogame не является игровым движком – это фреймворк типа pygame, написанный на языке C#. По сути, это просто набор библиотек, которые содержат классы и методы для подгрузки ресурсов, запуска простейшего игрового цикла и работы с графикой и геометрией. Как это все скомпоновать и пользоваться этим каждый решает сам. Хочешь, чтобы у тебя в игре была физика? Напиши ее, друг! Monogame позволит создать тебе экземпляры класса Rectangle, для которых есть статический метод, который отвечает на вопрос – пересекаются ли два указанных прямоугольника? Дальше – сам. Никто тебя не ограничивает! Для меня это стало настоящим спасением, потому что, спустившись на уровень ниже движка, я наконец-то понял, как работают игры. Отсутствие возможностей для меня стало плюсом, потому что, как следствие, отсутствовала перегруженность – если мне было что-то нужно, я писал это сам. В процессе работы над первой и второй версиями KARC я даже стал лучше понимать устройство Unity, потому что для реализации своих идей необходимо было создавать подобный ему функционал.

Позднее я даже понял, что мне не нужен фреймворк, чтобы сделать игру – для забавы я пару раз написал простенькие проекты типа модели «Хищник-жертва» с двухмерной графикой и обсчетом столкновений на связке C# + Windows Forms. И, подняв уровень своего навыка, решил завершить реализацию своей первой идеи, потому что дело надо доводить до конца. Так как вся игра будет написана на C#  , то данная серия статей рассчитана на людей, которые уже знают синтаксис и возможности этого языка, представляют, что такое классы, методы, свойства и события. Однако, автор не претендует на то, что пишет идеальный код, а предлагаемые им решения являются наиболее оптимальными, поэтому всегда будет рад конструктивной критике.

3. Теория

3.1 Подготовка рабочей среды

Как я уже сказал, разработка будет вестись на фрейморке Monogame. Проект я буду писать в Visual Studio 2019 (забегая вперед - потом будет 2022). Для того, чтобы использовать Monogame последней на данный момент версии 3.8, нужно скачать соответствующее расширение.

Можно пойти другим путем – создать пустой проект и докачать к нему соответствующие пакеты через NuGet, но я не советую – придется вручную качать пакеты для каждого проекта (а там не один пакет), а потом вручную же писать начальный код для того, чтобы это счастье работало как нужно (подключение графики и так далее). В то время как установка через расширения создаст в Студии шаблон нового проекта, в котором все, что нужно, написано сразу.

Создание нового проекта Monogame
Создание нового проекта Monogame
Запуск нового проекта Monogame
Запуск нового проекта Monogame

Давайте, подробно пройдемся по структуре автоматически созданного кода.

3.2 Структура проекта

Точкой входа в программу, как всегда, в C # , является статический метод Main статического класса Program. Если заглянуть в соответствующую вкладку, то мы увидим несколько скучных строк, подобных тем, которые можно увидеть, например, в Windows Forms:

Этот код нам недвусмысленно намекает, что метод Main создает экземпляр некоего класса Game1, из которого вызывает метод Run (который как раз и запускает нашу игру). Можно сделать вывод, что внутри Game1 как раз и происходит все самое интересное, и, забегая вперед, это действительно так. Пройдемся по структуре этого класса. Начнем с верха.

С самого начала (1) мы видим то, что класс использует три библиотеки, названия которых начинаются с Microsoft.XNA. Не стоит удивляться – Monogame вырос из майкрософтовского фреймворка, который те перестали поддерживать. Люди с этим не смирились – так и появился Monogame. Далее (2) мы видим, что наш класс Game1 наследуется от Game. Если интересно, можно перейти к определению родительского класса и посмотреть, какие там есть еще поля и методы помимо тех, что встретим далее. После этого (3) видим два приватных поля, которые отвечают за графику, но пока не используются. Скоро мы это поправим. Конструктор (4) подключает графику, задает корневую директорию для ресурсов игры и делает мышку видимой. Дальше идет переопределенный метод Initialize (5), который вызывается при инициализации нашей игры. По задумке авторов, сюда мы должны помещать всякие ресурсы, которые не имеют отношения к графике. Сейчас там вызывается только родительский метод Initialize, который запускает следующий переопределенный метод – LoadContent (6). Здесь, как раз, следует загружать графические ресурсы типа спрайтов и всего остального, что не имеет прямого отношения к коду. Технически, шарп, разумеется, не различает, какой метод для чего нужен. Ваши ресурсы типа спрайтов, звуков и музыки будут храниться после загрузки в обычных полях, поэтому вам никто не мешает подгружать ресурсы хоть внутри метода Initialize, хоть внутри игрового цикла, хоть написать собственный метод и вызывать его в конструкторе кроме того, что в большинстве случаев это просто глупо. Поэтому советую придерживаться этого деления. Затем начинается самое интересное.

Если все, что было до этого, вызывалось один раз, то методы Update и Draw прогоняются каждый игровой цикл. В Update должна обновляться «логика» игры, а Draw каждый цикл рисует игровое поле. Опять же, деление относительно условное, поэтому если вы особый эстет, то можете запихнуть логику в метод Draw, а отрисовку – в метод Update, и оно даже будет работать. Относительно условное деление потому, что в игровом цикле сначала вызывается Update, а уже потом, что логично, Draw, поэтому если вы, например, попытаетесь отрисовать объект, который еще не создан, то программе это не понравится.

Если подытожить, то структурные отличия того, что мы увидели, от обычного C# проекта небольшие: по сути, это необходимость использования методов Update и Draw, которые вместе и составляют игровой цикл. Во всем остальном мы видим обычный C# - код с некоторыми специфическими инструментами, которых коснемся в следующий раз. В моей следующей статье мы подумаем, как вместить так понравившийся мне паттерн MVP в прокрустово ложе игрового цикла, а также создадим что-то осязаемое, помимо голубого экрана.

Tags:
Hubs:
Total votes 8: ↑7 and ↓1+6
Comments13

Articles