Як створити файл збереження для гри на C ++?


33

Я кодую свій заключний курс програмування відеоігор, і я хочу знати, як створити файл збереження для своєї гри, щоб користувач міг грати, а потім повертатися пізніше. Будь-яка ідея, як це робиться, кожна річ, яку я робив раніше, - це програми для одноразового виконання.



2
Ви також можете використовувати SQLite
Нік Швелідзе

1
@Shvelo Хоча ви могли це зробити, схоже, це додасть багато складності, яка не обов'язково потрібна.
Нейт

Відповіді:


38

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

Є бібліотеки, які пропонують серіалізуючі функції. Деякі згадуються в інших відповідях.

Змінні, які вас зацікавлять, ймовірно, стосуватимуться стану гри. Наприклад, ви, ймовірно, захочете знати цей тип інформації

  1. Гравець грав на рівні 3
  2. Гравець був на світових координатах X, Y
  3. У гравця у своєму рюкзаку є три предмети
    1. Зброя
    2. Броня
    3. Їжа

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

Коли ви починаєте свою гру, ви починаєте як звичайно для "нової" гри (це завантажує ваші текстури, моделі тощо), але у відповідний час ви завантажуєте значення зі збереженого файлу назад в об'єкт ігрового стану, замінюючи "за замовчуванням" новим ігровий стан. Тоді ви дозволяєте гравцеві відновити гру.

Я значно спростив це тут, але ви повинні отримати загальну думку. Якщо у вас є конкретніше запитання, поставте тут нове запитання, і ми можемо спробувати допомогти вам у цьому.


Я розумію, що мені потрібно зберегти, але що я хотів би знати, що це саме такий спосіб, чи збережеш ти його у файлі .txt у проекті, цих модифікованих змінних чи іншим способом
Tucker Morgan,

Так, якщо ваша гра проста, текстового файлу може бути достатньо; вам потрібно пам’ятати, що кожен може редагувати текстовий файл і таким чином робити власні ігри для збереження…
Nate

Збереження текстових файлів - не лише для простих ігор. 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;
    }
}

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
}

Потім я просто переписую / завантажую дані до та з файлу, використовуючи основні значення файлу IO. InvenCount - це кількість структур елементів, які зберігаються після основної структури SaveGameData у файлі, тому я знаю, скільки з них прочитати після отримання цих даних. Ключовим тут є те, що коли я хочу зберегти щось нове, якщо не є його перелік предметів тощо, все, що мені довелося зробити, - це додати значення структурі десь. Якщо його список елементів, то мені доведеться додати пропуск для читання, як я вже мав на увазі для об'єктів Item, лічильник у головному заголовку, а потім записи.

Це має і недолік зробити різні версії файлу збереження несумісними один з одним із спеціальними обробками (навіть якщо це лише значення за замовчуванням для кожного запису в основній структурі). Але в цілому це дозволяє легко розширювати систему, лише додаючи нове значення даних і додаючи до нього значення при необхідності.

Знову ж таки, досить багато способів зробити це, і це може призвести більше до C, ніж до C ++, але це зробило роботу!


1
Варто також зазначити, що це не залежить від платформи, не працюватиме для рядків C ++, або для об'єктів, на які посилається через посилання або вказівники, або будь-яких об'єктів, що містять будь-яке з вищевказаного!
Кілотан

Чому ця платформа не є незалежною? Він добре працював на ПК, системах PS * та fwrite 360 ​​.. (pToDataBuffer, sizeof (тип даних), countOfElements, pToFile); працює для всіх цих об’єктів, припускаючи, що ви можете отримати вказівник на їх дані, розмір об'єкта, а потім їх кількість, яку ви хочете написати .. і прочитати відповідність цього ...
Джеймс

Вона є незалежною від платформи, там просто немає гарантії того, що файли , збережені на одній платформі , можуть бути завантажені на інший. Що є неактуальним, наприклад, для збереження даних про ігри. Очевидно, що вказівник даних на дані та розміри-memcpy може бути трохи незручним, але він працює.
близько

3
Насправді немає жодної гарантії, що вона буде працювати для вас назавжди - що трапиться, якщо випустите нову версію, побудовану з новим компілятором або навіть новими параметрами компіляції, що змінює підшивку структури? Я б дуже наполегливо висловив задоволення від використання сировинної структури fwrite () лише з цієї причини (я, кажучи з досвіду цього, до речі).
пухнастий

1
Йдеться не про '32 біта даних '. Оригінальний плакат просто запитує "як зберегти свої змінні". Якщо ви переміщуєте змінну безпосередньо, то ви втрачаєте інформацію на різних платформах. Якщо вам доведеться попередньо обробити перед fwrite, то ви залишили найважливішу частину відповіді, тобто. як обробити дані, щоб вони були збережені правильно, і включили лише тривіальний біт, тобто виклик fwrite, щоб щось поставити на диск.
Кілотан

3

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

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

x y score coins

І так файл виглядатиме так:

14 96 4200 100

Що означало б, що він опинився на позиції (14, 96) із оцінкою 4200 та 100 монет.

Вам також потрібно написати код, щоб завантажити цей файл (використовуйте ifstream).


Збереження ворогів можна зробити, включивши їхнє місце у файл. Ми можемо використовувати цей формат:

number_of_enemies x1 y1 x2 y2 ...

Спочатку number_of_enemiesзчитується, а потім кожна позиція читається простою петлею.


1

Одне додавання / пропозиція означатиме додавання рівня шифрування до вашої серіалізації, щоб користувачі не могли редагувати свої значення на "999999999999999999999". Однією з вагомих причин цього було б запобігти цілому переповненню (наприклад).


0

Я думаю, що найкраща ставка - це стимулювання :: серіалізація, оскільки її легко поширити у вашій ієрархії. Вам потрібно лише зателефонувати у функцію збереження / завантаження верхнього об'єкта, і всі ваші класи легко створюються.

Дивіться: http://www.boost.org/doc/libs/1_49_0/libs/serialization/doc/index.html


0

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


0

Ваша гра буде компрометувати структури даних (сподіваємось?), Що вам потрібно перетворити в байти (серіалізувати), щоб ви могли їх зберігати. В майбутньому ви можете завантажити ці байти назад і перетворити їх у свою первісну структуру (дезаріалізація). У C ++ це не так складно, оскільки рефлексія дуже обмежена. Але все ж деякі бібліотеки можуть вам тут допомогти. Я написав про це статтю: https://rubentorresbonet.wordpress.com/2014/08/25/an-overview-of-data-serialization-techniques-in-c/ В основному, я б запропонував вам поглянути на бібліотека злаків, якщо ви можете націлити C ++ 11 компіляторів. Не потрібно створювати проміжні файли, як у протобуфі, тому ви заощадите деякий час там, якщо хочете швидких результатів. Ви також можете вибрати між бінарними та 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: :( прапори), наприклад:

  • 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

Спробуйте задокументувати запитання; не потрібно все коментувати. І не пояснюючи, що ви робите, я в своєму коментарі мав на увазі, що ви, як правило, вводите стратегію, яку ви пропонуєте, хоча б з коротким абзацом. (наприклад, "Однією з методик є використання формату бінарних файлів з оператором потоку. Ви повинні бути обережними для читання та запису в тому ж порядку, bla bla lba").
Vaillancourt

2
І за допомогою gotos ви отримаєте лінчування в громадському місці. Не використовуйте gotos.
Vaillancourt
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.