Горячее
Лучшее
Свежее
Подписки
Сообщества
Блоги
Эксперты
Войти
Забыли пароль?
или продолжите с
Создать аккаунт
Я хочу получать рассылки с лучшими постами за неделю
или
Восстановление пароля
Восстановление пароля
Получить код в Telegram
Войти с Яндекс ID Войти через VK ID
Создавая аккаунт, я соглашаюсь с правилами Пикабу и даю согласие на обработку персональных данных.
ПромокодыРаботаКурсыРекламаИгрыПополнение Steam
Пикабу Игры +1000 бесплатных онлайн игр
“Рецепт Счастья” — увлекательная игра в жанре «соедини предметы»! Помогите Эмили раскрыть тайны пропавшего родственника, найти сокровища и восстановить её любимое кафе.

Рецепт Счастья

Казуальные, Головоломки, Новеллы

Играть

Топ прошлой недели

  • AlexKud AlexKud 38 постов
  • SergeyKorsun SergeyKorsun 12 постов
  • SupportHuaport SupportHuaport 5 постов
Посмотреть весь топ

Лучшие посты недели

Рассылка Пикабу: отправляем самые рейтинговые материалы за 7 дней 🔥

Нажимая кнопку «Подписаться на рассылку», я соглашаюсь с Правилами Пикабу и даю согласие на обработку персональных данных.

Спасибо, что подписались!
Пожалуйста, проверьте почту 😊

Помощь Кодекс Пикабу Команда Пикабу Моб. приложение
Правила соцсети О рекомендациях О компании
Промокоды Биг Гик Промокоды Lamoda Промокоды МВидео Промокоды Яндекс Директ Промокоды Отелло Промокоды Aroma Butik Промокоды Яндекс Путешествия Постила Футбол сегодня

Gamedev + Урок

С этим тегом используют

Инди Разработка Инди игра Игры Unity Компьютерные игры YouTube Образование Школа Обучение Учеба Английский язык Учитель Преподаватель Все
117 постов сначала свежее
22
GrimmIronwill
GrimmIronwill
2 года назад
Лига Разработчиков Видеоигр
Серия Gamemaker Studio 2: серия гайдов

GameMaker Studio 2. Урок 6. Алгоритмы поиска путей. Оптимизация игры⁠⁠

Привет!
Сегодня будет много теории и много практики. Поговорим о том, какой инструментарий нам предлагает GMS 2 для создания путей и как делать делать их самостоятельно. Поговорим о том, почему и когда стоит использовать базовые инструменты или написанные самостоятельно, а также поговорим о том, как сделать игру быстрее.

Сразу оговорюсь, что многое из того, что вы будете видеть - это моя адаптация того, что можно было свободно найти в интернете, в том числе скрипты. Я не являюсь их создателем или первооткрывателем, а потому везде будут присутствовать ссылки на те источники, что я использовал сам в своё время.
Может, вам их будет проще понять :)


Ссылки на предыдущие гайды:

Первый гайд - Знакомство.

Второй гайд - События отрисовки, коллизия, скрипты.

Третий гайд - Камера и разрешение экрана.

Четвертый гайд - Иерархия объектов. Глобальные переменные.
Пятый гайд - Структуры данных. Сетка комнаты и размещение объектов по сетке.

Оглавление:
- Поиск путей. Что это и как работает?
- Встроенные средства для работы с путями.
- Алгоритмы поиска путей. Какие существуют?
- Алгоритмы поиска путей. Скрипты.
- Оптимизация. Что это и зачем?
- Оптимизация. Исправляем ошибки и учимся их избегать.

Поиск путей. Что это и как работает?

Как понятно из названия, поиск путей решает ровно одну задачу - он ищет ближайший путь от точки А до точки Б. Все алгоритмы поиска путей решают эту задачу, но каждый по своему.
О существующих алгоритмах мы поговорим ниже. Пока стоит разобраться, как они работают в общих чертах.

Итак, представим сетку комнаты, которую мы делали ранее. Это набор точек, каждая из которых имеет собственные координаты.
Так как сетка у нас квадратная, то каждая точка, кроме крайних, имеет четыре (восемь, если учитывать диагонали) "соседей". Таким образом, из одной точки мы можем попасть в любую соседнюю.
Любой алгоритм поиска путей занимается тем, что пытается попасть из точки А в точку Б, перебирая соседние клетки. Занимая соседнюю клетку, он проходится по её "соседям" и так до тех пор, пока не будет достигнута точка конца или не кончатся "соседи".
На самом деле, необязательно иметь именно квадратную сетку, чтобы поиск путей работал, ведь поиск путей, по своей сути, работает на более высоком уровне: графах.

Граф - это модель, состоящая из множества вершин и множества соединяющих их рёбер.
Ниже - пример графа из заданий по информатике на экзаменах.

GameMaker Studio 2. Урок 6. Алгоритмы поиска путей. Оптимизация игры Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок, Игры, Стратегия

Таким образом, любая сетка - это лишь определённое представление графа.

Встроенные средства для работы с путями.

Для работы с путями в GMS 2 существует ряд функций, которые начинаются с приписки: mp, которая расшифровывается как планирование движения (motion planning), а также path.
Чтобы была возможность строить пути, прежде всего нужно создать сетку комнаты. Для этого существует команда:

global.grid_mp = mp_grid_create(0, 0, cells_x, cells_y, CellWidth, CellHeight)
Где cells_x, cells_y - количество сеток по осям x, y, а CellWidth и CellHeight - ширина и высота одной клетки.

После создания сетки комнаты, вам остаётся только задать две точки и вы уже сможете строить между ними пути. В step событии любого объекта создайте скрипт, чтобы устанавливать эти точки, а затем в draw событии напишите следующий код:
var new_path = path_add()
var mp_path = mp_grid_path(global.grid_mp, new_path, fcell_coords[0] * CellWidth + 16, fcell_coords[1] * CellHeight + 16, scell_coords[0] * CellWidth + 16, scell_coords[1] * CellHeight + 16, 1)
if mp_path
{
draw_path(new_path, fcell_coords[0] * CellWidth + 16, fcell_coords[1] * CellHeight + 16, false)
}
Где fcell_coords и scell_coords - два массива, хранящие клетки по x,y старта и конца соответственно.
Функция path_add() - создаёт путь.
Функция mp_grid_path() строит путь между указанными координатами по сетке. Если путь существует, то возвращает true, иначе - false. Если путь существует, то сохраняет в переменную пути, указанную в скобках, точки, которые предстоит пройти.

Таким образом у вас будет отрисован путь по клеткам.
Чтобы увидеть сетку, нужно написать:
draw_set_alpha(0.25)
mp_grid_draw(переменная_которая_хранит_сетку)
draw_set_alpha(1)
Чтобы добавить на неё препятствия существуют команды:
mp_grid_add_cell() // Закрашивает одну определённую клетку, делая непроходимой.
mp_grid_add_instances()  // Закрашивает все клетки, на которых расположены определённые объекты.
mp_grid_add_rectangle() // Закрашивает определённую прямоугольную область.
Создать путь - это только первый шаг. Второй - это запустить по нему объект двигаться. Для этого есть функция:
path_start(путь, скорость, что_сделать_в_конце_пути, абсолютный_путь_или_к_текущей_позиции)
Всё. Так легко и просто можно создать сетку, расположить на ней непроходимые объекты и строить пути между точками, в последствии их запуская.

Но у такого подхода есть и минусы. Главный из них - ограниченность инструментария. Он позволяет нормально работать только с квадратными сетками и не учитывает "стоимость" клеток, по которым проложен путь.
Рекомендую использовать встроенные средства в тех случаях, когда вы создаёте простую игру, где о подобных мелочах можно не заботиться. Они достаточно хорошо оптимизированы. А мы идём дальше.

Алгоритмы поиска путей. Какие существуют?

Если всё, что делают алгоритмы поиска путей - это перебирают соседние клетки и пытаются построить путь между точками, то главное отличие между ними заключается в том, по какому принципу идёт отбор этих соседей.

Жадный алгоритм.
Суть данного алгоритма отражена в его названии. На каждом шаге он делает наилучший на данный момент выбор. То есть, всегда двигается по самым "дешёвым" вершинам графа, пока в итоге не будет достигнута точка конца.

Поиск в ширину.

Это рекурсивный алгоритм поиска всех вершин графа или дерева. Обход начинается с корневого узла и затем алгоритм исследует все соседние узлы. Затем выбирается ближайший узел и исследуются все неисследованные узлы.
Сам алгоритм:
1. Перейдите на соседнюю нерассмотренную вершину. Отметьте как рассмотренную. Отобразите это. Вставьте ее в очередь.
2. Если смежная вершина не найдена, удалите первую вершину из очереди.
3. Повторяйте шаг 1 и шаг 2, пока очередь не станет пустой.
Если же говорить своими словами, то данный алгоритм равномерно исследует все доступные точки. Такой алгоритм часто применяется при генерации карт, либо для их исследования. Правда, он не строит пути как таковые. Он просто показывает, как мы можем посетить все точки на карте.
Источник

Алгоритм Дейкстры.
Данный алгоритм находит кратчайшие пути от одной из вершин графа до всех остальных. Работает только для графов без рёбер с отрицательным весом, иными словами - не может работать в тех случаях, когда стоимость клетки отрицательная. Это важно.
Таким образом, суть алгоритма заключается в том, чтобы найти найти все возможные пути из точки А в каждую из других существующих, а затем среди них выбрать самые дешёвые.
Алгоритм действенный и полезный, но достаточно медленный, если речь заходит о поиске пути между двумя конкретными точками. Но, это не беда, ведь существует следующий алгоритм.

Алгоритм A* (A star).
Данный алгоритм является вариацией алгоритма Дейкстры, скрещённого с жадным поиском и эвристическим поиском. То есть, он старается выбирать и самые дешёвые пути, и самые "близкие" к конечной точке.

Что за эвристический поиск?
Эвристический поиск находит расстояние между двумя точками и расширяет границы к конечной точке больше, чем в другие стороны. В зависимости от типа сетки, существует два способа определения расстояния.

Квадратная сетка.
abs(a[0] - b[0]) + abs(a[1] - b[1]) // a[0],b[0] - координаты по оси x, a[1], b[1] - по оси y. abs возвращает абсолютное (всегда положительное независимо от результата) значение.
Гексогональная сетка.
(abs(qa - qb) + abs(ra - rb) + abs(sa - sb)) / 2 // где q, r, s - это координаты в кубической системе.

Источник для гексов
Статья с Хабра с подробным разбором, взятая за основу.

Алгоритмы поиска путей. Скрипты.

Переходим ко вкусному.
Поиск соседей на квадратной сетке.
***
Алгоритм А* на GML.
Небольшая особенность данного скрипта. Если клетка, в которую мы хотим прийти, является непроходимой, то мы всё равно строим до неё путь. Встать на неё мы всё равно не сможем, но получим ближайший к данной точке путь.
Важно. Работает это только для соседних к конечной клеток.
Как работает:
Если найден, возвращает путь в виде массива между двумя точками. Точки старта и конца должны быть переданы в виде их порядкового номера. Если путь не найден, возвращает noone.
Соответственно, чтобы отрисовать путь - нужно будет пройтись по массиву из точек.

Поиск соседей на гексагональной сетке отличается только координатами. Вместо x и y мы используем q, r, s, которые являются трёхмерным представлением двумерных координат.
col - это столбец, row- это строка. x и y по сути.
q = col
r = row - (col - (col & 1)) / 2
s = -q - r

Лучше использовать трёхмерные координаты, так как в зависимости от поворота гекса могут быть различия в координатной системе.

По сути - всё. Дальше стандартно: проверяем, что мы не выходим за границы сетки и если стоимость гекса != 0 (или больше 0), то добавляем его.

Оптимизация. Что это и зачем?

Я думаю, каждый знаком с термином "оптимизация". Если же нет, то вики говорит нам:

Оптимизация — процесс максимизации выгодных характеристик, соотношений (например, оптимизация производственных процессов и производства), и минимизации расходов.
Мы, как разработчики, должны позаботиться об оптимизации игры. Первое, что бросается в глаза - это оптимизация кода. Покрывая наши потребности, он должен при этом быть максимально простым и лёгким, чтобы не давать лишнюю нагрузку на ПК.

Конкретно в случае с GMS, есть некоторые моменты, которые стоит запомнить.
- Используйте локальные переменные.
Вместо того, чтобы постоянно получать результат функции, сохраните его один раз в переменную и используйте её в дальнейшем.
Вместо постоянного обращения к глобальным переменным, сохраните глобальную переменную в локальную и обращайтесь к ней. И так далее. Это быстрее.

- Старайтесь использовать объекты по минимуму.
На фоне предыдущих гайдов это прозвучит несколько странно, ведь мы заполняли комнату объектами полностью. Делалось это с целью теста, не более того.
В нашем с вами случаем, ребята, гораздо быстрее хранить информацию о каждой клетке в массиве, а клетки отрисовывать через тайлмап.
Объект, даже если он занимается только отрисовкой собственного спрайта, нагружает систему больше. Поэтому проще отрисовать спрайт отдельно. Прирост ФПС составит до 6 раз. ( Замерял, выросло с 500 до 2500 в среднем :) )

О том, как это работает и реализацию покажу ниже. :)

- Не рисуйте объекты, которые находятся за границами камеры.
Очевидный, но важный совет, так как draw event является крайне ресурсоёмким, как и step.

- Не делайте проверку коллизий там, где это не нужно. Если делаете, то сохраняйте результат в локальную переменную.

Оптимизация. Исправляем ошибки и учимся их избегать.

Первое, что мы сделаем - это создадим один большой спрайт, на котором разместим все наши игровые спрайты. Важно, чтобы первая клетка была пустой!
Затем - создайте объект Tileset и назначьте ему этот спрайт.
На скриншоте ниже показано, почему первая клетка должна быть пустой.

GameMaker Studio 2. Урок 6. Алгоритмы поиска путей. Оптимизация игры Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок, Игры, Стратегия

Дальше - создайте слой комнаты типа "Тайл" и назначьте ему тайлсет.

GameMaker Studio 2. Урок 6. Алгоритмы поиска путей. Оптимизация игры Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок, Игры, Стратегия

Всё, теперь мы готовы использовать вместо объектов - тайлы. Причём сразу для всех видов: и для фона, и для непосредственно отрисовки объектов, и для украшательств: спрайтовых теней.
Теперь в oGrid нужно будет заменить старый код на новый.
Ссылка на код.

Кратко пробежимся по тому, что мы используем и как.
lay_id_blocks = layer_get_id("Название_Слоя") - Здесь мы получаем ID нашего слоя.

var map_id_blocks = layer_tilemap_get_id(lay_id_blocks) - Здесь мы получаем слой тайлов, используя ID слоя.
tilemap_set(map_id_blocks, Tile.ground, _x, _y) - Здесь мы устанавливаем у слоя тайлов, по определённым координатам, определённую картинку. Tile.ground возвращает 1, следовательно будет отрисована первая клетка из нашего тайлсета. Нулевая клетка - пустая, используется для удаления тайлов.
С-но, для получения текущего тайла существует команда tilemap_get(map_id_blocks, клетка_x, клетка_y). Если по указанной позиции установлен тайл, вернёт его номер. Если нет - вернёт ноль. Вернёт -1, если есть ошибка.

Специально не обращаю внимание на то, как пришлось переписывать код, так как по сути изменилось лишь взаимодействие, которое вкратце - описано выше.

Для отрисовки теней используется отдельный слой, где в определённом порядке расположены тени. Это важно, так как используется скрипт для автоматического тайлинга, то есть - автоматического определения того, какую тень нам нужно рисовать.

Ссылка на оригинальный скрипт для интересующихся:
https://github.com/iAmMortos/autotile

Как ускорить проекты, сделанные на Gamemaker Studio 2 под Windows

Здесь есть вся нужная информация. Делается с помощью компиляции под С++.
https://help.yoyogames.com/hc/en-us/articles/235186048-Setti...

Будем прощаться, ребята.

Ссылка на исходник
Там два файла. YYZ - для тех, кто хочет повтыкать в код. В папке - уже скомпилированный проект, для тех, кто просто хочет потыкать и посмотреть, как это всё добро работает.

Темы, которые осталось разобрать:
- Сохранение. Встроенное VS самописное. Работа с файлами.

- Звуки.

Дальше в формате небольших постов будут выкладываться полезные скрипты (и не только) для стратегий, плюс буду рассказывать о ходе разработки. Фактически, уже на данном этапе у вас есть "скелет" игры.

Спасибо всем, кто читал и кто поддерживает. Вы зайки.
Есть вопросы - пишите в комментарии, помогу.
Есть пожелания или замечания - буду рад выслушать.

Показать полностью 3
[моё] Разработка Gamedev Программирование Инди Инди игра Gamemaker Studio 2 Образование Длиннопост Урок Игры Стратегия
2
28
GrimmIronwill
GrimmIronwill
2 года назад
Лига Разработчиков Видеоигр
Серия Gamemaker Studio 2: серия гайдов

GameMaker Studio 2. Урок 5. Структуры данных и с чем их едят, а также grid (сетка комнаты), размещение объектов по сетке⁠⁠

Привет! Сегодня разберёмся, что такое массивы и структуры данных. Создадим сетку комнаты, научимся размещать объекты по этой сетке.

P.S. Передаю привет тому человеку, который ставит минусы на всё, что я пишу. Счастья тебе, здоровья.


Ссылки на предыдущие гайды:

Первый гайд

Второй гайд

Третий гайд

Четвертый гайд

Сегодня мы поговорим о следующем:

- Что такое массивы и для чего они нужны? Что такое структуры данных?

- Какие виды структур данных существуют в GMS? Их особенности.
- Реализуем сетку карты.

Что такое массивы и для чего они нужны?

Массив - это структура данных, которая хранит набор значений. Мы можем "взять" каждое такое значение, обратившись к массиву по индексу.

Индекс - это порядковый номер элемента в массиве.

Важно знать:

1. Индексы в массивах начинаются с нуля.

2. В массив можно запихнуть массив, в него запихнуть массив и так до бесконечности. Отсюда следует:

2.1. Массив называется одномерным, если в него не вложены другие массивы. Если в массив вложен другой массив, то это уже двумерный массив. Если в массиве есть массив с массивом - трёхмерный. И так далее.

Массив - структура данных. Что такое структура данных?
Структура данных - это "контейнер", который хранит данные в определённом формате. Соответственно, массив - один форматов хранения данных.

Какие виды структур данных существуют в GMS? Их особенности.


Первый структура данных, которую мы разберём называется Array (массив). Его можно создать несколькими способами:

1.array = array_create(10, noone)
2. array = [1, 2, 3, 4] // внутри пишем любые свои значения
3.
array[0] = 1
array[1] = 2

И так далее. Также можно объявить сколько угодно мерный массив:

array[0][0][0][0] = 1.

Такие массивы преспокойно встраиваются в сохранение стандартными средствами GMS. Также здесь в автономном режиме работает "сборщик мусора". И это важно, поскольку в структурах данных (приписка DS) этого нет. Соответственно, их придётся удалять вручную.

Часто в мануале говорится, что лучше использовать именно array, а не другие аналогичные структуры, так как меньше шанс "выстрелить себе в ногу". Впрочем, если покурить тот же мануал, то использовать DS можно вполне спокойно, выигрывая при этом в скорости работы программы.

Structs или структуры.

По сути, тот же массив, но вместо индексов - у нас именованные значения. Структуры используются для упорядоченного хранения информации. Пример объявления структуры из мануала:

// Create event
mystruct =
{
pos_x : x,
pos_y : y,
count : 1000
};
// Clean Up event
delete mystruct;

Таким образом получается, что левое значение у нас выступает в качестве хранилища для какого-то значения, которое будет записано справа через двоеточие.

Обращение к struct для вычленения из него данных похоже на таковое для объектов. Нам нужно назвать структуру, а затем через точку - нужную нам "переменную".

show_debug_message(mystruct.pos_x)

С-но, в каждом значении структуры можно хранить другое значение. Напоминает HTML-код.

mystruct =
{
a :
{
aa : "Example"
},
b :
{
bb : "Another"
},
};

В таком случае, нам нужно будет обратиться к структуре, к значению, а затем к значению внутри значения.

show_debug_message(mystruct.a.aa)

Также для простоты к структуре можно обращаться через with

with(mystruct)
{
a += other.x;
}

Ну и последнее, что стоит сказать. Мы можем использовать функции-конструкторы. Они позволяют создавать новые структуры по шаблону, который будет описан в такой функции.

// Функция-конструктор.
function Vector2(_x, _y) constructor
{
x = _x;
y = _y;
static Add = function(_vec2)
{
x += _vec2.x;
y += _vec2.y;
}
}
// создаем новую структуру
v2 = new Vector2(10, 10);

Важное правило!!!
Хотите избежать утечек памяти - всегда удаляйте все структуры данных, когда они не нужны.

DS Grids или сетки.

Формально - это двумерный список. Позволяет, как ни странно, создавать сетку и назначать каждой клетке какое-либо числовое значение.

Для хранения позиций клетки всегда следует использовать целые значения. Нецелые будут автоматически округлены движком, что может быть непредсказуемо.

Как создать:

переменная = ds_grid_create(ширина в клетках, высота в клетках)
Ещё раз обращаю внимание, что с помощью DS Grid мы можем назначить каждой клетке отдельное значение. В случае с созданием своего алгоритма поиска путей это означает, что в зависимости от типа местности, алгоритм будет находить наиболее оптимальный путь опираясь на итоговую стоимость пути. Просто как пример.
А ещё сетка используется для создания тех самых "клеток", по которым идёт расположение построек, к примеру.

DS Lists или списки.

По своей сути схож с array. Для списка не важен порядок загрузки и выгрузки элементов. Для работы со списками по умолчанию больше инструментов, чем для работой с array. Например, встроенная функция для поиска какого-либо значения в списке, чего у array по умолчанию нет.

Применять нужно в тех местах, где важна скорость работы и неизвестно конечное число элементов. В ином случае лучше применять array.

переменная = ds_list_create();
Вставить новое значение:
ds_list_add(переменная, значение)

DS Maps или словари.

Словарь по своей сути похож на struct: он хранит данные в паре "ключ : значение". Если представить это образно, то это две колонки. Первая - это ключ. Второе - значение. Обращаясь к первой колонке, к ключу, мы можем спокойно получить значение. Или получить все ключи по определённому значению.
Мы будем их использовать в следующем гайде, когда будем разбирать пути и будем писать свои функции для их создания.
Способ создания:

переменная = ds_map_create()
Вставить новое значение:
ds_map_add(переменная, ключ, значение)

Мануал рекомендует использовать вместо DS Maps - структуры. Собственно, с ними нам тоже придётся поработать.

DS Queues, DS Priority Queues, DS Stacks

Всё это - "очереди", только по разному работающие. Разберём сначала Stacks и Queues.


Stacks
это структура типа LIFO (last-in-first-out / последним пришёл - первым ушёл). Как и со списками, мы можем "вычленять" последние значения из стаков с помощь команды pop. Особенность LIFO в том, что в таком случае мы получим последнее значение, которое загружали в список.

С Queue ситуация обратная. Там используется FIFO (first-in-first-out / первым пришел - первым ушёл). То есть, забирая значение из queue (очередь), мы получим первое значение, которое вставили.

Важная оговорка. pop - берёт значение из массива, при этом удаляя его из него.

Priority Queue или приоритетная очередь - это та же очередь, но в которой каждое значение имеет свой "вес" - приоритет, что влияет на расположение данных в структуре и позволяет отбирать, скажем, наиболее дешёвые пути. Как говорит мануал, полезно для создания таблиц лидеров или информационных списков. Мы же будем это использовать для написания алгоритма поиска путей А* (A star, А звездочка).

Хоть и очень кратко, поверхностно, но мы рассмотрели структуры данных, с которыми будем или не будем работать. В последствии, когда будем их применять, я уже более подробно остановлюсь на том, почему был выбран тот или иной вид структур данных.

Создаём сетку комнаты.

1. У объекта oGameManager пропишем две дополнительные макро переменные, которые будут хранить ширину и высоту одной клетки.

#macro CellWidth 32

#macro CellHeight 32

2. Создадим объект oGrid, который расположим в нашей основной комнате.

3. У этого объекта создадим событие Create, где будем создавать нашу сетку.

3.1. Для наглядности, создадим сетку с помощью команды mp_grid_create
mp в данном случае - это "motion planning" - планирование движения. Система, которая позволяет создать сетку комнаты и сразу же по ней создавать пути, используя встроенные алгоритмы. Они не дают всего необходимого функционала, поэтому этот пункт выполняем исключительно для наглядности.

global.grid = mp_grid_create(0, 0, ceil(room_width / CellWidth), ceil(room_height / CellHeight), CellWidth, CellHeight)

3.1.1. Перейдём в событие Draw, где отрисуем сетку.

draw_set_alpha(0.1)
mp_grid_draw(global.grid)
draw_set_alpha(1)

3.1.2. Зайдём в игру и посмотрим, что получилось.

GameMaker Studio 2. Урок 5. Структуры данных и с чем их едят, а также grid (сетка комнаты), размещение объектов по сетке Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

Как видно, у нас получилась квадратная сетка. Используя функции mp_grid_* мы можем закрашивать нашу сетку, делая определённые квадраты непроходимыми. Но мы не можем назначать таким клеткам свою стоимость.
Иными словами, если будет два одинаковых по длине пути, но один через условные зыбучие пески, а второй по асфальту, то получим 50/50, что персонаж выберет медленный и потенциально опасный путь, просто потому что мы не можем указать, какой из путей будет приоритетнее.

Подробнее о том, когда и какие функции поиска путей использовать, мы будем говорить в следующем гайде. Продолжим.

3.2. Удаляем или комментируем то, что мы написали. Сейчас будем работать со структурами данных.
3.2.1. Создаём сетку через ds_grid_create в событии create

var cells_x = ceil(room_width / CellWidth)
var cells_y = ceil(room_height / CellHeight)
global.grid = ds_grid_create(cells_x, cells_y)

Ок. По сути, сетка готова. Теперь по ней мы можем создавать объекты. Давайте же заполним всю нашу комнату oDirt, а дальше уже будем разбираться.
3.2.2. Всё там же в Create нам нужно пройтись циклом по осям x, y, создавая объект oDirt по указанным координатам. Затем мы передадим получившиеся координаты, в клетках, в переменные внутри объектов, для удобства.

for (var _y = 0; _y < cells_y; ++_y)

{

for (var _x = 0; _x < cells_x; ++_x)

{

ds_grid_set(global.grid, c, u, 0) // 0 - это стоимость клетки. Ноль - значит непроходима.

var inst = instance_create_layer(_x * CellWidth + 16, _y * CellHeight + 16, "Instances", oDirt)

inst.coordx = _x

inst.coordy = _y

}

}

Зачем при создании объекта мы плюсовали 16 пикселей?
Напомню, что точка origin - отсчёта - у наших объектов находится в центре их спрайтов. Следовательно, располагая объект по координатам (предположим, (0, 0)), часть объекта будет выходить за границы комнаты.

3.2.3. После запуска всё работает. Проверим координаты каждой клетки. Для этого вернёмся к игроку и в событии Draw GUI сделаем проверку: если позиции мыши по x,y (относительно комнаты) содержит в себе oObject, то выводим на экран coordx и coordy.
if instance_position(mouse_x, mouse_y, oObject)
{
var inst = instance_position(mouse_x, mouse_y, oObject)
scrOutlinedText(dmxg + 10, dmyg + 10, c_white, c_black, string(inst.coordx) + " " + string(inst.coordy), depth, fontArialRusSmall, fa_left, fa_top)
}
Проверяем.
GameMaker Studio 2. Урок 5. Структуры данных и с чем их едят, а также grid (сетка комнаты), размещение объектов по сетке Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

Работает! Впрочем, это только первая часть того, что нам нужно, не так ли? Помимо создания карты, мы должны иметь возможность располагать объекты самостоятельно, удалять их при необходимости. Да и с расположением земли не всё так гладко, как хотелось бы. Мы ведь совсем не добавили фон!

Начнём с конца, ибо это, на самом-то деле, достаточно просто.
У нас с вами есть два пути.
1. Мы создаём слой для спрайтов, на котором у нас будут спрайты фона.
2. Мы создаём для каждого нашего объекта "переднего плана" дубликат для заднего фона. По сути, увеличиваем количество объектов в комнате в два раза, соответственно увеличивая и нагрузку.

Работать мы будем с первым вариантом по той причине, что он будет работать быстрее, а также предоставляет нам весь необходимый функционал, потому и смысла во втором варианте нет.

Первый вариант, несмотря на преимущество в скорости работы, имеет существенный минус. Мы не сможем обращаться к спрайтам по их координатам, да и в целом будем сильно ограничены в работе с ними. Придётся хранить ID каждого отрисованного нами спрайта. Для этого нам придётся создать вторую переменную, которая будет хранить координаты клеток (по x, y), а также id спрайта.
Так как мы знаем размер одной клетки, мы можем легко получить индекс той клетки, на которую сейчас наведены. Следовательно, можем получить и ID текущего спрайта - удалить его или назначить новый.

Получаем порядковый номер клетки. Для этого берём координату x текущей клетки, к ней плюсуем произведение текущей координаты y и максимального размера сетки по x. Т.Е. формула:

cell_id = x + y * max_x

В коде же это будет выглядеть следующим образом:

var cell_id = mouse_x div CellWidth + mouse_y div CellHeight * ceil(room_width / CellWidth)

Итак, работаем.

1. Создаём новый слой - Asset Layer, который назовём Backs.
2. Перейдём к объекту oGrid.
3. Добавим ещё одну глобальную переменную сразу после объявления сетки - global.grid_array = []
4. Чуток допишем цикл. Сделаем так, чтобы в список у нас добавлялись значения x и y.

var cells_x = ceil(room_width / CellWidth)

var cells_y = ceil(room_height / CellHeight)

global.grid = ds_grid_create(cells_x, cells_y)

global.grid_array = []

for (var _y = 0; _y < cells_y; ++_y)

{

for (var _x = 0; _x < cells_x; ++_x)

{

ds_grid_set(global.grid, _x, _y, 0) // 0 - это стоимость клетки. Ноль - значит непроходима.

var inst = instance_create_layer(_x * CellWidth + 16, _y * CellHeight + 16, "Instances", oDirt)

inst.coordx = _x

inst.coordy = _y

array_push(global.grid_array, [_x, _y])

}

}

5. Ок. Осталось ставить спрайты на "фон". Мы могли бы делать это и в цикле, при создании объектов, но зачем нам лишняя нагрузка? Правильно, незачем. Перейдём к объекту oDirt и у него создадим событие Destroy. Этот код будет выполняться перед уничтожением объекта.
С-но, алгоритм прост. Рисуем спрайт, сохраняя его ID. Узнаём индекс нужного нам массива и в него вставляем ID нарисованного спрайта.

var back = layer_sprite_create("Backs", x - 16, y - 16, sDirtBG)
var cell_id = coordx + coordy * ceil(room_width / CellWidth)
array_push(global.grid_array[cell_id], back)
ds_grid_set(global.grid, coordx, coordy, 1) // Делаем проходимым
6. Чтобы проверить, что всё работает, перейдём к игроку и сделаем так, чтобы по нажатию на кнопку мыши, у нас удалялся объект. Это пишется в step.
if instance_position(mouse_x, mouse_y, oObject)
and mouse_check_button_pressed(mb_right)
{
var inst = instance_position(mouse_x, mouse_y, oObject)
with(inst)
{
instance_destroy();
}
}
GameMaker Studio 2. Урок 5. Структуры данных и с чем их едят, а также grid (сетка комнаты), размещение объектов по сетке Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

Как видим, фон рисуется исправно. Так как мы сохранили ID спрайта, мы можем к нему обратиться и в любой момент можем его удалить. Впрочем, последнее сейчас нам нужно исключительно в целях отладки нашего проекта.
По аналогии, мы можем размещать объекты, но я бы предпочёл сразу сделать систему, которая позволит нам ставить любые объекты, которые мы захотим.

Если упрощать, то нам нужно сделать следующие шаги, чтобы данную систему реализовать в полной мере.
1) Реализовать глобальную переменную, чтобы мы могли отслеживать событие: открыто сейчас какое-либо меню или нет. Простой переключатель, который будет закрывать старое меню при переключении на новое.
2) Сделать родительский объект или функцию для всех меню, так как логика взаимодействия у нас не отличается.
3) Расписать отдельно логику для каждого пункта. Конкретно сейчас, нам нужно таким образом реализовать меню строительства.

Теперь о самом меню строительства.
При активации оно должно выводить список всех возможных к постройке объектов. Следовательно, самое оптимальное здесь - использовать массив, в который мы передадим все эти объекты. Желательно заранее предусмотреть систему, которая убережёт нас от ситуаций, когда интерфейс уходит за границы экрана.

После того, как будет готов вывод объектов - нужно будет настроить логику взаимодействия с этими объектами. Выглядеть оно должно следующим образом:
- Нажали ЛКМ по объекту - объект, на который мы сейчас наведены, стал активным. Если был иной объект - он заменяется на новый.
- Нажали ПКМ где угодно - выбранный объект сбросился.
Если же нажали ЛКМ по игровому полю - то поставили активный сейчас объект, удалив при этом старый.
Для удобства также желательно отрисовывать спрайт активного объекта под курсором мыши.

Непосредственно реализация.

Начнём с того, что заблокируем наш пользовательский интерфейс на нужных нам значениях. Пусть это будет FHD. Тогда, в объекте oGameManager, в Create, нужно будет добавить одну строку кода:

display_set_gui_size(1920, 1080);
Её же нужно будет убрать в oSSize.
Всё, теперь наш GUI заблокирован на FHD. Даже если размер окна меньше или больше, координаты мыши будут подстраиваться под данные значения.
Также это означает, что наш пользовательский интерфейс не будет скакать при изменении размеров окна, следовательно - не нужно париться над его перерисовкой.

Дальше. Сделаем само меню и разместим его.
GameMaker Studio 2. Урок 5. Структуры данных и с чем их едят, а также grid (сетка комнаты), размещение объектов по сетке Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

Нового здесь ничего нет, поэтому повторяться не буду. Всего это шесть новых объектов.
В oGameManager пропишем переменную-тумблер:

global.placing = noone
Переменную "активности" кнопки нам следует прописать у каждого объекта-меню, чтобы мы могли отслеживать, какой именно из объектов сейчас рисуется.

Теперь логика меню. Возьмём за основу кнопку "Строительство".
Сделаем список из нескольких объектов, которые мы бы хотели выводить в списке. Дополним их названиями, так как они нам пригодятся позже.
object_list = [["Земля", oDirt], ["Камень", oRock], ["Металл", oMetal], ["Уголь", oCoal]]
Реализовывать кнопки мы будем через отдельный объект - oCell.
Для этого, его нужно создать. Задать ему спрайт в виде квадрата. Сделать так, чтобы он отрисовывался в событии GUI и при этом хранил следующие значения:
Объект, который он должен рисовать, название этого объекта, какое сейчас меню открыто.
object_name = noone
object_to_draw = noone
menu = noone
Вернёмся к объекту меню строительства.

Код в step:
dmxg = device_mouse_x_to_gui(0)
dmyg = device_mouse_y_to_gui(0)
if instance_position(dmxg, dmyg, self) and mouse_check_button_pressed(mb_left)
{
// Если не активно - делаем активным
if !active
{
active = !active
global.placing = "Building";
var startx = x + sprite_width * 2 + 5
var starty = y - sprite_height
for (var i = 0; i < array_length(object_list); ++i)
{
var inst = instance_create_layer(startx + 64 * i + 5, starty, "Instances", oCell)
inst.object_name = object_list[i][0]
inst.object_to_draw = object_list[i][1]
inst.menu = "Building"
}
}
else
{
active = !active
global.placing = noone
}
}
Теперь у oCell:
step:
if menu != noone and global.placing != menu
{
instance_destroy();
}
Draw GUI:
draw_self()
if object_to_draw != noone
{
draw_sprite(object_get_sprite(object_to_draw), 0, x + 32, y + 32)
}
Результат:
GameMaker Studio 2. Урок 5. Структуры данных и с чем их едят, а также grid (сетка комнаты), размещение объектов по сетке Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

Это только часть того, что нам нужно, верно?
Во-первых, мы не выводим название. Делать мы это будем отдельно, конечно же.
Во-вторых, у нас нет ограничения для объектов по оси X, а по оси Y они не двигаются вообще.

Начнём с первого, так как это просто. Выводить название объекта мы будем при наведении на него.
Для этого дополним Draw GUI у oCell. Если мы наведены на объект - нарисовать чёрный полупрозрачный прямоугольник и поверх него текст для лучшей отчётливости. И немного настроим "глубину", чтобы не было перекрытия этого текста.

if object_to_draw != noone

{

draw_sprite(object_get_sprite(object_to_draw), 0, x + 32, y + 32)

if instance_position(device_mouse_x_to_gui(0), device_mouse_y_to_gui(0), self)

{

depth = -1000

draw_set_font(fontButtonText20)

var width = string_width(object_name)

var height = string_height(object_name)

draw_set_alpha(0.33)

draw_set_color(c_black)

draw_rectangle(x, y - height, x + width, y, false)

draw_set_color(c_white)

draw_set_alpha(1)

scrOutlinedText(x, y - height, c_white, c_black, object_name, depth, fontButtonText20, fa_left, fa_top)

}

else

{

depth = 0

}

}


Второе.
Вернёмся к объекту меню строительства.
Так как мы знаем конечное количество объектов, мы можем посчитать, какое количество пикселей они будут занимать. Всё, что нам остаётся - определиться с количеством объектов на одну строку. Я предлагаю 10.
Мы не хотим, чтобы эти объекты создавались ниже определённой границы, потому точку старта по Y будем считать относительно неё. В общем, нужно будет заменить всего пару строк при создании объектов.
var starty = display_get_gui_height() - 64 - 64 * (array_length(object_list) div 10)
И
var inst = instance_create_layer(startx + 64 * (i mod 10) + 5, starty + (i div 10) * 64, "Instances", oCell)

Конечный результат меню:

GameMaker Studio 2. Урок 5. Структуры данных и с чем их едят, а также grid (сетка комнаты), размещение объектов по сетке Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

Теперь логика взаимодействия и размещения.

Нам нужно завести ещё одну переменную, которая будет хранить объект, который мы хотим использовать. Иными словами, при клике на объект oCell, мы должны из него "вычленять" тот объект, который он хранит. Как следствие, получим спрайт, который будем отрисовывать в качестве "призрака".

Сделаем же это. Для этого у объекта oPlayer пропишем переменную:

place_object = noone;
Дальше - просто. В oCell в Step делаем проверку. Если нажали на себя - oPlayer.place_object = object_to_draw;
Код:
if instance_position(device_mouse_x_to_gui(0), device_mouse_y_to_gui(0), self)
and mouse_check_button_pressed(mb_left)
{
oPlayer.place_object = object_to_draw;
mouse_clear(mb_left)
}
Теперь у oPlayer настроим отрисовку объекта. Для этого в draw пропишем код:

if place_object != noone

{

var cell_x = mouse_x div CellWidth * CellWidth

var cell_y = mouse_y div CellHeight * CellHeight

draw_sprite(object_get_sprite(place_object), 0, cell_x + 16, cell_y + 16)

// Обводочка по границам объекта, для удобства.

draw_set_color(c_white)

draw_rectangle(cell_x, cell_y, cell_x + 32, cell_y + 32, true)

}

Результат:
GameMaker Studio 2. Урок 5. Структуры данных и с чем их едят, а также grid (сетка комнаты), размещение объектов по сетке Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

Может выглядеть кривовато, но это из-за спрайта земли. Проверял на простых квадратах - всё встаёт четко в границы. :)

Всё, что осталось - настройка логики.
Нажали ЛКМ - разместили объект. При этом, фон нам нужно очистить в любом случае. Нажали ПКМ - удалили объект, на который наведены. Если объекта нет, то удаляем фон.
Естественно, всё это сейчас - в целях теста. В дальнейшем мы будем ставить не сами объекты, а их призраки, которые человечки будут строить. В общем, целиком код выглядит так:

Step у oPlayer:

var cell_x = mouse_x div CellWidth
var cell_y = mouse_y div CellHeight
var cell = cell_x + cell_y * ceil(room_width / CellWidth)
if mouse_check_button_pressed(mb_right)
{
// Сбрасываем объект или удаляем объект/фон
if place_object != noone
{
place_object = noone
}
else
{
// Если в текущей позиции нет объекта - удаляем фон, иначе - объект
var inst = instance_position(cell_x * CellWidth, cell_y * CellHeight, oObject)
if inst
{
with(inst)
{
instance_destroy();
}
}
else
{
if array_length(global.grid_array[cell]) > 2
{
layer_sprite_destroy(global.grid_array[cell][2])
array_delete(global.grid_array[cell], 2, 1)
}
}
}
mouse_clear(mb_right)
}
if mouse_check_button_pressed(mb_left)
{
var place_x = mouse_x div CellWidth * CellWidth + 16
var place_y = mouse_y div CellHeight * CellHeight + 16
if place_object != noone
and !instance_position(device_mouse_x_to_gui(0), device_mouse_y_to_gui(0), oGBuild)
{
// Удаляем объект, если находим.
var inst = instance_position(cell_x * CellWidth, cell_y * CellHeight, oObject)
if inst
{
with(inst)
{
instance_destroy()
}
}
// Удаляем фон
if array_length(global.grid_array[cell]) > 2
{
layer_sprite_destroy(global.grid_array[cell][2])
array_delete(global.grid_array[cell], 2, 1)
ds_grid_set(global.grid, cell_x, cell_y, 0)
}
// Ставим новый объект
var new_inst = instance_create_layer(place_x, place_y, "Instances", place_object)
new_inst.coordx = mouse_x div CellWidth
new_inst.coordy = mouse_y div CellHeight
mouse_clear(mb_left)
}
}

Собственно говоря, система размещения объектов сделана. Осталось её дорабатывать напильником.
Результат:

GameMaker Studio 2. Урок 5. Структуры данных и с чем их едят, а также grid (сетка комнаты), размещение объектов по сетке Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

Так как в следующем гайде мы будем активно работать с персонажами, будем заставлять их двигаться по путям, предлагаю сразу сделать под них заготовку. Для этого создадим несколько объектов, настроив у них связь родитель-ребёнок.

GameMaker Studio 2. Урок 5. Структуры данных и с чем их едят, а также grid (сетка комнаты), размещение объектов по сетке Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

Собственно, OChar - родитель oCharPlayer и oCharNoControl.
oCharNoControl - родитель oCharNeutral и oCharEnemy.

В следующем гайде мы много поговорим про создание путей, как они работают. Используя алгоритм поиска пути, сделаем алгоритм, по которому наши человечки будут двигаться по созданным ими путям.

План. Как обычно.
- Пути. Один большой гайд, включая скрипты поиска путей для различных сеток в т.ч. с примерами из моего личного проекта.

- Сохранение. Встроенное VS самописное.

- Звуки.

Небольшое объявление. В формате гайда я объясняю, как сам делаю те или иные вещи, как их понимаю. Когда гайды подойдут к концу - останется только заполнить игру механиками, спрайтами, звуками, музыкой. Это я планирую делать в дальнейшем в виде блога или вроде того.

Есть вопросы или что-то не получается - обращайся, помогу.

Есть пожелания или замечания - буду рад выслушать.

Ссылка на скачивание файла проекта для ЛЛ:
https://disk.yandex.ru/d/w31aDLE9ClPy9Q

Показать полностью 9
[моё] Разработка Gamedev Программирование Инди Инди игра Gamemaker Studio 2 Образование Длиннопост Урок
7
15
GrimmIronwill
GrimmIronwill
3 года назад
Лига Разработчиков Видеоигр
Серия Gamemaker Studio 2: серия гайдов

GameMaker Studio 2. Урок 4. - Иерархия объектов. «Объекты-родители» и их «дети». Глобальные переменные⁠⁠

Привет!

Приношу свои извинения за долгое отсутствие. Лето, выходные, ну вы понимаете. :)

Ссылки на предыдущие гайды:
Первый гайд
Второй гайд
Третий гайд

Недавно проводил один из стримов, на котором частично воссоздал старые добрые "танчики" с Денди (Battle City). Исходники, которые можно будет допилить самому, приложу к посту ниже. Там тоже есть что потыкать.

Иерархия объектов

Кратко:
Тоже самое, что и с классами. Есть родительский класс и дочерний, который наследует его свойства. В нашем случае, слово "класс" заменяется на "объект, но смысл не меняется.
Важно: свойства эти можно "переписать", создав событие заново. В нём же можно дополнительно указать, если мы хотим сохранить свойства, при этом дополнив их.

Подробно:
Перед созданием любого объекта, мы должны чётко представлять его предназначение. Часто множество объектов исполняют схожие функции, но с небольшими отклонениями. Таким образом, вырисовывается проблема: создавать стопицот объектов с одинаковым кодом - бред, который захламляет код и делает его нечитаемым.
Решение этой проблемы - создание универсального "шаблона", на основе которого будут строиться другие объекты с нужными дополнениями.
Таким образом, наш шаблон становится родителем. А все, кто создан по этому шаблону - его "детьми" (дочерними объектами). Они перенимают его свойства, но могут их в нужной степени корректировать.

Работа иерархической системы в GML (GameMaker Language) имеет особенность, с которой быстро придётся познакомиться на практике.
Все "дочерние" объекты по умолчанию считаются частью "родительского".
У нас есть общий родительский объект для всех блоков. При этом, в комнате его у нас нет. Но есть его дочерние объекты.
Следовательно, делая проверку на наличие родительского объекта, у нас высветится "истина".
Делайте проверки на наличие тех или иных объектов в комнате обдуманно.

Как это использовать - можете придумать сами. Ниже примеры, которые использовал я.
- Для создания систем юнитов, зданий
- ИИ, который выбирает приоритетные цели на основе их родительского объекта.
- Для создания инвентарной системы.
- Для создания меню.

Какие проблемы могут возникнуть при работе с иерархической системой.

Самая частая проблема, которая была у меня - это проектировка системы иерархии. Дело в том, что чем более интерактивный проект подразумевается, тем сложнее система иерархии объектов.
Для меня решением стали блок-схемы, которые позволяют это всё наглядно нарисовать и расположить. В виде блок схем удобно делать каркас проекта.
Сайт, на котором их рисую я.

Собственно, по этой причине для данного гайда была выбрана относительно простая в реализации игра.
Основа в упрощённом виде выглядит так:

GameMaker Studio 2. Урок 4. - Иерархия объектов. «Объекты-родители» и их «дети». Глобальные переменные Разработка, Gamedev, Программирование, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок, Обучение

Небольшие пояснения, слева направо.

- Есть общий "Шаблон" под персонажа, который включает базовые их возможности и характеристики.
- На основе этого шаблона строятся два других: для подконтрольных и неподконтрольных персонажей. При этом, для последних также следует разделение на основе поведения: будут они враждебно или "мирно" настроены. С-но, общая логика работы написана в главном родительском объекте, а в дочерних - некоторые правки к ней.

Аналогично с объектами.
- Всё, что есть в игре, кроме персонажей - у нас будет объектом, с которым мы можем взаимодействовать.
- Каждый такой объект мы делим по двум типам: постройки, которые что-то делают (или не делают), но суть - должны быть построены. И природные объекты, которые игрок ставить не сможет.
Дальше, соответственно, они снова делятся.

Как это выглядит в движке.

Создадим два объекта: oObject и oNature.
Первый - родительский объект для всех типов объектов, второй - только для "природных". При этом, у нас также есть объект для земли - oDirt.
Перейдём к oObject.
Нажмём у него на Parent. Далее - на плюсик, где поставим ему дочерним объект oNature.

GameMaker Studio 2. Урок 4. - Иерархия объектов. «Объекты-родители» и их «дети». Глобальные переменные Разработка, Gamedev, Программирование, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок, Обучение

Аналогично проделаем с oNature для oDirt. Как видно, родитель у него уже выставился.

GameMaker Studio 2. Урок 4. - Иерархия объектов. «Объекты-родители» и их «дети». Глобальные переменные Разработка, Gamedev, Программирование, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок, Обучение

Ок. Родительские объекты сделали.

Перейдём обратно к oObject и создадим у него событие Create.
Теперь немного мыслей в слух.

Базово, каждый наш объект должен иметь следующий перечень "настроек":
- Количество ХП. Так как каждый объект интерактивен, следовательно, каждый объект можно атаковать. С-но, вводим переменную hp и приравниваем её к 0 по умолчанию.
- Координаты по сетке. coordx, coordy. Не столько необходимость, сколько просто упрощение. Делаем их равными -1 по умолчанию, так как мы в любом случае будем их переназначать при размещении объектов.
- Может объект гореть или нет - flammable. False по умолчанию.

Пока остановимся на этом. Перейдём к объекту oNature.

GameMaker Studio 2. Урок 4. - Иерархия объектов. «Объекты-родители» и их «дети». Глобальные переменные Разработка, Gamedev, Программирование, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок, Обучение

Как видно, событие "Create" у нас здесь уже есть. Оно унаследовалось от нашего родительского объекта oObject.
Если создать ещё одно событие Create, то мы перепишем родительское. Но, можно предоставить себе выбор, что мы хотим сделать.
Для этого, по существующему событию мы нажмём правой кнопкой мыши.

У нас появится выпадающее меню из трёх опций:
- Open Parent Event - открыть это же событие в родительском объекте.
- Inherit Event - унаследовать событие
- Override Event - переписать событие.

GameMaker Studio 2. Урок 4. - Иерархия объектов. «Объекты-родители» и их «дети». Глобальные переменные Разработка, Gamedev, Программирование, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок, Обучение

С-но, нам нужно выбрать второй пункт: унаследовать событие.

Тогда у нас откроется окно кода с готовой строкой:

event_inherited();

Это значит, что то, что мы прописали в oObject, для oNature у нас унаследуется и будет активно для этого объекта. И при этом, ниже этой строки кода мы можем добавить то, что нам нужно.

Следуя плану, сейчас нам нужно убрать связь между oNature и oDirt, создать ещё два объекта (oResource и oNotResource) и уже затем выставить связь между oNotResource и oDirt.

Собственно говоря, для не-ресурсных объектов - это всё.
Для ресурсных - нам нужно объявить, какие ресурсы он может содержать.

Открыв объект oResource (родительский для всех блоков, с которых что-то будет добываться), создадим его с наследованием от родителя.

Далее - обозначим ресурсы, приравняв их все к нулю.

Metal = 0;
Coal = 0;
Wood = 0;
Stone = 0;
Gem = 0;

По сути - всё.

Теперь, когда будем создавать ресурсные объекты, в качестве родителя будем у них выставлять oResource. Создавая объект, будем наследовать событие создания у oResource, переписывая нужные нам строки кода.

Проверим, как это всё работает.

Создадим объект oRock.
Поставим ему в качестве родителя oResource.
Создадим у него событие Create, унаследовав код.
Пишем код:

event_inherited();
Stone = irandom_range(5, 15)
Далее, чтобы проверить, что всё работает как мы и рассчитывали, в Draw сделаем вывод текста на экран. Выводим текущее количество камня. Не забываем про отрисовку!
GameMaker Studio 2. Урок 4. - Иерархия объектов. «Объекты-родители» и их «дети». Глобальные переменные Разработка, Gamedev, Программирование, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок, Обучение

Как видно, при спавне у каждого блока получилось разное количество камня. При этом, если вывести количество других ресурсов, которые мы не переписывали, мы получим нули. Следовательно, другие переменные также подтянулись от родителя.

Добавлю ещё кое что прежде, чем закрыть эту тему.
Когда выстраивается взаимодействие между объектами игры, которые не находятся под прямым управлением игрока, очень часто, чтобы избежать ошибок, придётся делать проверку на наличие объекта в комнате. Для этого используется команда: instance_exists(объект);.
Это ещё одна вещь, которую нам позволяет делать иерархия и которая немного развязывает руки в разработке.

Глобальные переменные

В прошлом гайде мы их уже использовали, поэтому здесь - только справка. Всего у нас есть четыре типа переменных:

1. Локальные переменные. Объявляются с помощью ключевого слова var

var название = значение

Эти переменные нельзя использовать где-либо, кроме текущего события. С-но, в другом событии они будут недоступны, также будут недоступны и для изменения из посторонних объектов.

2. Обычные переменные (не знаю, как правильно их обозвать:D)
Мы с ними уже активно работали, объявляются они просто:

название = значение

В дальнейшем, их можно вызывать из других объектов, используя with или обращаясь к объекту через переменную, хранящую его id. К примеру:

inst.coordx = 1;
3. Глобальные переменные. Данный вид переменных может быть вызван в любом событии любого объекта и изменён там. Объявляются следующим образом:

global.название = значение

В основном, они используются для хранения важных (в целом для игры) значений, которые могут меняться в её процессе. Работать с ними нужно предельно осторожно и всегда знать, где и в каких местах значения переменной перезаписываются.

4. Макро переменные. По сути, те же глобальные переменные, но за одним исключением. Они не могут быть изменены. Также используются для хранения значений, которые являются константами и могут пригодиться в нескольких местах программы. Объявляются следующим образом:

#macro Название Значение

На этом будем закругляться.
В следующем гайде мы рассмотрим несколько способов создания сетки комнаты. Будем размещать объекты по этой сетке: во время генерации карты и во время непосредственно игры. Вместе с этим, наконец создадим персонажей, а в гайде с путями - научим их передвигаться.

Над картинкой сейчас париться я не буду, да и вам не советую. Не тот этап разработки. Хватит простой, тестовой графики, чтобы можно было понять, что и где находится.

План, как всегда.
- Массивы и с чем их едят, а также grid (сетка комнаты), размещение объектов по сетке. Включая объяснение, в каких случаях лучше использовать встроенные функции, в каких – писать свои с нуля.

- Пути. Один большой гайд, включая скрипты поиска путей для различных сеток в т.ч. с примерами из моего личного проекта.

- Иные способы хранения информации в GMS2, когда их стоит или не стоит использовать.

- Сохранение. Встроенное VS самописное.

- Звуки.


Есть вопросы или что-то не получается - обращайся, помогу.
Есть пожелания или замечания - буду рад выслушать.

Ссылки на скачивание:
Исходник

Полуготовые танчики
Показать полностью 6
[моё] Разработка Gamedev Программирование Инди игра Gamemaker Studio 2 Образование Длиннопост Урок Обучение
5
20
artevgen
artevgen
3 года назад
Лига Разработчиков Видеоигр

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2)⁠⁠

В предыдущих сериях: я собрался написать туториал как создать игру на Unity с нуля и рассказал об этом тут, затем мы установили необходимые программы, создали проект и даже начали делать главное меню. Сегодня продолжаем.

Краткое содержание:
• Скроллинг текста
• Создание скрипта
• Скрипт работы кнопок главного меню
• Внешние ссылки
• Создание локализации с помощью JSON

Сейчас мы сделаем поле для блока "Дополнительно". В своей игре в этом блоке я расположил благодарности членам комьюнити ВК, которые активно срутся в беседе помогают развивать игру. Этот пункт меню в принципе не обязателен, однако на нём мы потренируемся работать с компонентом для скроллинга текста (это один из примархов нашей игры).

Создайте новое поле extrasList аналогично предыдущим. Затем добавьте ему дочерний пустой объект viewport, которому так же добавьте дочерний объект content. В итоге у нас получилась своеобразная матрёшка extrasList -> viewport -> content.

К extrasList прикрепляем компонент Scroll Rect. Эта важная курица будет контролировать работу скроллинга содержимого.

В качестве Content установите наш одноимённый объект content, уберите галочку с Horizontal (нам пока не требуется горизонтальный скроллинг), Movement Type --> Clamped, Viewport --> viewport.

• Movement Type отвечает за поведение контента при достижении нижней или верхней точки скроллинга. Unrestricted унесёт содержимое в дальние дали, Elastic даст ему плавно отскочить и вернуться назад, а Clamped просто резко остановит.

Не хватает только полосы прокрутки. Создайте для extrasList дочерний объект UI/Scrollbar. Как видите - у него есть два компонента (Image и Scrollbar) и два дочерних объекта. В первую очередь сделаем его вертикальным. Для этого в компоненте Scrollbar установите Direction --> Bottom to Top. Теперь нужно разместить его в правой части extrasList и растянуть на всю высоту экрана. Это, равно как и изменение его цветов вы теперь сможете сделать самостоятельно ;) Когда всё будет готово, назначьте объект Scrollbar в качестве Vertical Scrollbar в компоненте Scroll Rect.

Далее сделайте размеры viewport и content побольше - подстать extrasList - и добавьте текстовый компонент в content. Для корректного отображения скроллинга в content нужно добавить еще один компонент - Content Size Fitter и установить:

Horizontal Fit --> Unconstrained

Vertical Fit --> Preferred Size

• Обычно для конструкций скроллинга требуется установка компонента Mask для объекта viewport. Этот компонент будет скрывать текст за пределами границ viewport. Сейчас он нам не нужен, так как текст доходит до края экрана.
При желании попробовать добавьте компоненты Mask и Image для viewport и отключите у Mask галочку на Show Mask Graphic. Текст будет обрезаться на границах viewport.

Полюбуемся результатом.

• В правом нижнем углу можно разместить кнопки, ведущие в ваш Дискорд, ВК, сайт итд. Можете этого не делать, но я далее все равно покажу как это прописать в скрипте.

Каждую сцену нужно добавлять в билд через меню File - Build Settings. Причем сцена, зарегистрированная под номером 0 будет являться стартовой. Давайте добавим нашу сцену в билд.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Я долго откладывал этот момент, но больше тянуть кота за яйца смысла нет - будем писать код. Сейчас нам нужно заставить работать все (ну, почти) кнопки меню.

ВАЖНО! Я постараюсь максимально понятно и просто объяснить что и как работает, но знайте - мои объяснения не обучат вас C#. Вы просто научитесь пользоваться теми функциями, которые использую в своей игре я. Обязательно ищите дополнительную информацию, читайте справочники и учебники и шерстите форумы каждый раз, когда вам что-то непонятно.

Откройте Notepad++ или любой другой редактор кода. Измените кодировку на UTF-8, а синтаксис на C#. Затем сохраните файл как mainMenu.cs в папку Assets/Scripts.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

В самом начале скрипта нам нужно указать необходимые пространства имен - говоря проще хранилища функций, которые мы будем использовать. Объяснение каждого из них займёт очень много места и времени, поэтому просто скопируйте их.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Linq;
using System.Text;
using UnityEngine.SceneManagement;

Затем создайте класс mainMenu - он должен называться так же как и сам файл. К классу допишите : MonoBehaviour - это означает наследование от базового класса Unity.

Подробнее о пространствах имен

Подробнее о клаcсах

Подробнее о MonoBehaviour

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

В первую очередь создадим метод для работы кнопки "Новая игра". По плану он должен открывать поле справа, где игрок сможет выбрать слот для новой игры и начать её.

Нам понадобится переменная типа GameObject, которая будет указывать на поле newGameList.

public class mainMenu : MonoBehaviour {
public GameObject newGameList;
}

Слово public перед переменной означает, что она будет доступна для других классов. Пока что все переменные и методы мы будем делать публичными.

Теперь создадим метод menuNewGame(), который будет показывать/скрывать newGameList. Фактически он проверяет активен ли объект newGameList в иерархии - если да, то скрывает, а если нет, то активирует.

public void menuNewGame() {

if (newGameList.activeInHierarchy == true) newGameList.SetActive(false);

else {

newGameList.SetActive(true);

}

}

Подробнее о конструкции if... else

Старайтесь всегда соблюдать отступы в коде и не лепить всё в одну кучу - это просто удобно для чтения.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Теперь сохраним файл и вернемся в Unity. Чтобы всё заработало как надо, мы должны прикрутить к кнопке newGame скрипт mainMenu в качестве компонента. Затем в этом компоненте в поле newGameList указать одноимённый объект. В компоненте Button в событии On Click () укажите нашу кнопку и выберите из списка наш новый метод menuNewGame().

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост
Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Теперь можно запустить игру и посмотреть как работает кнопка. Если не работает - ищите где что пропустили :)

Аналогичную операцию с переменной, методом и назначением скрипта  нужно проделать с кнопками "Загрузить игру", "Настройки" и "Дополнительно".

• Тут есть одна важная деталь. Каждый раз прикручивая скрипт к кнопке, нам придется назначать в инспекторе переменные. Это особенность редактора Unity. Можно, конечно, делать отдельный скрипт и класс для каждой кнопки, но это шило на мыло. А расписывать всё только через скрипт мы не будем (пока что...).

Обратите особое внимание на то, чтобы в каждом методе активация одного поля меню приводила к деактивации другого.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Как видите, я постоянно делаю пометки красным цветом. Это не очень удобно в скриптах - там для этого есть отдельный инструмент комментариев. Если вы поставите двойной слеш //, то вся остальная часть строки будет превращена в комментарий, который никак не будет учитываться компилятором. В комментарии можно заключать сразу целые блоки кода с помощью /* закомментированный блок */.

Всегда комментируйте свой код - причем делайте это так, чтобы ваши записи были понятны другому человеку. Это облегчает работу, понимание структуры, поможет вспомнить свой код. Да и просто это правило хорошего тона.

Я даже специально скриншот для этого сделаю.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост
Конечно же нет смысла комментировать каждую строку - это слишком громоздко. Я сделал это в качестве примера. Однако, указывать вкратце что и как делает очередной метод стоит. Не перенебрегайте этим! Спасибо мне потом скажете.

Теперь сделаем скрипт для кнопок "Выход" и ссылок на соцсети. Для выхода из приложения используется простая команда Application.Quit(); Её нужно добавить в метод exitGame() и описанным ранее способом привязать к кнопке exit.

Для перехода по ссылке используем команду Application.OpenURL("адрес ссылки"); аналогичным образом.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост
• Экономьте время и силы. Прикрепите скрипт mainMenu к одному объекту, сделайте все привязки, затем скопируйте компонент. Выделите все необходимые объекты и массово вставьте скопированный компонент со всеми привязками.

Это далеко не единственный способ программировать кнопки. Программирование - это достаточно гибкая штука. Способов, которыми можно реализовать выполнение задачи множество и ни один из них не может быть универсальным идеалом. Ваша задача - найти самый удобный для себя.

Сейчас мы просто обязаны немного поговорить о тексте. Как его хранить в игре?

1. Прописывать прямо в скрипт. Это наихудший вариант из всех, которые только можно представить. Громоздкие файлы, низкая читабельность кода, презрение коллег по цеху, низкая самооценка. Никогда не пишите текст в ваших скриптовых файлах! Ни единого слова!

2. Хранить его в отдельных текстовых файлах, а затем импортировать с помощью библиотек LINQ. Это хороший способ - я так поначалу и делал. На каждую сцену у меня был свой текстовый документ со строками необходимого текста. Проблема наступила, когда его стало слишком много и я начал теряться в структуре. Вторая проблема наступила, когда я решил добавить несколько языков в локализацию - это создало необходимость делать более громоздкий код и в разы больше файлов.

3. Хранить текст в JSON. Этот вариант на данный момент идеален для меня, так как я готовлю локализацию с 11 языками. Этот формат позволяет мне поддерживать хранение текста сразу блоками, а не строками и структурировать его. Подробнее про формат JSON можно почитать тут.

Вот этот формат мы и будем использовать на протяжении всего туториала. Я крайне редко встречал уроки (возможно даже и не встречал) и курсы, где с самого начала ученику предлагают хранить информацию в JSON, так что это будет своего рода эксперимент.

Схема будет такая:
• Создаем базу данных текста в Excel или Google Таблицы
• Создаем класс под эту базу данных в проекте
• Импортируем БД в JSON и добавляем в проект
• Создаем скрипт, распаковывающий JSON, и расставляющий значения переменных текста

Благодаря возможностям JSON, мы сразу получаем возможность делать локализацию своей игры - переводить её на другие языки. Не вижу смысла откладывать эту тему на потом. Такие серьезные моменты лучше решать сразу.

Я буду объяснять на примере Гугл Таблиц (Google Spreadsheets), но тут с Excel различий мало. Создайте новую таблицу.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост
• В программировании первая цифра, первый элемент, первый объект - это всегда ноль. Всё начинается с нуля. Привыкайте считать с нуля и тогда вы не словите когнитивный диссонанс при написании кода.

Поле id нам нужно по большей мере для себя - чтобы не путаться, а также для повышения производительности. Оно будет нумерально совпадать с индексом элемента в списке, который мы создадим позднее. Начинайте его всегда с нуля и по порядку.

Key - это поле нам нужно для упрощенного обращения к элементу. Мы будем "вызывать" элемент по Key и требовать "какие ваши доказательства" предоставить необходимое значение.

• Не забудьте внести в таблицу весь текст главного меню - кнопки меню, заголовки настроек, текст из "Дополнительно".
• Обратите внимание, что названия языков мы не вносим в таблицу. Они отображаются в родном начертании в компоненте Dropdown объекта optionsList - language - Dropdown.
Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Хорошо, сделали - что дальше? Как запихать это в игру? Для начала нам нужно превраить нашу таблицу в json. Сохраните её в xlsx - стандартный формат экселя, который поддерживается и гугл таблицами. Далее зайдите на сайт, сохраните его, создайте его закладку, выбейте татуировку ссылки на левой руке - Beautifytools (https://beautifytools.com/excel-to-json-converter.php). Откройте с помощью него нашу таблицу и экспортируйте в json.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Нам понадобится новая папка для хранения текстовых баз данных (а у нас уже именно база данных, представьте себе). Assets/Resources/textDB

Там нам понадобится новый файл с кодировкой UTF-16 LE с BOM и синтаксисом JSON.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Добавляем конвертированную в джейсон таблицу в файл и получаем следующий результат.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост
• Если вам непонятно почему это так расположено и что это за знаки, то почитайте про синтаксис JSON

Теперь всё это нужно аккуратно запихать в игру. Для начала нам потребуется новый скриптовый файл dataBase.cs. Добавьте ему такие же пространства имен, как и файлу mainMenu. Этот файл будет содержать классы для импорта различных таблиц в формате JSON.

Импорт будет происходить в список List<> - этот класс в языке C# позволяет хранить объекты одного типа, добавлять/удалять элементы, сортировать и прочие прелести жизни. Каждый блок из файла json будет рассматриваться как отдельный объект и импортироваться в список, откуда мы его с лёгкостью будем использовать в игре.

Создадим в скрипте dataBase.cs класс TextDB, в котором объявим список List<Textdata>. Textdata - это класс, который описывает наши текстовые объекты.

• Разъяснение. Текстовым объектом в нашем случае является каждая строка таблицы со своим id, Key и блоками russian, english, japanese.

Так, класс Textdata мы уже начали использовать, однако еще не объявили. Какое нахальство! Давайте скорее это сделаем, пока компилятор не надавал нам по рукам. В этом же скрипте объявите класс Textdata. В нём нам потребуются переменные согласно столбцам таблицы.


public int id; // int - это 32-битный формат целых чисел от -2147483648 до 2147483647

public string Key; // string - это строковый формат. Хранит одну строку текста

public string russian;

public string english;

public string japanese;

"Одна строка" - понятие растяжимое. В языке программирования знак переноса строки ( '\n') - это такой же символ как и буква. Переменная формата string может содержать очень много текста -  до 2^31 символов (2 147 483 648).
Подробнее о типах данных
Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Осталась "мелочь" - написать в скрипте mainMenu.cs команду на импорт базы текста и подставить значения в зависимости от языка. Классы, которые наследуют от MonoBehaviour могут использовать специальные методы движка Unity. В нашем случае потребуется метод Start() который выполняется сразу после инициализации объекта. В него мы и добавим инструкции по импорту базы. Также понадобится объявить переменную textDatabase класса TextDB.

public TextDB textDatabase;
void Start() {
TextAsset asset0 = Resources.Load("textDB/mainMenu") as TextAsset;
textDatabase = JsonUtility.FromJson<TextDB>(asset0.text);
}

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Для того, чтобы видеть в инспекторе нашу базу данных, добавьте в скрипте dataBase.cs перед классами строку [System.Serializable].

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Теперь, если вы запустите игру и выберете объект, к которму прикреплён скрипт mainMenu, вы увидите список импортированных тесктовых объектов.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Прекрасно! Следующий шаг - это настроить функцию выбора языка. Язык мы выбираем при помощи всплывающего списка Dropdown в разделе настроек. Давайте создадим для него переменную public Dropdown langDropdown, чтобы было проще к нему обращаться. Также в скрипте mainMenu создадим новый метод setLanguage().  Прикрепите скрипт mainMenu к объекту Dropdown и в поле Lang Dropdown укажите этот объект. В разделе On Value Changed прикрепите метод setLanguage().

Что будет происходить? Каждый раз, когда вы будете выбирать язык во всплывающем меню, будет меняться числовое значение Dropdown и вызываться метод setLanguage(). У каждого языка получится свой порядковый номер: русский - 0, английский - 1, японский - 2. На основании этого номера мы и будем менять текстовые блоки.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Для быстрого доступа к текстовым блокам создадим для каждого из низ переменную класса Text и назначим их соответствующим полям скрипта mainMenu в инспекторе.

public Text newGameText;
public Text loadGameText;
...
Однотипные переменные можно объявлять через запятую.
public Text newGameText, loadGameText, optionsText, extraText, exitText, langText, audioText, musicText, creditsText;

Теперь напишем смену блоков текста при помощи оператора switch. Этот оператор похож на if...else.  Также мы применим цикл foreach, который будет перебирать элементы списка с текстом и выбирать нужный согласно критерию. Критерием будет значение Key. Значение из Dropdown получим с помощью запроса langDropdown.GetComponent<Dropdown>().value

Подробнее об операторе switch можно почитать здесь

Подробнее о циклах и цикле foreach

public void setLanguage() {
switch (langDropdown.GetComponent<Dropdown>().value) {
case 0:
foreach (Textdata p in textDatabase.text) {
if (p.Key == "newgame") newGameText.text = p.russian;
else if (p.Key == "loadgame") loadGameText.text = p.russian;
else if (p.Key == "options") optionsText.text = p.russian;
else if (p.Key == "extra") extraText.text = p.russian;
else if (p.Key == "exit") exitText.text = p.russian;
else if (p.Key == "language") langText.text = p.russian;
else if (p.Key == "ambience") audioText.text = p.russian;
else if (p.Key == "music") musicText.text = p.russian;
else if (p.Key == "credits") creditsText.text = p.russian;
}
break;
}
}

В конструкции switch нам также необходимо прописать блоки для case 1: и case 2:

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

• Надписи "свободный слот" в меню новой игры и загрузки можно пока не трогать - для них припасен отдельный урок ;)
Язык игры - это настройка, а настройка должна сохраняться. Нет смысла сохранять настройки непосредственно в файле сохранения самой игры - для этого нам отлично подойдёт реестр. С помощью функции PlayerPrefs мы можем сохранять числа и текст. Для этого нам нужно создать запись в реестре и передать ей значение - в нашем случае номер языка - а затем сохранить.

PlayerPrefs.SetInt("language", langDropdown.GetComponent<Dropdown>().value); 
PlayerPrefs.Save();

Запись с именем "language" будет создана автоматически, а если она уже существует, то поменяет своё значение. Добавьте эти строки в метод setLanguage() до или после конструкции switch.

Теперь нам нужно прописать код, который проведёт операции с локализацией при старте игры.

Что нам нужно:
• Если игра запускается впервые - установить язык как на устройстве пользователя или английский (иначе японец не сможет прочитать надписи на русском и поменять язык)
• Если игра уже запускалась и язык сохранён, то установить этот язык
• Установить значение value объекта Dropdown согласно текущему языку

Нам понадобится новый метод setLangAtStart(), в котором мы определим язык устройства и установим его или английский.


public void setLangAtStart() {

if (!PlayerPrefs.HasKey("language"))

{

if (Application.systemLanguage == SystemLanguage.Russian) PlayerPrefs.SetInt("language", 0);

else if (Application.systemLanguage == SystemLanguage.English) PlayerPrefs.SetInt("language", 1);

else if (Application.systemLanguage == SystemLanguage.Japanese) PlayerPrefs.SetInt("language", 2);

else PlayerPrefs.SetInt("language", 1);

PlayerPrefs.Save();

}

langDropdown.GetComponent<Dropdown>().value = PlayerPrefs.GetInt("language");

setLanguage();

}


Метод проверяет сохранен ли язык в настройках, если нет, то устанавливает как у пользователя или английский. Далее он вызывает метод setLanguage() и, для того, чтобы не возникало ошибок, меняет значение value компонента Dropdown на номер текущего языка. Теперь добавьте метод setLangAtStart() в метод Start(), чтобы он автоматически запускался при старте игры.

Теперь внесём небольшой корректив в конструкцию switch и заменим условие (langDropdown.GetComponent<Dropdown>().value) на (PlayerPrefs.GetInt("language")). Теперь игра будет запускаться с тем языком, который вы установили в прошлый сеанс.

Дополнительно можно сделать так, чтобы менялось название игры при смене языка. Фактически нам нужно просто подменить изображение. Создадим для этого переменную logo класса Image и новый метод changeLogo(). В нём мы используем команду Resources.Load<Sprite>("имя файла"); для обращения к содержимому папки Resources и загрузке оттуда необходимого изображения.

public void changeLogo() {
if (PlayerPrefs.GetInt("language") == 0) logo.sprite = Resources.Load<Sprite>("Images/game_name");
else logo.sprite = Resources.Load<Sprite>("Images/game_name_eng");
}

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #2) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Единственное, что пока осталось без нашего внимания - это настройки музыки и звуков. К ним мы вернемся позднее отдельной статьей. В следующих постах мы наконец-то создадим свою первую игровую сцену, настроим переходы и научимся сохранять игру. Всем добра и пёсиков!


Материалы из поста

Содержание туториала (Google Docs с ссылками на Пикабу)

Игра "Питерский Квест" в Google Play

Архив версий apk и Windows

Показать полностью 21 3
[моё] Разработка Gamedev Программирование Unity Инди игра Инди Csharp Туториал Для чайников Обучение Урок Игры Видеоигра Квест RPG Видео Без звука Длиннопост
16
18
GrimmIronwill
GrimmIronwill
3 года назад
Лига Разработчиков Видеоигр
Серия Gamemaker Studio 2: серия гайдов

GameMaker Studio 2. Урок 3. Камера. Разрешение экрана⁠⁠

Привет!
Это третья часть гайда, где мы будем делать:
- Камеру с зумом, которая будет следовать за игроком, посредством настроек и кода.
- Изменение разрешения экрана и переключение режимов экрана
- Сделаем ещё пару скриптов для управления перемещением игрока. Какой из них оставить - решать вам.

Скажу сразу, что будет много кода.
Также немного затронем тему следующего гайда - глобальные переменные.

Также обозначу, что у объекта oPlayer можно выключить свойство Persistent.

Ссылка на первый гайд
Ссылка на второй гайд

P.S.
За спрайты огромное спасибо моему другу
https://vk.com/parijer

Ссылка на мою группу ВК
https://vk.com/club209675869

Камера.

Включение камеры.
Перейдём в комнату с игроком.

Во вкладке слева-снизу (Properties - Room1) открываем вкладку "Viewports and Cameras"

Ставим галочку напротив "Enable Viewports" и "Clear Viewport Background"


Немного теории.

Камера - объект, который содержит информацию о том, что камера видит и что нужно отображать на экране. При этом, камера имеет два параметра:

View (Вид) - то, что камера видит, основываясь на позиции, проекции и повороте камеры.

View Port - область дисплея, на которой будет отображаться то, что камера видит.

GameMaker Studio 2. Урок 3. Камера. Разрешение экрана Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок, Видео, Без звука

Первый способ создания камеры. Простой.

1. Открываем "Viewport 0"

2. Ставим галочку напротив "Visible" для себя.

3. В "Camera Properties" выставляем нужные нам настройки камеры, меняя параметры "Width" и "Height". Поиграйтесь сами, посмотрите, как меняется область камеры и выберите нужные себе значения.

4. Чуть ниже есть надпись "Object Following". Здесь выбираем объект, за которым камера будет следовать. Выбираем игрока.

5. По сути, всё. Камера готова. Но, как вы можете заметить, игрок её двигает только тогда, когда подойдёт к границе. Чтобы это исправить, в "Horizontal Border" и "Vertical Border" выставьте те же параметры, что выставляли в "Camera Properties".

6. Изменяя "Horizontal Speed" и "Vertical Speed" вы сможете настроить "плавность" слежения камеры.

Всё. Камера готова и будет следовать за персонажем - игроком.

GameMaker Studio 2. Урок 3. Камера. Разрешение экрана Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок, Видео, Без звука
GameMaker Studio 2. Урок 3. Камера. Разрешение экрана Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок, Видео, Без звука

Второй способ создания камеры. Сложный.

Сложный он по той причине, что делать мы это всё будем непосредственно через код. :)

Для начала - просто включите Viewports и Clear Viewport Background

Приступим
1. Создаём объект oCamera.

2. Теория.

2. Сделаем нашу комнату с главным меню (rmMainMenu) "Persistent" - постоянной.

3. Перейдём в объект oGameManager. Создаём у него событие "Create". В нём пропишем код:

global.CameraSizes = [[320, 180],[640, 360], [960, 540]] // Разрешённые размеры экрана.
global.CameraNum = array_length(global.CameraSizes) - 1 // Переменная, которая будет отображать, какой из массивов мы используем, допустимые значения от 0 до (длина списка - 1)
global.CameraWidth = global.CameraSizes[global.CameraNum][0] // Ширина камеры.
global.CameraHeight = global.CameraSizes[global.CameraNum][1] // Высота камеры.
#macro CameraScale 2 // Масштаб камеры. Константа.
#macro CameraSpeed 0.1 // Скорость камеры. Константа.
window_set_fullscreen(false) // Выключаем полноэкранный режим при запуске :)
var windowWidth = global.CameraWidth * CameraScale // Ширина окна = ширина камеры * масштаб
var windowHeight = global.CameraHeight * CameraScale // Высота окна = высота камеры * масштаб
surface_resize(application_surface, global.CameraWidth * CameraScale, global.CameraHeight * CameraScale); // Переопределяем "поверхность", чтобы соотношение сторон спрайтов соответствовало нашему экрану.
window_set_size(global.CameraWidth * CameraScale, global.CameraHeight * CameraScale); // Устанавливаем размер окна (Необязательно, но желательно)
window_set_position(display_get_width() / 2 - windowWidth / 2, display_get_height() / 2 - windowHeight / 2); // Располагаем наше окно по центру дисплея

На этом шаге мы провели инициализацию настроек. Как вы можете заметить, мы использовали новый тип переменных - это глобальные и макро переменные. Их можно применять в любых частях программы, подробнее об их работе будет в следующем гайде.

Что происходит в данном коде: мы задаём базовые размеры камеры и её масштаб. Говорим, что хотим запускаться в оконном режиме, после чего устанавливаем ширину окна равную размеру камеры, помноженному на масштаб. Масштаб необходим для корректного отображения, но вообще - с этими настройками можно поиграться самому.

Любые переменные, где используется число, а не переменная, можно спокойно взять и настроить под себя.
Однако, нужно не только изменить размер окна - этого недостаточно для корректного отображения. Нужно также изменить размер нашего "холста".
Gamemaker Studio 2 не отрисовывает ничего непосредственно на экран. Для отрисовки наш движок использует "холст" - surface, иначе - application_surface. Таким образом, при изменении размеров окна, нужно менять и размеры холста, как следствие - проводить подгонку UI под новые размеры.
4. Перейдём к объекту oCamera. Разместим его в комнате, где есть игрок. Создадим событие "Create", где создадим саму камеру, скажем, за кем ей следить и настроим её.

Код:

// Если в комнате есть игрок - следим за ним, иначе - за комнатой.
if instance_exists(oPlayer)
{
cameraTarget = oPlayer;
}
else
{
cameraTarget = room;
}
global.Camera = camera_create_view(0, 0, global.CameraWidth, global.CameraHeight); // Создаём камеру по нужной нам ширине и высоте.
// Включаем и устанавливаем камеру.
view_enabled = true;
view_visible[0] = true;
view_set_camera(0, global.Camera);

5. Создадим step событие у камеры, где сделаем так, чтобы она плавно двигалась за игроком и не показывала то, что находится вне комнаты. Также сделаем возможность приближать и отдалять камеру на колёсико мыши - зум.

Код:

var cameraX = camera_get_view_x(global.Camera);
var cameraY = camera_get_view_y(global.Camera);
// Делаем переменные стабильными для зума
cameraWidth = camera_get_view_width(global.Camera);
cameraHeight = camera_get_view_height(global.Camera);
var targetX = cameraTarget.x - cameraWidth / 2;
var targetY = cameraTarget.y - cameraHeight / 2;
// Поддерживаем входные значения в диапазоне от 0 до размеров комнаты
targetX = clamp(targetX, 0, room_width - cameraWidth);
targetY = clamp(targetY, 0, room_height - cameraHeight);
// Возвращаем позицию камеры. Делает передвижение плавным
cameraX = lerp(cameraX, targetX, CameraSpeed);
cameraY = lerp(cameraY, targetY, CameraSpeed);
// Автоподгон размера камеры, если вдруг вышли за рамки
if cameraWidth > 1440 or cameraWidth < 320
{
cameraWidth = global.CameraWidth
cameraHeight = global.CameraHeight
}
// Зум
var wheel = mouse_wheel_down() - mouse_wheel_up(); // Возвращает true/false (-1 / 0 / 1)
if (wheel != 0)
{
wheel *= 0.1; // * 10%
// Определяем, сколько добавить к ширине / высоте
var addWidth = cameraWidth * wheel;
var addHeight = cameraHeight * wheel;
// Где 1440 и 320 - можете вписать свои значения. Это область, которую мы будем показывать.
// Не советую делать привязку к текущему экрана, т.к. тогда чем меньше будет разрешение, тем меньше будет максимальная видимая область.
// Для большого разрешения тоже будет минус - ничего не будет видно.
if (cameraWidth + addWidth < 1440) and (cameraWidth + addWidth > 320)
{
cameraWidth += addWidth;
cameraHeight += addHeight;
// Делаем входные значения стабильными
var prevWidth = cameraWidth;
var prevHeight = cameraHeight;
cameraWidth = clamp(cameraWidth, CameraWidth / 2, room_width);
cameraHeight = clamp(cameraHeight, CameraHeight / 2, room_height);
// Исправляем искривление при зуме. Если разрешение 16:9 - все ок, иначе - исправляем.
if (cameraWidth / cameraHeight == 1.777777777777778) &&
(prevWidth == cameraWidth || prevHeight == cameraHeight) {
// Фиксим позицию камеры
cameraX -= addWidth / 2;
cameraY -= addHeight / 2;
// Опять делаем значения стабильными
cameraX = clamp(cameraX, 0, room_width - cameraWidth);
cameraY = clamp(cameraY, 0, room_height - cameraHeight);
}
else {
cameraWidth = prevWidth - addWidth;
cameraHeight = prevHeight - addHeight;
}
}
}
camera_set_view_pos(global.Camera, cameraX, cameraY);
camera_set_view_size(global.Camera, cameraWidth, cameraHeight);

Всё. Камера готова, её можно отдалять-приближать и она будет следить за игроком!

Теперь сделаем меню настроек, чтобы мы могли менять наше разрешение, включать полноэкранный режим и немного перепишем интерфейс.

1. Немного изменим расположение нашего меню: так как размер окна у нас может динамически изменяться, неизменяемые значения в расположении кнопок нам сделают только хуже. С-но, нам нужно перейти к объекту oMMenu и в "Create" изменить код следующим образом:


buttons = [oMStart, oMLoad, oMSettings, oMExit]
var wwidth = global.CameraWidth * CameraScale
var wheight = global.CameraHeight * CameraScale
var bwidth = wwidth * 0.25
var bheight = wheight * 0.1
for (var i = 0; i < array_length(buttons); ++i)
{
//instance_create_layer(room_width / 2, room_height / 2 - 50 + 100 * i, "Instances", buttons[i], {width : bwidth, height : bheight})
inst = instance_create_layer(wwidth / 2, wheight * 0.5 + bheight * i, "Instances", buttons[i])
inst.width = bwidth
inst.height = bheight
}

5. Так как код для отрисовки обводки кнопок у нас везде одинаковый, сделаем из него функцию, которую везде будем вызывать. Назовём её: scrOutlinedBox.

Код там будет следующий:

function scrOutlinedBox()
{
if point_in_rectangle(dmxg, dmyg, x - width / 2, y - height / 2, x + width / 2, y + height / 2)
{
draw_set_color(c_white)
draw_set_alpha(0.15)
draw_rectangle(x - width / 2, y - height / 2, x + width / 2, y + height / 2, false)
draw_set_alpha(1)
}
}

dmxg и dmyg мы не объявляем, так как мы их уже используем в нашем коде, это важно. По сути, это просто сокращение кода.


Не забудьте добавить отрисовку спрайта через draw_sprite_streched() :)

Код для текста кнопки выбора размеров:

scrOutlinedText(x, y, c_white, c_black, "Разрешение экрана: " + string(global.CameraSizes[global.CameraNum][0] * CameraScale) + "x" + string(global.CameraSizes[global.CameraNum][1] * CameraScale), depth, fontArialRusSmall, fa_center, fa_middle)

Код для кнопки смены режима экрана:

var fullscreen = window_get_fullscreen()
if fullscreen == 0
{
fullscreen = "Выкл."
}
else
{
fullscreen = "Вкл."
}
scrOutlinedText(x, y, c_white, c_black, "Полноэкранный режим: " + fullscreen, depth, fontArialRusSmall, fa_center, fa_middle)

Теперь немного настроим логику.

step у oSFullscreen:

dmxg = device_mouse_x_to_gui(0)
dmyg = device_mouse_y_to_gui(0)
if point_in_rectangle(dmxg, dmyg, x - width / 2, y - height / 2, x + width / 2, y + height / 2)
and mouse_check_button_pressed(mb_left)
{
window_set_fullscreen(!window_get_fullscreen())
}

step у oSBack

dmxg = device_mouse_x_to_gui(0)
dmyg = device_mouse_y_to_gui(0)
if point_in_rectangle(dmxg, dmyg, x - width / 2, y - height / 2, x + width / 2, y + height / 2)
and mouse_check_button_pressed(mb_left)
{
room_goto(rmMainMenu)
}

step у oSSize

dmxg = device_mouse_x_to_gui(0)
dmyg = device_mouse_y_to_gui(0)
if point_in_rectangle(dmxg, dmyg, x - width / 2, y - height / 2, x + width / 2, y + height / 2)
and mouse_check_button_pressed(mb_left)
{
if global.CameraNum < array_length(global.CameraSizes) - 1
{
global.CameraNum += 1
}
else
{
global.CameraNum = 0
}
global.CameraWidth = global.CameraSizes[global.CameraNum][0]
global.CameraHeight = global.CameraSizes[global.CameraNum][1]
surface_resize(application_surface, global.CameraWidth * CameraScale,global.CameraHeight * CameraScale) // Пересобираем surface
window_set_size(global.CameraWidth * CameraScale,global.CameraHeight * CameraScale);
oSSettings.surface_changed = true;
}

С-но, что мы здесь делаем:

1) Меняем размер окна

2) Меняем размер холста

3) Так как размер холста изменился, нам нужно изменить расположение всех элементов GUI, которые мы располагали до этого (они же ставились под старые размеры разрешения). Поэтому мы обращаемся к oSSettings и говорим, что наш холст изменился и устанавливаем новые значения.

4) Чтобы oSSettings это поняла, в Create нам нужно добавить surface_changed = false.

Здесь же создадим пустой список: all_buttons = []

Затем добавить в цикл for, в конец, строку:

array_push(all_buttons, inst)

Таким образом, мы запоминаем ID тех кнопок, что мы уже поставили.

Затем в step написать следующий код:

if surface_changed
{
var windowWidth = global.CameraWidth * CameraScale
var windowHeight = global.CameraHeight * CameraScale
var bwidth = windowWidth * 0.4
var bheight = windowHeight * 0.1
for (var i = 0; i < array_length(all_buttons); ++i)
{
all_buttons[i].x = windowWidth / 2
all_buttons[i].y = windowHeight / 2 + (windowHeight * 0.1) * i
all_buttons[i].width = bwidth
all_buttons[i].height = bheight
}
surface_changed = false
}

5) Расположение интерфейса нам нужно поменять и в другом меню - oMMenu.

Здесь нужно будет добавить два ивента: Room Start и Room End, а в Create добавить две переменные:

old_size = global.CameraWidth * CameraScale
new_size = global.CameraWidth * CameraScale

Теперь, что мы делаем.

Когда происходит событие "конец комнаты" (смена комнаты) - мы запоминаем текущий размер экрана.

Когда происходит событие "начало комнаты" - мы сравниваем старый размер с новым и если они разные - меняем расположение всех кнопок.

Код Room End

old_size = global.CameraWidth * CameraScale

Код Room Start

new_size = global.CameraWidth * CameraScale
if new_size != old_size
{
var windowWidth = global.CameraWidth * CameraScale
var windowHeight = global.CameraHeight * CameraScale
var bwidth = windowWidth * 0.25
var bheight = windowHeight * 0.1
for (var i = 0; i < array_length(all_buttons); ++i)
{
all_buttons[i].x = windowWidth / 2
all_buttons[i].y = windowHeight * 0.5 + bheight * i
all_buttons[i].width = bwidth
all_buttons[i].height = bheight
}
}

Подобный код нам теперь придётся писать везде, где есть GUI, так как наши элементы интерфейса мы закрепляем лишь единожды, а здесь - располагаем их заново.

Осталось подправить одну недоработку в коде у камеры, а именно - Зум.

В step у камеры, перед зумом добавим проверку, которая будет принудительно изменять размер камеры допустимый, если мы выходим за границы.

if cameraWidth > global.CameraWidth * 1.5 or cameraWidth < global.CameraWidth * 0.5
{
cameraWidth = global.CameraWidth
cameraHeight = global.CameraHeight
}

Теперь немного про скрипты.

Подправим передвижение. Сделаем его немного удобнее для нашей игры.

Сделаем следующее: если игрок подойдёт к краю комнаты и продолжит идти, то окажется на противоположной её стороне.

Для этого в "step" событии напишем следующий код после вызова функции для передвижения:

move_wrap(true, true, sprite_width);

Если наш объект зайдёт за границы экрана на ширину своего спрайта - мы его развернём. Используем только ширину, т.к. грани спрайта у нас одинаковые. Если бы ширина и высота разнились, пришлось бы писать этот код дважды: для ширины и для высоты соответственно, причём вместо "true" выставляя "false" в нужных местах соответственно.

Чтобы почитать о функции подробно - наведитесь на неё и нажмите на колёсико мыши.


Сделаем возможность передвижения камеры с помощью мыши: нажав ПКМ, мы перетаскиваем камеру туда, куда нам нужно.

Так как камера у нас привязана к игроку, передвигать мы будем именно игрока.

Создадим скрипт, который назовём: "scrMoveToMouse"

Код, который в нём будет написан, будет проверять, нажата ли ПКМ и если да - передвигаться игрока к координатам мыши в комнате, делая это плавно.

function scrMoveToMouse()
{
// Перемещение игрока к мыши (и камеры к игроку как следствие)
if mouse_check_button(mb_right)
{
x = lerp(x, mouse_x, 0.05)
y = lerp(y, mouse_y, 0.05)
}
}

Всё. Достаточно вызвать функцию. Теперь, нажимая ПКМ, мы будем двигаться в нужную нам точку.

Похожая функция, но для передвижения в определённую сторону в том случае, если мышь находится у границ экрана.

Создадим ещё один скрипт и назовём его: "scrMoveToEdge"

Нам нужно проверять позицию мыши относительно GUI, после чего проверять X и Y:

- Находится ли мышь у левой или правой границы. Для этого возьмём 10% от текущего разрешения за основу. Соответственно, в одном случае - умножим итоговую ширину экрана на 0.1 - если мышь слева и на 0.9 - если справа.

- Аналогичные действия для верха и низа, но с высотой экрана.

Код:

function scrMoveToEdge()
{
var dmxg = device_mouse_x_to_gui(0)
var dmyg = device_mouse_y_to_gui(0)
if dmxg < (CameraScale * CameraWidth) * 0.1
{
x -= player_speed
}
else if dmxg > (CameraScale * CameraWidth) * 0.9
{
x += player_speed
}
if dmyg < (CameraScale * CameraHeight) * 0.1
{
y -= player_speed
}
else if dmyg > (CameraScale * CameraHeight) * 0.9
{
y += player_speed
}
}

Как вы могли заметить, у нас здесь стоит две связи: if else if. Сделано это для того, чтобы игрок мог передвигаться по диагонали, наводясь в углы.

Чтобы не происходило наслоение друг на друга, сделаем так, чтобы каждая функция возвращала true в случае использования и false, если нет.

Код.

scrMove

function scrMove(spd)
{
var key_up = keyboard_check(ord("W")) or keyboard_check(vk_up);
var key_left = keyboard_check(ord("A")) or keyboard_check(vk_left);
var key_right = keyboard_check(ord("D")) or keyboard_check(vk_right);
var key_down = keyboard_check(ord("S")) or keyboard_check(vk_down);
var movement_dir = point_direction(0, 0, key_right - key_left, key_down - key_up);
var movement_input = (key_right - key_left != 0) or (key_down - key_up != 0);
if (movement_input)
{
var h_speed = lengthdir_x(spd, movement_dir);
var v_speed = lengthdir_y(spd, movement_dir);
x += h_speed;
y += v_speed
return true
}
else
{
return false
}
}

scrMoveToEdge

function scrMoveToEdge(pspeed)
{
var dmxg = device_mouse_x_to_gui(0)
var dmyg = device_mouse_y_to_gui(0)
if dmxg > (CameraScale * global.CameraWidth) * 0.1
and dmxg < (CameraScale * global.CameraWidth) * 0.9
and dmyg > (CameraScale * global.CameraHeight) * 0.1
and dmyg < (CameraScale * global.CameraHeight) * 0.9
{
return false
}
if dmxg < (CameraScale * global.CameraWidth) * 0.1
{
x -= pspeed
}
else if dmxg > (CameraScale * global.CameraWidth) * 0.9
{
x += pspeed
}
if dmyg < (CameraScale * global.CameraHeight) * 0.1
{
y -= pspeed
}
else if dmyg > (CameraScale * global.CameraHeight) * 0.9
{
y += pspeed
}
return true
}

scrMoveToMouse

function scrMoveToMouse()
{
// Перемещение игрока к мыши (и камеры к игроку как следствие)
if mouse_check_button(mb_right)
{
x = lerp(x, mouse_x, 0.05)
y = lerp(y, mouse_y, 0.05)
return true
}
else
{
return false
}
}

Осталось настроить вызов функций.

Функции выполняются в любом случае. И в любом случае возвращают какой-то результат. Если мы пропишем функцию в проверку - она будет выполняться и возвращать результат. Но, так как мы возвращаем и true, и false, нам, соответственно, нужно сделать проверку на false:

Если мы не двигаемся на клавиши -> Если мы не двигаемся к мыши -> Если мы не двигаемся к краю.

Таким образом, если мы двигаемся на клавиши - мы не сможем двигаться к мыши и к краю.

Если мы двигаемся к мыши - мы не сможем двигаться к краю.

Код в Step у oPlayer:

if !(scrMove(player_speed))
{
if !(scrMoveToMouse())
{
if !(scrMoveToEdge(player_speed))
{
}
}
}
move_wrap(true, true, sprite_width); // Разворот

Реализуем это именно через лесенку, так как нам нужно, чтобы одновременно выполнялся только один скрипт.

Как растянуть один спрайт на весь фон


1. Выбираем наш фон в "Layers - Room1"
2. Выбираем нужный спрайт.
3. Ставим галочки:
3.1. Horizontal Tile
3.2. Vertical Tile

GameMaker Studio 2. Урок 3. Камера. Разрешение экрана Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок, Видео, Без звука

На этом мы подходим к концу.
Приношу свои извинения, если всё выглядит рвано или слишком мало объяснений. В процессе всё несколько раз переписывалось и могли остаться шероховатости.
В следующем гайде будет подробно разобрана иерархия объектов, виды переменных (локальные, глобальные, обычные, макро), как их используют и для чего.
В этом же гайде я сделал чуточку больше, чем хотел изначально и надеюсь, что вам это будет на пользу.

Видео с тем, как это всё выглядит по факту.

Насчёт кнопок: как бы вы их реализовали?
Также, через Nine Slice и отрисовку текста отдельно или сделав готовые кнопки с текстом?

Я склоняюсь ко второму варианту, но для тестирования предпочту использовать первый :)

Оставшиеся гайды:
- Иерархия объектов. «Объекты-родители» и их «дети». Решение часто встречающихся проблем и немного про то, как удобно выстраивать взаимодействие с объектами. Глобальные переменные.

- Массивы и с чем их едят, а также grid (сетка комнаты), размещение объектов по сетке. Включая объяснение, в каких случаях лучше использовать встроенные функции, в каких – писать свои с нуля.

- Пути. Один большой гайд, включая скрипты поиска путей для различных сеток в т.ч. с примерами из моего личного проекта.

- Иные способы хранения информации в GMS2, когда их стоит или не стоит использовать.

- Сохранение. Встроенное VS самописное.

- Звуки.

Ссылка на файл, чтобы всё можно было потыкать самому:
https://disk.yandex.ru/d/A3TRUz39ByvjOQ

Есть вопрос - задавай, постараюсь ответить.

Что-то не получилось, вылезла ошибка и так далее - тоже пиши, помогу или поможем.
Ну и я всегда рад критике.

Показать полностью 4 1
[моё] Разработка Gamedev Программирование Инди Инди игра Gamemaker Studio 2 Образование Длиннопост Урок Видео Без звука
9
44
artevgen
artevgen
3 года назад
Лига Разработчиков Видеоигр

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1)⁠⁠

В прошлой части мы скачали Unity, подготовились к работе, создали проект, первую сцену и на ней объекты Canvas и Background. В этот раз мы создадим главное меню нашей игры.

Для начало запомните важную вещь - в 2D игре интерфейс построен на слоях. Одно поверх другого. А также связь Parent - Child (Children) - когда один объект находится в другом и свойства родительского объекта влияют на свойства потомка. Именно по этим принципам мы и будем строить наше меню. Давайте попробуем визуально представить как его можно расположить на экране.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео

Левый блок - это будет объект mainMenu. Он будет родителем для объектов "название игры", "пункты меню" и "ваше лого". Они тоже будут содержать в себе объекты. Начнем.

Создайте пустой объект (Empty Object) и сделайте его дочерним объекта Canvas (в иерархии перетяните один объект на другой). Другой вариант - клик ПКМ по объекту Canvas и затем выбор создания пустого объекта. Так он автоматически станет дочерним. Переименуйте его в mainMenu.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео

Чтобы mainMenu стало видно на экране, ему нужно добавить компонент Image через меню Add Component в инспекторе (как добавлять компонент я рассказывал в прошлом посте). Так, ну и где же он?

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео

Чтобы придать нашему меню нужное положение, воспользуемся его компонентом Rect Transform. В разделе Anchors устанавливаем:

- Min X: 0.03 Y: 0

- Max X: 0.3 Y: 1

Затем Left, Right, Top, Bottom ставим по нулям.

Что это всё означает? Anchors (якоря) устанавливают границы нашего объекта относительно размера экрана. Min X: 0.03 прикрепляет левый край (ставит якорь) объекта на уровне 0.03 ширины экрана. Max X: 0.3 прикрепляет правый край на уровне 0.3 - что в итоге дает нам ширину объекта не в пикселях, а в процентах. Он будет занимать в ширину 27% экрана с небольшим отступом слева в 3%. Аналогично с Y - только это уже про высоту объекта.
Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео

Теперь установим нашему меню вместо белого пятна приятный глазу цвет. В компоненте Image нажмите на поле Color и установите:

- R - 0.012 (красный)

- G - 0.012 (зеленый)

- B - 0.023 (синий)

- A - 0.7 (альфа-канал или прозрачность)

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео

Для закрепления материала по размещению объекта на экране создадим два декоративных элемента - две полоски по бокам от плашки меню.

1. Создаём два пустых объекта как Child объекта mainMenu - line1 и line2

2. Назначаем Anchors для line1

- Min X: -0.03 Y: 0

- Max X: -0.01 Y: 1

3. Назначаем Anchors для line2

- Min X: 1.01 Y: 0

- Max X: 1.03 Y: 1

4. Устанавливаем обоим объектам цвет как у mainMenu
• Если устанавливать Anchor больше или меньше единицы, то границы объекта будут выходить за пределы родительского объекта.
• Можно копировать компонент (например Image) через функцию Copy Component и вставлять его другому объекту или вставлять его значения такому же компоненту другого объекта через функцию Paste Component. Пример того и другого на видео.

Теперь можно похвалить себя и подышать свежим воздухом. Далее нам нужно создать объекты для названия игры, пунктов меню и логотипа. Делаем мы это по тому же принципу - создаем дочерние объекты в mainMenu - top, middle и bottom. Для наглядности сразу добавляем им компонент Image с белым цветом (потом изменим).

• Можно выделить в иерархии сразу несколько объектов про помощи Ctrl+левый клик и добавить компонент всем сразу

Аккуратно распределяем объекты на объекте 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 пикселей.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео

Затем добавим название игры.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео

Нарисуем декоративную линию.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео

Добавьте слоган внизу, затем подрежьте инструментом "Рамка" лист по самые края логотипа.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео

Далее удаляем слой фона, чтобы сделать логотип прозрачным. Затем устанавливаем размер изображения с шириной в 1000 пикселей. После этого нам очень важно сделать так, чтобы каждая сторона изображения была кратна 4 - это необходимо для применения алгоритмов сжатия, чтобы уменьшить вес игры. Так как обрезать уже нечего, то через "Размер холста" делаем высоту 212 пикселей. Итоговый размер 1000х212.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео

Когда всё готово, вы должны поменять цвет всех объетов на белый. Зачем? На белое изображение в Unity мы легко сможем наложить любой цвет, если нам приспичит. На чёрный - не получится.

Сохраните полученое изображение в формате PNG и положите его в папку проекта Assets/Resources/Images (создайте её). Сам исходный файл PSD сохраните в отдельной папке вне проекта, где вы будете хранить исходники графики.

Самое время добавить созданное название в игру! Создаем для top дочерний объект gameName, добавляем ему компонент Image и выравниваем на экране. Для того, чтобы изображение не искажалось, в компоненте Image активируем Preserve Aspect - эта функция позволит сохранить пропорции.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео

Отлично! Гейб Ньюэлл вами был бы доволен.

Теперь займёмся объектом middle и сделаем в нем несколько кнопок, которые будут вести в нужное место - "Новая игра", "Загрузить игру", "Настройки", "Об игре", "Выход".

Нам пригодится полезный компонент под названием Vertical Layout Group. Он выравнивает дочерние объекты по вертикали, регулирует их высоту, ширину и отступы. Это означает, что нам не придётся вручную устанавливать размеры и положение кнопок. Его можно сразу добавить к объекту middle.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео
• Я предпочитаю пользоваться старой версией компонента Text в Unity. Новая версия называется TextMeshPro. Если в вашей версии Unity нет Button, а есть только Button - TextMeshPro, то посмотрите в подпункте Legacy. Или почитайте как работает TextMeshPro

Таким же образом добавьте еще 4 кнопки и настройте Vertical Layout Group у объекта middle.

Padding - это отступы всего списка кнопок относительно объекта.

Spacing - это отступы между кнопок.

Потыкайте настройки, чтобы понять, как это работает, а потом сделайте как на скриншоте. Как видите, теперь у нас есть аккуратный список кнопок.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео

Обратите внимание на иерархию - у каждой кнопки есть дочерний объект Text. Это такой же объект, как и все, только с прикрепленным компонентом Text. В нем можно вписать текст объекта, указать шрифт, размер, начертание, цвет, выравнивание. Этих настроек вполне достаточно для нашей игры. Установите размер шрифта 24 пикселя.

• Если вы хотите другой шрифт, то создайте папку Assets/Resources/Fonts и добавьте его туда. Он будет доступен в поле выбора шрифта.
Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео

Теперь можно нажать кнопку 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.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео

Теперь создайте в Фотошопе новое изображение размером 400х400 пикселей. Нарисуйте в нем прямоугольник с границей, но без заливки, как показано на скриншоте.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео

А затем нарисуйте прямоугольник поменьше, но с заливкой.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео

Удалите слой фона, сделайте прямоугольники белого цвета и сохраните как PNG в папку Assets/Resources/Images под именем frame_1.

В Unity нам потребуется отредактировать границы спрайта изображения, для того чтобы мы могли его без искажений растягивать в разные стороны. В дереве проекта выберите наше новое изображение frame_1, нажмите в инспекторе Sprite Editor и в открывшемся окне установите Border (границы) на 12 пикселей. Именно в этих местах спрайти будет растягиваться, подгоняясь под размеры кнопки. Закройте это окно и нажмите Save.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео

Примените новый спрайт ко всем кнопкам и в компоненте Image установите Image Type на Sliced. Надо поправить цвет кнопок.

- R - 0.012 (красный)

- G - 0.012 (зеленый)

- B - 0.012 (синий)

- A - 0.7 (альфа-канал или прозрачность)

Если вам хочется немного свободно места между слотами, то добавьте на кнопки отступы по 5 пикселей с каждой стороны.

Цвет текста кнопок так же меняем на белый.
Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео
• Не забывайте, что однотипные объекты можно выделять вместе CTRL+клик и массово менять настройки компонентов. Это очень экономит время.

Так как нам нужно точно такое же поле для раздела "Загрузка игры", мы можем просто скопировать это. Деактивируйте объект newGameList, нажав на галочку возле его имени в инспекторе, затем в иерархии щёлкните по нему ПКМ и нажмите Duplicate. Теперь нужно только его переименовать в loadGameList, чтобы не путаться. Деактивируйте его, чтобы не мешался.

Раздел "Настройки". Создайте такое же поле, но только без кнопок. Этот раздел посложнее предыдущего, поэтому сделаем для начала небольшие намётки.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео

Первым сделаем блок выбора языка с выпадающим списком. Нужно создать объект 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 с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео

Теперь нам нужно сделать выпадающий список. В Unity уже есть готовое решение - нам нужно только правильно его настроить. Добавьте в language объект UI -> Dropdown и устанвите ему Anchors X: (0.5, 1) Y (0, 1). Сразу же можно установить ему почти чёрный цвет с прозрачностью 0.78.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 2 - Как сделать главное меню игры (пост #1) Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост, Вертикальное видео

В нём достаточно много дочерних объектов. Разверните его полностью и внимательно поменяйте настройки. По шаблону "Объект: Компонент --> настройки".

- 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

- Viewport: пропускаем.

- 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 делаем черный цвет.

Таким же образом создаём слайдер для "Громкости звуков".

Готово! Вы - великолепны!

Лимит картинок опять исчерпан, поэтому продолжим в следующий раз.

Экспериментируйте с оформлением и не бойтесь трогать все настройки подряд. В этот раз я приложу ссылку на архив с проектом из поста.


Материалы из поста

Содержание туториала (Google Docs с ссылками на Пикабу)

Игра "Питерский Квест" в Google Play

Архив версий apk и Windows

Показать полностью 22 3
[моё] Разработка Gamedev Программирование Unity Инди игра Инди Csharp Туториал Для чайников Обучение Урок Игры Видеоигра Квест RPG Видео Без звука Длиннопост Вертикальное видео
6
30
artevgen
artevgen
3 года назад
Лига Разработчиков Видеоигр

Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги⁠⁠

Для тех, кто не в курсе - это серия туториалов по написанию игры с нуля до выпуска, основанное на моей уже готовой и развивающейся игре. Пост с введением тут. Уровень сложности туториала подходит для орков или маленьких детей. Я рассчитываю, что вы умеете в элементарные операции наподобие: открыть сайт, скачать картинку/программу, переименовать файл, создать папку, помнить где она.

Мы будем делать текстовую RPG с элементарной (но приятной) графикой, сюжетом, прокачкой, открытым миром на движке Unity и языке программирования C# (си шарп), а также будем разжевывать все используемые моменты движка и языка по мере использования. А то многие туториальщики просто бросают в меня кусок кода, как будто я знаю, что с ним делать.

Список того, что нам понадобится:
• Unity. Скачать с официального сайта
• Adobe Photoshop или любой другой удобный вам графический редактор
• Notepad++. Скачать. (очевидно, что подойдёт любой другой редактор кода)
Это наш минимальный набор для начала работы.

Первое, что нам нужно сделать - это правильно установить Unity. Информацию об этом можно найти быстрее, чем узнать, где живёт Джон Коннор, но я обещал с нуля. По ссылке скачивается программа Unity Hub, которая служит этаким вахтёром на пути к работе с движком. Открываем её. Нам нужно сделать две вещи - скачать редактор и создать проект.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост
Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Далее следует Очень Важный Момент Который Нельзя Пролюбить! Я сам порой его упускаю.  Здесь мы должны выбрать компоненты для установки. Не хотите заморачиваться - выбирайте все пункты.

Минимально необходимый уровень, если вы работаете на Windows:

Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост
Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост
Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Всё. Дальше не прерываем процесс и ждём, пока установится. Только после этого можно будет создать свой проект. Попейте чаю и погуляйте с собакой, подготовьте себе рабочее место.

Небольшой оффтоп. Что я узнал для себя за пару лет работы в домашних условиях:
• Соцсети отвлекают - закройте их, отключите уведомления на телефоне
• Стул должен быть удобным, клавиатура должна стоять под комфортным углом, а помещение нужно регулярно проветривать
• Музыка может помочь в работе - ищите такую, которая не заставит вас подпевать, танцевать или махать хаером. Рекомендую Lo-Fi, Binaural Beats и прочую динамически ровную музыку без слов.
• Ходите гулять!

Второй этап - создание проекта.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

В следующем окне вы увидите много шаблонизированных проектов, с которыми у вас будет прекрасная возможность ознакомиться самостоятельно. В их числе есть даже обучающие сэмплы. Нам нужен самый простой вариант.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Далее спокойненько ждём пока Unity создаст необходимые файлы для проекта и откроет его. Вуаля! Первый восторг, первое достижение "Открыл редактор" получено!

Тут важно сделать два замечания:
• Я буду знакомить вас с интерфейсом только по мере необходимости. Это мой подход
• Мы будем делать игру с расчётом на мобильные устройства, а не большие экраны стационарных компов и ноутов. Разницу и смысл этого буду объяснять по мере работы

Нам нужно переключить проект в режим Android. Для этого жмём File - Build Settings - Android - Switch Platform (подробно на скрине)

Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Теперь мы разберемся в проекте и настроим его иерархию. Нам нужно зайти по пути D:/Unity Projects/Piter Quest (ну или путь, который вы выбрали) и открыть там папку Assets. Именно в этой папке будут храниться все наши ресурсы - картинки, текст, скрипты, слёзы. Эта папка - самая главная (намёк на бекапы).

Как вы это всё там расположите движку практически безразлично, а вот нам нет.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Нам понадобится содать папку для хранения всех ресурсов игры под названием Resources. Для хранения сцен уже есть папка Scenes. Для хранения скриптов сделаем папку Scripts.

Папка Resources должна быть сделана именно в таком написании с заглавной буквы. Это специальная папка для движка - впоследствии мы будем получать доступ к её содержимому малюсеньким кусочком кода. Подробнее о ней, а также о других специальных папках можно почитать по ссылке.

Далее поговорим про святая святых - наше рабочее пространство. Ниже скриншот рабочего стола по умолчанию. Можете оставить всё как есть, но как по мне - такое расположение окон не очень удобно для нашей игры.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Настроим интерфейс рабочего поля редактора под меня под себя. Это проще показать, чем объяснить. На видео я перетаскиваю вкладки и меняю размер окон (мамкин программист). Вернуть как было или попробовать заготовленные пресеты можно в Window - Layouts.

"Эй! Мы игру-то делать будем сегодня?!". Да-да, сейчас начнем. Оговорюсь сразу - мы делаем резиновую верстку под альбомную ориентацию.

• Альбомная ориентация - это когда вы переворачиваете телефон (он типа широ-о-окий)
• Резиновая верстка - это термин верстальщиков. Означает, что все элементы оформления интерфейса, которые мы будем создавать в игре, будут автоматически подгоняться под формат экрана мобильного устройства. Их размеры будут задаваться не в пикселях, а в процентах.
Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Итак, давайте начнём делать нашу первую сцену - Главный Экран Игры или в простонародье главное меню.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост
Я уже успел переименовать сцену. Чтобы это сделать - кликните правой кнопкой мыши (ПКМ) по SampleScene и выберите Save Scene As. Назовите её MainScene (или как хотите). Затем удалите SampleScene.

В первую очередь нам понадобится главный холст - или как его кличут в Unity - Canvas. В нашем случае Canvas будет содержать в себе все элементы интерфейса игры за исключением фона. Фон мы сделаем позже.

Для начала нажмите на плюс в левом верхнем углу окна Hierarchy или щёлкните ПКМ по пустому месту в этом окне, выберите UI - Canvas.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

В иерархии у нас появилось два объекта - Canvas и EventSystem. EventSystem отвечает за все взаимодействия пользователя с игрой и обязательно должен присутствовать в каждой сцене (иначе ни одна кнопка работать не будет). Сейчас мы немного настроим Canvas. Нажмите на него в иерархии и давайте посмотрим что о нём говорит Inspector.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

В компоненте Canvas Scaler установите UI Scale Mode --> Scale With Screen. В появившихся новых настройках:

- Reference Resolution установите как 1600 и 900

- Screen Match Mode --> Match Width Or Height

- Match --> 0

- Reference Pixels --> 100

Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост
Это мои настройки под мою мою игру, которую мы создаем в этом туториале. Используйте их, если вы пока слабо разбираетесь в Unity. Если же вы разбираетесь неплохо, то устанавливайте свои.

Для поднятия морального духа и понимания того, как мы дальше будем делать главное меню, давайте сделаем симпатичный фон. Нам понадобится сделать новый объект с компонентом Sprite Renderer. Для этого щелкните ПКМ на пустом месте в иерархии или нажмите на плюс в верхнем левом углу и выберите Create Empty. Это пустой объект, к которому мы сами будем добавлять компоненты.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

У нас появился новый объект GameObject. Потяните его и перенесите в иерархии выше Canvas.

С этого момента я больше не буду подробно писать как создать объект в Unity. Надеюсь, с двух раз вы запомнили и научились ;)

Переименуем его в Background (через инспектор или нажатием ПКМ) и добавим к нему компонент Sprite Renderer. Для этого в инспекторе нажмите Add Component и выберите Sprite Renderer. Тут есть два способа. Первый - искать по категориям (Render - Sprite Renderer), второй - начать набирать в строке название и Unity отфильтрует лишнее. Мне удобнее второй.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Нам потребуется фоновая картинка. Чтобы не нарушать авторские права возьмите своё творчество, купите или найдите необходимое на бесплатных фотостоках. Рекомендую Pixabay (потребуется VPN). Когда найдёте нужную, создайте в папке проекта Assets/Resources новую папку Backgrounds и поместите картинку туда. Не забудьте переименовать.

Для того, чтобы фон всегда заполнял экран при любом разрешении и не появлялось пустых участков, нам понадобится изменить размер картинки на 2400x1200 в Фотошопе. Этот способ я выработал сам для своей игры и под свои настройки (это важно). Откройте картинку в Фотошопе и сейчас мы её немного трансформируем.

- Изображение --> Размер изображения

- Ширина --> 2400 пикселей. Ок.

- Изображение --> Размер холста

- Высота --> 1200 пикселей. Ок. Сохраняем как jpeg

Формат изображений для Unity не jpeg, а png. Однако, в данном случае нет смысла тратить время на сохранение фонов в png. Unity при сборке сама всё прекрасно переконвертирует. Это только для фонов! К элементам интерфейса мы подойдём гораздо внимательнее.
Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост
Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост
Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост
Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

В компоненте Sprite Renderer объекта Backgroung в поле Sprite выберите нашу новую картинку или перетяните её в это поле из списка файлов проекта (Assets - Resources - Backgrounds). В компоненте Transform измените значения поля Scale (масштаб) на x = 1.2, y = 1.2, z = 1. Этот нехитрый способ поможет нашему фону аккуратно и без пробелов смотреться на экранах с соотношением сторон до 23:9 (это такой длинный смартфон-лопата).

Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Если вы работаете на не самом новом ноутбуке или мониторе, то выставьте предварительное разрешение на 1280х720. На моем ноуте с разрешением 1600х900 это идеальный вариант.

Путь дурака. Игра на Unity с нуля до выпуска. Часть 1 - Подготовка и первые шаги Разработка, Gamedev, Программирование, Unity, Инди игра, Инди, Csharp, Туториал, Для чайников, Обучение, Урок, Игры, Видеоигра, Квест, RPG, Видео, Без звука, Длиннопост

Заканчивается место в посте. Главное меню будем делать в следующем. Я как раз его пишу и увидим мы его ночью или завтра утром.

Небольшое послесловие. Скорее всего эта серия постов растянется надолго. Моя задача - объяснить вам разные важные моменты, ничего не упустив, и усовершенствовать свои знания. Пока я писал этот пост, я нашел в своих знаниях несколько пробелов и с удовольствием их восполнил. Пишите комментарии - в них сила - только старайтесь сильно не бугуртить. Я критику воспринимаю спокойно и с юмором (с недавних пор).

Не ждите новых постов - занимайтесь игростроем самостоятельно и сами ищите информацию.


Содержание туториала (Google Docs с ссылками на Пикабу)

Игра "Питерский Квест" в Google Play

Архив версий apk и Windows

Показать полностью 23 1
[моё] Разработка Gamedev Программирование Unity Инди игра Инди Csharp Туториал Для чайников Обучение Урок Игры Видеоигра Квест RPG Видео Без звука Длиннопост
2

Попробовать мобильный офис

Перейти
Партнёрский материал Реклама
specials
specials

Мобильный офис до 100 тысяч рублей⁠⁠

Ноутбуки используют не только для работы: на них смотрят сериалы, редактируют фото, запускают игры и монтируют ролики. Поэтому теперь требования к устройству такие: быть легким для дороги, надежным для горящих дедлайнов и стильным, чтобы не прятать в переговорке. А еще — легко работать в связке с другими гаджетами.

Протестировали TECNO MEGABOOK K15S вместе со смартфоном TECNO CAMON 40 и наушниками TECNO в рабочих и бытовых сценариях от Zoom-звонков до перелета, а теперь рассказываем, как себя показала техника.

Первое впечатление от дизайна ноутбука

Первое, что заметно — это вес. При диагонали 15,6 дюйма и полностью металлическом корпусе K15S весит всего 1,7 кг. Это примерно на 15% меньше, чем аналоги. Устройство не обременяет ни в офисе, ни в такси. Ноутбук поместился в стандартный городской рюкзак, было удобно достать его в кафе за завтраком и по дороге в такси, чтобы быстро отработать клиентские правки.

1/4

Дизайн сдержанный, без ярких акцентов, с матовой поверхностью. Правда, на ней остаются следы от рук. Так что если приходится постоянно открывать ноутбук в присутствии клиентов или партнеров, лучше купить прозрачный кейс. Визуально и тактильно устройство ощущается надежно: не выскальзывает и не двигается по столу, благодаря специальным резиновым накладкам на задней части.

Шарнир работает мягко: чтобы открыть крышку даже одной рукой, не нужно придерживать корпус. Чтобы показать коллеге или клиенту презентацию, достаточно раскрыть экран на 180°. Это удобно и для работы лежа, и для подставок, которые требуют определенного угла обзора.

Также отметим 9 портов: USB-A, USB-C, HDMI, слот для карты памяти — можно забыть о переходниках.

В TECNO MEGABOOK K15S предустановлен Windows 11. Ноутбук готов к работе сразу после включения. Никаких лишних установок и обновлений. Все настроено и оптимизировано для вашей многозадачности.

Экран: яркая картинка и комфорт ночью

Экран — 15,6 дюйма, IPS-матрица с разрешением Full HD. Углы обзора отличные: изображение остается четким, даже если смотреть сбоку, цвета не искажаются. Есть антибликовое покрытие. Тестировали ноутбук при разном освещении: можно спокойно работать у окна. Когда солнце бьет прямо в экран, текст по-прежнему остается читаемым, картинки не искажаются. Это редкость в бюджетных моделях.

1/2

Неважно, работаете вы ночью или играете, выручит клавиатура с регулируемой четырехуровневой подсветкой. При среднем уровне в темноте все видно, глаза не устают. Из плюсов для тревожных людей: включали ноутбук в самолете и электричке, никто вокруг не жаловался на яркость. Все регулируется кнопками, не нужно лишний раз заходить в настройки.

Стеклокерамический крупный тачпад — 15 см. Он не залипает, не промахивается, срабатывает с первого касания. Не возникает дискомфорта, даже если несколько часов редактировать документы без мышки. После перехода с других устройств немного непривычно, что тачпад работает в двух направлениях: нижняя часть отзывается нажатием, верхняя — касанием.

В кнопку питания встроен сканер отпечатка пальцев. К нему можно быстро привыкнуть, особенно если сидишь в опенспейсе или работаешь в дороге. Один легкий тап пускает в систему даже с мокрыми руками. Безопасно, удобно и не нужно постоянно вводить пароли.

Производительность: рендерим видео, открываем вкладки

Ноутбук работает на AMD Ryzen 7 5825U (опционально можно выбрать версию техники Intel Core i5-13420H). Восьмиядерный AMD с поддержкой 16 потоков подходит для ресурсоемких операций вроде рендеринга или работы с большими массивами данных. Встроенная графика Radeon справляется с редактированием видео в Full HD или играми.

1/4

Во время монтажа 30-минутного ролика в DaVinci Resolve и параллельной работе в Photoshop с несколькими большими PSD-файлами система сохраняла стабильность. Не было ни зависаний, ни заметного падения производительности. Ноутбук уверенно держит в фоне 10 приложений одновременно. Если запущены браузер с 20 вкладками, видеозвонок в Telegram, Excel с объемной таблицей и софт для монтажа, система не тормозит и не перегревается. Переход между окнами остается плавным, ничего не «проседает», даже при одновременном скачивании файлов и редактировании видео.

Базовая комплектация включает 16 ГБ оперативной памяти в двух слотах. При необходимости можно легко увеличить этот показатель до 32 ГБ, заменив стандартные модули на более емкие. Помимо установленного SSD на 1 ТБ предусмотрен дополнительный слот, поддерживающий диски объемом до 2 ТБ.

Чтобы во время нагрузки системы охлаждения не выходили из строя, в ноутбук встроен эффективный вентилятор, способный рассеивать до 35 Вт тепла. Устройство не греется, его спокойно можно держать на коленях. Это решение дополнено тремя режимами работы, которые переключаются простой комбинацией клавиш Ctrl+Alt+T. Тихий режим идеален для работы ночью или в общественных местах, сбалансированный подходит для повседневных задач. Производительный, на котором запускали рендеринг видео и игры, практически не шумит.

Автономность: 15 часов без подзарядки

Протестили автономность MEGABOOK K15S в условиях, знакомых каждому деловому путешественнику. Утром перед вылетом зарядили ноутбук до 100% и взяли его в рейс Москва — Калининград. В зале ожидания провели созвон, потом три часа смотрели сериал и в дороге до отеля редактировали документы. К моменту приезда оставалось 40% заряда: хватило бы еще на пару часов продуктивной работы.

1/3

MEGABOOK K15S может автономно работать до 15 часов и позволяет не оглядываться на индикатор заряда. Заявленное время достигается при типичном офисном использовании: одновременная работа с документами в Word и Excel, ведение переписки, видеоконференции, веб-серфинг.

Если все же понадобится, за  час восполняется до 70% батареи. Компактный адаптер мощностью 65 Вт на базе нитрида галлия поместился даже в карман пиджака. Один блок питания заряжает и ноутбук, и смартфон, и наушники. Экономия места: не нужно никаких дополнительных проводов.

Звук, который реально слышно

В TECNO MEGABOOK K15S установлены два мощных динамика по 2.5 Вт. Звук с глубокими низами, без пластикового дребезжания, объемный. Благодаря DTS можно смотреть видео даже в шумном помещении. В тестах специально включали сцены с шагами и выстрелами: локализация настолько точная, что в наушниках нет необходимости.

Та же стабильность и в микрофоне. Благодаря AI-шумоподавлению голос передается чисто. Во время тестовых звонков из оживленного кафе собеседник не услышал ни разговоры за соседним столом, ни городской шум. И все это — на расстоянии до пяти метров.

Кстати, о созвонах. В ноутбуке встроена обновленная камера. Она отслеживает положение лица, а еще есть физическая шторка приватности. Например, можно закрыть шторку для комфортных видеоконференций.

Для тех, кто предпочитает гарнитуру, идеально подойдут беспроводные наушники TECNO FreeHear 1 из экосистемы бренда. Когда не хотелось делиться разговорами с окружающими, подключали их. Чистый звук с акцентом на средние частоты, 11-мм драйверы, которые выдают неожиданную детализацию. Музыку слушать приятно: и фоновый плейлист на телефоне, и вечерний сериал на ноутбуке. Автономно работают наушники 6 часов, с кейсом — до 30 часов. 

1/2

Bluetooth 5.4 обеспечивает стабильное соединение на расстоянии до 10 метров. Удобная C-образная форма разработана специально для длительного ношения — после восьмичасового рабочего дня в ушах не возникает дискомфорта. Наушники поддерживают одновременное подключение к ноутбуку и смартфону. Переключение между устройствами происходит быстро и без заминок.

Через фирменное приложение Welife можно выбрать один из четырех эквалайзеров и отследить местоположение гарнитуры в случае утери. А еще кастомизировать виджет для управления наушниками. Функция настройки персонализированного дизайна доступна для устройств на Android и позволяет гибко изменить внешний вид окна подключения: вплоть до установки фоновой картинки или собственного фото.

Первые пару использований может потребоваться время, чтобы привыкнуть к нестандартной форме вкладышей, но уже с третьего раза они надеваются вслепую за секунду. Что особенно приятно:  собеседники отмечают, что звук от микрофона более приятный и четкий, чем у дорогих известных моделей.

Бесшовная синхронизация со смартфоном

Благодаря функции OneLeap ноутбук синхронизируется со смартфоном TECNO. Подключение происходит за пару секунд: достаточно один раз подтвердить сопряжение. После этого открывается доступ к бесшовному переключению между устройствами — объединенному буферу обмена, дублированию экранов и передаче файлов без кабелей и пересылок в мессенджерах.

Функция выручила, когда нужно было открыть приложение, у которого нет веб-версии. Удобно работает и буфер обмена: скопировал текст на одном устройстве — вставил на другом. Например, код, полученный в сообщении на телефоне, вводится в браузере на ноутбуке. Экономит минуты, а иногда и нервы. А когда в дороге пропал Wi-Fi, ноутбук сам подключился к мобильному интернету через смартфон.

1/2

TECNO CAMON 40 и сам по себе — мощный рабочий инструмент.  Смартфон выделяется камерой высокого качества 50 Мп, ярким AMOLED-экраном 120 Гц и множеством функций, которые упрощают процесс мобильной съёмки и использование искусственного интеллекта TECNO AI.

Телефон работает на HIOS 15.0.1 на базе Android 15.В фирменную оболочку встроен искусственный интеллект:

  • Голосовой помощник Ella. Отвечает на вопросы, помогает с задачами и управлением устройством.

  • Решение задач. Наводите камеру на задачу, ИИ решает ее.

  • AI Редактор фотографий. Интеллектуальная обработка в одно касание.

  • Быстрый поиск. Находит адрес на экране и запускает навигацию, распознает объекты и события, автоматически добавляет их в календарь.

Технические характеристики

  • Процессор и память. 8 ядер, 16 потоков, Кэш L3 16 МБ, частота до 4.5 ГГц Графический процессор AMD Radeon™ graphics SSD 512 ГБ или 1 ТБ, М.2, 2280, PCle 3.0 Nvme DDR4 16 ГБ, 3200 МГц.

  • Дисплей. 15.6", TFT, Full HD (1920×1080), 16:9, 280нит, 45% NTSC, 16.7 млн цветов, 60 Гц, 141 ррі.

  • Веб-камера. 1 Мп, шторка приватности.

  • Порты. 9 портов: 1*TF Card (microSD), 1*HDMI 1.4, 1*USB-A 3.1,

    1*USB-A 3.2, 1*3.5mm аудиовход, *Ethernet RJ45 до 1 Гбит, 2*Туре-С (Full Function), 1*слот для замка Kensington.

  • Другое. Сканер отпечатка пальца в кнопке питания. Клавиатура с подсветкой (4 уровня яркости). Тачпад с поддержкой одновременно 4 касаний.

  • Батарея. 70 Вт∙ч (6150 мА∙ч), Li-Pol, 11.55 B 65 Вт Type-C GaN, 20 В, 3.25 А, кабель 1.8 м (Туре-С-Type-C).

  • Габариты. 17.3 мм (высота), 359.5 мм (ширина), 236 мм (глубина).

  • Вес. 1,7 кг.


Если хотите создать собственную экосистему, в которой технологии подстроятся под ритм дня, попробуйте технику TECNO. Мощный ноутбук, быстрый смартфон и наушники соединяются в единое пространство. Быстрое переключение между устройствами, синхронизация файлов и стабильное соединение без лишних настроек.

КУПИТЬ НОУТБУК TECNO

Реклама TECNO Mobile Limited, Юридический адрес: Flat N, 16/F., Block B, Универсальный промышленный центр, 19-25 Shan MeiStreet, Fotan, New Territories, Гонконг

Показать полностью 17
Электроника Гаджеты Ноутбук Длиннопост
49
GrimmIronwill
GrimmIronwill
3 года назад
Лига Разработчиков Видеоигр
Серия Gamemaker Studio 2: серия гайдов

GameMaker Studio 2. Урок 2. События отрисовки. Коллизия. Как работают скрипты. Как подключить русский шрифт. Переходы между комнатами⁠⁠

Привет! Это вторая часть урока.
Первая:
GameMaker Studio 2. Урок 1. Знакомство

Сегодня у нас много матчасти и не очень много кода.

Конкретизация разработки.

Как было сказано в прошлом посте, игра, которая будет сделана в ходе данных гайдов, будет относиться к жанру стратегий, а в качестве источников для "вдохновения" у нас - "Oxygen Not Included" и "Rimworld". Теперь же конкретно поговорим с вами о том, что в игре должно быть реализовано, по пунктам.

-) Персонажи.

--) Персонаж - игровой "объект", способный перемещаться по игровому миру и взаимодействовать с ним. Делятся на подконтрольных и неподконтрольных игроку. Игрок НЕ имеет прямого управления над своими подопечными, кроме специального "боевого" режима.

--) Каждый персонаж должен иметь или уметь следующее:

---) Взаимодействовать с предметами, расположенными на карте: подбирать их в свой инвентарь или выкладывать; взаимодействовать с рабочими станциями; сражаться (наносить, получать урон).

---) Каждый персонаж смертен. Каждый персонаж должен иметь ряд характеристик, которые он будет пытаться "поддерживать" на должном уровне, чтобы не умереть и продолжать быть эффективным. Эти характеристики:

----) Сон

----) Голод

----) Настроение

---) Каждый персонаж уникален и должен отличаться от других. Черты, навыки с различными уровнями прокачки. Этот пункт может игнорироваться в случае, если это не подходит под конкретного персонажа.

-) Интерактивные объекты.

--) Объекты, с которыми можно взаимодействовать через интерфейс игры и с которыми могут взаимодействовать персонажи или иные игровые сущности с какой-то целью.

---) Пример: верстак для создания вещей; исследовательский стол для открытия новых вещей и пр.

-) Интерфейс.
--) Простой, понятный. Желательно, на русском языке.
--) Хотя бы минимально настраиваемый.


Скажу сразу, что персонажей мы делать будем позже. Начнём мы с интерактивных объектов и размещения их в мире. Но, это будет потом.

Сейчас - продолжаем изучать матчасть.

События отрисовки.

Матчасть.

Событие отрисовки добавляется на объект как и любое другое, через кнопку "Добавить событие (Add Event)".

Имеется два события:

-) Draw

--) Событие, которое отрисовывает объект в комнате.

-) Draw GUI.

--) Событие, которое отрисовывает объект на экране.

GameMaker Studio 2. Урок 2. События отрисовки. Коллизия. Как работают скрипты. Как подключить русский шрифт. Переходы между комнатами Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

Иными словами, событие отрисовки GUI позволяет нам закрепить на экране наш интерфейс и то, что мы хотим игроку показывать всегда. Draw - стандартная отрисовка.

Возникает вопрос: зачем нам отдельно добавлять событие отрисовки, если объект спокойно отрисовывается и без него?

Ответ: в событии отрисовки можно написать код, который позволит нам менять параметры отрисовки, будь то: изменение размера (соотношения сторон); изменение цвета; создание дополнительных графических эффектов; etc.

ВАЖНО!

Если вы добавили событие отрисовки, то вам необходимо написать в коде следующую строку:

draw_self();

В противном случае, объект не будет отрисован.
Проделайте это сами. :)

Итак. Что мы можем делать в событиях отрисовки:
-) Отрисовка спрайтов:
--) 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"

GameMaker Studio 2. Урок 2. События отрисовки. Коллизия. Как работают скрипты. Как подключить русский шрифт. Переходы между комнатами Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

Хотя маска может иметь несколько форм, о которых будет написано ниже, устанавливается она исходя из четырёх позиций - левого верхнего и правого нижнего углов. Именно эта маска считывает столкновения, а не сам спрайт.

Mode - режим маски. Варианты:
- Автоматическая
- На всё изображение
- Ручная

Type - тип маски. Варианты:
- Прямоугольник
- Прямоугольник с поворотом
- Эллипс (работает медленнее)
- "Алмаз" (ромб) (работает медленнее)
- Предрасчет / Точный (медленнее) - ГМС постарается сам подстроить маску под форму объекта.
- Предрасчет / Точный по кадрам.

Мой совет:
Если игра небольшая, ставьте ручной режим и настраивайте сами те объекты, для которых это важно. Особенно те объекты, столкновение с которыми должно происходить только в определённой позиции (скажем, как в Стардью, сталкиваешься с основанием дерева, но не с его кроной).

В нашей игре данная настройка нам практически не пригодится, так как будет использоваться система поиска путей. Тем не менее, с помощью маски можно будет реализовать "выталкивание" застрявших персонажей, а также настроить косметическое "расталкивание" персонажей, если те проходят через занятую другим персонажем клетку.

Как работают скрипты.

Скрипт (функция) - именованный блок кода, выполняющий определённую задачу.
Все команды, которые мы использовали до этого, являются такими "скриптами". Иногда я буду называть их функциями, оба варианта правильные.

Создать скрипт просто. Для этого нажмите ПКМ по папке "Scripts" и выберете там "Script". Назовите его scrMove
У вас откроется следующее окно:

GameMaker Studio 2. Урок 2. События отрисовки. Коллизия. Как работают скрипты. Как подключить русский шрифт. Переходы между комнатами Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

scrMove - название нашей функции. В круглых скобках мы в неё можем передавать аргументы. Аргумент - это переменная, которая используется только в функции, передаётся в неё при вызове и может иметь любое название. В скобках напишите spd - это будет как раз наша переменная скорости.
Вырежьте код движения из Step-события у игрока и вставьте в скрипт, а в Step-событии игрока напишите следующий код:
scrMove(player_speed);

Получится следующая картина:

GameMaker Studio 2. Урок 2. События отрисовки. Коллизия. Как работают скрипты. Как подключить русский шрифт. Переходы между комнатами Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок
GameMaker Studio 2. Урок 2. События отрисовки. Коллизия. Как работают скрипты. Как подключить русский шрифт. Переходы между комнатами Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

Как вы заметили, "x" и "y" у нас по прежнему работают. Это потому, что мы выполняем скрипт в объекте oPlayer: данные переменные подтягиваются из объекта автоматически.

Когда нужно применять скрипты?
-) Когда код, который мы пишем, нужно использовать несколько раз.
-) Когда код, который мы пишем, занимает много места, чтобы его было проще отладить.
-) Когда нам нужно получить какое-либо значение на основе каких-либо данных, так как скрипт может не просто выполнять код, но и возвращать значения.

Для того, чтобы скрипт вернул какое-то значение, нужно написать:

return название_переменной/значение.
Переменную можно написать только одну. Команда return прерывает выполнение скрипта. Переменную / возвращаемое значение можно не писать, тогда скрипт просто прервётся.
Конструкцию с прерыванием следует добавлять везде, где это может повлиять на игру, особенно её работоспособность.

Раньше скрипты выполнялись медленнее, чем код, написанный просто в объекте. Сейчас это не так (или, по крайней мере, не так критично).

Также стоит понимать, что создав объект "Скрипт", вы, по сути, создали просто файл для хранения кода. В одном скрипте может быть собрано сразу множество разных функций, отвечающих за выполнение одной задачи. При вызове обращаться нужно именно к имени функции.

Скрипт для отрисовки текста с обводкой.

Создаём новый скрипт и называем его scrOutlinedText
Нам понадобятся следующие аргументы:
-) xPos - позиция текста по X
-) yPos - позиция текста по y
-) col - цвет текста
-) outlineCol - цвет обводки
-) text - текст
-) curdepth - текущая "глубина" объекта.
-) Шрифт, которым будем рисовать.
-) Позиционирование по X
-) Позиционирование по Y

Общая схема работы скрипта:
1) Назначаем максимальную "глубину".
2) Рисуем текст цветом обводки.
3) Поверх рисуем текст нужным нам цветом, но с небольшим смещением.
4) Возвращаем глубину к первоначальным показателям.

Код:
https://pastebin.com/TahYZKc8
P.S.
Изначально код не мой, а честно стырен подсмотрен с интернета, но я в него добавил больше переменных для лучшей настройки и отрисовки. Фактически, значение глубины тоже можно передавать как аргумент, чтобы разный текст выводился по разному.

GameMaker Studio 2. Урок 2. События отрисовки. Коллизия. Как работают скрипты. Как подключить русский шрифт. Переходы между комнатами Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

Создание русского шрифта

Проверим, как работает скрипт.
Сначала мы создадим русский шрифт. Для этого найдём папку "Fonts", нажмём ПКМ и создадим "Font".
Выбирайте любой шрифт и размер, который хотите. Нам с вами нужна кнопка "Add", чтобы добавить диапазон с русским шрифтом.

GameMaker Studio 2. Урок 2. События отрисовки. Коллизия. Как работают скрипты. Как подключить русский шрифт. Переходы между комнатами Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок
GameMaker Studio 2. Урок 2. События отрисовки. Коллизия. Как работают скрипты. Как подключить русский шрифт. Переходы между комнатами Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

Здесь напишем 1040 to 1105, чтобы захватить весь русский алфавит, затем нажмём "Add Range".
Шрифт называйте на Ваше усмотрение. Я назову его: fontArialRusSmall

Теперь перейдём к объекту игрока. Создадим событие Draw GUI и напишем следующий код:
scrOutlinedText(10, 10, c_yellow, c_red, "Скорость: " + string(player_speed), depth, fontArialRusSmall, fa_left, fa_top)

Результат:

GameMaker Studio 2. Урок 2. События отрисовки. Коллизия. Как работают скрипты. Как подключить русский шрифт. Переходы между комнатами Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

Можете заменить c_yellow и c_red на любые цвета. Помните, что можно передавать цвета в HEX с помощью # и $ (в обратном порядке).

Скрипт для поиска значения в массиве:

Так как эту тему мы не проходили, заострять внимание не буду, но вдруг кому пригодится:
https://pastebin.com/1VTNg4Bp

function scrFindInArray(array, value)
{
if is_array(array)
{
for (var i = 0; i < array_length(array); i++)
{
if array[i] == value
{
return i
}
}
return noone
}
else
{
return noone
}
}
Работает просто, но встроенной функции подобного толка в GMS нет:
- Проверяет, что аргумент является массивом.
- Обходим массив и, если какое-то значение является его элементом, возвращаем индекс. Если же совпадений не было или аргумент не массив - возвращаем "noone" - в GML это значение равняется -4.
По сути, код можно и упростить, убрав проверку на то, что переданное значение является массивом, но с ней меньше шансов накосячить. :)

Переходы между комнатами. Создаём меню.

Наконец, с матчастью мы закончили. Приступим к тому, что создадим меню нашей игры.
Создаём новую комнату и обзываем её rmMainMenu. Затем нажмите на значок "домика", чтобы поменять стартовую комнату (ту, что отображается при запуске игры).

GameMaker Studio 2. Урок 2. События отрисовки. Коллизия. Как работают скрипты. Как подключить русский шрифт. Переходы между комнатами Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

Сделайте так, чтобы домик был напротив созданной комнаты.
Заметьте, что при создании новой комнаты у неё изменяются все настройки! Это важно.

Создадим в "Objects" папку "Меню", где создадим следующие объекты: "oMStart", "oMSettings", "oMExit", "oMMenu".
OMStart - кнопка для старта нашей игры.
OMSettings - кнопка для перехода в настройки игры.
oMExit - кнопка для выхода из игры
oMLoad - кнопка для загрузки игры
oMMenu - управляющий объект, который будет располагать наши кнопки.

Зайдём в комнату MainMenu и поставим там объект oMMenu.
Затем перейдём в код oMMenu и выберем событие создания (Create)

Там напишем следующий код:

buttons = [oMStart, oMLoad, oMSettings, oMExit]
Здесь мы поместили все наши кнопки в массив. Таким образом, мы можем обратиться к объекту-кнопке, используя название массива и квадратные скобки: 
buttons[0] - будет обращаться к первому элементу массива (oMStart)
Теперь нужно каждую из этих кнопок разместить. Но, чтобы правильно их разместить, сделаем на все кнопки один спрайт. Размер подбирайте под себя.

У каждого объекта создадим два события: Draw, которое оставим пустым, и Draw GUI, где напишем draw_self();
Там же напишем:
scrOutlinedText(x, y, c_white, c_black, "Здесь текст кнопки", depth, fontArialRusSmall, fa_center, fa_middle)
Где "Здесь текст кнопки" - заменить на нужное: "Начать игру", "Загрузить игру", "Настройки", "Выход"

Вернёмся в OMMenu.
Нажмите правой кнопкой мыши. Наведитесь на пункт "Code Snippets" и выберите пункт 5.
В данном меню расположены готовые шаблоны кода, а мы создали шаблон цикла.
Напишите следующий код:

for (var i = 0; i < array_length(buttons); ++i)

{

instance_create_layer(room_width / 2, room_height / 2 - 50 + sprite_get_height(sButton) / 2 + 100 * i, "Instances", buttons[i])

}

Этот код создаёт инстансы объектов, которые перечислены в нашем массиве, по центру комнаты, плавно опускаясь вниз. Не забудьте поставить Origin у спрайта кнопки по центру.
Координаты настраивайте под себя. Для этого изменяйте первые два аргумента (идут до "Instances".)
Скриншот:
GameMaker Studio 2. Урок 2. События отрисовки. Коллизия. Как работают скрипты. Как подключить русский шрифт. Переходы между комнатами Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

Теперь нужно сделать отслеживание нашей мышки. Кнопку "Настройки" мы сделаем чуть позже. Пока перейдём к кнопке для выхода из игры.

До текста отрисовки нашего текста, нам нужно сделать небольшую подсветку нашей кнопки, чтобы мы знали, что она работает. Для этого нам нужно отслеживать позицию нашей мыши относительно интерфейса, для чего есть специальная команда.

dmxg = device_mouse_x_to_gui(0)
dmyg = device_mouse_y_to_gui(0)

ОК. Теперь мы отслеживаем нашу мышку относительно GUI. Нужно проверить, наведены ли мы на конкретный объект и если да - нарисовать поверх него полупрозрачный белый прямоугольник. Для этого есть команда:
instance_position(x, y, obj)

В x и y мы передадим переменные выше, а в качестве объекта напишем "self", чтобы проверять координаты нашего объекта.

Код:

if instance_position(dmxg, dmyg, self)
{
draw_set_color(c_white)
draw_set_alpha(0.15)
draw_rectangle(bbox_left, bbox_top, bbox_right, bbox_bottom, false)
draw_set_alpha(1)
}
Уже после этого пишем наш текст.
Теперь при наведении на кнопку "выхода" мы увидим, что она "активна".

Вырежем наши две переменные, добавим их в событие create. Теперь мы можем использовать эти переменные в любом коде этого объекта.

Теперь перейдём в событие step. Сюда также добавим наши две переменные. Теперь они будут постоянно обновляться.
Начнём настраивать логику работы.

Можете скопировать тот код, что мы писали выше, но удалить всё изнутри.
Нам нужно сделать проверку:
Если мы наведены на объект И нажата левая кнопка мыши - выйти из игры. То есть, немного её дополнить.

Для проверки нажатия кнопки левой мыши будем использовать команду:
mouse_check_button_pressed(mb_left)
Для выхода из игры:
game_end()

В итоге, в коде step будет следующий код:

dmxg = device_mouse_x_to_gui(0)
dmyg = device_mouse_y_to_gui(0)
if instance_position(dmxg, dmyg, self) and mouse_check_button_pressed(mb_left)
{
game_end()
}
Итого, если мы наведёмся на кнопку выхода - она подсветится. Нажмём - игра закроется.

Аналогичный код с подсветкой и проверкой просто скопируем в oMStart. В других кнопках логику работы в step пока не настраиваем на них, работаем с кнопкой начала игры.
Нужно внести одно изменение.

Вместо game_end() нам нужно написать код для смены уровня.
Для этого напишем:
room_goto(Room1)
Где Room1 - название комнаты, в которую вы хотите переместиться. Оно должно совпадать с названием комнаты в браузере ассетов.

Итого, мы можем выйти из игры или перейти в комнату, где у нас есть игрок, которым мы можем управлять. Осталось дополнить написать код, чтобы мы могли вернуться обратно в меню, при этом так, чтобы положение игрока на экране сохранялось.

Для этого нам придётся создать новый объект и назвать его oGameManager.
Разместим его в самой первой комнате и отметим у него чекбокс "Persistent"
Затем напишем у него в Step код:

Если индекс текущей комнаты != 0 и нажата ESC - деактивируем все инстансы, кроме текущего и переходим в меню.

if room != 0 and keyboard_check_pressed(vk_escape)
{
instance_deactivate_all(true)
room_goto(rmMainMenu)
}
Также отметим комнату с игроком как "Persistent".
Чтобы объекты в комнате, при возвращении, были активированы, немного дополним наш oGameManager. Для этого создадим у него событие "Room Start". 
GameMaker Studio 2. Урок 2. События отрисовки. Коллизия. Как работают скрипты. Как подключить русский шрифт. Переходы между комнатами Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

При старте комнаты, если эта комната не является меню, мы будем запускать все инстансы.

if room != 0
{
instance_activate_all()
}
Всё. Теперь мы можем спокойно выходить в меню из игры и возвращаться обратно при необходимости.

Реализация сложнее. О том, как работает Nine Slice.

Ниже будет приведена реализация меню с использованием функции Nine Slice, которая позволит нам делать кнопки других размеров.
Используя Nine Slice мы не сможем проверять, наведены ли мы сейчас на нужный нам объект. Также эта функция годится только для отрисовки типовых форм с типовым шрифтом. Если у вас спрайт кнопки идёт с текстом, то данная функция не подойдёт.
Чтобы отслеживать нажатия и наведение, нам необходимо проверять, находятся ли координаты мыши в нужной области. Для этого существует команда

point_in_rectangle(px, py, x1, y1, x2, y2)

Итак. Первый шаг - это убрать спрайт у каждой нашей кнопки.
Для этого откроем объект каждой кнопки, нажмём на выбор спрайта и выберем "None".

Шаг второй.
У каждой кнопки в событии Create мы пропишем:

width = 0;
height = 0;
При создании кнопок мы и так будем знать их x и y координаты, а ширину и высоту будем передавать.

Шаг третий
Нарисуем спрайт в виде одноцветного квадрата размером 9х9 пикселей.
Затем добавим ему две обводки разных цветов, каждая шириной в 1 пиксель.
Получим следующую картину:
GameMaker Studio 2. Урок 2. События отрисовки. Коллизия. Как работают скрипты. Как подключить русский шрифт. Переходы между комнатами Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

Теперь перейдём в настройки спрайта.
Назовём его sButtonSlice.
Слева выберем и откроем пункт Nine Slice.

После чего поставим галочку "Activate Nine Slice".
Далее - смотрим на наш спрайт и настраиваем его, как показано на скриншоте ниже:

GameMaker Studio 2. Урок 2. События отрисовки. Коллизия. Как работают скрипты. Как подключить русский шрифт. Переходы между комнатами Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Образование, Длиннопост, Урок

Чтобы двигать фиолетовые полоски - достаточно на них навестись и зажать ЛКМ.
В правом окошке вы можете порастягивать данный спрайт, как хотите и посмотреть на итоговый результат.
Растягивающаяся рамка для кнопок готова!

Шаг четвертый.

Пропишем в Draw GUI отрисовку кнопки вместо draw_self():

draw_sprite_stretched(sButtonSlice, 0, x - width / 2, y - height / 2, width, height)

Таким образом, мы отрисовываем кнопку.

Шаг пятый.

Вернёмся в oMMenu, в Create, где немного допишем наш код.
Мы создадим переменную, которой будем присваивать значение ID только что созданного объекта. Затем, обращаясь к этой переменной, мы будем менять у данного объекта параметры: ширину и высоту.

buttons = [oMStart, oMLoad, oMSettings, oMExit]
var bwidth = 400
var bheight = 96
for (var i = 0; i < array_length(buttons); ++i)
{
inst = instance_create_layer(room_width / 2, room_height / 2 - 50 + 100 * i, "Instances", buttons[i])
inst.width = bwidth
inst.height = bheight
}

Меняя значения bwidth и bheight вы сможете сами настроить нужные размеры кнопок.

Обратите внимание, что создавая инстанс мы можем передать struct - то есть структуру данных.
Тогда не нужно будет обращаться к инстансу через переменную, но придется удалить код

width = 0;
height = 0;
При создании наших объектов, а сам код для создания инстанса преобразится следующим образом:

for (var i = 0; i < array_length(buttons); ++i)
{
instance_create_layer(room_width / 2, room_height / 2 - 50 + 100 * i, "Instances", buttons[i], {width : bwidth, height : bheight})
}
Какой из вариантов удобнее - решать вам.
Вариант с переменными, которые объявляются при создании объекта, я лично считаю более надёжным, а функциональных отличий между данными командами не так много. С другой стороны, такая возможность появилось недавно и её нужно тестировать.


Шаг шестой.

Теперь задача - сделать так, чтобы мы могли считывать, наведены ли мы сейчас на кнопку.

Для этого мы берём координаты x и y, после чего проверяем, находится ли наша мышь в области:

x - width / 2, y - height / 2, x + width / 2, y + height / 2.

Остальное - копируем из прошлого кода. Получается следующий блок:

if point_in_rectangle(dmxg, dmyg, x - width / 2, y - height / 2, x + width / 2, y + height / 2)
{
draw_set_color(c_white)
draw_set_alpha(0.15)
draw_rectangle( x - width / 2, y - height / 2, x + width / 2, y + height / 2, false)
draw_set_alpha(1)
}


Шаг седьмой.

Проделать тоже самое в событии Step. (По сути, просто заменяем instance_position)

if point_in_rectangle(dmxg, dmyg, x - width / 2, y - height / 2, x + width / 2, y + height / 2)
and mouse_check_button_pressed(mb_left)
{
//Тут код нужной кнопки
}


Шаг восьмой.

Донастраиваем все кнопки.

Какой из вариантов использовать - решать Вам.

Можно вообще использовать следующий вариант:

Создать спрайт нужных размеров и залить его белым цветом с "прозрачностью" в 1 единицу. Совершенно незаметно. Используя bbox_left / top / right / bottom нарисовать поверх прозрачной основы кнопку и проверять уже не через "point_in_rectangle", а как в первом способе.

На этом сегодняшний гайд подходит к концу.
Вкратце мы разобрали события отрисовки; как с ними работать; как работать со скриптами; как работать со шрифтами; а также вы можете смело скопировать себе и использовать несколько других скриптов в своих проектах.

Что нужно подготовить к следующему гайду:
- Минимум два спрайта "породы" - на фон (темнее) и для объекта (светлее). Размер одного спрайта - 32х32.
- Желательно спрайты нескольких объектов, вроде стен, дверей. Исходите из того, что размер одной клетки будет равен 32х32 пикселей.
- Спрайт для неба/космоса/прочего на фон на ваше усмотрение.

Я подготовлю свои варианты и, используя их, на следующем занятии мы с вами разберёмся, как работает камера.
Сделаем меню настроек с возможностью переключения в полноэкранный режим.

И небольшой спойлер на будущее.
Взаимодействовать с миром можно будет двумя способами: через ЛКМ и наведением "тела" игрока на тот или иной объект.

Загрузить файл проекта и всё пощупать самому можно по ссылке:
Яндекс диск

Оставшиеся темы:

- Камера и её настройка. Разные способы реализации: от простого к сложному.

- Иерархия объектов. «Объекты-родители» и их «дети». Решение часто встречающихся проблем и немного про то, как удобно выстраивать взаимодействие с объектами. Глобальные переменные.

- Массивы и с чем их едят, а также grid (сетка комнаты), размещение объектов по сетке. Включая объяснение, в каких случаях лучше использовать встроенные функции, в каких – писать свои с нуля.

- Пути. Один большой гайд, включая скрипты поиска путей для различных сеток в т.ч. с примерами из моего личного проекта.

- Иные способы хранения информации в GMS2, когда их стоит или не стоит использовать.

- Сохранение. Встроенное VS самописное.

- Звуки.

Есть вопрос - задавай, постараюсь ответить.
Что-то не получилось, вылезла ошибка и так далее - тоже пиши, помогу или поможем.

Показать полностью 14
[моё] Разработка Gamedev Программирование Инди Инди игра Gamemaker Studio 2 Образование Длиннопост Урок
13
Посты не найдены
О нас
О Пикабу Контакты Реклама Сообщить об ошибке Сообщить о нарушении законодательства Отзывы и предложения Новости Пикабу Мобильное приложение RSS
Информация
Помощь Кодекс Пикабу Команда Пикабу Конфиденциальность Правила соцсети О рекомендациях О компании
Наши проекты
Блоги Работа Промокоды Игры Курсы
Партнёры
Промокоды Биг Гик Промокоды Lamoda Промокоды Мвидео Промокоды Яндекс Директ Промокоды Отелло Промокоды Aroma Butik Промокоды Яндекс Путешествия Постила Футбол сегодня
На информационном ресурсе Pikabu.ru применяются рекомендательные технологии