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

Пикабомбер

Аркады, Пиксельная, 2D

Играть

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

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

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

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

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

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

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

Gamedev + Образование

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

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

GameMaker Studio 2. Урок 8. Звук⁠⁠

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

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

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

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

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

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

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

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

Седьмой гайд - Сохранения и их загрузка. Бонус в конце!

План:
- Создание звука
- Подключим звук к кнопкам
- Глобальная настройка звука
- Заставим человечков издавать звук
- Музыка
- Тест звука
- Немного бонусов

Тема сегодня небольшая, поэтому сразу перейдём к делу.

Создание звука.


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

Как видно, там несколько форматов: OGG, WAV и MP3. Использовать мы можем все из них.
Сохраните архив, распакуйте в удобное место. Можете сразу скопировать путь до него. Дальше нам нужно создать звук и загрузить его. Для этого найдём папочку Sound и там создадим, собственно, Sound.

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

Звук назовём sndButton.
Итак. Если сейчас мы нажмём на появившийся звук, то увидим небольшое меню настроек для нашего звука. Пока нам это не интересно. Чтобы загрузить звук, нужно нажать на три точки справа от названия звука.

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

Там всё просто: указываем путь до нужного нам файла. У меня это "modern2.wav". Можете выбирать любой формат, пока это не столь важно. Стоит лишь отметить, что форматы отличаются не только весом, но и качеством звука и возможностью на него влиять внутри движка.

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

Обратите внимание: внизу указанного меню у вас есть вкладка Audio Group. Это ничто иное, как группа звуков. Её можно (и нужно) использовать в тех случаях, когда нам нужно уменьшить или увеличить только определённые звуки. Например, сделать три настройки звука: общий, музыка, игра. С-но, здесь будет три аудио группы: стандарт, музыка, игра.
Чтобы добавить свою аудио группу нужно в верхней части экрана найти вкладку Tools, там - Audio Groups.

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

Подключим звук к кнопкам.

Для удобства используем объект oMMenu, который сделаем родительским объектом для всех наших кнопок в меню, в т.ч. в настройках.
Не забудьте прописать event_inherited(); у дочерних объектов в тех событиях, которые нужно будет унаследовать.

Для начала, в Create объявим одну переменную:

collided = false;
Она позволит нам проигрывать звук при наведении только один раз.

Теперь в Step'e сделаем простую конструкцию: если наведены на текущий объект и раньше его не касались, то проигрываем звук и говорим, что мы его коснулись.
var dmxg = device_mouse_x_to_gui(0);
var dmyg = device_mouse_y_to_gui(0);
if instance_position(dmxg, dmyg, self) != noone
{
if !collided
{
audio_play_sound(sndButton, 10, false);
collided = true;
}
}
else
{
collided = false;
}
Вот так просто мы сделали следующее:

Соответственно, чтобы проиграть звук нам была нужна команда:
audio_play_sound(Звук, Приоритет,  Зациклить)

Приоритет - то, в каком порядке звук обрабатывается. Значения либо от 0 до 100, либо от 0 до 1. Чем больше число - тем выше приоритет.

Глобальная настройка звука.

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

Для этого нужно в событии step будет прописать следующий код:

var dmxg = device_mouse_x_to_gui(0)
var dmyg = device_mouse_y_to_gui(0)
var sound = audio_get_master_gain(0);
if instance_position(dmxg, dmyg, self) != noone
{
if mouse_check_button_pressed(mb_left)
or mouse_check_button(mb_right)
{
if dmxg > x + 260
{
if sound < 1
{
sound += 0.01;
}
}
else if dmxg < x + 60
{
if sound > 0
{
sound -= 0.01;
}
}
audio_master_gain(sound) // Устанавливаем глобальный звук.
}
}
audio_master_gain(x) принимает на вход значение от 0 до 1 (0% - 100%) и устанавливает громкость звуков.

Есть похожая функция, но которая называется audio_set_master_gain(ID слушателя, Громкость). Соответственно, она необходима нам в том случае, если у нас несколько "слушателей". Полезно при создании мультиплеера, либо для того, чтобы позволить нашему ИИ ориентироваться на звук.
Результат:

Заставим человечков издавать звук.

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

Здесь всё предельно просто.
Перейдём к oCharPlayer и создадим у него событие - alarm 0. Это событие - "будильник", или, если по нормальному, таймер, в котором будет работать код раз в определённое количество кадров. А теперь пошагово:

Create:

s_emit = audio_emitter_create(); // Создаём эмиттер.
audio_max_dist = CellWidth * 6; // Макс. расстояние, на котором будет слышно звук
audio_fall_at = CellWidth * 2; // Расстояние, на котором звук начнёт "падать"
audio_falloff_set_model(audio_falloff_linear_distance); // Указываем модель, по которой будет происходить затухание звука
audio_emitter_position(s_emit, x, y, 0); // Устанавливаем стартовую позицию эмиттера.
audio_emitter_falloff(s_emit, audio_fall_at, audio_max_dist, 1); // Устанавливаем затухание.
var len = audio_sound_length(sndButton); // Узнаём длину звука в секундах.
audio_play_sound_on(s_emit, sndButton, false, 1); // Говорим звуку играть.
alarm[0] = room_speed * len; // Запускаем бесконечный таймер, где будет проигрываться звук.
Alarm 0:
var len = audio_sound_length(sndButton);
audio_play_sound_on(s_emit, sndButton, false, 1);
alarm[0] = room_speed * len;
Step:
audio_emitter_position(s_emit, x, y, 0);
Весь код нужно добавлять в конец существующих событий соответственно.
Итак, что у нас получилось? Сейчас увидим.

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

Музыка.

По аналогии с oSSound, создадим oSMusic. Разница в том, что в oSMusic мы в Create сразу пропишем переменную music, равную 0.1. Она и будет отвечать у нас за громкость музыки. Соответственно, нужно sound заменить на music в коде и всё.

Создаём файл для музыки: sndMusic
Заходим в Tools -> Audio Groups. Создаём аудио группу под названием audio_music.
Жмакаем "add asset" и выбираем sndMusic.

Теперь перейдём к oGameManager.
В Create допишем:

if !audio_group_is_loaded(audio_music)
{
audio_group_load(audio_music);
}
audio_group_set_gain(audio_music, 0.1, 0)
Следом в Step:
if !audio_is_playing(sndMusic)
{
audio_play_sound(sndMusic, 10, true)
}
По сути, обозначенный выше код будет загружать нашу аудио группу музыки в память и начнёт её проигрывать.

Тест звука.

Немного бонусов.

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

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

Там так-то всё просто. Общая логика работы следующая:
Когда нажали (разово) левую кнопку мыши, мы запоминаем координаты клетки, на которую были наведены.
Далее, отдельно проверяем: если кнопка мыши зажата и координаты первой клетки запомнены, на постоянной основе обновляем координаты второй. Заодно создаём прямоугольник с заполнением или без.
Ну и при отпускании ЛКМ - проверяем, что первая координата у нас задана, обновляем картинку и обнуляем все использованные переменные.

Соответственно, как это выглядит при работе:

Ссылка на проект

Сайт, откуда можно брать бесплатные ассеты: звуки, музыку, спрайты и прочее.
Либо из маркета YoYo. На ваше усмотрение.

Серия гайдов подошла к концу. У нас есть заготовка, которую остаётся дорабатывать, рефакторить и всячески дополнять, чтобы создать полноценную игру. Впрочем, это не значит, что я перестану писать всякие статейки, нет.
Буду и дальше разбирать интересные темы, делиться скриптами и заодно рассказывать о ходе работы над проектом.
На повестке: моддинг без кода ( для игроков:D )

Спасибо всем за тёплые слова и поддержку.
До скорых встреч.

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

Student Simulator. Level 19: семь смертных грехов по психотипам Бартла, инвесторы против паблишеров⁠⁠

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


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


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


Психологическая монетизация и смертные грехи


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

Student Simulator. Level 19: семь смертных грехов по психотипам Бартла, инвесторы против паблишеров Обучение, Образование, Gamedev, Инди игра, Длиннопост

Если коротко и на примере Киану Ривза, суть в том, что Киллеры любят убивать (да ладно?!), демонстрировать силу, участвовать в PvP-боях, рейтингах, марафонах, соревнованиях и так далее.

Student Simulator. Level 19: семь смертных грехов по психотипам Бартла, инвесторы против паблишеров Обучение, Образование, Gamedev, Инди игра, Длиннопост

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

Student Simulator. Level 19: семь смертных грехов по психотипам Бартла, инвесторы против паблишеров Обучение, Образование, Gamedev, Инди игра, Длиннопост

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

Student Simulator. Level 19: семь смертных грехов по психотипам Бартла, инвесторы против паблишеров Обучение, Образование, Gamedev, Инди игра, Длиннопост

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

Student Simulator. Level 19: семь смертных грехов по психотипам Бартла, инвесторы против паблишеров Обучение, Образование, Gamedev, Инди игра, Длиннопост

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


А именно:


- Киллеры готовы платить за пропуск прокачки; мощное оружие; визуальное подтверждение силы.

- Карьеристы могут купить VIP (премиум), если он даст больше денег или ресурсов; мощную защиту, которая позволит не зависеть от других игроков; сундуки - если они дают ощущение власти и богатства.

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

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


Перейдём к самому интересному - обвинению четырёх психотипов игроков во всех смертных грехах (и это не фигура речи). Далее почти цитирую слайды с лекции.

Student Simulator. Level 19: семь смертных грехов по психотипам Бартла, инвесторы против паблишеров Обучение, Образование, Gamedev, Инди игра, Длиннопост

1. Гордыня - всегда быть первым, везде быть первым

- Киллеры – убить всех людей! Я - д`Артатьян, а вы…

- Карьеристы – взобраться на вершину рейтинга!

- Социальщики – быть самым известным и популярным!

- Исследователи – хочу все знать!


2. Зависть - сравнение себя с другими игроками

- Киллеры – хочу меч “Тысячи душ” и чтобы при покупке в чат написали, пусть завидуют!

- Карьеристы – хочу уникальную ачивку!

- Социальщики – хочу уникальный скин!

- Исследователи – поскромнее остальных, но завидуют знаниям.


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

- Киллеры – не хотят злиться, что не могут убить.

- Карьеристы – не хотят злиться, что их убивают.

- Социальщики – просто не хотят злиться.

- Исследователи – философы, ведь смерть - это тоже интересно.


4. Леность - я сюда отдыхать пришёл, а не работать!

- Киллеры – а можно не качаться и сразу все купить?

- Карьеристы – я пашу с утра до ночи, но готов купить, а за это ачивка будет?

- Социальщики – лень - это грех? Да ладно?

- Исследователи – очень интересно, а можно как-то все автоматизировать?


5. Алчность - больше золота! Но не для всех

- Киллеры – равнодушны, за золото же не убить! Или убить?

- Карьеристы – где деньги дают? А много?

- Социальщики – равнодушны, это же игра!

- Исследователи – а за золото можно открыть пасхалку?


6. Жадность (чревоугодие) - мне всего много и по скидке!

- Киллеры – Хочу убить всех!

- Карьеристы – Лишние вещи есть? Что знает нет?

- Социальщики – Вещи? Играть? А это не чат что ли?

- Исследователи – кому вещи отдать?


7. Похоть - универсальный грех для всех!

- Киллеры – секс? Ммм... а заверните, попробую!

- Карьеристы – мне без очереди и дайте два!

- Социальщики – секс в игре? хочешь займемся виртом?

- Исследователи – тут еще и секс есть? Так что же вы сразу об этом не сказали! /me “Ушел проверять секс”.


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


Инвесторы и паблишеры: где искать и с чем идти


В самом конце курса нам прочитали лекции об инвестициях в играх два брата: Кирилл и Роман Гурские, лидеры игрового направления GEM Capital.

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


А вот чем отличаются инвесторы от паблишеров.

Student Simulator. Level 19: семь смертных грехов по психотипам Бартла, инвесторы против паблишеров Обучение, Образование, Gamedev, Инди игра, Длиннопост

Паблишеры:

- инвестируют в продукт;

- в первую очередь смотрят на продукт;

- рассчитывают на окупаемость, хотят долю в выручке, контролируют производство игры;

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


Инвесторы:

- инвестируют в компанию;

- в первую очередь смотрят на людей;

- хотят владеть частью компании;

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


Где искать и тех, и других?

Student Simulator. Level 19: семь смертных грехов по психотипам Бартла, инвесторы против паблишеров Обучение, Образование, Gamedev, Инди игра, Длиннопост

Google советовать не буду, скажу, что в список полезных ресурсов входят App2top и Investgame. Кроме того, создатели игры Torn Away поделились табличкой с огромным списком издателей. Кстати, если будет время, почитайте их грустную, долгую и трогательную историю.

Также имеет смысл ходить на профильные конференции: DevGAMM и White Nights.


С чем идти к инвестору?

Student Simulator. Level 19: семь смертных грехов по психотипам Бартла, инвесторы против паблишеров Обучение, Образование, Gamedev, Инди игра, Длиннопост

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


Ошибки, которые наши преподаватели советуют избегать:


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


2. Пыль в глаза. Лучше честно рассказать о своих сильных и слабых сторонах.


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


4. Важные вопросы остались не заданными. Не надо стесняться, можно смело задавать интересующие и непонятные вопросы. А ещё можно попросить фидбек на инвестора у компаний, с которыми он уже работает.


5. Обида на отказ. С инвестором не стоит прекращать общаться после первого отказа, имеет смысл оставаться на связи, присылать квартальные обновления по прогрессу. И если прогресс оказался достойным - нет может вполне превратиться в да!


На этой оптимистичной ноте предлагаю закончить на сегодня:) Спасибо за то, что прочитали и до скорой встречи!


P. S. Дочитывать Student Simulator также можно в VK, твиттере и телеге.

P. P. S. На случай, если кого-то заинтересуют технические подробности про курс, на котором я учусь - традиционно оставляю ссылку на программу “Менеджмент игровых проектов”.

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

GameMaker Studio 2. Урок 7. Сохранения и их загрузка. Бонус в конце!⁠⁠

Привет!
Сегодня поговорим о реализации сохранений в GMS, их разницу со встроенными сохранениями и немного затронем тему работы с файлами.

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

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

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

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

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

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

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

Краткий план на сегодня. Интересна конкретная тема - поиск по странице вам в помощь.
- Теория. Как работают сохранения?
- Встроенный функционал GMS. Как и когда использовать? Плюсы и минусы.
- Реализация собственной системы сохранений.
- Бонус! 

Теория. Как работают сохранения?

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

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

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

Встроенный функционал GMS. Как и когда использовать? Плюсы и минусы.

В GMS'е имеется две функции с лаконичными названиями, которые вы можете использовать в своём проекте.
Сохранение игры:

game_save("Название_Файла.Формат") - где ".Формат" официальный мануал предлагает использовать .dat

Загрузка игры:

game_load("Название_Файла.Формат")

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

В событии нажатия F5:

if room == Room1

{

game_save("save.dat");

}

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

if file_exists("save.dat") and room == Room1
{
game_load("save.dat");
}

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

Теперь поговорим о тонкостях работы.

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

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

А ещё эта система сохранений в будущем, скорее всего, окончательно станет нерабочей.

Итак, из плюсов у нас только скорость установки. Минусы перечислены выше.
Как итог, скажу следующее: использовать данную систему следует на свой страх и риск. Если вы знаете, что ваша игра не нуждается в нормальных сохранениях и носит исключительно экспериментальный характер, то сойдёт и так. Если же вы игрой заняты серьёзно, то вам нужно будет создать свою систему сохранений.
Благо, что это просто! :)
И плюсов у неё много. Например, устойчивость к обновлениям как игры, так и движка ;)

Реализация собственной системы сохранений.

Для ЛЛ.
Общий алгоритм:
Сохранение -> Заносим всю информацию о каждом изменяемом объекте (в т.ч. о тайлах) комнаты в файл.
Загрузка:
- Если файл сохранения есть -> Удаляем все объекты из комнаты -> Читаем информацию из файла -> Создаём объекты заново и вносим в них правки.

Прежде, чем что-то писать, для начала изменим немного то, что уже есть.
Перейдём к объекту oGrid и создадим у него события: User Event 0, 1.

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

Это, как понятно из названия, пользовательское событие. Мы можем его вызывать только тогда, когда нам нужно, используя event_user(0) (где 0 - это номер события).

Итак.
В событие №0 мы переносим весь код из Create, кроме объявления cells_x/y, а также заполнения тайлмапа и создания grid_array.
В событие №1 перенесём код, который у нас создаёт карту.

var lay_id_blocks = layer_get_id("BlockLayer");
var map_id_blocks = layer_tilemap_get_id(lay_id_blocks);
var lay_id_shadows = layer_get_id("ShadowLayer");
var map_id_shadows = layer_tilemap_get_id(lay_id_shadows);
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 - это стоимость клетки. Ноль - значит непроходима.
tilemap_set(map_id_blocks, Tile.ground, _x, _y)
tilemap_set(map_id_shadows, 47, _x, _y)
}
}
Да, всё правильно: grid_array нам больше не нужен. Его наличие будет создавать больше неудобств, чем пользы. Соответственно, нужно будет изменить все места, где он ранее использовался, удалив его. У меня это уже проделано, поэтому рекомендую в последствии скачать проект, если возникнут трудности. Затронуты, в основном, скрипты поиска пути и код у игрока.

P.S. Хоть от grid_array мы и избавились из-за того, что он больше не нужен, нам нужны некоторые формулы.
Получение ID клетки:
id = cell_x + cell_y * max_x;
Соответственно, клетки по x и по y можно получить следующим образом:
cell_x = id mod max_x;
cell_y = id div max_x;

max_x - максимальное количество клеток по оси x, можно получить с помощью функции ds_grid_width(global.grid);


Теперь перейдём в oGameManager. У нас там было событие Room Start, где мы активировали все инстансы. Его нам нужно будет дополнить.

if room == Room1

{

instance_activate_all()

if !instance_exists(oGrid)

{

var inst = instance_create_layer(0, 0, "Instances", oGrid)

inst.cells_x = 65;

inst.cells_y = 55;

with (inst)

{

event_user(0);

event_user(1);

}

}

}

Что мы сделали: если в комнате нет сетки, то мы создаём oGrid, устанавливаем свои значения клеток по x, y и запускаем событие создания сетки. Это нас убережёт от постоянного пересоздания сетки при заходе в комнату.

Наконец, приступаем к созданию скриптов.

Всего их два. scrSave и scrSaveLoad - для сохранения и для загрузки сохранений соответственно.

scrSave - скрипт сохранения
Алгоритм:
Создаём пустой массив. Затем - проходимся по всем изменяемым объектам, сохраняя информацию из них в виде структур в массив.
Затем переводим получившийся массив структур в формат json. Чтобы минимизировать расходы памяти, мы заносим эту информацию в буффер, сохраняем буффер в файл и удаляем буффер.

Кода много, поэтому ссылкой:
https://pastebin.com/YBYejqe0

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

Оговорюсь, что в готовом скрипте у нас применяются настройки для oGameManager. Это временная мера, так как настройки игры должны лежать отдельно от файлов сохранения.

Ссылка:
https://pastebin.com/kCNT0zcW

Чтобы проверить работоспособность, на тех же F5 и F6 пропишите эти скрипты.

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

Бонус.

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

Это будет скрипт для прямого управления человечком.
Кратко, алгоритм выглядит так.
На стороне игрока:
- Выделяем человечка.
- Нажимая на любую свободную клетку (или ту, что занята, но рядом имеет свободные), ставим конечную точку пути.
- Рассчитываем путь и передаём его человечку.
- Используя массив с точками, которые нужно пройти, создаём встроенный путь (path) и наполняем его, затем - запускаем. Profit!

Для начала, у oPlayer, в Step'е, где мы ранее создавали пути, всё немного допишем и перепишем. Когда устанавливаем первую точку пути, если у нас в этой же клетке есть подконтрольный персонаж - запоминаем его ID.
Когда будем устанавливать вторую точку пути - если ID персонажа существует, то передаём ему получившийся массив с точками пути, "перевернув" его. Нужно это, чтобы ближайшая к персонажу на данный момент точка шла "нулевой".

Скрипт на переворачивание массива.
https://pastebin.com/uL5MhpmX

Новый код для создания пути:
https://pastebin.com/dWJVuFuB
П.С. Переменную char нужно предварительно создать в Create.

Create у oCharPlayer:

path = noone;
bpath = noone;
spd = 4;
User Event(0) у oCharPlayer:
if path_exists(bpath)
{
path_delete(bpath)
}
bpath = path_add();
var max_x = ds_grid_width(global.grid);
path_set_closed(bpath, false);
for (var i = 0; i < array_length(path); ++i)
{
var path_x = (path[i] mod max_x) * CellWidth + 16;
var path_y = (path[i] div max_x) * CellWidth + 16;
// Стоимость пути. Выше стоимость клетки в DS grid = ниже скорость.
var cost = 100 div ds_grid_get(global.grid, path[i] mod max_x, path[i] div max_x);
path_add_point(bpath, path_x, path_y, cost);
}
path_start(bpath, spd, path_action_stop, false)
Вкратце: если путь уже существует, то мы его удаляем. Затем создаём новый.
Делаем путь "открытым". Соль в чём: "закрытый" путь = зацикленный, то есть достигнув конечной точки пути, наш человечек затем вернётся в самое его начало. Соответственно, открытый путь эту проблему решает.
Затем мы добавляем точки в путь. Важно, передаём мы их в виде координат комнаты, а не сетки. Как бонус - код выше учитывает стоимость клетки, поэтому "замедление" человечка уже реализовано.
Хотите сделать возможность ещё и ускорения - меняйте базовое значение пустой клетки.

Draw у oCharPlayer:
draw_self();
if path_exists(bpath)
{
draw_path(bpath, x, y, false)
}
Задача: просто отрисовываем путь в технических целях. :)

Step у oCharPlayer:
if path_exists(bpath)
{
if x == path_get_x(bpath, 1) and y == path_get_y(bpath, 1)
{
path_delete(bpath);
}
}
Задача: удалить путь, когда он нам больше не нужен.

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

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

P.S. юниты не считаются за "препятствие", а потому спокойно могут проходить сквозь друг друга.

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

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

И да. В следующем гайде, скорее всего, без бонусов тоже не обойдётся. Я немного разленился, но постараюсь это исправить. :)

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

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

Показать полностью 1 1
[моё] Разработка Gamedev Программирование Инди Инди игра Gamemaker Studio 2 Образование Длиннопост Урок Игры Стратегия Обучение Видео Без звука
2
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
10
ValerieZen
ValerieZen
3 года назад
Лига Разработчиков Видеоигр

Student Simulator. Level 18: правила распития на выставках, идеальный игровой ролик и культура цензуры⁠⁠

Всем привет! Пролетели ещё две недели учёбы, я внимательно прослушала лекции и выбрала для вас самое крутое и полезное! Для тех, кто впервые наткнулся на этот дневник и увидел аж 18-ю часть, напоминаю - я учусь делать игры в Вышке (она же НИУ ВШЭ), а предыдущая часть здесь.


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


Заканчивая коктейльную аналогию, перехожу к теме одного из занятий по маркетингу, а именно…


Трезвый взгляд на игровые выставки


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

- White Nights

- DEVGAMM

- GamesCom

- PAX (EAST/WEST)

- China Joy

- Casual Connect


А вообще по миру проходит примерно 200 выставок в год - вряд ли инди-команда сможет успеть на все:) Но при этом каждая выставка - это возможность за пару дней получить максимум выгоды!

Student Simulator. Level 18: правила распития на выставках, идеальный игровой ролик и культура цензуры Обучение, Gamedev, Образование, Инди игра, Длиннопост

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


- зачем нам туда: кого хотим найти, что показать, с кем и зачем поговорить, что нового узнать?

- с чем нам туда идти: есть ли билд, у кого запросить стенд?


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

Student Simulator. Level 18: правила распития на выставках, идеальный игровой ролик и культура цензуры Обучение, Gamedev, Образование, Инди игра, Длиннопост

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


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


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


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

Не поверите, но правила корректного поведения на вечеринках тоже есть:)

Student Simulator. Level 18: правила распития на выставках, идеальный игровой ролик и культура цензуры Обучение, Gamedev, Образование, Инди игра, Длиннопост

Вот они:


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

2. Лучше держаться ближе к тем, с кем вы успели познакомиться (правило на радость интровертам) - ведь они могут познакомить вас с кем-то ещё.

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

4. С собой нужно всегда иметь определённую сумму денег.


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


Маркетинговые ассеты: starter pack


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


Основные ассеты такие:


1. Логотип. Это может быть оригинальное написание названия игры/студии; символ, вызывающий ассоциацию с продуктом или комбинация символа с текстом. Логотип должен быть простым, индивидуальным, привлекательным и охраноспособным.

Student Simulator. Level 18: правила распития на выставках, идеальный игровой ролик и культура цензуры Обучение, Gamedev, Образование, Инди игра, Длиннопост

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

Student Simulator. Level 18: правила распития на выставках, идеальный игровой ролик и культура цензуры Обучение, Gamedev, Образование, Инди игра, Длиннопост

Иконки очень важны для мобильных игр, поэтому их стоит тестировать, например, через Google Play Experiments, SplitMetrics или Storemaven. Хорошая иконка способна добавить 35% загрузок, а плохая - убить до 50%.


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

Student Simulator. Level 18: правила распития на выставках, идеальный игровой ролик и культура цензуры Обучение, Gamedev, Образование, Инди игра, Длиннопост

4. Скриншоты. Каждый скриншот должен отражать яркий, сочный момент геймплея, и на его понимание должно хватать 1-2 секунд. При снятии скриншотов нужно избегать артефактов, неудачных углов съёмки и слепых пятен. Хороший скриншот способен объяснить, что умеет ваша игра, чем выделяется из общей массы и демонстрирует игру в наиболее выгодном свете. Скриншотов должно быть… несколько:) Для магазина хватит четырёх, но пользователям столько будет маловато.

Student Simulator. Level 18: правила распития на выставках, идеальный игровой ролик и культура цензуры Обучение, Gamedev, Образование, Инди игра, Длиннопост

4. Ролик. Это визитная карточка проекта, по которому его будет узнавать пресса. По ролику 55% пользователей решают, скачивать ли приложение (по данным Google Analytics). Пока команда готовит ролик, она успевает кучу раз перессориться и поскандалить - это нервный процесс.


Хороший ролик - это когда:


- минимум 70% от хронометража - геймплей;

- минимум 2 фичи проекта видны в первые 10 секунд;

- удовольствие можно получить как со звуком, так и без;

- есть локализация;

- есть призыв поиграть в финале.


Чтобы всё это получилось, нужно написать ТЗ на ролик от маркетинга (описать, какие фичи показываем и какие чувства должен вызывать ролик), провести мозговой штурм, расписать подробное ТЗ на производство ролика, сделать раскадровку, драфт, показать маркетологам и внести корректировки, докрутить финальную версию и сделать локализацию.


5. Маркетинговые тексты. Основные правила: суть игры должна быть понятна по первым двум строчкам, предложения должны быть короткими и ясными при беглом просмотре, юмор всегда уместен (но должен вписываться в тематику игры), локализация обязательна.


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


Культурализация как понимание игры на особом уровне


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


В культурализацию может входить:

- адаптация шуток;

- анализ соответствия требованиям законодательства;

- анализ отсутствия грубых нарушений этикета;

- адаптация игры под возрастные требования;

- перерисовка изображений в соответствии с культурными особенностями страны;

- изменение жестов персонажей.


Здесь можно выделить некие “культурные переменные”, на которые стоит обратить особое внимание:


1. Религия. Культуры, выстроенные на религии или традициях, часто бывают менее гибкими, чем светские культуры.

Student Simulator. Level 18: правила распития на выставках, идеальный игровой ролик и культура цензуры Обучение, Gamedev, Образование, Инди игра, Длиннопост

Так, файтинг Kakuto Chojin сняли с продажи из-за того, что в её озвучке были строки из Корана. А разработчикам EA Sports UFC 2 пришлось извиняться и менять анимацию, потому что в их игре при выходе в октагон Хабиб Нурмагомедов осенял себя крестным знамением - и это заметили фанаты бойца.


2. История. Многие страны старательно защищают свои интерпретации исторических событий.

Student Simulator. Level 18: правила распития на выставках, идеальный игровой ролик и культура цензуры Обучение, Gamedev, Образование, Инди игра, Длиннопост

Например, в Age of Empires японская армия Ямато вторгается на Корейский полуостров и захватывает империю Чосон. Но в версии для Южной Кореи империя Чосон захватывает Японию.


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

Student Simulator. Level 18: правила распития на выставках, идеальный игровой ролик и культура цензуры Обучение, Gamedev, Образование, Инди игра, Длиннопост

Иногда разработчикам приходится цензурировать игру, которая кажется людям слишком жестокой, как в случае с Manhunt 2, или перекрашивать персонажа, как в Chuchel или менять анимации героям, как сделали с Трейсер в Overwatch.


4. Геополитика. Самое явное влияние - это спорные территории.

Student Simulator. Level 18: правила распития на выставках, идеальный игровой ролик и культура цензуры Обучение, Gamedev, Образование, Инди игра, Длиннопост

К примеру, Ghost Recon 2 запретили в Южной Корее из-за наличия в ней воинственного северокорейского генерала и боевых действий с Северной Кореей.


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


Для этого нужно:


- познакомиться с рынком: получить базовое представление о чувствительном контенте, задавать вопросы, назначить ответственных;

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

- оценивать критичность: классифицировать найденные проблемы, документировать важные решения;

- вносить только те изменения, без которых точно не обойтись.


На этой назидательной ноте и закончим:) Спасибо за то, что дочитали и до скорой встречи!

P. S. Читать Student Simulator также можно в VK, твиттере и телеге.

P. P. S. На случай, если кого-то заинтересуют технические подробности про курс, на котором я учусь - традиционно оставляю ссылку на программу “Менеджмент игровых проектов”.

Показать полностью 11
[моё] Обучение Gamedev Образование Инди игра Длиннопост
3
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
Посты не найдены
О нас
О Пикабу Контакты Реклама Сообщить об ошибке Сообщить о нарушении законодательства Отзывы и предложения Новости Пикабу Мобильное приложение RSS
Информация
Помощь Кодекс Пикабу Команда Пикабу Конфиденциальность Правила соцсети О рекомендациях О компании
Наши проекты
Блоги Работа Промокоды Игры Курсы
Партнёры
Промокоды Биг Гик Промокоды Lamoda Промокоды Мвидео Промокоды Яндекс Директ Промокоды Отелло Промокоды Aroma Butik Промокоды Яндекс Путешествия Постила Футбол сегодня
На информационном ресурсе Pikabu.ru применяются рекомендательные технологии