GrimmIronwill

GrimmIronwill

Пикабушник
поставил 341 плюс и 32 минуса
Награды:
Пятничное [мое]
441 рейтинг 71 подписчик 9 подписок 17 постов 10 в горячем

«Рука Мастера» - для симуляции миров НРИ

Немного новостей с поприща софта для любителей ДНД и любых других НРИ.
Скоро увидит свет приложение «Рука Мастера», оно же «The Hand of a Master».
Которое выступает в роли конструктора симуляции ваших миров.

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

Теперь не придётся держать в голове много лишних и чисто механических параметров что бы мир казался живым. Приложение это сделает это за вас. А если вам захочется что-то из них поменять, вы всегда это можете сделать. Так как приложение даёт доступ ко всем переменным на карте. Будь то город, или количество пшеницы у конкретного отряда на дороге.

История же разработки «Руки» мастера на данный момент насчитывает всего два месяца. Изначально проект делался на движке «Gamemaker», но, ввиду достаточно сложного для реализации интерфейса, вскоре игра перешла на «Godot» новейшей версии, после чего разработка пошла ударными темпами. Смена движка также позволила использовать больше ресурсов компьютера: сделать инструмент многопоточным, а также хорошо оптимизированным, из-за чего тормоза начинаются только на действительно гигантских объемах информации. Тем не менее, так как проект ещё в разработке, многое дорабатывается в процессе, начиная от интерфейса и заканчивая архитектурой.

От себя, как от разработчика, я бы рекомендовал многим из вас расширять свой «кругозор». Например, я бы впредь не стал использовать «Gamemaker» для сложных, нагруженных тонной информации игр и предпочту «Godot». Но если требуется сделать что-то маленькое, быстрое – Гейммейкер я считаю более рациональным вариантом.

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

Узнать подробнее о ходе разработки можно в группе ВК, прочие ссылки находятся там же:
https://vk.com/club214480055

«Рука Мастера» - для симуляции миров НРИ Настольные ролевые игры, Dungeons & Dragons, Godot, Разработка, Gamedev, Инди игра, Компьютерные игры, Инди

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



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

Инструментарий позволит:
• Загружать любое изображение в качестве вашей карты (готово).
• Создавать свои собственные фракции (в разработке).
• Создавать свои ресурсы и цепочки производств (готово).
• Создавать отряды и города, караваны и лагеря бандитов(готово).
• Создавать свои собственные типы юнитов для отрядов и городов(готово).
• Использовать любые изображения в качестве иконок для всего перечисленного выше(готово).
• Создавать автоматически или вручную дороги для отрядов или между городами(готово).
• Использовать и сохранять шаблоны уже готовых миров, созданных другими пользователями(готово).
И многое, многое другое.

Показать полностью 1

Gamemaker. Прокручивающийся текст

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

Видео с демонстрацией работы и объяснениями. Текст тот же самый, поэтому кому как удобнее - выбирайте.

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

Gamemaker. Прокручивающийся текст Разработка, Программирование, Игры, Gamedev, Game maker, Gamemaker Studio 2, Гайд, Видео, YouTube, Длиннопост

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

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

Ниже представлен код события Draw GUI. Событие Draw должно быть пустым.

Gamemaker. Прокручивающийся текст Разработка, Программирование, Игры, Gamedev, Game maker, Gamemaker Studio 2, Гайд, Видео, YouTube, Длиннопост

Остаётся сделать только непосредственно прокрутку текста.
Прокрутка текста реализована изменением переменной yy, значение которой должно держаться в промежутке от 0 до (высота текста - высота спрайта), чтобы игрок не мог прокручивать бесконечно вверх или вниз, максимум - мог увидеть самую нижнюю строку текста полностью. Происходит это в событии "Step" соответственно.

Gamemaker. Прокручивающийся текст Разработка, Программирование, Игры, Gamedev, Game maker, Gamemaker Studio 2, Гайд, Видео, YouTube, Длиннопост

И небольшая иллюстрация того, как это всё дело просчитывается.

Gamemaker. Прокручивающийся текст Разработка, Программирование, Игры, Gamedev, Game maker, Gamemaker Studio 2, Гайд, Видео, YouTube, Длиннопост

Этот код можно доработать при необходимости под ваши нужды, в том числе добавить различных удобств, вроде скроллбара.

Ссылка на скачивание проекта:
https://drive.google.com/file/d/1aJUFs81zFuVEpBIynM504KiBAtn...

Показать полностью 4

GameMaker Studio 2. Про GUI

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

Напоминалка для запросившего пост: @Veslo7601

План:
- Теория. Что такое GUI?
- Практика.
- Как включить отрисовку GUI?
- Как удобнее использовать GUI?
- Зачем настраивать слой GUI и как?
- Как взаимодействовать с GUI?
- Как изменить курсор правильно?

Теория.
Что такое GUI?

GUI - графический пользовательский интерфейс, позволяющий взаимодействовать с программой (игрой) через графические элементы: иконки, меню, кнопки и т.п.

В GMS имеется два слоя, с которыми мы можем работать.
Первый зовётся application_surface. Это холст, на который происходит отрисовка любых форм в событиях draw, draw begin и draw end.
Второй - слой GUI, который рисуется выше, чем application_surface, перекрывая его. Отрисовка на него происходит, соответственно, в событиях draw GUI, draw GUI begin и draw GUI end.

Слой GUI по умолчанию равен размеру application_surface, который равен размерам комнаты. При этом, GUI автоматически подстраивается под размеры окна игры таким образом, чтобы вмещаться в него полностью.

"Глубина" (depth) работает точно также, как и на стандартном слое отрисовки. Чем меньше значение - тем "выше" рисуется объект. Если у объектов глубина одинаковая, то поверх будет рисоваться последний добавленный.

Практика.
Как включить отрисовку GUI?


Здесь всё просто.

GameMaker Studio 2. Про GUI Разработка, Gamedev, Программирование, Gamemaker Studio 2, Длиннопост, Урок, Игры, Обучение, Текст

Шаг первый.
У объекта создаём событие Draw (Рисовать) и внутри пишем одну команду:

exit;
Указанная функция говорит компилятору прекратить выполнение кода в данном событии, что немного оптимизирует игру.
Шаг второй.
Создаём событие Draw GUI (Рисовать GUI) и внутри пишем
draw_self()
Либо любую другую команду отрисовки, которую хотите.

Как удобнее использовать GUI?

Располагать элементы интерфейса проще всего вручную. Самый простой способ - подстроить размер комнаты под предполагаемый размер GUI (либо настроить сетку под данные размеры) и расположить объекты там, где вам нравится. Про размер GUI будет чуть ниже.

Так как слой глубины (depth) на слое GUI работает точно также, как и на слое комнаты, имейте ввиду, что:
- При расположении объектов на одном и том же слое, перекрывать их будет тот объект, что поставлен последним. Настроить порядок создания объектов вы можете вручную. Для этого в комнате выделите слой с объектами и в инспекторе прокрутите до вкладки Instances. Все изменения будут отображаться в реальном времени, что удобно.
- Глубину можно настроить вручную в любом событии, кроме события отрисовки. Для этого есть переменная depth; помните, что чем меньше значение - тем выше рисуется объект.

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

Зачем настраивать слой GUI и как?

Слой GUI сделан таким образом, чтобы всегда вмещаться в размер дисплея/игрового окна или application surface. Как следствие, появляется масштабирование тех объектов, которые мы рисуем на данном слое, чтобы они соответствовали заданным параметрам.

Объекты, которые мы рисуем на слое GUI, по умолчанию будут масштабироваться в зависимости от размеров application_surface. Чтобы этого избежать нужно установить размер GUI вручную.
Для этого существует команда:

display_set_gui_size(широта, высота);

При этом стоит помнить, что GUI продолжает подстраиваться под размер окна. Соответственно, нам также нужно зафиксировать размер окна. Для этого существуют следующие команды:

window_set_size(широта, высота), либо включить полноэкранный режим: window_set_fullscreen(true);
После указанных действий картинки на экране и в редакторе будут одинаковыми. Главное помните, что масштабирование к размеру окна происходит в любом случае.
Дальше работать предстоит методом подбора. Я обычно выставляю размер GUI (1920x1080) и расставляю все элементы относительно указанных значений.

Размер масштабирования вы также можете настроить. Для этого существует команда:
display_set_gui_maximise(Масштабирование по X, масштабирование по Y, смещение по оси X, смещение по оси Y).
Простой вызов функции без аргументов приведёт к тому, что масштабирование будет вестись от позиции (0, 0) игрового окна с такой шириной и высотой, чтобы покрыть всё окно.
Такой способ может быть использован заместо display_set_gui_size в том случае, если размер окна и размер GUI, под который вы всё расставляли, одинаков. В ином случае лучше всё же воспользоваться display_set_gui_size();

Если указать (-1, -1), то произойдёт сброс до стандартных настроек. Т.Е. отрисовка будет вестись от позиции (0, 0) вашего application_surface и будет пытаться показать всю его область.
Другие значения приведут к масштабированию интерфейса в соответствии с заданными параметрами. (2, 2) = 200% по X и Y от размера игрового окна.

Как взаимодействовать с GUI?

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

device_mouse_x_to_gui(0);
device_mouse_y_to_gui(0);
Соответственно, первая переводит координату X мыши в относительную к GUI, вторая - координату Y.
Так как это функции, рекомендую назначать в начале шага их локальным переменным, чтобы избежать постоянных перерасчётов.
Дальше работа с ними происходит ровно также, как и с (mouse_x / mouse_y).

Как изменить курсор правильно?

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

cursor_sprite = sprCursor;
Также, если вы хотите скрыть "системный курсор", то для этого существует команда:
window_set_cursor(cr_none)
Со списком всех возможных курсоров можно ознакомиться по ссылке ниже:
https://manual.yoyogames.com/GameMaker_Language/GML_Referenc...

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

Показать полностью 1

Gamemaker Studio 2. Редактор гексовых карт

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

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

Получившаяся в итоге программка позволяет загрузить в неё (пока только в виде ассетов!) любую текстуру или спрайт и затем применять для создания карты. Всё, что нужно - в объекте objManager дописать строчку в global.layer с названием спрайта. Для удобства, у нас имеются три слоя, на которых мы можем рисовать и с которых по отдельности можем что-то стирать.
Слой земли/воды, слой природных объектов и объектов игроков.

Gamemaker Studio 2. Редактор гексовых карт Gamemaker Studio 2, Разработка, Gamedev, Программирование, Инди, Длиннопост, Пятничный тег моё

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

1. Не используйте спрайты гексов.
Причина этого кроется в "не идеальности" гексовых спрайтов как таковых. Различия в один пиксель или пару градусов рушат всё, в том числе - формулы, которые приведены в источнике.

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

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

3. Учите геометрию.
Было бы смешно, если бы не было так грустно. После того, как мы нарисовали границы гекса - нам нужно его чем-то заполнить. На этом моменте со всех ног влетает геометрия.
Смотрим пункт выше и понимаем, что рисовать условную землю в виде гекса - затея так себе. Опять же, не совпадут углы - и появятся "проплешины" внутри нашего гекса.
Я этот момент решил с помощью прямоугольных спрайтов, который масштабируется в размерах и обрезаются под конкретный гекс.

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

Ссылка на исходники.
https://disk.yandex.ru/d/_OSgSS-huMtgzg

Показать полностью 1

Механики для разбора. (GMS2)

Доброго времени суток.
Хочу спросить у сообщества, разборы каких игровых механик или игр в целом они хотели бы видеть. Если есть идея - прошу в комментарии, буду рад.

На данный момент лежат материалы по RTS на базе Дюны и потихоньку разрабатывается RPG, будут посты с объяснением некоторых механик из них.

Пост без рейтинга.

Gamemaker Studio 2. Инвентарь

Привет-привет.
Сегодня разберём с вами, что такое инвентарь и как его реализовать в GMS2.

Для начала, небольшой план. Хочешь увидеть что-то конкретное - выделяй один из пунктов ниже и ищи по тексту.
Инвентарь с точки зрения компьютера.
Структура инвентаря.
Структура предметов.
Логика работы инвентаря.
- Подбор предметов.
- Меняем предметы местами в инвентаре.

Инвентарь с точки зрения компьютера.

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

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

Структура инвентаря.

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

В Create у данного объекта нам нужно написать следующий код:

obj = { name : "", id : noone, sprite : noone, stack : 0,}
cellId = noone;

Переменная obj будет хранить основную информацию об объекте отрисовки: имя, которое нужно выводить на экран, айди объекта (не экземпляра!), спрайт и количество в ячейке.
cellId - переменная, которая хранит номер текущей ячейке в массиве.

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

enum objectTypes { any, weapon, potion, shield,
armorHead, // Шлем
armorChest, // Нагрудник
armorBracers, // Наручи
armorGloves, // Перчатки
armorPants, // Штаны
armorBoots, // Ботинки}

Чтобы получить значение - нужно указать энумиратор, а затем выбрать значение. Пример: objectTypes.any - вернёт 0, т.к. этот элемент стоит первым.

Теперь что касается массива. Нам нужно хранить в нём: имя объекта, тип объекта, спрайт объекта, текущее и максимальное количество предметов в ячейке и, конечно же, тип ячейки.
Заполнять их будем через цикл, сначала по оси x, затем по оси y, одновременно создавая клетки инвентаря.
Храниться эти значения будут у объекта игрока.
Не забудьте деактивировать ячейки. Полный код по ссылке ниже:
https://pastebin.com/Sa0MjYKN

Для удобства, сделайте открытие и закрытие инвентаря на какую-либо кнопку.
В моём случае, код для этого также по ссылке ниже:
https://pastebin.com/hffJXm0m

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

Структура предметов.

Со структурой инвентаря разобрались - теперь по предметам. На самом деле, здесь всего четыре строки.
name - для обозначения имени нашего предмета.
type - для обозначения особой ячейки, в которую можно будет поместить только этот предмет. Используется энумиратор objectTypes.нужный_предмет
maxStack - максимальное количество предметов в одной ячейке.
quantity - базовое количество предметов в одном экземпляре. Соответственно, это значение мы сможем изменять, чтобы выбрасывать и подбирать сразу множество предметов.

Логика работы инвентаря.

- Подбор предметов
.

Делайте на ваш вкус и цвет. Я предлагаю разобрать для начала наиболее простой случай: подбор при столкновении.
Для этого у объекта игрока делаем событие столкновения с предметом, где пишем следующий код:
https://pastebin.com/unZqXSfB
Код здесь можно было бы написать компактнее, но для удобства чтения решено было оставить так. :)

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

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

- Меняем предметы местами в инвентаре.

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

Данный код по ссылке ниже:
https://pastebin.com/f8mEmF1T

Если хотите потыкать проект и поразбираться в кодах самостоятельно, то вот:
https://disk.yandex.ru/d/WVTCchskv_QaGQ

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

Показать полностью

Gamemaker Studio 2. Линия видимости и радиус видимости. (Line of sight). Алгоритмы

Всем приятного дня и хорошего настроения.

Сегодня разберёмся с вами, как в играх с сеткой реализовать такую механику, как линия и радиус видимости.
Для прошаренных: всё просто, будем использовать алгоритм Брезенхема и midpoint circle.

Этап первый. Подготовка.

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

Для этого нам понадобится:
- Один (лучше два) слоя тайлов.
- Одна переменная типа ds_grid для хранения значений.
- Изучить парочку алгоритмов (будут приведены в рабочем виде ниже).
Слои тайлов нам нужны для того, чтобы отрисовывать:
А) Землю, на которой будет стоять игрок (очень условно).
Б) Тень, которая эту землю будет закрывать.

Создаём слои тайлов и сетку.

Создать слой тайлов можно здесь.

Gamemaker Studio 2. Линия видимости и радиус видимости. (Line of sight). Алгоритмы Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Длиннопост, Урок, Игры, Обучение, Гифка

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

Gamemaker Studio 2. Линия видимости и радиус видимости. (Line of sight). Алгоритмы Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Длиннопост, Урок, Игры, Обучение, Гифка

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

Для неё у меня отдельный объект - oGrid. Размещаем его в Create и пишем следующий код:

var width = room_width div CellWidth;
var height = room_height div CellHeight;
global.grid = ds_grid_create(width, height);
var lay_id_ground = layer_get_id("GroundTiles");
var map_id_ground = layer_tilemap_get_id(lay_id_ground);
var lay_id_shadows = layer_get_id("ShadowTiles");
var map_id_shadows = layer_tilemap_get_id(lay_id_shadows);
for (var yy = 0; yy < height; ++yy) {
for (var xx = 0; xx < width; ++xx) {
ds_grid_set(global.grid, xx, yy, 1);
tilemap_set(map_id_ground, 1, xx, yy); tilemap_set(map_id_shadows, 1, xx, yy);
}}
Ссылочка на pastebin для удобного копирования:
https://pastebin.com/1cLfwHGg

Далее - создаём объект, который будет "следить". У меня это oPlayer. В Create ему пока объявим только одну переменную:
lineOfSight = [];
И поместим его в комнату. Проверьте результат. Ваша комната должна состоять из тайлов с обоих слоёв: земли и тени.

Алгоритмы.

Переходим ко вкусной части.

Для построения линии видимости нам с вами понадобится алгоритм, который будет рассчитывать, какие точки в заданном диапазоне нам нужно "закрасить" (или, в нашем случае, наоборот очистить).
Мы будем использовать алгоритм Брезенхема.
Вы можете более подробно ознакомиться с историей алгоритма в интернете.
Вариация данного алгоритма, которую мы будем использовать, представлена ниже. Можете смело копировать скрипт и вставлять.
https://pastebin.com/vfRMaXxp

x1, y1, x2, y2 - это точки начала и конца соответственно. В нашем случае, x1 и y1 мы получаем путём деления без остатка (div) x и y объекта на ширину / высоту клетки.
x2, y2 - наша конечная точка, которую получаем также путём деления без остатка уже mouse_x и mouse_y также на ширину / высоту клетки.
grid - наша сетка. Туда передаём global.grid.

Проверяем работоспособность.

Для этого перейдём в oPlayer, создадим у него событие отрисовки (draw) и напишем следующий код:
https://pastebin.com/Uz5RX41S
По сути всё просто.
Сначала запоминаем старые значения нашей линии видимости, затем строим новую линию и если новая линия не совпадает со старой - то "закрашиваем" старую тенями, а новую - наоборот, очищаем от них.

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

Ниже - гифка с примером того, как это работает. Серое - стены. Клетки со стоимостью 0 в ds_grid.

Gamemaker Studio 2. Линия видимости и радиус видимости. (Line of sight). Алгоритмы Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Длиннопост, Урок, Игры, Обучение, Гифка

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

Скрипт для построения круга.

Алгоритм, взятый за основу, зовётся midpoint circle. Опять же, можете почитать про него в интернете. Самое для нас важное - он позволяет строить круги любого неотрицательного радиуса. Отрицательные значения приводят к казусам...
Ссылка:
https://pastebin.com/aG2q8S0L

Как это выглядит:

Gamemaker Studio 2. Линия видимости и радиус видимости. (Line of sight). Алгоритмы Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Длиннопост, Урок, Игры, Обучение, Гифка

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

Ссылка на реализацию:
https://pastebin.com/SXTFNhWR
Вызывается каждый раз, когда меняем своё местоположение или ставимся в первый раз. Radius равен четырём по умолчанию, circleOfSight = []; оба объявляются в Create.
Данная реализация хорошо работает с радиусом до четырёх. Но стоит увеличить радиус - как возникают артефакты. Они продемонстрированы ниже.

Gamemaker Studio 2. Линия видимости и радиус видимости. (Line of sight). Алгоритмы Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Длиннопост, Урок, Игры, Обучение, Гифка

Из-за чего они возникают? Всё очень просто. Дело в самом алгоритме Брезенхема. Он просто не проходится по данным точкам в текущем своём виде.

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

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

Ссылка на модифицированный алгоритм Брезенхема:

https://pastebin.com/q24bVPbR

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

GIF с итогом работы.

Gamemaker Studio 2. Линия видимости и радиус видимости. (Line of sight). Алгоритмы Разработка, Gamedev, Программирование, Инди, Инди игра, Gamemaker Studio 2, Длиннопост, Урок, Игры, Обучение, Гифка

Вот как-то так, ребята. Нового ничего не придумал, всё честно понатырено с интернетов и адаптировано. Если кому-нибудь это пригодится - буду рад.
Ссылка на источник, чтобы можно было самому всё потыкать:
https://disk.yandex.ru/d/puHuKcDoMQU5nQ

По итогу, мы получили несколько алгоритмов для построения линий (различной ширины), а также алгоритм для радиуса видимости.

UPD:// Есть проблемы с ФПС, пока думаю, как их решить. Возникают они из-за частого обращения к ds_grid (полагаю), либо из-за копирования массивов. Пока разбираюсь, позже отпишу в комментарии, если решу проблему.

Показать полностью 6

Gamemaker Studio 2. Система для создания простых модификаций

Привет!

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

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

Описание системы

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

Реализация

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

Ссылка на код:
https://pastebin.com/T6NaBMsb

Дубль с пастебина:
Функция-конструктор для объектов:

function scrWeaponsAdd(_sprite_index, _images, _name, _distance, _penetration, _damage) constructor{
sprite_index = _sprite_index;
images = _images;
name = _name;
distance = _distance;
penetration = _penetration;
damage = _damage;
}
oWeaponsManager:
// Если файл уже существует - заполняем по новой.
if file_exists("weapons.txt") {
global.weapons = [];
var _buffer = buffer_load("weapons.txt");
var _string = buffer_read(_buffer, buffer_string);
buffer_delete(_buffer);
var _loadData = json_parse(_string);
// Загружаем оружие.
for (var i = 0; i < array_length(_loadData); ++i) {
// Если информация является строкой или такого спрайта не существует - создаём его и сохраняем
if is_string(_loadData[i].sprite_index) or !sprite_exists(_loadData[i].sprite_index) {
var name = _loadData[i].sprite_index;
if file_exists(name) {
var sprite = sprite_add(name, _loadData[i].images, false, false, 0, 0);
// Устанавливаем точку отсчёта в центре спрайта
sprite_set_offset(sprite, sprite_get_width(sprite) / 2, sprite_get_height(sprite) / 2);
_loadData[i].sprite_index = sprite;
}
}
array_push(global.weapons, _loadData[i]);
}
}
// Создаём с нуля, если не существует
else {
global.weapons = [
new scrWeaponsAdd(sPistol, 1, "Пистолет", 10, 1, 10),
]
var _string = json_stringify(global.weapons);
var _buffer = buffer_create(string_byte_length(_string) + 1, buffer_fixed, 1);
buffer_write(_buffer, buffer_string, _string);
buffer_save(_buffer, "weapons.txt");
buffer_delete(_buffer);
}
#endregion
#region Удаляем те объекты, что не смогли загрузиться
var i = 0;
while i != array_length(global.weapons) {
if is_string(global.weapons[i].sprite_index) {
array_delete(global.weapons, i, 1);
i -= 1;
}
i += 1;
}
#endregion
#region Создаём прошедшие проверку объекты. Дебаг.
for (var i = 0; i < array_length(global.weapons); ++i) {
var inst = instance_create_layer(32 + i * 32, 32, "Instances", oWeapon);
inst.sprite_index = global.weapons[i].sprite_index;
}
#endregion

Спрайты можно загружать как анимированные, так и одиночные. Всё зависит от параметра images, который должен быть равен количеству кадров. Принимает на вход: .png, .jpeg/jgp, .json, .gif.

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

Сочетанием клавиш "Windows + R" (или просто открыв окно "выполнить", либо прокликав пусть самостоятельно), вписывем туда: %localappdata%
Откроется папка Диск:\Пользователи\Пользователь\AppData\Local
Здесь ищем папку, название которой совпадает с названием нашего проекта. У меня это Pikabu.

Именно в эту папку у нас сохраняются файлы, с которыми мы работали. То есть, и сохранения, и файл weapons.txt, у нас находится здесь.
Чтобы добавить новое оружие - скинем его картинку в эту папку, затем немного подредактируем файл weapons.txt

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

Скопируем первую часть строки, что в фигурных скобках. Добавим запятую после закрывающей фигурной и вставим скопированный текст.
Что нам нужно заменить:
images - с 1 меняем на нужное число кадров. Как видно по скрину выше, у меня это число будет равняться трём, так как я хочу импортировать "анимированную" картинку. Важно знать, что на это число будет разделена картинка по ширине. Для анимации картинка должна быть выполнена в виде линии, где меняется только ширина. Хотите спрайт 32х32 с тремя кадрами анимации - делайте картинку 96х32 и размещайте в каждой новой "клетке" то, что нужно отрисовать.
sprite_index - пишем в кавычках полное название нашего файла. Полное название - это с учётом его формата. В моём случае - "sPistol3.png"
name, distance, penetration, damage - меняем по своему усмотрению, это уже чисто игровые параметры.
Сохраняем файл и заходим в игру, чтобы посмотреть результат.

Эпилептикам советую быть осторожнее.

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

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

Теперь поговорим о минусах.

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

Если что-то пошло не по плану, посмотреть исходный код можно здесь:
https://disk.yandex.ru/d/bOSzQ5bj8IdN9w

P.S.
Комната была уменьшена в размерах с 1920х1080 до 128х128, чтобы показать оба объекта.
P.P.S.
Если не получается найти папку, то можете в коде oWeaponsManager, после создания файла weapons.txt, прописать следующий код:
show_debug_message(filename_path("weapons.txt"))
И запустить игру. В выводе выскочит сообщение с путём прямиком до нужной папки.

Если что-то не получилось - пишите, постараюсь помочь.

Показать полностью 2
Отличная работа, все прочитано!