сохранение / загрузка состояния игры?

12

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

Я видел, как Цезарь IV использовал mySQL.

какие-либо предложения.

NativeCoder
источник

Ответы:

20

Я почти уверен, что это чаще всего делается в проприетарном (двоичном) формате, просто записывая каждую переменную из любой структуры состояния игры, которую вы используете, в файл или читая этот файл обратно в экземпляр структуры. Например, в C ++ вы можете обрабатывать объект как массив байтов и просто fwrite () его в файл или fread () из файла и привести его обратно в форму объекта.

Если вы используете Java или другой язык, вы можете использовать сериализацию, чтобы упростить задачу. Объяснение Java находится внизу этого ответа; другие языки (более высокого уровня, чем C ++), безусловно, имеют свои собственные библиотеки сериализации или встроенные функции, которые вы должны использовать. Независимо от того, насколько неэффективной может быть процедура сериализации, в наше время пространство на жестком диске дешевое, особенно когда состояние игры совсем не должно быть очень большим, и это мешает вам писать и поддерживать свои собственные процедуры сериализации.

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

Любое шифрование локальных файлов на диске - только запутывание ; любой хакер просто перехватит данные сразу после того, как ваша программа расшифрует их. Я лично против такого рода вещей, особенно с однопользовательскими играми. Я считаю, что владельцы игры (платящие клиенты, обратите внимание) должны иметь возможность взломать игру, если они этого хотят. В некоторых случаях это может создать большее чувство общности, и для вашей игры могут быть разработаны «моды», которые привлекают к вам больше клиентов. Самый свежий пример, который приходит на ум - это недавно выпущенный мод Portal in Minecraft . Он публикуется на новостных сайтах геймеров по всему Интернету, и вы можете поспорить, что он увеличил продажи Minecraft.


Если по какой-то причине вы настолько безумны, чтобы использовать Java для разработки игр, как я, вот краткое объяснение сериализации в Java:

Храните все данные о состоянии игры в одном классе, сделайте класс сериализуемым путем реализации Serializable, используйте transientключевое слово, если вы смешиваете специальные экземпляры переменных в классе (временные объекты не сериализуются), и просто используете ObjectOutputStreamдля записи в файл и ObjectInputStreamдля чтения из файла. , Вот и все.

Ricket
источник
2
Хехе, Ведьмак не зашифровал свои данные. Очень легко открыть файл сохраненной игры в шестнадцатеричном редакторе, немного возиться с ним и получить все обнаженные карточки цыплят ... о боже, мне очень жаль!
Энтони
Я использовал двоичную сериализацию .NET для сохранения игр, и C #, возможно, является несколько более разумной платформой для разработки игр, чем Java. Мне нравится, как он автоматически сохраняет весь граф объектов автоматически. Раньше это было намного сложнее. Конечно, теперь есть проблема исключения ненужных частей, таких как текстуры, но это не так уж и сложно.
BlueMonkMN
Я не согласен с тем, что C # является немного более популярным, чем Java, для разработки игр. Более разумный подразумевает некоторую субъективность, и, как разработчик XNA и Java, я предпочитаю Java. Не стесняйтесь перечислять инструкции по сериализации в C #, если хотите, я никогда не делал этого, но я могу отредактировать это в своем ответе. Сериализация Java сохраняет весь граф объектов, а ключевое слово 'transient' для переменных исключает ненужные фрагменты, такие как текстуры; любые непереходные члены должны быть Serializable, или вы можете написать собственный метод writeObject, чтобы обойти это.
Рикет
Pickle сохранит весь граф объектов для вас и прост в использовании. Когда приходит время десериализации, вы возвращаете полностью гидратированные объекты, просто как пирог: docs.python.org/library/pickle.html
drhayes
При использовании встроенного двоичного форматера исключить некоторые поля из сериализации в C # / .NET так же просто, как добавить их в атрибут [NonSerialized]. Однако легко упустить из виду, что генерируемые компилятором события (т. Е. Любое событие без пользовательских операторов добавления / удаления) создают вспомогательное поле для хранения своих подписчиков. Таким образом, легко случайно (и неосознанно) включить обработчики событий в сериализованный граф объектов. Чтобы обойти это, вам нужно добавить атрибут NonSerialized в объявление события, но вам нужно добавить квалификатор «field:»:[field: NonSerialized]
Майк Штробель
3

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

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

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

JustBoo
источник
0

Большинство игр, которые я видел, просто используют рукописный код для чтения / записи из двоичных файлов. Часто формат на диске является точной копией структуры памяти объекта, поэтому загрузка выполняется просто:

  1. Прочитать файл в память.
  2. Привести указатель на буфер к типу объекта.

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

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

необычайно щедрый
источник
0

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

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

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

Один из способов избежать этого состоит в том, чтобы каждый базовый тип данных реализовывал методы Save (версия) и Load (версия), которые достаточно умны, чтобы знать, какие данные сохранять и загружать для каждой версии игры. Таким образом, вы можете поддерживать обратную совместимость ваших сохраненных игр и безуспешно терпеть неудачу, если пользователь пытается загрузить сохраненную игру из более новой версии игры, чем у него.

kevin42
источник
1
В качестве краткого примечания: реализация функций «сохранить» и «загрузить» для каждой версии игры быстро превращается в кошмар обслуживания. Я нашел лучшее решение - встроить номер версии, написать функции «сохранить / загрузить» для текущей версии игры и написать функцию «преобразования» из самой последней не текущей версии в текущую версию. Тогда просто держите все свои функции преобразования рядом. Попытка загрузить игру с десятью версиями запускает десять функций преобразования последовательно, а затем встроенную функцию «загрузить текущую версию игры».
ZorbaTHut
Гораздо проще поддерживать в долгосрочной перспективе - поддержание последовательности функций «загрузить каждый предыдущий игровой файл» быстро превращается в операцию за полиномиальное время.
ZorbaTHut
В играх, над которыми я работал, это не так сложно - вместо хранения нескольких копий методов загрузки / сохранения один метод использует номер версии, чтобы определить, какие байты читать и записывать. Это становится еще проще, если вы не просто пишете структуры, но реализуете загрузку и сохранение как операции на уровне примитивного типа данных (например, ReadByte / ReadFload / etc). Затем, если вы добавите нового участника, вы можете сделать что-то вроде if (version> 10) {someNewByte = ReadByte (); } Реализация этого способа упрощает поддержку различных платформ порядка байтов.
kevin42
Другой способ избежать улучшения поддержки управления версиями - это избегать того, чтобы методы сериализации / десериализации ваших объектов записывали / считывали отдельные значения непосредственно из потока. Вместо этого объекты могут записывать свои собственные данные в пакет свойств, используя пары ключ / значение, а затем записывать пакет в поток. Во время десериализации объекты могли читать значения из пакета по имени. Но вместо конкретных проверок номеров версий, как предложил kevin42, вы можете просто иметь правила для обработки пропущенных значений (т. Е. Использовать стандартное значение по умолчанию, когда нужное значение отсутствует в пакете).
Майк Штробель
0

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

Например, такая игра, как Peggle, может иметь начальный макет доски, количество шаров, текущий счет (0 для начальной доски) и т. Д. Тогда сохраненная игра может использовать точно такой же формат.

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

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

Зан Рысь
источник