Сохранение в игре.

Темы, связанные с проектированием и программированием roguelike-игр

Модераторы: Sanja, Максим Кич

Аватара пользователя
Cfyz
Сообщения: 776
Зарегистрирован: 30 ноя 2006, 10:03
Откуда: Санкт-Петербург
Контактная информация:

Re: Сохранение в игре.

Сообщение Cfyz » 09 фев 2017, 14:17

Jesus05 писал(а):Когда мне нужно вывести какие-то данные в поток <...> я пишу в своей части кода. <...> и никоим образом не считаю, что это я "приклеил" костыль к чужому классу.
Это я и назвал игрой терминами. Потому что это спорный вопрос, является ли функция выше частью класса или нет. Ведь она расширяет методы работы с классом и как бы модифицирует его интерфейс: объекты класса приобретают возможность вывода в поток и теперь могут быть переданы в функции, ожидающие объекты, способные выводиться в поток. С точки зрения синтаксиса С++ это отдельная функция. А вот с точки зрения внешней работы с классом -- уже не так очевидно.

В C# например, можно пойти еще дальше:

Код: Выделить всё

namespace Extensions
{
    public static class FooExtensions
    {
        public static Serializer Write(this Foo v, Serializer s) { ... }
    }
}
И потом

Код: Выделить всё

foo.Write(s);
Является ли здесь Write частью класса? И т. д. Граница не сказать, что строгая.

Jesus05 писал(а):Я все больше склоняюсь к мысли что данные класса и поведение класса это 2 разные сущности и жить они должны отдельно.
Не, ну это наверное уже перебор >_<. Как вот данные и поведение, например, хеш-таблицы могут быть разными сущностями?
Пытается раскуклиться

Аватара пользователя
Jesus05
Сообщения: 1840
Зарегистрирован: 02 дек 2009, 07:50
Откуда: Норильск, сейчас Санкт-петербург.
Контактная информация:

Re: Сохранение в игре.

Сообщение Jesus05 » 09 фев 2017, 14:30

Cfyz писал(а):
09 фев 2017, 14:17
Не, ну это наверное уже перебор >_<. Как вот данные и поведение, например, хеш-таблицы могут быть разными сущностями?
Хорошо:
"Большинство игровых объектов, являются двумя смешанными сущностями. Описанием объекта и действиями над объектом."
Хотя.....
хеш-таблица это данные, да у нее есть логика и это "логика данных".
Как и список это данные, вектор - данные.
Инвентарь даже если это по факту вектор уже игровой объект, и у него 2 сущности данные (вектор с "стандартной логикой поведения вектора") и игровое поведение (отдавание предметов, оценка кол-ва предметов, сбор предметов в стеки или еще что-нить)
Если данных только 1 тип проблемы почти нет, их легко сохранить, но если игровому объекты нужна группа данных то возникает проблема с группировкой этих данных и у данных начинает появляться "своя логика" - "логика данных" такая как ID предмета не может быть вне диапазона известных предметов, температура на улице не может быть больше 1000 и меньше 0 и т.д. это уже логика не игрового объекта это логика которую правильнее переложить на данные. и сохранением\загрузкой тоже должны заниматься данные класса.

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

Аватара пользователя
Oreyn
Сообщения: 297
Зарегистрирован: 07 авг 2013, 14:59

Re: Сохранение в игре.

Сообщение Oreyn » 09 фев 2017, 20:35

Jesus05 писал(а):
09 фев 2017, 07:06
Наверное у нас немного разный подход идеалогически.
Я считаю - класс не должен заниматься своей сериализацией. У него другая задача.
Я не люблю многозадачные классы, если ты предметы ты должен быть предметом, а не сериализуемым предметом, или предметом-монстром-клеткой_карты-с сериализаций одновременно.
Адд2: Хотелось бы какой-нить фреймворк\прекомпилер который.
требовал минимального вмешательства в код такого класс:

SERIALIZABLE();
Да, подход разный. Оба будут работать, главное при дальнейшем развитии проекта не забывать добавляя свойство, прописывать их в сейв лоаде. Как по мне, когда эту функции в этом же листинге в этом же классе, сразу за обьявлением переменных, шансов что я их замечу и вспомню про них больше =)
Видел подобную аннотацию в Спринге под Джаву, там выбираешь провайдер, и эта беда, после такой аннотации даже в оракл сама создает строку подключения, таблицы, индексы и сама их заполняет.
Но по факту это будет что-то вроде "шеллоу копи". Копируем и сохраняем все свойства как есть. И мне кажеться что ручное прописывание всех сохраняемых полей для "дип копи" будет более подходить. Особенно если нужно сделать что-то большее чем скопировать параметры (создать доп. экземпляры объектов для внутренних свойств или еще что)
Jesus05 писал(а):
09 фев 2017, 14:30
Кстати, вот, а почему создание класса никто не против что-бы было вынесено в отдельный класс, а сохранение обязательно должны быть строго внутри класса, конечно фабрика обращается к открытым членам класса, но думаю допустимо создать "дополнительный" интерфейс для класса с которым могла-бы общаться только фабрика.
Вообще фабрике не обязательно делать нью, сет сет сет каждый параметр объекта и вообще знать про них.
Она делает Нью и в параметре передает ссылку на корень структуры данных. И уже сам объект в своем лоаде сделает присвоение своим инкапсулированным данным, к которым у него прямой доступ.
И задача фабрики запросить у менеджера шаблонов стартовую конфигурацию либо если это загрузка лоад конфигурацию да передать объекту. И вернуть просителю ссылку на новый объект. Все.
Jesus05 писал(а):
09 фев 2017, 14:30
Но засада в том, что будет писец :) если в сях на каждый вид игровых данных писать отдельный класс, да и потом все эти проверки на граничность убьют напрочь производительность (я не настолько верю в оптимизирующий компилятор).
Сделай меньше игровых сущностей. Зачем каждому гоблину свой класс? Чем он от другого моба отличается? Именем, буквой и кол-вом ХП? Один класс моб на всех и только параметры разные. Все итемы тоже в один класс. Если не нравится много кода в одном классе - выноси в интерфейсы тематические куски.
Конечно с производительностью хороший вопрос. Как часто ты собираешься делать сейв? После каждого хода? Может только при смерти или выходе? По большому счету, пытаться заблокировать таким образом игрока от сейвскама напрасная трата времени. Тут больше вопрос предпочтений игрока. Захочет - заскамит. Артмани тоже можно специально запутывать, но стоит ли оно того?
Из работающих решений против сейв скама - клиент сервер, с сохранением ходов на сервера. Но тут тогда больше подход сохранения сидов рандома и инкрементальная запись действий подойдет. Заодно и реплеи можно делать.
Максим Кич писал(а):
09 фев 2017, 11:15
Но в целом, у нас видение архитектуры совпадает.
High five =)

UPD: В эту же точку входа можно и конструктор копирования отправить.
Будет в его теле что-то вроде this.load(copy.save()); Где сейв возвращает контекст сохранения объекта с которого делаем копию.
Да, глянул внимательней свою реализацию сейв лоада. У меня объекты с перегруженными сейв лоадами не напрямую лезут в жсон и что-то пишут, а только оперируют с временной дом структурой сейва в памяти (Которая существует пока производится сейв). В которой у каждой ноды есть коллекция с парами свойство - значение, массивами и ссылками на другие ноды. Сейв возвращает созданный фрагмент такой временной структуры, лоад принимает, парсит и присваивает. При этом лоад проверяет граничные параметры и есть ли вообще свойство. Кроме того там же я реализовал совместимость со старыми сейвами, если перечень менялся в лоаде остались проверки на загрузку параметров, которые были в старых сейвах и конвертация их в новый формат.
И уже только в конце единая функция конвертит эту временную дом структуру сейва в жсон, проходясь рекурсивно по нодам.
По сути можно легко перейти и на хмл и на хоть плейн текст. Главное чтобы функции преобразования на входе и выходе работали с той же временной дом структурой сейва.

Аватара пользователя
Jesus05
Сообщения: 1840
Зарегистрирован: 02 дек 2009, 07:50
Откуда: Норильск, сейчас Санкт-петербург.
Контактная информация:

Re: Сохранение в игре.

Сообщение Jesus05 » 10 фев 2017, 07:24

Oreyn писал(а):
09 фев 2017, 20:35
Но по факту это будет что-то вроде "шеллоу копи". Копируем и сохраняем все свойства как есть. И мне кажеться что ручное прописывание всех сохраняемых полей для "дип копи" будет более подходить. Особенно если нужно сделать что-то большее чем скопировать параметры (создать доп. экземпляры объектов для внутренних свойств или еще что)
Если хочется все-же ручками, и для сей тогда сгодитятся Qt-шные свойства.
https://github.com/simonask/reflect тоже годиться для ручной рефлексии, на вид для ручного управления выглядит не плохо, но конечно надо пробовать что-бы понять подходит или нет.
https://github.com/RAttab/reflect тоже думаю сгодиться для ручной рефлексии.
Boost сериализации, вполне себе ручная.
Oreyn писал(а):
09 фев 2017, 20:35
Сделай меньше игровых сущностей. Зачем каждому гоблину свой класс?
А я не говорил на каждого гоблина по классу, я говорил каждый тип данных должен быть отдельным классом.
HP отдельный класс данных
DAMAGE отдельный класс данных
Броня отдельный класс данных
значение_параметра (Cила, ловкость) отдельный класс. (не по классу на силу и ловкость, а класс для хранения значений параметров)
параметр отдельный класс для типа параметра (2 поля enum типа параметра + значение_параметра)
уровень_навыка по аналогии с параметром.
навык по аналогии с параметром.
Монстр_данные (список параметров (по умолчанию либо пустой, либо заполненный тем что есть у всех) + список навыков + HP + броня(если постоянная) + методы доступа к этим данным)
Монстр_поведение это уже класс не данных это класс поведения, здесь не должно быть расчетов как отнимается ХП при нанесении дамага, как сериализуются данные этого монстра, и другой работы которую выполняют типы данных здесь строго поведение монстра.
Oreyn писал(а):
09 фев 2017, 20:35
Конечно с производительностью хороший вопрос. Как часто ты собираешься делать сейв? После каждого хода? Может только при смерти или выходе?
На самом деле тот проект планировался к переносу под андроид после написания, а там выход из приложения несколько туманное понятие, и лучше-бы сохранять данные пользователя настолько часто насколько возможно, но сохранение занимало порядка 2 секунд (на более менее большой карте (1000 * 1000 клеток), а карта генерилась процедурно, небольшими блоками, в процессе исследования и могла расти до 2 000 000 000 * 2 000 000 000), и делать его каждый ход было накладно.

Аватара пользователя
Oreyn
Сообщения: 297
Зарегистрирован: 07 авг 2013, 14:59

Re: Сохранение в игре.

Сообщение Oreyn » 10 фев 2017, 09:38

Jesus05 писал(а):
10 фев 2017, 07:24
Если хочется все-же ручками, и для сей тогда сгодитятся Qt-шные свойства.
По большому счету задача сейв лоада - сохранить загрузить текущее состояние объектов без потерь данных.
Если уже готовый велосипед может это делать минимизируя дальнейшие телодвижения программиста, сохраняя скопом все параметры объекта. И облегчая дальнейшее расширение проекта.
Почему бы и нет. Я так понял что ты пока просто не нашел еще этот велосипед.
Jesus05 писал(а):
10 фев 2017, 07:24
А я не говорил на каждого гоблина по классу, я говорил каждый тип данных должен быть отдельным классом.
HP отдельный класс данных
DAMAGE отдельный класс данных
Я тоже так когда-то думал.
Мол вот у класса ХР есть внутри базовое хп, вот бустед хп, вот макс хп. Он сам там все контролирует субстрактит и инкрементит и баффает и возвращает к базовому значению. Точно также с другими скиллами и статами.
Но по сути, так как он знает только свои параметры и ничего о вышестоящем мобе, весь класс сводится к проверкам граничных значений.
А основная кухня нанесения демеджа происходит в самом мобе.
Когда другой моб его ранит он вызывает функцию моба цели takeDamage куда передает структуру урона, которая и является единственной точкой входа для понижения ХП.
Функция рассчитывает броню, иммунитеты, и считает сколько дамаги по факту прошло, вызывает понижение ХП которое вынесенно в отдельный класс. (Который, какой молодец, проверил граничные условия.) И если дамага больше нуля мобовский takeDamage вызывает мобовскую приватную onTakeDamage().
onTakeDamage смотрит есть ли у моба специальный тег FARTING и тогда создает вонючее облако вокруг в восьми клетках.
Потом тот же мобовский takeDamage смотрит если ХП в нуле или ниже, то вызывает onDeath().
Которая в свою очередь проверяет есть ли у моба тег EXPLODE_ON_DEATH, и генерирует взрыв, плюс спавнит труп и лут в нем.
В итоге takeDamage еще и возвращает атакеру структуру дамаги которая прошла по факту, и тот еще проверяет должен ли он за ее счет вампирить ХП если у него такая опция.
Такие же точки входа для лечения и баффа ХП опять таки находятся в мобе называются healDamage и buffHP и тоже проверяют кучу параметров моба.

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

Со всяким дамеджем и броней та же песня, их классы максимум что могут сделать - проверить граничные условия. Если же ты хош перенести takeDamage прямо в класс ХП, ему тогда нужна обратная кольцевая ссылка на класс моба и знать его в хеадере, чтобы дергать его свойства и функции. Мой подход мне показался ближе к кип ит симпл стьюпид.
Jesus05 писал(а):
10 фев 2017, 07:24
На самом деле тот проект планировался к переносу под андроид после написания, а там выход из приложения несколько туманное понятие, и лучше-бы сохранять данные пользователя настолько часто насколько возможно, но сохранение занимало порядка 2 секунд (на более менее большой карте (1000 * 1000 клеток), а карта генерилась процедурно, небольшими блоками, в процессе исследования и могла расти до 2 000 000 000 * 2 000 000 000), и делать его каждый ход было накладно.
В андроиде есть по идее событие типа "лост фокус", когда пользователь переключается из твоего приложения в список задач или сворачивает его. Собственно оттуда приложение можно и остановить. Сейвся по нему. Ну или раз в сколько-то ходов.

Аватара пользователя
Jesus05
Сообщения: 1840
Зарегистрирован: 02 дек 2009, 07:50
Откуда: Норильск, сейчас Санкт-петербург.
Контактная информация:

Re: Сохранение в игре.

Сообщение Jesus05 » 10 фев 2017, 10:32

Хорошо. Остановлюсь на этом. Надеюсь ТС-у помогут ссылки и размышления высказанные в пределах нашего обсуждения и он заставит Бискупа нервно курить в сторонке.

Аватара пользователя
Cfyz
Сообщения: 776
Зарегистрирован: 30 ноя 2006, 10:03
Откуда: Санкт-Петербург
Контактная информация:

Re: Сохранение в игре.

Сообщение Cfyz » 10 фев 2017, 11:15

Jesus05 писал(а):<...> каждый тип данных должен быть отдельным классом. HP отдельный класс данных, DAMAGE отдельный класс данных, Броня отдельный класс данных <...> Монстр_поведение это уже класс не данных это класс поведения, здесь не должно быть расчетов как отнимается ХП
Oreyn писал(а):Я тоже так когда-то думал. Мол вот у класса ХР есть внутри базовое хп, вот бустед хп, вот макс хп. Он сам там все контролирует субстрактит и инкрементит и баффает и возвращает к базовому значению. Точно также с другими скиллами и статами.
Господа, кажется вы изобрели Entity Component System =).
Пытается раскуклиться

altmax
Сообщения: 173
Зарегистрирован: 15 сен 2012, 11:59

Re: Сохранение в игре.

Сообщение altmax » 10 фев 2017, 17:56

Jesus05 писал(а):
10 фев 2017, 10:32
Хорошо. Остановлюсь на этом. Надеюсь ТС-у помогут ссылки и размышления высказанные в пределах нашего обсуждения и он заставит Бискупа нервно курить в сторонке.
Ссылки не особо помогли, а вот рассуждения натолкнули на массу новых идей, так что всем спасибо.
И я считаю, то класс должен сам хранить и обрабатывать свои данные, т.е. сериализация должна быть в самом классе, тогда мы сможем изменять его не затрагивая другие классы. Что в общем то соответствует паттернам программирования GRASP - low coupling, high kohesion, Information Expert.

altmax
Сообщения: 173
Зарегистрирован: 15 сен 2012, 11:59

Re: Сохранение в игре.

Сообщение altmax » 12 фев 2017, 19:37

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

Аватара пользователя
Максим Кич
Администратор
Сообщения: 1642
Зарегистрирован: 03 дек 2006, 20:17
Откуда: Витебск, Беларусь
Контактная информация:

Re: Сохранение в игре.

Сообщение Максим Кич » 13 фев 2017, 09:32

altmax писал(а):
12 фев 2017, 19:37
Появились новые идеи - вообще все данные в игре можно хранить в базе данных - например Sqlite, не требует сервера, интегрируется в экзешник или отдельно в виде dll весом около 800 кб.
Я прошу занести в протокол, что я предупреждал: такой подход кроет в себе немало подводных камней. То есть тут будет два варианта: или вся логика будет выстроена вокруг работы с БД, что не то, чтобы плохо, скорее неспецифично для такого вида софта, или будет «одна страна — две системы» с кучей интересных и сложнодиагностируемых багов на стыке.
Dump the screen? [y/n]

altmax
Сообщения: 173
Зарегистрирован: 15 сен 2012, 11:59

Re: Сохранение в игре.

Сообщение altmax » 13 фев 2017, 12:27

Максим Кич писал(а):
13 фев 2017, 09:32
или будет «одна страна — две системы» с кучей интересных и сложнодиагностируемых багов на стыке.
А уж сколько багов будет если делать самописный сериализатор данных для сохранений. Думаю, там их будет побольше, чем при использовании базы данных.

Аватара пользователя
Jesus05
Сообщения: 1840
Зарегистрирован: 02 дек 2009, 07:50
Откуда: Норильск, сейчас Санкт-петербург.
Контактная информация:

Re: Сохранение в игре.

Сообщение Jesus05 » 13 фев 2017, 12:31

Просто база данных тебе мало, что даст.
Замучаешься руками SELECT`ы да UPDATE`ы писать.

Аватара пользователя
Максим Кич
Администратор
Сообщения: 1642
Зарегистрирован: 03 дек 2006, 20:17
Откуда: Витебск, Беларусь
Контактная информация:

Re: Сохранение в игре.

Сообщение Максим Кич » 13 фев 2017, 12:45

altmax писал(а):
13 фев 2017, 12:27
Максим Кич писал(а):
13 фев 2017, 09:32
или будет «одна страна — две системы» с кучей интересных и сложнодиагностируемых багов на стыке.
А уж сколько багов будет если делать самописный сериализатор данных для сохранений. Думаю, там их будет побольше, чем при использовании базы данных.
На самом деле — меньше. Потому что выбор тут не между «самописным сериализатором» и «базой данных», а «самописным сериализатором в формат подходящий для сериализации» и «самописным сериализатором в формат, плохо подходящий для сериализации». И, да, это были одни из самых эпичных граблей в моей карьере, так что я не на голом месте предостерегаю.
Dump the screen? [y/n]

altmax
Сообщения: 173
Зарегистрирован: 15 сен 2012, 11:59

Re: Сохранение в игре.

Сообщение altmax » 13 фев 2017, 12:54

Максим Кич писал(а):
13 фев 2017, 12:45
И, да, это были одни из самых эпичных граблей в моей карьере, так что я не на голом месте предостерегаю.
А поподробнее можно? Ну чтобы не наступить на те же грабли.

Аватара пользователя
Максим Кич
Администратор
Сообщения: 1642
Зарегистрирован: 03 дек 2006, 20:17
Откуда: Витебск, Беларусь
Контактная информация:

Re: Сохранение в игре.

Сообщение Максим Кич » 13 фев 2017, 13:40

altmax писал(а):
13 фев 2017, 12:54
Максим Кич писал(а):
13 фев 2017, 12:45
И, да, это были одни из самых эпичных граблей в моей карьере, так что я не на голом месте предостерегаю.
А поподробнее можно? Ну чтобы не наступить на те же грабли.
Ну, у меня была необходимость сохранения и редактирования достаточно сложных вложенных структур. Причём, в клиенте есть редактор с кнопкой «Сохранить», а на сервере — несколько таблиц MySQL (как и вся база) по которым это добро раскладывалось.

Так вот, когда у нас пользователь жмёт на «Сохранить», у нас может быть несколько вариантов:
  • Элемент уже существует и на клиенте и в базе, и у него есть id
  • Элемент существует на клиенте, в базе его ещё нет — id у него тоже нет
  • Элемент удалён на клиенте, в базе он ещё есть
И, поскольку структура имела произвольную вложенность… это был ад. То есть, в целом всё неплохо работало. Но в частности отдельные элементы умудрялись исчезнуть из вида по весьма занятное траектории.

Собственно говоря, для комфортного сохранения данных таким образом, каждое изменение должно было бы сохраняться на сервере, что было возможно, но противоречило ТЗ. В конечном итоге, я сделал второй подход к снаряду и сериализовал уже в XML (чуть лучше подходил, чем JSON) — разница была поразительная.
Dump the screen? [y/n]

Ответить

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 5 гостей