Как мне создать файл сохранения для игры C ++?

33

Я пишу свой финал для курса «Программирование видеоигр» и хочу знать, как создать файл сохранения для моей игры, чтобы пользователь мог играть, а затем вернуться позже. Любая идея, как это сделать, все, что я делал раньше, было программами с одним запуском.

Такер Морган
источник
2
Вы также можете использовать SQLite
Ник Швелидзе
1
@Shvelo Несмотря на то, что вы могли бы сделать это, похоже, это добавит много сложности, которая не обязательно нужна.
конец

Ответы:

38

Вам нужно использовать сериализацию, чтобы сохранить переменные в памяти на жестком диске. Существует много типов сериализации, в .NET XML является распространенным форматом, хотя доступны двоичные и JSON-сериализаторы. Я не большой программист на C ++, но быстрый поиск показал пример сериализации в C ++:

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

Переменные, которые вас заинтересуют, вероятно, будут связаны с состоянием игры. Например, вы, вероятно, хотите знать этот тип информации

  1. Игрок играл на уровне 3
  2. Игрок был в X, Y мировых координатах
  3. У игрока в рюкзаке три предмета
    1. Оружие
    2. броневой
    3. питание

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

Когда вы запускаете игру, вы начинаете как обычно для «новой» игры (при этом загружаются ваши текстуры, модели и т. Д.), Но в соответствующее время вы загружаете значения из файла сохранения обратно в объект состояния игры, заменяя новый «по умолчанию» новым состояние игры. Затем вы позволяете игроку возобновить игру.

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

Nate
источник
Я понимаю, что мне нужно сохранить, но что я хотел бы знать, каков точный путь: сохраните ли вы его в файле .txt в проекте, эти измененные переменные или каким-либо другим способом
Такер Морган,
Да, если ваша игра проста, текстового файла может быть достаточно; Вы должны иметь в виду, что каждый может редактировать текстовый файл и, таким образом, создавать свои собственные сохраненные игры ...
Нейт
Сохранение текстовых файлов не только для простых игр. Paradox использовал структурированный текстовый формат для сохранения файлов для всех игр, которые они создали, используя тот же движок, что и флагманский движок Europa Universalis. Особенно в поздней игре эти файлы могут быть огромными.
Дэн Нили
1
@DanNeely Справедливо сказать, что вы не можете использовать текстовый формат для хранения большого количества сложных данных, но, вообще говоря, когда ваши данные настолько сложны, преимущества другого формата (двоичного, XML и т. Д.) Становятся более выраженными.
Нейт
1
@NateBross Согласен. Игры Paradox были очень мод-дружественными и использовали аналогичный (идентичный?) Формат для данных сценария. Хранение большей части их данных в виде текста означало, что им не нужно вкладывать средства в инструменты редактора сценариев для публичного использования.
Дэн Нили
19

Обычно это зависит от вашей игры. Я уверен, что вы уже узнали о записи и чтении из файлов в ваших классах. Основная идея:

  1. При выходе из игры запишите значения, которые вы хотите сохранить в файл.
  2. При загрузке игры проверьте, существует ли файл сохранения, если он есть, загрузите считанные значения в вашу программу. Если файл не существует, продолжайте, как вы делаете сейчас, и установите значения их начальные / значения по умолчанию.

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

Например (в быстром псевдокоде):

SaveGame(FileInput file) {
    file.writeInt(playerLevel);
    file.writeInt(playerHealth);
    file.writeInt(gameProgress);
}

LoadGame(FileInput file) {
    if(file.exists()) {
        playerLevel= file.readInt();
        playerHealth = file.readInt();
        gameProgress = file.readInt();
    } else {
        playerLevel = 1;
        playerHealth = 100;
        gameProgress = 0;
    }
}
MichaelHouse
источник
1
Этот метод хорош и мал, хотя я бы порекомендовал добавить несколько простых тегов для кусков данных. Таким образом, если позже вам нужно изменить что-то, что обычно находится в середине файла, вы можете сделать это, и единственное «преобразование из старого», которое вам нужно сделать, находится внутри этого одного блока. Это не так важно для однократного назначения, но если вы продолжите работу после того, как люди начнут получать сохраненные файлы, это будет просто кошмар, просто использование прямых байтов с позицией, являющейся единственным идентификатором.
Лунин
1
Да, это не создает перспективных файлов сохранения. Это также не работает для данных с переменным размером байтов, таких как строки. Последнее легко исправить, сначала записав размер данных, которые должны быть записаны, а затем используя их при загрузке, чтобы прочитать правильное количество байтов.
MichaelHouse
6

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

struct SaveGameData
{
    int              characterLevel; // Any straight up values from the player
    int              inventoryCount; // Number of items the player has on them or stored or what not
    int[STAT_COUNT]  statistics;     // This is usually a constant size (I am tracking X number of stats)
    // etc
}

struct Item
{
    int itemTypeId;
    int Durability; // also used as a 'uses' count for potions and the like
    int strength;   // damage of a weapon, protection of armor, effectiveness of a potion
    // etc
}

Затем я просто записываю / копирую данные в файл и из файла, используя базовые значения файлового ввода-вывода. InventoryCount - это число структур Item, которые сохраняются после основной структуры SaveGameData в файле, поэтому я знаю, сколько из них нужно прочитать после извлечения этих данных. Ключевым моментом здесь является то, что когда я хочу сохранить что-то новое, кроме списка элементов или чего-либо подобного, все, что мне когда-либо нужно, это добавить значение в структуру some where. Если это список элементов, тогда мне нужно будет добавить проход чтения, как я уже подразумевал, для объектов Item, счетчик в главном заголовке и затем записи.

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

Опять же, есть несколько способов сделать это, и это может привести к C, а не к C ++, но это уже сделано!

Джеймс
источник
1
Стоит также отметить, что это не зависит от платформы, не будет работать для строк C ++ или для объектов, на которые ссылаются ссылки или указатели, или для любых объектов, содержащих любое из вышеперечисленного!
Kylotan
Почему эта платформа не является независимой? Он отлично работал на ПК, системах PS * и 360 .. fwrite (pToDataBuffer, sizeof (тип данных), countOfElements, pToFile); работает для всех этих объектов, предполагая, что вы можете получить указатель на их данные, а также размер объекта, а затем количество их, которое вы хотите написать .. и чтение соответствует этому ..
Джеймс
Он не зависит от платформы, просто нет гарантии, что файлы, сохраненные на одной платформе, могут быть загружены на другую. Что, к примеру, не имеет значения для сохранения игровых данных. Очевидно, что указатель на data-and-size-memcpy может быть немного неуклюжим, но это работает.
оставил около
3
На самом деле нет никакой гарантии, что он будет работать для вас вечно - что произойдет, если вы выпустите новую версию, созданную с новым компилятором, или даже новые параметры компиляции, которые изменят заполнение структуры? Я настоятельно, настоятельно рекомендую использовать raw-struct fwrite () только по этой причине (кстати, я говорю об этом по опыту).
пушистый
1
Это не о «32 битах данных». Оригинальный постер просто спрашивает «как мне сохранить мои переменные». Если вы напишите переменную напрямую, вы потеряете информацию на разных платформах. Если вам нужно выполнить предварительную обработку до fwrite, то вы пропустили самую важную часть ответа, т.е. как обработать данные так, чтобы они правильно сохранялись и включали только тривиальный бит, т.е. вызов fwrite, чтобы положить что-то на диск.
Kylotan
3

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

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

x y score coins

И так файл будет выглядеть так:

14 96 4200 100

Что означало бы, что он был на позиции (14, 96) со счетом 4200 и 100 монет.

Вам также нужно написать код для загрузки этого файла (используйте ifstream).


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

number_of_enemies x1 y1 x2 y2 ...

Сначала number_of_enemiesчитается, а затем каждая позиция читается с помощью простого цикла.

Pubby
источник
1

Одно добавление / предложение может добавить уровень шифрования к вашей сериализации, чтобы пользователи не могли редактировать свои значения в тексте как "9999999999999999999". Хорошая причина сделать это - предотвратить целочисленные переполнения (например).

Styler
источник
0

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

См. Http://www.boost.org/doc/libs/1_49_0/libs/serialization/doc/index.html.

Killrazor
источник
0

Для полноты картины я хочу упомянуть библиотеку сериализации c ++, которую я лично использую и еще не упоминал: cereal .
Он прост в использовании и имеет приятный, чистый синтаксис для сериализации. Он также предлагает несколько типов форматов, которые вы можете сохранить (XML, Json, Binary (включая переносную версию с сохранением порядка байтов)). Он поддерживает наследование и только для заголовков,

LukeG
источник
0

Ваша игра скомпрометирует структуры данных (надеюсь?), Которые вам нужно преобразовать в байты (сериализовать), чтобы вы могли хранить их. В будущем вы можете загрузить эти байты обратно и преобразовать их обратно в исходную структуру (десериализация). В C ++ это не так сложно, потому что рефлексия очень ограничена. Но все же, некоторые библиотеки могут помочь вам здесь. Я написал статью об этом: https://rubentorresbonet.wordpress.com/2014/08/25/an-overview-of-data-serialization-techniques-in-c/ По сути, я бы предложил вам взглянуть на библиотека зерновых, если вы можете использовать компиляторы C ++ 11. Не нужно создавать промежуточные файлы, как с protobuf, так что вы сэкономите там время, если хотите быстрых результатов. Вы также можете выбрать между двоичным и JSON, так что это может помочь при отладке.

Кроме того, если безопасность является проблемой, вы можете зашифровать / расшифровать данные, которые вы храните, особенно если вы используете удобочитаемые форматы, такие как JSON. Здесь полезны такие алгоритмы, как AES.

Рубен Торрес Бонет
источник
-5

Вы должны использовать fstreamдля файлов ввода / вывода. Синтаксис простой EX:

#include <fstream>
// ...
std::ofstream flux ; // to open a file in ouput mode
flux.open("myfile.whatever") ; 

Или

#include <fstream>
// ...
std::ifstream flux ; // open a file in input mode
flux.open("myfile.whatever") ;

В вашем файле возможны другие действия: добавление , двоичный файл , усечение и т. Д. Вы должны использовать тот же синтаксис, что и выше, вместо этого мы помещаем std :: ios: :( flags), например:

  • ios::out для выходной операции
  • ios::in для операции ввода
  • ios::binary для бинарной (необработанной) операции ввода-вывода вместо символьной
  • ios::app для начала писать в конце файла
  • ios::trunc если файл уже существует, замените старый контент и замените его новым
  • ios::ate - установить указатель файла «в конце» для ввода / вывода

Пример:

#include <fstream>
// ...
std::ifstream flux ;
flux.open("myfile.whatever" , ios::binary) ;

Вот более полный, но простой пример.

#include <iostream>
#include <fstream>

using namespace std ;

int input ;
int New_Apple ;
int Apple_Instock ;
int Eat_Apple ;
int Apple ;

int  main()
{
  bool shouldQuit = false;
  New_Apple = 0 ;
  Apple_Instock = 0 ;
  Eat_Apple = 0 ;

  while( !shouldQuit )
  {
    cout << "------------------------------------- /n";
    cout << "1) add some apple " << endl ;
    cout << "2) check apple in stock " << endl ;
    cout << "3) eat some apple " << endl ;
    cout << "4) quit " << endl ;
    cout << "------------------------------------- /n";
    cin >> input ;

    switch (input)
    {
      case 1 :
      {
        system("cls") ;

        cout << "------------------------------------ /n";
        cout << " how much apple do you want to add /n";
        cout << "------------------------------------ /n";      
        cin >> New_Apple ;

        ifstream apple_checker ;
        apple_checker.open("apple.apl") ;
        apple_checker >> Apple_Instock ;
        apple_checker.close() ; 

        Apple = New_Apple + Apple_Instock ;

        ofstream apple_adder ;
        apple_adder.open("apple.apl") ;
        apple_adder << Apple ;
        apple_adder.close() ;

        cout << "------------------------------------ /n";
        cout << New_Apple << " Apple has been added ! /n";
        cout << "------------------------------------ /n";
        break;
      }

      case 2 :  
      {
        system("cls") ;

        ifstream apple_checker ;
        apple_checker.open("apple.apl") ;
        apple_checker >> Apple_Instock ;
        apple_checker.close() ;

        cout << "------------------------------------ /n";
        cout << " there is " << Apple_Instock ;
        cout << "apple in stock /n" ;
        cout << "------------------------------------ /n";
        break;
      }

      case 3 :
      {
        system("cls") ;

        cout << "------------------------------------ /n";
        cout << "How many apple do you want to eat /n" ;
        cout << "------------------------------------ /n";
        cin >> Eat_Apple ;

        ifstream apple_checker ;
        apple_checker.open("apple.apl") ;
        apple_checker >> Apple_Instock ;
        apple_checker.close() ;

        Apple = Apple_Instock - Eat_Apple ; 

        ofstream apple_eater ;
        apple_eater.open("apple.apl") ;
        apple_eater << Apple ;
        apple_eater.close() ;

        cout << "----------------------------------- /n";
        cout << Eat_Apple ;
        cout << " Apple has been eated! /n";
        cout << "----------------------------------- /n";
        cout << Apple << " Apple left in stock /n";
        cout << "----------------------------------- /n";
        break;
      }

      case 4 :
      {
        shouldQuit = true;
        break;
      }

      default :
      {
        system("cls") ;

        cout << "------------------------------------ /n";
        cout << " invalide choice ! /n";
        cout << "------------------------------------ /n"; 
        break;
      }
    }
  }
  return 0;
}
Франциско Форсье
источник
4
-1 Это очень плохой ответ. Вы должны правильно отформатировать и отобразить код и объяснить, что вы делаете, никто не хочет расшифровывать кусок кода.
Vaillancourt
Спасибо, Кату, за комментарий, который ты прав, я должен объяснить мой код более подробно. Можете ли вы рассказать мне, как я форматирую свой источник с веб-сайта, потому что я новичок в такого рода
вещах
С этого сайта или для этого сайта? Для получения справки по форматированию сообщений вы можете посетить страницу справки по форматированию . Рядом с заголовком области текста, которую вы используете для публикации, есть восклицательный знак.
Vaillancourt
Попробуйте задокументировать то, что спросили; Вам не нужно все комментировать. И не объясняя, что вы делали, в моем комментарии я имел в виду, что вы обычно представляете стратегию, которую вы предлагаете, по крайней мере, с коротким абзацем. (Например, «Один из методов - использовать двоичный формат файла с оператором потока. Вы должны быть осторожны, чтобы читать и писать в одном и том же порядке, бла-бла-лаба»).
Vaillancourt
2
А с помощью gotos вы будете линчевать в общественных местах. Не используйте gotoс.
Vaillancourt