Программная начинка в мою игру про доисторического охотника
Делаю игру на конкурс - пошаговый выживач в каменном веке, но с пришельцами. Я не делал раньше таких игр, нет опыта. Я его приобретаю и делюсь с вами. Этот пост будет особенно интересен тем, кто использует или присматривается к движку Game Maker. Я расскажу вам, как использовал структуры данных.
Кратко о концепте:
- Игрок - представитель доисторического племени, умеет ходить, сражаться, и немного крафтить. Его цель - вернуть похищенное пришельцами племя... или найти женщину, чтобы создать новое.
- Визуально игра выполнена в виде наскального рисунка
- Игровой мир - большое поле, состоящее из квадратных ячеек. Игрок и противники по ним перемещаются. Противники могут быть больше 1 клетки.
- У игрока есть инвентарь на 8 предметов - именно столько он может унести. Среди предметов есть оружие, еда и материалы.
- Оружие игрок создаёт из материалов. Оно ломается со временем.
- Еду игрок находит или создаёт из материалов. Еда работает как баф (здоровья и регенерации) на очень длительное время. В Valheim так сделано.
- Игровой процесс пошаговый. Ход игрока. После - ход игрового мира. Ход занимают не только движение и атака, но и крафты, использование предметов. И даже взять/сбросить один предмет - это один ход. Нужно будет чётко планировать ресурсы.
Игрок бродит по игровому миру. Находит новые материалы. Создаёт из них более сильное оружие и еду. Это позволяет ему исследовать игровой мир дальше, идти в более опасные зоны.
Пошаговость. В этом у меня уже есть опыт. Я делал сокобан Sig.NULL. Помню, что это далось мне тяжело. В этот раз хочу улучшить свой навык. Выделю всю логику пошаговости в отдельный объект-контроллер.
Контроллер в событии Step (тот же update) ждёт ввод игрока. Как только ввод произошёл - делает ход игры. Это отдельная функция, в которой будет много рассчётов. Какой враг где сгенерировался, куда пошёл, кого атаковал. Здесь же будут действовать бафы на игрока. И эксклюзивные условия на некоторые предметы.
Игровое поле. Какого оно будет размера? Не 10*10, и даже не 30*30. Минимум 100*100, а то и больше. Если в каждой ячейке этого поля будет объект, Game Maker не вывезет поддержку 10 000 экземпляров.
Тогда остаются сетки. В Game Maker есть структуры данных: стеки, списки, очереди, словари, сетки. Для них нужно отдельно выделять память и управлять ими отдельными функциями. Сетки - это большой двумерный массив. В каждой ячейке может быть цифра, строка, структура, и т.д.
В сетке удобно хранить геодату игрового мира. Где расположены горы, леса, моря. Всё, что обычно статично. Противники буду спавниться вокруг игрока. Они будут объектами. И лут на игровом поле тоже будет сделан объектами.
Игровое поле будет рисоваться только в зоне видимости. Нужно учесть, что для каждого элемента геодаты может быть несколько вариаций изображений. Например, дерево или камень имеют по пять вариаций.
Неужели мне ещё нужно хранить номер изображения для каждой ячейки? К счастью, нет.
Когда я занимался шейдерами, подсмотрел там функцию псевдо-рандома. В языке GLSL нету рандома в принципе, поэтому умельцы научились генерировать число по заданному значению. Обычно этим значением является время, которое передают в шейдер. Но мне важно другое - это функция, которая на одно значение всегда выдаёт однозначный результат.
При помощи этой функции я могу динамически рассчитать нужный номер картинки по координатам.
Игровые данные. Самое важное - это инвентарь. Тут всё просто - массив на 8 ячеек. Но есть нюанс: мне нужно отслеживать каждый игровой предмет. Например, у игрока два копья. Для каждого из них своя шкала прочности. Игрок может скинуть копьё, а потом подобрать. Это должно быть то же самое копьё с той же самой прочностью.
Я использовал для этого словари. Это ещё одна структура данных в Game Maker. Проще говоря, это ассоциативный массив. Доступ к данным я получаю по строковому ключу.
Для предметов у меня два таких словаря. Первый (ITEMSLIB) содержит в себе шаблоны для каждого из предметов. Ключи - названия этих предметов. Второй словарь (ITEMS) содержит информацию предметах, которые созданы в мире игры. При создании предмета в мире игры, для него копируется шаблон и создаётся уникальное название по алгоритму хэша md5. Там получается билеберда, но мне не важно - этого никто не увидит. Дальше в игре я использую это уникальное название в инвентаре, для лута, при крафте и т.д. Так я легко вытащу нужные данные из записи словаря.
Другие игровые данные очень простые: здоровье игрока, текущая еда, текущее оружие. Всё это - обычные глобальные переменные.
Рецепты. Сложнее с ними. Рецепт - это набор предметов, которые производят новый предмет. В моих рецептах могут участвовать инструменты. Например, нож + дерево = копьё. Дерево исчезает, а нож - нет. Обычно нет. Если у ножа кончилась прочность - он тоже исчезает. Я снова использовал структуру словарей, чтобы хранить в них данные о рецепте в JSON формате.
Каждый рецепт производит один предмет. Это облегчает задачу - мне не нужно беспокоиться о переполнении инвентаря. Как минимум один предмет из рецепта исчезает - на его место встанет созданный предмет.
Мне нужно проверять, что игрок их может сделать любой рецепт прямо сейчас. Здесь важна производительность такой проверки. Идеальный вариант: для каждого ингредиента проверить его наличие в инвентаре. Тогда можно закончить проверку уже на первом ингредиенте. Но есть нюанс! Если в рецепте два одинаковых предмета, а в инвентаре всего один такой? Нужны дополнительные проверки.
Я пошёл другим путём. Создал отдельный список ингредиентов. Прохожусь по инвентарю, если встречаю ингредиент - выкидываю его из списка. Если список ингредиентов после этого пустой - игрок может сделать этот рецепт. Если в рецепте два одинаковых предметов - алгоритм их выкинет только если встретит в инвентаре такой предмет дважды.
На большой выборке рецептов это может быть не очень эффективным и подтормаживать. В этом случае я буду держать список активных рецептов и обновлять его при каждом изменении инвентаря. Возможно, удастся считать изменения только для добавленных/выброшенных предметов.
Мораль?
В Game Maker достаточно технических инструментов для реализации простых и непростых концепций. Есть структуры данных - списки, стеки и словари. Это я ещё не говорю про наследование и полиморфизм - это тоже всё есть.
Если вы уже пользуетесь Game Maker - присмотритесь к функционалу структур данных (data structures, функции начинаются на ds_)..
Понравился проект? Держите несколько ссылок:
- Веду девлог этого и других проектов в своей группе ВК.
- Обсуждаю свои и чужие проекты на моём дискорд-сервере.
- Жду вашей поддержки на своей бусти-страничке.
Спасибо, что прочитали!