Русская инди-игра про космос
Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1)
В прошлой части мы скачали Unity, подготовились к работе, создали проект, первую сцену и на ней объекты Canvas и Background. В этот раз мы создадим главное меню нашей игры.
Для начало запомните важную вещь - в 2D игре интерфейс построен на слоях. Одно поверх другого. А также связь Parent - Child (Children) - когда один объект находится в другом и свойства родительского объекта влияют на свойства потомка. Именно по этим принципам мы и будем строить наше меню. Давайте попробуем визуально представить как его можно расположить на экране.
Левый блок - это будет объект mainMenu. Он будет родителем для объектов "название игры", "пункты меню" и "ваше лого". Они тоже будут содержать в себе объекты. Начнем.
Создайте пустой объект (Empty Object) и сделайте его дочерним объекта Canvas (в иерархии перетяните один объект на другой). Другой вариант - клик ПКМ по объекту Canvas и затем выбор создания пустого объекта. Так он автоматически станет дочерним. Переименуйте его в mainMenu.
Чтобы mainMenu стало видно на экране, ему нужно добавить компонент Image через меню Add Component в инспекторе (как добавлять компонент я рассказывал в прошлом посте). Так, ну и где же он?
Чтобы придать нашему меню нужное положение, воспользуемся его компонентом Rect Transform. В разделе Anchors устанавливаем:
Затем Left, Right, Top, Bottom ставим по нулям.
Теперь установим нашему меню вместо белого пятна приятный глазу цвет. В компоненте Image нажмите на поле Color и установите:
- R - 0.012 (красный)
- G - 0.012 (зеленый)
- A - 0.7 (альфа-канал или прозрачность)
Для закрепления материала по размещению объекта на экране создадим два декоративных элемента - две полоски по бокам от плашки меню.
1. Создаём два пустых объекта как Child объекта mainMenu - line1 и line2
2. Назначаем Anchors для line1
3. Назначаем Anchors для line2
4. Устанавливаем обоим объектам цвет как у mainMenu
Теперь можно похвалить себя и подышать свежим воздухом. Далее нам нужно создать объекты для названия игры, пунктов меню и логотипа. Делаем мы это по тому же принципу - создаем дочерние объекты в mainMenu - top, middle и bottom. Для наглядности сразу добавляем им компонент Image с белым цветом (потом изменим).
Аккуратно распределяем объекты на объекте mainMenu, расставляя значения Anchors.
Top --> X: (0, 1) Y: (0.8, 0.96)
Middle --> X: (0, 1) Y: (0.15, 0.8)
Bottom --> X: (0, 1) Y: (0.04, 0.15)
Для создания небольших отступов или точного корректирования положения объекта можно задавать значения Left, Right, Top, Bottom в компоненте Rect Transform. Попробуйте поставить всем трем объектам эти значения по 2 пикселя. Главное не злоупотреблять этим, а то может верстка перестать быть стабильной.
Итак, мы нашли хорошее расположение для блоков и теперь можем удалить у них компоненты Image и Canvas Renderer, так как больше они нам не понадобятся.
Давайте сделаем для игры графическое название. Можно, конечно, и текстом, но графикой будет симпатичнее. Мы же делаем игру для людей, а не черновик для себя - так ведь?
Откроем Фотошоп и создадим в нём новый файл размером 1000х1000 пикселей.
Затем добавим название игры.
Нарисуем декоративную линию.
Добавьте слоган внизу, затем подрежьте инструментом "Рамка" лист по самые края логотипа.
Далее удаляем слой фона, чтобы сделать логотип прозрачным. Затем устанавливаем размер изображения с шириной в 1000 пикселей. После этого нам очень важно сделать так, чтобы каждая сторона изображения была кратна 4 - это необходимо для применения алгоритмов сжатия, чтобы уменьшить вес игры. Так как обрезать уже нечего, то через "Размер холста" делаем высоту 212 пикселей. Итоговый размер 1000х212.
Когда всё готово, вы должны поменять цвет всех объетов на белый. Зачем? На белое изображение в Unity мы легко сможем наложить любой цвет, если нам приспичит. На чёрный - не получится.
Сохраните полученое изображение в формате PNG и положите его в папку проекта Assets/Resources/Images (создайте её). Сам исходный файл PSD сохраните в отдельной папке вне проекта, где вы будете хранить исходники графики.
Самое время добавить созданное название в игру! Создаем для top дочерний объект gameName, добавляем ему компонент Image и выравниваем на экране. Для того, чтобы изображение не искажалось, в компоненте Image активируем Preserve Aspect - эта функция позволит сохранить пропорции.
Отлично! Гейб Ньюэлл вами был бы доволен.
Теперь займёмся объектом middle и сделаем в нем несколько кнопок, которые будут вести в нужное место - "Новая игра", "Загрузить игру", "Настройки", "Об игре", "Выход".
Нам пригодится полезный компонент под названием Vertical Layout Group. Он выравнивает дочерние объекты по вертикали, регулирует их высоту, ширину и отступы. Это означает, что нам не придётся вручную устанавливать размеры и положение кнопок. Его можно сразу добавить к объекту middle.
Таким же образом добавьте еще 4 кнопки и настройте Vertical Layout Group у объекта middle.
Padding - это отступы всего списка кнопок относительно объекта.
Spacing - это отступы между кнопок.
Потыкайте настройки, чтобы понять, как это работает, а потом сделайте как на скриншоте. Как видите, теперь у нас есть аккуратный список кнопок.
Обратите внимание на иерархию - у каждой кнопки есть дочерний объект Text. Это такой же объект, как и все, только с прикрепленным компонентом Text. В нем можно вписать текст объекта, указать шрифт, размер, начертание, цвет, выравнивание. Этих настроек вполне достаточно для нашей игры. Установите размер шрифта 24 пикселя.
Теперь можно нажать кнопку Play вверху рабочего стола и потыкать по кнопкам.
У каждой кнопки есть компонент Button, у которого есть целый ворох настроек как кнопка будет меняться при нажатии, наведении курсора итд. Однако, для мобильных платформ, где не мышка, а тачскрин, это не нужно. Поэтому отключите эту функцию, поставив Trasition --> none.
Далее можно удалить у кнопок компонент Image - для такого минималистичного дизайна он не нужен. Цвет текста сделайте белым.
В объекте bottom осталось место для вашего логотипа. Подыщите что-нибудь прикольное, нарисуйте в фотошопе и сделайте так же, как мы делали с названием игры.
Пришло время для правой части меню - не забыли про неё? Для начала определимся, что будет делать каждая кнопка.
Новая игра - выдает список слотов сохранений, при нажатии на который запустится новая игра и автоматически сохранится в этот слот.
Загрузка игры - аналогично, только загружает сохранение.
Настройки - выбор языка игры, громкости звука и музыки.
Дополнительно - авторы, ссылки, благодарности и прочая второстепенная информация.
Выход - закрывает приложение.
Сделаем на каждую кнопку кроме "Выход" своё поле с информацией, которое будет активироваться при нажатии. Создайте в Canvas дочерний объект newGameList, скопируйте для него компонент Image из объекта mainMenu и установите Anchors --> X: (0.31, 0.9) Y: (0, 1).
В нем создайте 6 кнопок для слотов сохранений и назовите их game_0, game_1, game_2, game_3, game_4, game_5. В этот раз мы не будем распределять их с помощью Vertical Layout Group, но сделаем их с небольшой графикой.
Для начала распределите их равномерно по объекту newGameList, используя Anchors.
Теперь создайте в Фотошопе новое изображение размером 400х400 пикселей. Нарисуйте в нем прямоугольник с границей, но без заливки, как показано на скриншоте.
А затем нарисуйте прямоугольник поменьше, но с заливкой.
Удалите слой фона, сделайте прямоугольники белого цвета и сохраните как PNG в папку Assets/Resources/Images под именем frame_1.
В Unity нам потребуется отредактировать границы спрайта изображения, для того чтобы мы могли его без искажений растягивать в разные стороны. В дереве проекта выберите наше новое изображение frame_1, нажмите в инспекторе Sprite Editor и в открывшемся окне установите Border (границы) на 12 пикселей. Именно в этих местах спрайти будет растягиваться, подгоняясь под размеры кнопки. Закройте это окно и нажмите Save.
Примените новый спрайт ко всем кнопкам и в компоненте Image установите Image Type на Sliced. Надо поправить цвет кнопок.
- R - 0.012 (красный)
- G - 0.012 (зеленый)
- A - 0.7 (альфа-канал или прозрачность)
Если вам хочется немного свободно места между слотами, то добавьте на кнопки отступы по 5 пикселей с каждой стороны.
Цвет текста кнопок так же меняем на белый.
Так как нам нужно точно такое же поле для раздела "Загрузка игры", мы можем просто скопировать это. Деактивируйте объект newGameList, нажав на галочку возле его имени в инспекторе, затем в иерархии щёлкните по нему ПКМ и нажмите Duplicate. Теперь нужно только его переименовать в loadGameList, чтобы не путаться. Деактивируйте его, чтобы не мешался.
Раздел "Настройки". Создайте такое же поле, но только без кнопок. Этот раздел посложнее предыдущего, поэтому сделаем для начала небольшие намётки.
Первым сделаем блок выбора языка с выпадающим списком. Нужно создать объект language внутри optionsList и установить ему Anchors X: (0.03, 0.97) Y (0.85, 0.95). Внутри language создаем объект header, устанавливаем Anchors X: (0, 0.48) Y (0, 1), добавляем ему компонент Text с размером шрифта 24 и выравниванием по правому краю и по центру.
Теперь нам нужно сделать выпадающий список. В Unity уже есть готовое решение - нам нужно только правильно его настроить. Добавьте в language объект UI -> Dropdown и устанвите ему Anchors X: (0.5, 1) Y (0, 1). Сразу же можно установить ему почти чёрный цвет с прозрачностью 0.78.
В нём достаточно много дочерних объектов. Разверните его полностью и внимательно поменяйте настройки. По шаблону "Объект: Компонент --> настройки".
- Dropdown: Dropdown --> Transition "none", список Options - поменять Option A итд на названия языков игры.
- Label: Text --> Русский, размер шрифта 24, цвет белый.
- Arrow: Rect Transform --> PosX -20, Wigth 40, Height 40.
- Template: Rect Transform --> Height 450; цвет сделать как у объекта Dropdown
- Content: Rect Transform --> Height 80.
- Item: Rect Transform --> Height 80; Toggle --> Transition "none"
- Item Background: цвет сделать как у объекта Dropdown
- Item Checkmark: Rect Transform --> PosY -5
- Item Label: Rect Transform --> Left 25, Top 15; Text --> размер шрифта 24, цвет белый.
Если ничего не пропустить, то результат будет как на видео ниже.
По аналогии с языковой панелью делаем объект music, размещаем в нём объект header с компонентом Text (пишем "Громкость музыки"). Нам понадобится слайдер - добавляем его UI/Slider с Anchors X: (0.5, 1) Y (0.3, 0.7).
В компоненте Slider отключаем Transition.
В дочернем объекте Background делаем черный цвет.
Таким же образом создаём слайдер для "Громкости звуков".
Готово! Вы - великолепны!
Лимит картинок опять исчерпан, поэтому продолжим в следующий раз.
Экспериментируйте с оформлением и не бойтесь трогать все настройки подряд. В этот раз я приложу ссылку на архив с проектом из поста.
Бункер 21. Исповедь разработчика #2
Привет всем! Продолжаю писать свою историю о разработке игры, изменившей всю мою жизнь! Клавиатуру по-прежнему мучает Пётр, а всем читающим желаю сил, добра, и надеюсь подарить хоть капельку мотивации! Делать игры - это круто!
Месяц первый. Снова в бой!
Как же так, снова первый месяц? Фактически - нет. Но для той версии игры, которую большинство игроков знаю сейчас - это лишь самое начало разработки. А так, суммарно прошло уже почти полгода.
Какие вводные у меня на этот момент:
- - Страница игры в Google Play и примерное понимание как её оформлять.
- - Около 200 отзывов к игре и что-то вроде 2000 установок. Примерно!
- - Есть ядро игры, которое показало себя хорошо в одних местах, и не очень - в других. В частности страдала оптимизация загрузки и доставки уровня, что вызывало много недовольства. Уровни грузились очень долго.
- - Есть какой-то набор текстур и 3D моделей, уже введенных в игру.
- - Есть опыт взаимодействия с игроками.
Оставил для дальнейшей разработки из этого списка я только опыт и знания. Всю работу нужно было начинать с нуля.
Первый этап работы начался с блокнота. Я стал описывать сюжет игры. Мне нужно было построить его "тезисно", без деталей.
Глава 1. Расшифровка.
- - Герой приехал из "вылазки". Повод чтобы побродить по локации и осмотреться, всё ли ОК пока его не было.
- - Нужно отдохнуть. Небольшой квест по сбору предметов. Обучение работе с инвентарем.
- - Рассуждает о своей жизни. Вводит игрока в сюжет.
- - Отдых и пробуждение ночью. Переход в тёмное время суток. Обучение крафту для создания факела, пригодится.
- - Уровень с вождением. Никаких врагов, мягкий геймплей, чтобы дать игроку обдумать полученную инфу за простой поездкой.
- - Деревня. Сбор оставленных вещей, первая встреча с легкими врагами. Медведи убиваются легко.
- - Игрок получает новое оружие - пистолет или ружье. Обучение стрельбе на расстоянии, упрощенная система прицеливая и стрельбы.
- - Новый день. Герой прибыл ко входу в Бункер. Новые враги, стреляющие. Новые механики управления персонажем. Избегание попаданий и ответная стрельба.
- - Босс. Для входа в Бункер нужно справиться с массивным монстром, сочетающим в себе всё предыдущее - стрельба в обе стороны, ближняя атака.
- - Головоломка. После битвы с боссом игроку нужно отдохнуть от сражений, заняв мозги.
- - Вход в бункер. В качестве награды - пасхалка на видном месте, достижение о победе. Записки с раскрытием сюжета. Новая мотивация - исследовать Бункер.
Примерно так выглядит зарисовка первой главы игры.
Когда основные моменты описаны, я перехожу к расшифровке. Играть должно быть интересно, то есть мир должен быть условно-живым. Для этого нужно придумать историю самого мира.
То, что будет мотивировать главного героя, объяснять его жизнь, поступки, и присутствие тех или иных вещей. Проще говоря, чтобы не возникало конфликтов с логикой.
Нужно было определиться, на какие вопросы должна отвечать каждая глава.
- - 1 глава - завязка истории, знакомство с героем, поверхностное объяснение происходящего, чтобы оно стало привычным игроку и не вызывало вопросов.
- - 2 глава - подступ к страшной тайне этого мира. Пока без объяснений, просто обозначение таковой. Объяснение лишь мелких деталей.
- - 3 глава - страшная тайна раскрыта, герой в шоке, игрок в экстазе, он сам к этому пришёл! Он молодец!
- - 4 глава - апокалипсис, лишь следствие беды. Всё оказалось гораздо глубже. Мир гибнет, и появляется лазейка - свалить из этого мира через портал.
- - 5 глава - портал позади. Впереди новый мир. Но что это за мир? Всё стало серьезнее, враги жёстче, лучшие друзья могут погибнуть, никто не защищён.
- - Конец. Герой в новом мире. завязка на следующую игру.
После определения плана я прописал предысторию игрового мира.
Прописал характер персонажа: язвительный, молодой, сильный, научился выживать, при этом добрый: спасает животных из ловушек, испытывает сострадание, переживает, пытается заводить друзей и постоянно надеется на лучшее.
Определившись с теоретической часть начал прорабатывать 3D окружение. Полностью low-poly, никаких текстур, персонажи безликие, определенный стиль, который в будущем будет угадываться моментально.
При этом я решил не торопиться, и выделять на каждый этап столько времени, сколько нужно.
Последующий месяц я не запускал движок, лишь работал с блокнотом и Блендером (Blender 3D), чтобы составлять картину мира., описывая её на бумаге и сразу моделируя. Прикидывал разные варианты, фантазировал, мне нужно было очертить игровой мир, сделать его единым.
Никаких ассетов, никаких заготовок, вся работа делается с нуля.
Через месяц у меня был набор моделей, из которых нужно было начинать строить игровой мир.
Пришло время запускать игровой движок!
PS: На это пока всё, всем большое спасибо, что оставляете комментарии и поддерживаете меня. Приятно!
Бункер 21. Исповедь разработчика #0
Всем привет! Меня зовут Пётр! Как-то я писал всякое про игры, когда их разрабатывал. Одной из таких является "Бункер 21". Знаковый для меня проект.
Недавно я опубликовал пост и поделился своей небольшой радостью - игра вышла в ТОП лучших игр в App Store.
Хочу выразить всем огромную благодарность за то, что разделили со мной моменты радости! Я просто в таком шоке пребывал, что люди могут писать слова поддержки просто так, по-доброму, без стёба и насмешек. Спасибо. Я физически не успевал даже банально следить за тем, с какой скоростью летят комментарии! Это что-то невероятное. Такого мощного заряда я давно не ощущал!
И, в связи с множеством вопросов и просьб рассказать про свой путь, я решил описать всё, как это было на самом деле. Очень надеюсь, что я смогу придать мотивации всем, кто только в начале разработки своих игр.
Сегодня игра отметилась следующими достижениями [которые многие лично застали]:
- Google Play - 1 место в категории "Приключения"
- Google Play - 18 место в категории "Лучшие игры" вне категорий
- Google Play - 2 000 000+ установок
- Google Play - 30 000 отзывов, с общей оценкой 4.8 (где-то 4.7, где-то 4.9)
- App Store - 1 место в категориях "Приключения", "Экшн"
- App Store - 1 место в категории "Лучшие платные игры" вне категорий
Предисловие
Начну с того, что это не история "успеха" в её привычном понимании.
Я не проснулся знаменитым, не стал миллионером, ничего внезапно на меня не сваливалось.
Всё было наоборот: каждый день я работал, иногда даже по ночам, практиковался, изучал новое, изгонял старое. Вся моя жизнь перестроилась.
Начну с того, чего я ожидал, когда начинал разработку.
Так как это всё же хобби, то мои ожидания от разработки были не такие завышенные. Будучи владельцем ютуб-канала по созданию игр, хотелось иметь за плечами какой-то кейс, глядя на который можно было бы сказать: "Да, я хочу учиться у этого парня!".
Для этого нужен был какой-то мощный и показательный проект, при этом созданный лично мной полностью (это важно!).
Таковым и стал Бункер. Названия у него на разных этапах было разное:
- In Head (DEMO игры)
- Бункер 3D (попытка сыграть на ностальгии бывших владельцев телефонов с J2ME)
- Подземный Бункер 3D (примерно тоже самое)
- Бункер 3D (вернулся к "истокам")
- Бункер 3D Сюжетный Квест (добавил ключевых слов)
- Бункер 2021 - Игра с Сюжетом (продолжил эксперимент)
- Бункер 2K21 (гугл запретил использовать даты в названиях)
- Бункер 21 - Сюжет Offline (нужно больше поисковых фраз)
- Бункер 21 - Выживание с Сюжетом (текущее и последнее)
Как можно видеть, я пытался сделать название понятным как людям, так и поисковому алгоритму Google Play.
Дало ли это результат? Сложно сказать. Я не аналитик и разбираюсь в этом очень слабо. Но, в какой-то момент игру просто попёрло в топы на какой-то невероятной ракетной тяге. Но это случится ещё не скоро.
Помимо манипуляций со страницей, я также четыре (ЧЕТЫРЕ, КАРЛ. ) раза переписывал игру с нуля. Прям с чистой страницы, ибо постоянно натыкался на неудобства из-за своей же недальновидности.
В итоге я выработал формулу, которая подходила бы мне для работы и позволяла расширять игру. Но и до неё мне было далеко. Опыта создания полноценных игр на этот момент я всё ещё не имел.
За всё время разработки я постоянно прокачивался в навыках. Я даже не предполагал, что делать игру, это блин не только "накидал на сцену объектов и В.У.А.Л.Я".
Создание игры - это целый список мероприятий и ритуалов, нарушать которые не то, что не рекомендуется, а просто нельзя!
Работая над игрой, я несколько раз переделывал всё с нуля. С каждым разом, когда я начинал заново переписывать ядро игры, я так же изменял сюжет, ибо каждое повторное прочтение давало мне повод усомниться в своих навыках
Продумывать приходится каждую мелочь. Вплоть до разных деталей типа "в начале игрок говорит, что читал в газете такие-то данные, в середине игры он должен подметить это, проходя мимо. [какая-то несущественная фигня]".
Игра выходила по главам. Каждая новая глава примерно раз в два месяца. Всего глав в игре пять, и последняя глава вышла совсем недавно. Суммарно я затратил на разработку игры ровно один год. С мая 2021 по май 2022.
При этом я не потратил ни рубля на раскрутку игры. Не привлекал маркетологов, аналитиков, астрологов. никого.
Всю работу по игре я сделал сам. Каждый видимый пиксель в игре был создан мной с нуля.
Пока я работал над игрой, я освоил следующие инструменты:
- Godot Engine (игровой движок)
- Blender 3D (моделирование)
- GIMP (работа с текстурами)
- Paint.NET (работа с текстурами)
- Wings 3D (моделирование)
- Audacity (обработка звука)
- LMMS (создание музыки и звуков)
Также я значительно прокачал знания по языкам GDScript и HLSL (модифицированная версия GLSL для шейдеров), освоил работу с Kotlin при написании модулей для Android, чтобы работала реклама от AdMob и РСЯ (реклама от Яндекс). Попутно освоил работу с XCode на Mac, чтобы создавать сборки для iOS (под айфоны).
Ну и по доходам. Заработал я за этот год примерно [скрыто] миллионов рублей. Цифру я раскрою в последней части этой истории. Но доход действительно исчисляется миллионами рублей.
Месяц первый.
Начну этот рассказ я с самого начала. Блокнот мой прямо сейчас в руках, тут всякие зарисовки, проработка персонажа, я даже штрихами пытался изображать его эмоции, как это было в игре "Doom" первых версий.
Несмотря на сказанное мною ранее об одном годе разработки, с мая 2021 по май 2022, история эта началась году так в 2019. Месяц, к сожалению, точно не назову, но это и не имеет большого значения.
Сидел я одним вечерком, листал ютуб, искал вдохновения, как моё внимание привлекло видео о том, что какой-то блогер создал свою игру. Заголовок красивый, картинка на превью сочная. Заинтриговал, зараза! Кликаю!
Прошло 40 минут, ролик закончился. Моему разочарованию не было предела. Мало того, что видео пОлно воды, так ещё и автор откровенно привирал, говоря сначала о том, что он создал клон [некоторой] игры, а по факту просто создал что-то совсем не похожее, ни визуально, ни в плане механик или геймплея.
Было досадно, но я всё же досмотрел видео до конца. Хоть я и расстроился потраченному времени, меня посетила мысль.
"Я ведь тоже блогер! У меня есть аудитория, ей такое будет интересно!".
Начал продумывать идеи. Разработка должна быть интересной. Именно сам процесс. Интересный и мне, и зрителю - это будет самый кайф!
Прошла неделя.
Первый набросок был готов. Я тщательно описывал каждый шаг разработки, опуская всякую техническую часть, оставляя всё самое интересное. Однако, на этапе монтирования итогового ролика я понимал, что не хватает чего-то очень нужного. Посмотрев финальный монтаж, я расстроился: всё вообще не то. Моё изначально интересное видео о разработке игры превратилось в "а щас мы двигаем это сюда, чтобы вот тут игрок. ". Скучно. Скучная нудятина.
Всю последующую неделю я обдумывал, что и как делать, пока просто не пришёл к выводу: "нафиг видео!".
Нужно переосмыслить концепт мероприятия.
Чего я хочу? Сделать крутой проект, который не стыдно будет показать. Проект должен показывать, что один человек может создать игру с полного нуля, с минимальной подготовкой. Я для этого специально взял максимально доступный и полностью бесплатный набор софта. Нужно ли при этом каждый шаг записывать на видео?
Изначально я думал, что ДА! Но потом понял, что нет. Потенциальному ученику "крутого ютубера" будет достаточно обзора итогового продукта. Обзор разработки не даст знаний, лишь введет в заблуждение. Да и мне тяжело. Многие вещи на этапе разработки приходилось переделывать заново по несколько раз, а это влечет за собой и перезапись видео. С "бубнежом" на камеру. Такое затянется на годы.
Я немного посчитал, что всё, что я могу сделать за час без скринкаста, с записью видео может длиться до трех часов. Жуть! А если работы по плану - месяц? Вот-вот.
К концу первого месяца я понял, что бросаться в работу с головой без предварительного плана - плохая идея. Причем план нужен не просто на какое-то ближайшее время, а прямо на весь период предполагаемой разработки.
Я попытался поставить себе сроки - сделать демку за неделю. Вроде неделя - это семь дней, в один день я могу тратить в среднем по 4 часа свободного времени. Итого получаем 24 часа чистого времени. В целом - должно хватить.
Начался второй месяц разработки игры.
Продолжение следует!
PS: Друзья, отпишитесь пожалуйста, интересно ли вам будет это чтиво. Не хочется тратить время впустую, ни своё ни ваше.
PPS: Простите, если кому-то не угодил. Я обычный человек, как и все.
Обновление платформера Steelborn и благодарность за обратную связь :)
UPD: Выяснилось, что оригинальная гифка не у всех проигрывалась, поэтому заменил её на несколько других, но передающих суть поста. Оригинальную гифку можно найти тут.
Привет, друзья! А вот и новость, интересная только двум моим подписчикам!Ранее я уже рассказывал про Steelborn, платформер который мы делаем уже третий год. Прошлая новость была посвящена появлению демоверсии игры, нынешняя — тому, что после получения большого количества обратной связи, мы проделали большую работу над ошибками и обновили демку. В разных соцсетях и нашем разделе в Steam было высказано достаточно большое количество предложений, которые мы с успехом (надеемся) реализовали. Причём успели мы как раз к Steam-фестивалю «Играм быть», в котором наша игра участвует, так что если захотите поддержать — будем рады :)Вот так Steelborn выглядит после всех изменений:
А это ссылка на Steam, если решите попробовать игру самостоятельно: https://store.steampowered.com/app/1786930/Steelborn/
GameMaker Studio 2. Урок 2. События отрисовки. Коллизия. Как работают скрипты. Как подключить русский шрифт. Переходы между комнатами
Сегодня у нас много матчасти и не очень много кода.
Конкретизация разработки.
Как было сказано в прошлом посте, игра, которая будет сделана в ходе данных гайдов, будет относиться к жанру стратегий, а в качестве источников для "вдохновения" у нас - "Oxygen Not Included" и "Rimworld". Теперь же конкретно поговорим с вами о том, что в игре должно быть реализовано, по пунктам.
--) Персонаж - игровой "объект", способный перемещаться по игровому миру и взаимодействовать с ним. Делятся на подконтрольных и неподконтрольных игроку. Игрок НЕ имеет прямого управления над своими подопечными, кроме специального "боевого" режима.
--) Каждый персонаж должен иметь или уметь следующее:
---) Взаимодействовать с предметами, расположенными на карте: подбирать их в свой инвентарь или выкладывать; взаимодействовать с рабочими станциями; сражаться (наносить, получать урон).
---) Каждый персонаж смертен. Каждый персонаж должен иметь ряд характеристик, которые он будет пытаться "поддерживать" на должном уровне, чтобы не умереть и продолжать быть эффективным. Эти характеристики:
---) Каждый персонаж уникален и должен отличаться от других. Черты, навыки с различными уровнями прокачки. Этот пункт может игнорироваться в случае, если это не подходит под конкретного персонажа.
--) Объекты, с которыми можно взаимодействовать через интерфейс игры и с которыми могут взаимодействовать персонажи или иные игровые сущности с какой-то целью.
---) Пример: верстак для создания вещей; исследовательский стол для открытия новых вещей и пр.
-) Интерфейс.--) Простой, понятный. Желательно, на русском языке. --) Хотя бы минимально настраиваемый.
Скажу сразу, что персонажей мы делать будем позже. Начнём мы с интерактивных объектов и размещения их в мире. Но, это будет потом.
Сейчас - продолжаем изучать матчасть.
События отрисовки.
Событие отрисовки добавляется на объект как и любое другое, через кнопку "Добавить событие (Add Event)".
Имеется два события:
--) Событие, которое отрисовывает объект в комнате.
--) Событие, которое отрисовывает объект на экране.
Иными словами, событие отрисовки GUI позволяет нам закрепить на экране наш интерфейс и то, что мы хотим игроку показывать всегда. Draw - стандартная отрисовка.
Возникает вопрос: зачем нам отдельно добавлять событие отрисовки, если объект спокойно отрисовывается и без него?
Ответ: в событии отрисовки можно написать код, который позволит нам менять параметры отрисовки, будь то: изменение размера (соотношения сторон); изменение цвета; создание дополнительных графических эффектов; etc.
Если вы добавили событие отрисовки, то вам необходимо написать в коде следующую строку:
В противном случае, объект не будет отрисован.Проделайте это сами. :)
Итак. Что мы можем делать в событиях отрисовки:-) Отрисовка спрайтов:--) draw_self() - отрисовывает спрайт текущего объекта с настройками по умолчанию.--) draw_sprite_ext( название спрайта в обозревателе, номер изображения, x, y, x-масштабирование, y-масштабирование, поворот, цвет, прозрачность) - отрисовка спрайта "расширенная" - т.е. с настройками. Можно отрисовать любой спрайт, как и везде, где его нужно отдельно указывать.
--) draw_sprite_part(название спрайта в обозревателе, номер изображения, x координата левой верхней точки спрайта, y координата левой верхней точки спрайта, ширина, высота, x, y) - отрисовка части спрайта--) draw_sprite_stretched(название спрайта в обозревателе, номер изображения, x, y, ширина, высота) - отрисовка спрайта с его "растяжением". Если у спрайта включить и настроить функцию Nine Slice (девять "срезов"), то можно создавать масштабируемые (с одинаковым разрешением, пропорциями и качеством по итогу) элементы интерфейса: окошки, кнопки, etc.
-) Отрисовка фигур:--) draw_circle(x, y, радиус, заполнение (True/False)) - рисуем круг. Аналогичное есть для прямоугольника (rectangle), стрелки (arrow), эллипса (ellipse), линии (line).--) draw_button(x, y, x2, y2, up) - Отрисовка кнопки. Где up - True или False, нажата кнопка или нет.
-) Настройки отрисовки.--) ВАЖНО. Любые настройки отрисовки нужно проводить ПЕРЕД отрисовкой. Можно делать в любом событии, но для удобства лучше тут же.--) draw_set_color(col) - устанавливаем цвет отрисовки. Базовые значения цветов начинаются с приписки "c_". Пример: c_black, c_red. ---) Для продвинутых - можно использовать HEX, как в CSS: #11CCFF как пример. Используется стандартная RGB система. Если заменить решётку на $, то система сменится на BBGGRR, т.е. наоборот.--) draw_set_alpha(alpha) - прозрачность отрисовки --) draw_set_font(font) - устанавливаем шрифт--) draw_set_halign(halign) - Расположение текста по горизонтальной оси (fa_ left/center/right)--) draw_set_valign(valign) - Расположение текста по горизонтальной оси (fa_ top/middle/bottom)
Это та часть, что касается кода и с которой мы с вами будем непосредственно работать. Можете держать как памятку у себя перед глазами, когда начнём писать код.
Коллизия - иначе говоря, столкновение объектов, происходит благодаря "маске" спрайта. Если спрайт - это картинка объекта, то маска - это "твёрдое тело", отвечающее за считывание столкновений.Чтобы её посмотреть, откройте настройки спрайта и разверните пункт "Collision Mask"
Хотя маска может иметь несколько форм, о которых будет написано ниже, устанавливается она исходя из четырёх позиций - левого верхнего и правого нижнего углов. Именно эта маска считывает столкновения, а не сам спрайт.
Mode - режим маски. Варианты: - Автоматическая- На всё изображение- Ручная
Type - тип маски. Варианты:- Прямоугольник- Прямоугольник с поворотом- Эллипс (работает медленнее)- "Алмаз" (ромб) (работает медленнее)- Предрасчет / Точный (медленнее) - ГМС постарается сам подстроить маску под форму объекта.- Предрасчет / Точный по кадрам.
Мой совет:Если игра небольшая, ставьте ручной режим и настраивайте сами те объекты, для которых это важно. Особенно те объекты, столкновение с которыми должно происходить только в определённой позиции (скажем, как в Стардью, сталкиваешься с основанием дерева, но не с его кроной).
В нашей игре данная настройка нам практически не пригодится, так как будет использоваться система поиска путей. Тем не менее, с помощью маски можно будет реализовать "выталкивание" застрявших персонажей, а также настроить косметическое "расталкивание" персонажей, если те проходят через занятую другим персонажем клетку.
Как работают скрипты.Скрипт (функция) - именованный блок кода, выполняющий определённую задачу. Все команды, которые мы использовали до этого, являются такими "скриптами". Иногда я буду называть их функциями, оба варианта правильные.
Создать скрипт просто. Для этого нажмите ПКМ по папке "Scripts" и выберете там "Script". Назовите его scrMoveУ вас откроется следующее окно:
scrMove - название нашей функции. В круглых скобках мы в неё можем передавать аргументы. Аргумент - это переменная, которая используется только в функции, передаётся в неё при вызове и может иметь любое название. В скобках напишите spd - это будет как раз наша переменная скорости.Вырежьте код движения из Step-события у игрока и вставьте в скрипт, а в Step-событии игрока напишите следующий код:scrMove(player_speed);Получится следующая картина:
Как вы заметили, "x" и "y" у нас по прежнему работают. Это потому, что мы выполняем скрипт в объекте oPlayer: данные переменные подтягиваются из объекта автоматически.
Когда нужно применять скрипты?-) Когда код, который мы пишем, нужно использовать несколько раз.-) Когда код, который мы пишем, занимает много места, чтобы его было проще отладить.-) Когда нам нужно получить какое-либо значение на основе каких-либо данных, так как скрипт может не просто выполнять код, но и возвращать значения.
Для того, чтобы скрипт вернул какое-то значение, нужно написать:
Переменную можно написать только одну. Команда return прерывает выполнение скрипта. Переменную / возвращаемое значение можно не писать, тогда скрипт просто прервётся. Конструкцию с прерыванием следует добавлять везде, где это может повлиять на игру, особенно её работоспособность.
Раньше скрипты выполнялись медленнее, чем код, написанный просто в объекте. Сейчас это не так (или, по крайней мере, не так критично).
Также стоит понимать, что создав объект "Скрипт", вы, по сути, создали просто файл для хранения кода. В одном скрипте может быть собрано сразу множество разных функций, отвечающих за выполнение одной задачи. При вызове обращаться нужно именно к имени функции.
Скрипт для отрисовки текста с обводкой.
Создаём новый скрипт и называем его scrOutlinedTextНам понадобятся следующие аргументы:-) xPos - позиция текста по X-) yPos - позиция текста по y-) col - цвет текста-) outlineCol - цвет обводки-) text - текст-) curdepth - текущая "глубина" объекта.-) Шрифт, которым будем рисовать.-) Позиционирование по X-) Позиционирование по Y
Общая схема работы скрипта:1) Назначаем максимальную "глубину". 2) Рисуем текст цветом обводки.3) Поверх рисуем текст нужным нам цветом, но с небольшим смещением.4) Возвращаем глубину к первоначальным показателям.
Код: https://pastebin.com/TahYZKc8 P.S. Изначально код не мой, а честно стырен подсмотрен с интернета, но я в него добавил больше переменных для лучшей настройки и отрисовки. Фактически, значение глубины тоже можно передавать как аргумент, чтобы разный текст выводился по разному.
Создание русского шрифта
Проверим, как работает скрипт. Сначала мы создадим русский шрифт. Для этого найдём папку "Fonts", нажмём ПКМ и создадим "Font".Выбирайте любой шрифт и размер, который хотите. Нам с вами нужна кнопка "Add", чтобы добавить диапазон с русским шрифтом.
Здесь напишем 1040 to 1105, чтобы захватить весь русский алфавит, затем нажмём "Add Range".Шрифт называйте на Ваше усмотрение. Я назову его: fontArialRusSmall
Теперь перейдём к объекту игрока. Создадим событие Draw GUI и напишем следующий код:scrOutlinedText(10, 10, c_yellow, c_red, "Скорость: " + string(player_speed), depth, fontArialRusSmall, fa_left, fa_top)Результат:
Можете заменить c_yellow и c_red на любые цвета. Помните, что можно передавать цвета в HEX с помощью # и $ (в обратном порядке).
Скрипт для поиска значения в массиве:
Так как эту тему мы не проходили, заострять внимание не буду, но вдруг кому пригодится: https://pastebin.com/1VTNg4Bp
Переходы между комнатами. Создаём меню.
Наконец, с матчастью мы закончили. Приступим к тому, что создадим меню нашей игры.Создаём новую комнату и обзываем её rmMainMenu. Затем нажмите на значок "домика", чтобы поменять стартовую комнату (ту, что отображается при запуске игры).
Сделайте так, чтобы домик был напротив созданной комнаты.Заметьте, что при создании новой комнаты у неё изменяются все настройки! Это важно.
Создадим в "Objects" папку "Меню", где создадим следующие объекты: "oMStart", "oMSettings", "oMExit", "oMMenu".OMStart - кнопка для старта нашей игры.OMSettings - кнопка для перехода в настройки игры.oMExit - кнопка для выхода из игрыoMLoad - кнопка для загрузки игрыoMMenu - управляющий объект, который будет располагать наши кнопки.
Зайдём в комнату MainMenu и поставим там объект oMMenu.Затем перейдём в код oMMenu и выберем событие создания (Create)
Там напишем следующий код:
Теперь нужно каждую из этих кнопок разместить. Но, чтобы правильно их разместить, сделаем на все кнопки один спрайт. Размер подбирайте под себя.
У каждого объекта создадим два события: Draw, которое оставим пустым, и Draw GUI, где напишем draw_self();Там же напишем:
Где "Здесь текст кнопки" - заменить на нужное: "Начать игру", "Загрузить игру", "Настройки", "Выход"
Вернёмся в OMMenu.Нажмите правой кнопкой мыши. Наведитесь на пункт "Code Snippets" и выберите пункт 5.В данном меню расположены готовые шаблоны кода, а мы создали шаблон цикла.Напишите следующий код:
for (var i = 0; i < array_length(buttons); ++i)
Теперь нужно сделать отслеживание нашей мышки. Кнопку "Настройки" мы сделаем чуть позже. Пока перейдём к кнопке для выхода из игры.
До текста отрисовки нашего текста, нам нужно сделать небольшую подсветку нашей кнопки, чтобы мы знали, что она работает. Для этого нам нужно отслеживать позицию нашей мыши относительно интерфейса, для чего есть специальная команда.
ОК. Теперь мы отслеживаем нашу мышку относительно GUI. Нужно проверить, наведены ли мы на конкретный объект и если да - нарисовать поверх него полупрозрачный белый прямоугольник. Для этого есть команда:instance_position(x, y, obj)
В x и y мы передадим переменные выше, а в качестве объекта напишем "self", чтобы проверять координаты нашего объекта.
Уже после этого пишем наш текст.Теперь при наведении на кнопку "выхода" мы увидим, что она "активна".
Вырежем наши две переменные, добавим их в событие create. Теперь мы можем использовать эти переменные в любом коде этого объекта.
Теперь перейдём в событие step. Сюда также добавим наши две переменные. Теперь они будут постоянно обновляться.Начнём настраивать логику работы.
Можете скопировать тот код, что мы писали выше, но удалить всё изнутри.Нам нужно сделать проверку:Если мы наведены на объект И нажата левая кнопка мыши - выйти из игры. То есть, немного её дополнить.
Для проверки нажатия кнопки левой мыши будем использовать команду:mouse_check_button_pressed(mb_left)Для выхода из игры: game_end()
В итоге, в коде step будет следующий код:
Итого, если мы наведёмся на кнопку выхода - она подсветится. Нажмём - игра закроется.
Аналогичный код с подсветкой и проверкой просто скопируем в oMStart. В других кнопках логику работы в step пока не настраиваем на них, работаем с кнопкой начала игры.Нужно внести одно изменение.
Вместо game_end() нам нужно написать код для смены уровня.Для этого напишем:room_goto(Room1) Где Room1 - название комнаты, в которую вы хотите переместиться. Оно должно совпадать с названием комнаты в браузере ассетов.
Итого, мы можем выйти из игры или перейти в комнату, где у нас есть игрок, которым мы можем управлять. Осталось дополнить написать код, чтобы мы могли вернуться обратно в меню, при этом так, чтобы положение игрока на экране сохранялось.
Для этого нам придётся создать новый объект и назвать его oGameManager.Разместим его в самой первой комнате и отметим у него чекбокс "Persistent"Затем напишем у него в Step код:
Если индекс текущей комнаты != 0 и нажата ESC - деактивируем все инстансы, кроме текущего и переходим в меню.
При старте комнаты, если эта комната не является меню, мы будем запускать все инстансы.
Реализация сложнее. О том, как работает Nine Slice.
Ниже будет приведена реализация меню с использованием функции Nine Slice, которая позволит нам делать кнопки других размеров.Используя Nine Slice мы не сможем проверять, наведены ли мы сейчас на нужный нам объект. Также эта функция годится только для отрисовки типовых форм с типовым шрифтом. Если у вас спрайт кнопки идёт с текстом, то данная функция не подойдёт. Чтобы отслеживать нажатия и наведение, нам необходимо проверять, находятся ли координаты мыши в нужной области. Для этого существует команда
Итак. Первый шаг - это убрать спрайт у каждой нашей кнопки.Для этого откроем объект каждой кнопки, нажмём на выбор спрайта и выберем "None".
Шаг второй.У каждой кнопки в событии Create мы пропишем:
При создании кнопок мы и так будем знать их x и y координаты, а ширину и высоту будем передавать.
Шаг третийНарисуем спрайт в виде одноцветного квадрата размером 9х9 пикселей.Затем добавим ему две обводки разных цветов, каждая шириной в 1 пиксель.Получим следующую картину:
Теперь перейдём в настройки спрайта.Назовём его sButtonSlice.Слева выберем и откроем пункт Nine Slice.
После чего поставим галочку "Activate Nine Slice".Далее - смотрим на наш спрайт и настраиваем его, как показано на скриншоте ниже:
Чтобы двигать фиолетовые полоски - достаточно на них навестись и зажать ЛКМ.В правом окошке вы можете порастягивать данный спрайт, как хотите и посмотреть на итоговый результат.Растягивающаяся рамка для кнопок готова!
Шаг четвертый.
Пропишем в Draw GUI отрисовку кнопки вместо draw_self():
Таким образом, мы отрисовываем кнопку.
Вернёмся в oMMenu, в Create, где немного допишем наш код.Мы создадим переменную, которой будем присваивать значение ID только что созданного объекта. Затем, обращаясь к этой переменной, мы будем менять у данного объекта параметры: ширину и высоту.
Меняя значения bwidth и bheight вы сможете сами настроить нужные размеры кнопок.
Обратите внимание, что создавая инстанс мы можем передать struct - то есть структуру данных.Тогда не нужно будет обращаться к инстансу через переменную, но придется удалить код
При создании наших объектов, а сам код для создания инстанса преобразится следующим образом:
Какой из вариантов удобнее - решать вам.Вариант с переменными, которые объявляются при создании объекта, я лично считаю более надёжным, а функциональных отличий между данными командами не так много. С другой стороны, такая возможность появилось недавно и её нужно тестировать.
Теперь задача - сделать так, чтобы мы могли считывать, наведены ли мы сейчас на кнопку.
Для этого мы берём координаты x и y, после чего проверяем, находится ли наша мышь в области:
Остальное - копируем из прошлого кода. Получается следующий блок:
Шаг седьмой.
Проделать тоже самое в событии Step. (По сути, просто заменяем instance_position)
Шаг восьмой.
Донастраиваем все кнопки.
Какой из вариантов использовать - решать Вам.
Можно вообще использовать следующий вариант:
Создать спрайт нужных размеров и залить его белым цветом с "прозрачностью" в 1 единицу. Совершенно незаметно. Используя bbox_left / top / right / bottom нарисовать поверх прозрачной основы кнопку и проверять уже не через "point_in_rectangle", а как в первом способе.
На этом сегодняшний гайд подходит к концу.Вкратце мы разобрали события отрисовки; как с ними работать; как работать со скриптами; как работать со шрифтами; а также вы можете смело скопировать себе и использовать несколько других скриптов в своих проектах.
Что нужно подготовить к следующему гайду:- Минимум два спрайта "породы" - на фон (темнее) и для объекта (светлее). Размер одного спрайта - 32х32.- Желательно спрайты нескольких объектов, вроде стен, дверей. Исходите из того, что размер одной клетки будет равен 32х32 пикселей.- Спрайт для неба/космоса/прочего на фон на ваше усмотрение.
Я подготовлю свои варианты и, используя их, на следующем занятии мы с вами разберёмся, как работает камера.Сделаем меню настроек с возможностью переключения в полноэкранный режим.
И небольшой спойлер на будущее.Взаимодействовать с миром можно будет двумя способами: через ЛКМ и наведением "тела" игрока на тот или иной объект.
Загрузить файл проекта и всё пощупать самому можно по ссылке:Яндекс диск
- Камера и её настройка. Разные способы реализации: от простого к сложному.
- Иерархия объектов. «Объекты-родители» и их «дети». Решение часто встречающихся проблем и немного про то, как удобно выстраивать взаимодействие с объектами. Глобальные переменные.
- Массивы и с чем их едят, а также grid (сетка комнаты), размещение объектов по сетке. Включая объяснение, в каких случаях лучше использовать встроенные функции, в каких – писать свои с нуля.
- Пути. Один большой гайд, включая скрипты поиска путей для различных сеток в т.ч. с примерами из моего личного проекта.
- Иные способы хранения информации в GMS2, когда их стоит или не стоит использовать.
- Сохранение. Встроенное VS самописное.
Есть вопрос - задавай, постараюсь ответить.Что-то не получилось, вылезла ошибка и так далее - тоже пиши, помогу или поможем.