Декомпозирование кода на составные элементы в геймдеве - примеры и обсуждение
Здравствуйте! Недавно я опять начал пытаться запилить свою первую игрушку, и мне очень хотелось бы почитать от сидящих на этом сайте людей, как именно они подходили к разбиению своей игры (игр) на составные части и осуществляли взаимодействие между ними.
На какие именно основные составные части происходило разбиение всей игровой логики?
Как организовывался переход между основным игровым процессом и меню?
Как реализовывалась пауза? Инвентарь?
Какую общую структуру имел код, относящийся к поведению противников? К взаимодействию игрока с n различных предметов и сущностей на игровом уровне?
Был бы очень рад, если бы вы поделились своим опытом ♡
- 14 ноября 2020, 19:07
- 04
Но все-таки.
Весь текущий уровень состоит из объектов. Каждый объект принадлежит какому-то классу. Вся логика (создание, поведение, обновление, отрисовка) объекта находится в его классе.
След-но, каждому объекту / виду противника - свой класс.
Меню может существовать как отдельный объект, так и часть уровня.
Ставишь игру на паузу, если игра на паузе - показываешь меню паузы.
Главное меню делаешь отдельным уровнем.
Заводишь глобальную булевую переменную is_pause. По нажатию ESC переключаешь ее значение.
Потом в основном цикле игры если is_pause, то не вызываешь обновление игровой логики игры (update) и рисуешь меню паузы.
Если инвентарь может быть только у персонажа игрока - встраиваешь его функционал в класс игрока.
Если инвентарь может быть у любого персонажа, встраиваешь его в класс персонажа.
Инвентарь также можно использовать как содержимое сундуков, шкафов и игре. Тогда инвентарь имеет смысл сделать отдельным классом. Тогда каждый персонаж / сундук еще будет создавать свой объект "содержимое".
Устройство инвентаря - смотря какой инвентарь тебе нужен.
Если это фиксированное кол-во предметов - делаешь массив предметов.
Если кол-во предметов неограничено и их можно перемещать - список предметов.
Если это клеточный инвентарь - двумерный массив.
Персонаж при взаимодействии должен вызывать какую-то одну функцию объекта (interact / pickup / drop), код которой можно переопределить, на случай если тебе понадобится назначить какое-то событие на это действие или прописать уникальную реакцию.
То же - про ИИ. В идеале у персонажа игрока и ИИ должен быть один и тот же класс, с одинаковыми функциями (move, punch, rotate). ИИ только должен вместо управления игрока, самостоятельно решая какие функции когда вызывать.
Создавая каждому врагу отдельный класс, можно прийти к неприятной ситуации, когда невозможно унаследоваться от нескольких классов. Также классы могут сильно разбухать.
Если в игре много разных врагов со сложным поведением, то стоит посмотреть на компонентную систему. Это такой паттерн программирования, но в Unity он реализован из коробки. Каждый компонент отвечает за конкретную небольшую задачу (например, движение по земле) или атака. Объекты собираются из компонент как конструктор.
Если игра небольшая и сущностей немного, ECS может оказаться оверкиллом, маленькую традиционную ОО-иерархию наваять быстрее и проще. Но опять же зависит от движка, если ECS хорошо поддерживается из коробки, почему бы и нет.
В Godot вот комбинированный подход: игровые сущности — составные деревья из вложенных компонентов (и сами являются узлами общего дерева), а компоненты — классические объекты, которые наследуются ради расширения и переиспользования той или иной функциональности, и в первую очередь определяют обработчики событий.
А вот в Game Maker ECS прикручивать накладно, лучше сделать обычную иерархию, как-нибудь обойдя проблемные моменты вроде множественного наследования. Даже с костылями это выйдет сильно практичнее.
На самом деле я говорил не про ECS, а про то что ты описал в Godot =)
Да нету таких проблемных моментов. Есть люди которые хреново составляют иерархии наследований, а если прям супер-срочно нужно решить задачу уже на хреновой иерархии - это делается скриптами, благо модификаторов доступа никаких нет.
Далеко не всегда можно натянуть адекватную древовидную иерархию на произвольный набор сущностей (что из реального мира, что из упрощённого игрового), ведь она — лишь упрощённая модель. GM-скрипты тут непонятно чем помогут, если объект должен именно реализовывать отношение is-a для нескольких предков.
Это правда, но нет необходимости реализовывать сущности именно как иерархию. И не всё то иерархия, что не ECS. Мы так и не пришли к выводу, что из себя такое есть object_event_perform() в ГМе, с точки зрения архитектуры проекта. Но это только например, и только присказка, а сказка впереди:
В ГМС2.3 появились Tags, и поэтому можно маркировать сколько угодно объектов какими угодно тэгами. К сожалению онлайн справку по 2.3 не размещают как по 1 и 2, поэтому цитирую из встроенной вручную:
ECS - это один из паттернов DOD (низкоуровневый). Компоненты в классическом Юнити - это не ECS. Такие компоненты можно в любом языке написать самому.
Пользуясь случаем, хочу сказать, что есть еще литература и даже на русском (но не утверждаю, что ее полезно и обязательно читать, особенно новичкам)
Шаблоны игрового программирования
Спасибо!
Какая-то философия вместо конкретики. Не советую.
Я тоже в этом месте подофигел от бесполезной, трудновоспринимаемой воды, но самое главное из раздела вроде бы выписал:
Абстрагирование функции таким образом, чтобы она могла применяться к группе сущностей.
С опциональным применением: если в нашу функцию стекаются все инпуты, то мы можем записать в ней порядок совершённых действий.
Конечно, если я правильно воспринял материал.
А то знаете ведь, как говорят: "для человека, у которого из инструментов есть только молоток, все вещи начинают удивительным образом напоминать гвозди" (⌒_⌒;)ゝ
ну нет, если по-нормальному делать, а не на джем, то так поступать не надо. это убиться можно будет, если проверять каждую булевую переменную. А также потом и под инвенатрь и прочее что ли? Лучше на машине состояний всё сделать хотя бы.
Глобальную. А потом
В обновлении:
В рисовании:
и все.
а потом ещё если инвентарь, то апдейт_инвентарь, если настройки, то апдейт_настройки, если "вы точно хотите выйти?" то, вы_точно_хотите_выйти_апдейт и так далее
Так пихаешь все эти update внутрь функции update_pause.
А отрисовку инвентаря, настроек и вы точно хотите выйти - в draw_pause.
Если тебе не надо отрисовывать уровень во время паузы, то проще переключаться на отдельный уровень/состояние и всю логику/рисование, нужную во время паузы, делать там (при условии что состояние пред. уровня не будет теряться).
Мне кажется, что это, мягко говоря, не очень удобно.
Свои поделки в начале я примерно как-то так и делал, потому что не было времени думать, но быстро стало понятно, что это всё - путь в никуда.
Твой вариант?
Он же «конечный автомат». Завести
enum { MainMenu, InGame, Pause, Inventory } state;
, в основном циклеswitch (state)
с ветками-обработчиками, в разных обработчиках в зависимости от событийstate
может переключаться на другое.Но да, от движка сильно зависит, как это грамотнее реализовать.
А я разве не то же самое написал?
Мой вариант в заведении состояний для каждой сцены и задания условий для перехода. Тогда не надо городить if-ы, можно просто обновлять текущее состояние
Для каждого уровня делать свою паузу? Не проще одну для всех сделать?
это можно реализовать в виде наследования или примеси
А у меня немного другая концепция, поскольку паузу вызывает объект меню, то я решил вместо
булевой переменной туда вставлять объектную переменную.
То есть чтобы нажать на паузу нужно создать объект меню, например (псевдокодом):
Если это Unity, то там проще сделать Time.timescale = 0.
Зачем создавать/удалять какие-то объекты, если можно отключить MenuObject?
Удалять, чтобы вместо одного меню другое создать, помимо меню паузы есть ещё такие меню:
1) Меню Инвентаря (если есть конечно, у меня обычно меню инвентаря прямо в паузе, так что это одно меню)
2) Статистика персонажа (если RPG)
3) "Exit?" Yes/No (всякие разные диалоговые окна подтверждения при удалении или выходе)
4) Диалоговое меню с персонажами (тоже может ставить на паузу, но тут зависит от игры)
5) Матч3 боёвка (в одной моей игре планирую сделать, то что игра будет ставить на паузу при встречи с противником, и там будет в это время боёвка Матч3, это и "меню" и боёвка одновременно)
6) Меню кастомизации персонажей (одежда, оружие, причёска, покраска авто и другое)
7) Меню покупок и продажи
8) Меню крафта и рецептов
9) Меню карты и например выбора телепортации в другую точку мира
10) Результат гонки, мини-игры, боя или спортивного матча
11) Меню сохранения и загрузки
12) Меню настроек опций
может ещё какие меню забыл, поэтому я подумал, что лучше сделать универсально и все меню
присобачить к переменной MenuObject (и скрывать их всех не получится), хотя точнее лучше сделать список List или другой динамический список, и если список пуст, то игра не ставится на паузу, и игра обновляется в штатном режиме
Все что ты описал можно просто отключить/включить в нужный момент, не вызывая создание нового объекта каждый раз.
У тебя так мало оперативы что ли? То, что может вызываться неск. раз проще вкл/выкл, а то что появится один раз (например результат гонки) можно и удалять.
Мне кажется немного странно, если все меню будут висеть на уровне, хоть и в неактивном виде,
но в целом часто видел подобные решения, когда прямо на уровне ставят объект меню и потом прячут его, но мне почему-то в таком виде работать некомфортно, может просто из-за привычки, хз.
В требовательных играх частенько предзагружают всякие игровые объекты и прячут их в недоступных местах, чтобы когда они потребуются, игра не тормозила из-за ВНЕЗАПНОГО выделения памяти. Помнится, в Serious Sam, вылетев за границу карты, можно было встретить целые шеренги пассивных монстров :3
Для небольшой индюшки на современных компьютерах это уже неактуально, наверное.
согласен)
Я не сделал ни одной нормальной и полноценной игры на Юнити, но мне всегда казалась мысль делать паузу посредством "остановки таймера" каким-то варварством что ли.
В ГМС вот я делаю "скриншот"* экрана, отключаю обработку и рисование объектов, но оставляю включённым "управляющего". То есть зачем мне рисовать всю сложную сцену и прочие логики гонять, если они не нужны? + можно накинуть на "скриншот" какой-нибудь эффект\шейдер единожды, а опять же не тратить ресурсы.
* не знаю насколько это корректно называть скриншотом. Я беру то, что на экране (но исключается UI), сохраняю в сёрфейс\текстуру и затем вывожу это в паузе. В Юнити так можно?
Это и есть нормальный и логичный способ - остановка обновления игры
А вот это и есть варварство, когда за неимением в ГМС нормальной паузы разрабам приходится извращаться.
Так это не остановка обновления игры. Условно это каждый такт повторение position += velocity * 0.0f, грубо говоря.
Это как раз не изврат, я считаю. ГМС можно паузу и через сцену отдельную сделать - текущая просто в памяти остаётся (и не обновляется кстати)
Это только твоё мнение.
И тем не менее пауза в гамаке от этого лучше не станет.
То ли дело читать километровые статьи о том, что останавливается при TimeScale = 0, а что нет.
https://gamedevbeginner.com/the-right-way-to-pause-the-game-in-unity/
EDIT: Лол, Лентинант скинул её 16 часов назад, я просто дотуда не дочитал ещё.
Кситилон - самый не быстрый ковбой на диком западе.
Потому что я не останавливаю всего лишь время. Если мне нужна пауза, я останавливаю ещё и пространство.
А я себя только приучаю к такой мысли что надо делать так на GMS, но так пока не сделал)
ибо непривычная концепция и решил в первой игре себе на GMS такое не делать,
хотя понимаю что скриншоты снижают уровень нагрузки.
можно, но сложно (для меня, но нормальным программистам это должно быть легко), ибо нужно этот сурфейс создать вначале, а потом разместить, это где-то около часа или два работы, а ещё до этого гуглить надо, ибо я никогда сам не создавал такой сурфейс, тестируя правильно ли создал и правильно ли всё расположено),
ещё не забыть отключить все объекты которые нужно отключить, и не отключать те объекты что не нужно, очень много лишней возни, когда можно просто взять и ввести timeScale = 0; *yak*
PS я ещё забыл, что нужно обязательно запоминать список отключённых объектов, которые мы получили при нажатии на паузу, потому-что на уровне могут быть объекты отключённые по другим причинам, и их при выходе из паузы не стоит включать... В общем это реально очень сложный и комплексный скрипт.
PPS хотя конечно есть один простой вариант, забив даже на скриншот в юнити:
1) отключить игровую камеру
2) меню должно быть 100% заполненно картинками или рамками, чтобы не было видно отключение игровой камеры, а HUD камера норм
Да не, богом чувствуешь себя))) Остановил время и довольный!
Есть довольно интересная статья по этому поводу. Оказывается, timeScale не так уж и много чего останавливает, и это можно вполне гибко настраивать.
Там еще есть unscaledTime, который можно использовать чтобы продолжать обновлять часть игры (например, анимацию в паузе).
И нажатия/отпускания клавиш продолжат обрабатываться, если, например, положение персонажа игрока напрямую зависит от осей перемещения в Input Manager.
Ну в статье это всё есть.
Почитал. Про unscaledTime слышал, да. Но мне всё равно кажется крайне ненадёжной штука с паузой через timeScale.
В Юнити нет значит какой-то функции в одну строку, чтобы получить то, что нарисовано на экране? (вернее даже в буфере, до рисования интерфейса)
С гуями:
var texture = ScreenCapture.CaptureScreenshotAsTexture();
Без интерфеса можно через рендер текстуру картинку захватить, так чуть больше строчек кода получится конечно. Или через тот же ScreenCapture и вырубать интерфейс за кадр до снятия скриншота.
Спасибо! Может пригодится.
Что? Вы теперь и в других движках будете скриншотить для паузы как в гамаке?
Вообще, надо заметить, что скриншот снимать не обязательно. Можно просто заморозить текущее изображение в сёрфейс. Но так-то метод решения задачи - это вкусовщина, главное - её решать.
Это то же самое, это и есть "снять скриншот". Только на диск скриншот сохранять не обязательно.
Главное в разработке игры - начинать с разработки паузы. Ведь все знают, что игра со слишком сложно реализованной паузой - плохая игра.
Патамушта Пауза должна быть в любой игре дольше 10 минут! (если игра не мультиплеерная)
После того как быстро пауза делается в love2d (и без побочных эффектов), где у тебя полный доступ ко всему коду, делать паузу в GM, где большая часть кода закопана внутри движка и доступ есть не ко всему, выбирать 1 из 5 костыльных способов (каждый со своими побочками) мне как-то стремно.
Сделали хотя бы в GM возможность переписать основной цикл игры, чтобы вставить паузу туда.
Тогда паузу можно было сделать как в love2d.
Увы, во всех остальных движках стрёмно делать уже саму игру. Приходится выбирать.
А что такое «сама игра»? Это такая волшебная сущность, которая работает сама собой без кода?
Игра - это ощущение, а не результат выполнения программы. Мультимедийное приложение - всего лишь инициатор правильных реакций в мозге потребителя - игрока.
Блин, у меня столько шедевров в голове простаивает.
Я их прям так ощущаю, это убийцы сталкера прям! Самые гениальные игры на свете, жаль что только я смогу в них поиграть в своей голове.
Недолго ж тебя хватило.
Да потому что почитай что вы тут строчите. Тут только совсем уходить.
Игры без кода не существует. Смирись.
*Компьютерной
Без программирования. Это разное.
Кода не бывает без программирования.
Передача сообщения кодом Морзе - это программирование?
Это передача информации, результат выполнения этого кода.
А вот формирование кода постукиванием является программированием.
Программированием чего?
Вот я написал: print("Hello world!"); и могу скомпилировать этот код, а потом его сколько угодно раз запускать, и компьютер по готовой программе будет воспроизводить это сообщение.
Передача же сообщения - это просто передача сообщения. Код - это просто выбор способа записи.
Где-то на этом месте у меня окончательно израсходовался фермент, переваривающий демагогию. Прости, но тоже придётся расчехлять игнор-лист =\
А, программирование с кодированием перепутал.
Это ты меня хочешь запутать или я тебя?
Что-то я уже запутался.
И то и другое. Передача сообщения кодом Морзе это не программирование. Стало быть, код возможен без программирования.
*компьютерной
правила настольной игры - это ее геймплей
(c) Омар Хайям
А можно сделать как в дарк соулс - без паузы вообще, пусть игрок познает хардкор, если ему прижмет во время боссфайта.
Все равно игру можно свернуть и в диспетчере задач поставить процесс на паузу.
Можно даже скрипт написать или мини-программу чтобы автоматизировать и повесить это действие на одну кнопку.
А че, в ДС реально нельзя поставить игру на паузу во время боя с боссом?
Играл в первую часть, там вроде можно было.
Вроде нельзя было, хотя точно не помню...
В туалет всегда можно было отойти после того как проиграем боссу, у костра :D
"в диспетчере задач поставить процесс на паузу"
это как делается через диспетчер?
Не в системном, конечно, у него мало возможностей.
Process Explorer, контекстное меню, Suspend.
Там нет паузы в принципе. В игре даже можно ходить пока у тебя системное меню открыто.
Похоже, некоторых юзеров смутило слово "скриншот" в моём комментарии. Для ясности скажу, что я НЕ сохраняю изображение экрана в файл и потом загружаю его, а ДЕЛАЮ копирование содержимого экрана в виртуальную область отрисовки (в ГМС сёрфейс, в Юнити вероятно просто текстура).
Мне пока лень такое делать, но может потом пригодится)
Ведь движок я уже сделал и в ближайшее время ещё раз не хочу переписывать,
за последние года я много раз переделывал, в конце концов перейдя с JS на C#,
пока больше не хочу, надоело программировать, хочу уже игры делать,
а не возиться с такими мелочами как оптимизация паузы.
Это ты конечно напрасно. Я надеюсь, в ближайшие 12 минут и 34 секунды на тебя снизойдёт озарение, и ты наконец-то ознакомишься с вот этой, чрезвычайно для геймдевелопера полезной, книгой. И начнёшь, как и положено всем достойным разработчикам игр, писать свой движок вместо чтоб писать суб-движок движка.
Хорошая книженция, читал еще в институте.
Советую изучить ассемблер всем кто хочет получить массу удовольствия и потратить еще больше времени.
Но во времена, когда 3D-ускорителей не было, а 3D-графику хоцеца - у разрабов другого выбора не было.
ну тогда я игру ещё лет 10 не сделаю)
Вообще по хорошему я устал от программирования, точнее я люблю поверхностное программирование, когда уже готовы все инструменты, как в конструкторе, и немного там уровни скриптить, диалоги, работу переменных, баланс статов персонажей, но сами инструменты/вспомогательные функции программировать, от этого уже голова едет кругом, надоело, а мог бы игры делать.
А за книжку спасибо, гляну)
Все, прощай товарищ.
Ксит, что ты наделал?
Сначала подбиваешь людей попробовать GM, а потом "на вот эту книжку посмотри".
Потому что мне уже всё равно. Это была очевидная шутка, и её тоже никто не понял. О чём вообще говорить можно всерьёз? XD
лол, у меня сейчас голова не соображает, весь день кодил)
но посмотрев только название, всё встало на свои места)
Ну да, подбиваешь на GM ты всех тоже в шутку?
А потом они берут и игры выпускают.
А ты такой - ой, извините, я пошутил.
Примерно так и получается. Приходится потом делать лицо кирпичом, одевать пиджак, типа я издатель, консоли какие-то. А то прям неловко, шутка-то далеко зашла.
PS книга огонь, тогда мне ещё сверху надо +5 лет добавить на ассемблер, если захочу сделать игру по книге, глядишь и через 15 лет игру сделаю)
Не, не надо делать игры. Что игры, это пустое. Лучше напиши свою книгу о том как ты делал игру.
"Как за 10 лет не сделать ни одной игры"
А то сделать каждый дурак может. А ты попробуй не сделать.
А я бы действительно почитал. Я не читал всякие там "Кровь, Пот и Пиксели", а вот Алекса книгу почитал бы - это куда интересней. Реальные люди, реальные взгляды. Вряд ли конечно такая книга будет, скорее серия постов в блогах, большая часть из которых это поиски способа что-то сделать для игры. Но кто знает.
моя бы книга называлась "Как начать разработку 10 игр за 10 лет и ни одной не закончить"
Ну, я начал 30 за 15! Но всё ещё впереди.
Также список меню удобен тем, что самое верхнее меню всегда у меня является активным.
Но в GMS например я не научился работать со списками, и потому там у меня
всего одна переменная меню вместо списка объектов меню,
и также есть отдельная переменная меню Yes_No, для проверки Yes/No, например при выходе из игры.
Но ведь пауза может быть глобальным состоянием самой игры в принципе. Ну то есть есть 2 состояния игры (условно) - пауза и геймплей (главное меню игры - это "геймплей").
Когда выводим меню паузы (может быть так же на стейт машине, как ты и говоришь), то мы ставим игру на глобальную_паузу. С инвентарём аналогично. Короче я хочу сказать, что такая пауза просто отдельная штука, которая вызывается уже в стейтах, а не реализуется в стейтах.
Такое тоже может быть! Но я прихожу к мысли, что и её тоже лучше как состояние делать! Хотя это зависит от игры, наверн.