Làm cách nào để tạo tệp lưu cho trò chơi C ++?


33

Tôi đang viết mã cuối cùng cho khóa học Lập trình trò chơi video và tôi muốn biết cách tạo tệp lưu cho trò chơi của mình để người dùng có thể chơi và sau đó quay lại sau. Bất kỳ ý tưởng làm thế nào điều này được thực hiện, mọi điều tôi đã làm trước đây là các chương trình chạy đơn.



2
Bạn cũng có thể sử dụng SQLite
Nick Shvelidze 22/03/2016

1
@Shvelo Trong khi bạn có thể làm điều đó, có vẻ như nó sẽ thêm rất nhiều sự phức tạp không nhất thiết phải có.
Nate

Câu trả lời:


38

Bạn cần sử dụng tuần tự hóa để lưu các biến trong bộ nhớ vào ổ cứng. Có nhiều kiểu tuần tự hóa, trong .NET XML là một định dạng phổ biến, mặc dù có sẵn các trình tuần tự hóa nhị phân và JSON. Tôi không phải là một lập trình viên C ++, nhưng một tìm kiếm nhanh đã đưa ra một ví dụ về tuần tự hóa trong C ++:

Có thư viện, cung cấp chức năng tuần tự hóa. Một số được đề cập trong các câu trả lời khác.

Các biến bạn sẽ quan tâm có lẽ sẽ liên quan đến trạng thái trò chơi. Ví dụ, bạn có thể sẽ muốn biết loại thông tin này

  1. Người chơi đã chơi cấp 3
  2. Người chơi đã ở tọa độ X, Y
  3. Người chơi có ba món đồ trong ba lô
    1. Vũ khí
    2. Giáp
    3. Món ăn

Bạn sẽ không thực sự quan tâm đến kết cấu nào đang được sử dụng (trừ khi người chơi của bạn có thể thay đổi ngoại hình, đó là trường hợp đặc biệt), vì chúng thường giống nhau. Bạn cần tập trung vào việc lưu dữ liệu trò chơi quan trọng.

Khi bạn bắt đầu trò chơi, bạn bắt đầu như bình thường đối với trò chơi "mới" (điều này sẽ tải kết cấu, mô hình, v.v.) của bạn, nhưng vào thời điểm thích hợp, bạn tải các giá trị từ tệp lưu của bạn trở lại đối tượng trạng thái trò chơi thay thế "mới" mặc định trạng thái trò chơi. Sau đó, bạn cho phép người chơi tiếp tục chơi.

Tôi đã đơn giản hóa nó rất nhiều ở đây, nhưng bạn sẽ có được ý tưởng chung. Nếu bạn có một câu hỏi cụ thể hơn, hãy hỏi một câu hỏi mới ở đây và chúng tôi có thể cố gắng giúp bạn với nó.


Tôi hiểu những gì tôi cần lưu, nhưng những gì tôi muốn biết chính xác là gì, bạn có lưu nó vào một tệp .txt trong dự án, các biến được sửa đổi đó hay một cách khác
Tucker Morgan

Có, nếu trò chơi của bạn đơn giản, một tệp văn bản có thể là đủ; bạn cần lưu ý rằng bất kỳ ai cũng có thể chỉnh sửa tệp văn bản và do đó tự tạo trò chơi lưu của mình ...
Nate

Tập tin văn bản không chỉ dành cho các trò chơi đơn giản. Paradox đã sử dụng định dạng văn bản có cấu trúc để lưu tệp cho tất cả các trò chơi mà họ đã tạo bằng cùng một công cụ như công cụ Europa Universalis hàng đầu. Đặc biệt là trò chơi muộn, những tập tin này có thể là rất lớn.
Dan Neely

1
@DanNeely Một điểm công bằng, không có lý do gì bạn không thể sử dụng định dạng văn bản để lưu trữ nhiều dữ liệu phức tạp, nhưng nói chung, khi dữ liệu của bạn phức tạp, lợi ích của định dạng khác (nhị phân, xml, v.v.) trở nên rõ rệt hơn.
Nate

1
@NateBross Đồng ý. Các trò chơi Paradox rất thân thiện với mod và sử dụng định dạng tương tự (giống hệt nhau?) Cho dữ liệu kịch bản. Lưu trữ hầu hết dữ liệu của họ dưới dạng văn bản có nghĩa là họ không cần đầu tư vào các công cụ biên tập kịch bản để sử dụng công cộng.
Dan Neely

19

Thông thường, điều này là cụ thể cho trò chơi của bạn. Tôi chắc chắn bạn đã học về cách viết và đọc từ các tệp trong lớp của bạn cho đến nay. Ý tưởng cơ bản là:

  1. Khi thoát khỏi trò chơi, hãy viết các giá trị bạn muốn lưu vào một tệp.
  2. Khi tải trò chơi, hãy kiểm tra xem có tồn tại tệp lưu không, nếu có, hãy tải các giá trị đã đọc vào chương trình của bạn. Nếu tệp không tồn tại, hãy tiếp tục như bạn làm bây giờ và đặt các giá trị thành giá trị bắt đầu / mặc định của chúng.

Những gì bạn viết là tùy thuộc vào bạn, nó phụ thuộc vào trò chơi của bạn. Một cách viết là viết ra các biến bạn muốn theo một thứ tự cụ thể dưới dạng luồng byte. Sau đó, khi tải, đọc chúng vào chương trình của bạn theo cùng một thứ tự.

Ví dụ: (mã giả nhanh):

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
Phương pháp này rất hay và nhỏ, mặc dù tôi khuyên bạn nên đưa vào một số thẻ đơn giản cho khối dữ liệu. Bằng cách đó, nếu sau này bạn cần thay đổi một cái gì đó bình thường ở giữa tệp, bạn có thể làm như vậy và "chuyển đổi từ cũ" duy nhất bạn phải làm là trong một khối đó. Việc chuyển nhượng một lần không quan trọng lắm, nhưng nếu bạn tiếp tục làm việc sau khi mọi người bắt đầu lưu tệp thì đó là một cơn ác mộng khi chỉ sử dụng các byte thẳng với vị trí là định danh duy nhất.
Lunin

1
Đúng, điều này không tạo ra các tệp lưu trong tương lai. Nó cũng không hoạt động đối với dữ liệu có kích thước byte thay đổi như chuỗi. Điều thứ hai rất dễ khắc phục bằng cách trước tiên ghi kích thước của dữ liệu sắp được ghi, sau đó sử dụng dữ liệu đó khi tải để đọc đúng số byte.
MichaelHouse

6

Có thể có một số lượng lớn cách để làm điều này, nhưng cách đơn giản nhất mà tôi luôn tìm thấy và sử dụng cả cá nhân và chuyên nghiệp là tạo ra một cấu trúc có chứa tất cả các giá trị tôi muốn lưu.

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
}

Sau đó tôi chỉ cần fwrite / fread dữ liệu đến và từ một tệp bằng các giá trị IO tệp cơ bản. InventCount là số lượng cấu trúc Mục được lưu sau cấu trúc SaveGameData chính trong tệp để tôi biết có bao nhiêu trong số chúng sẽ đọc sau khi tìm nạp dữ liệu đó. Chìa khóa ở đây là khi tôi muốn lưu một cái gì đó mới, trừ khi đó là danh sách các mục hoặc tương tự, tất cả những gì tôi phải làm là thêm một giá trị vào cấu trúc một số nơi. Nếu đó là danh sách các mục thì tôi sẽ phải thêm một lượt đọc giống như tôi đã ngụ ý cho các đối tượng Item, một bộ đếm trong tiêu đề chính và sau đó là các mục.

Điều này không có nhược điểm là làm cho các phiên bản khác nhau của tệp lưu không tương thích với nhau khi xử lý đặc biệt (ngay cả khi đó chỉ là giá trị mặc định cho mỗi mục trong cấu trúc chính). Nhưng nhìn chung, điều này làm cho hệ thống dễ dàng mở rộng chỉ bằng cách thêm vào một giá trị dữ liệu mới và đưa một giá trị vào đó khi cần.

Một lần nữa, khá nhiều cách để làm điều này và điều này có thể dẫn đến C nhiều hơn C ++, nhưng nó đã hoàn thành công việc!


1
Cũng đáng lưu ý rằng đây không phải là nền tảng độc lập, sẽ không hoạt động đối với các chuỗi C ++ hoặc cho các đối tượng được đề cập qua các tham chiếu hoặc con trỏ hoặc bất kỳ đối tượng nào có chứa bất kỳ mục nào ở trên!
Kylotan

Tại sao nền tảng này không độc lập? Nó hoạt động tốt trên các hệ thống PC, PS * và 360 .. fwrite (pToDataBuffer, sizeof (datatype), CountOfElements, pToFile); hoạt động cho tất cả các đối tượng giả định rằng bạn có thể lấy một con trỏ tới dữ liệu của chúng và kích thước của đối tượng và sau đó là số lượng chúng muốn viết .. và đọc các kết quả khớp ..
James

nền tảng độc lập, có chỉ không có sự đảm bảo rằng các file được lưu trên một nền tảng có thể được nạp vào nhau. Điều này khá là không liên quan, ví dụ như tiết kiệm dữ liệu trò chơi. Các công cụ memcpy-to-data-and-size-memcpy rõ ràng có thể hơi khó xử, nhưng nó hoạt động.
leftaroundabout 22/03 '

3
Trên thực tế không có gì đảm bảo rằng nó sẽ tiếp tục làm việc cho bạn mãi mãi - điều gì xảy ra nếu bạn phát hành một phiên bản mới được xây dựng với trình biên dịch mới hoặc thậm chí các tùy chọn biên dịch mới thay đổi phần đệm cấu trúc? Tôi rất muốn, mạnh mẽ không khuyến khích việc sử dụng fwrite thô () vì lý do này một mình (tôi đang nói từ kinh nghiệm về điều này, tình cờ).
fluffy

1
Đó không phải là về '32 bit dữ liệu '. Các poster ban đầu chỉ đơn giản là hỏi "làm thế nào để tôi lưu các biến của tôi". Nếu bạn viết trực tiếp biến đó, thì bạn sẽ mất thông tin trên các nền tảng. Nếu bạn phải xử lý trước fwrite, thì bạn đã bỏ qua phần quan trọng nhất của câu trả lời, tức là. làm thế nào để xử lý dữ liệu sao cho nó được lưu chính xác và chỉ bao gồm bit tầm thường, tức là. gọi fwrite để đặt một cái gì đó vào đĩa.
Kylotan

3

Trước tiên, bạn cần phải quyết định những dữ liệu cần được lưu. Chẳng hạn, đây có thể là vị trí của nhân vật, điểm số của anh ta và số lượng xu. Tất nhiên, trò chơi của bạn có thể sẽ phức tạp hơn nhiều, và vì vậy bạn sẽ cần lưu dữ liệu bổ sung như số cấp và danh sách kẻ thù.

Tiếp theo, viết mã để lưu mã này vào một tệp (sử dụng luồng). Một định dạng tương đối đơn giản bạn có thể sử dụng như sau:

x y score coins

Và do đó, tập tin sẽ trông như sau:

14 96 4200 100

Điều đó có nghĩa là anh ta ở vị trí (14, 96) với số điểm 4200 và 100 xu.

Bạn cũng cần viết mã để tải tập tin này (sử dụng ifstream).


Cứu kẻ thù có thể được thực hiện bằng cách bao gồm vị trí của chúng trong tệp. Chúng tôi có thể sử dụng định dạng này:

number_of_enemies x1 y1 x2 y2 ...

Đầu tiên number_of_enemieslà đọc và sau đó mỗi vị trí được đọc với một vòng lặp đơn giản.


1

Một bổ sung / đề xuất sẽ thêm một mức mã hóa vào tuần tự hóa của bạn để người dùng không thể chỉnh sửa văn bản giá trị của họ thành "999999999999999999999". Một lý do tốt để làm điều này là để ngăn chặn tràn số nguyên (ví dụ).



0

Để hoàn thiện, tôi muốn đề cập đến một thư viện tuần tự hóa c ++, mà cá nhân tôi sử dụng và chưa được đề cập đến: ngũ cốc .
Nó dễ sử dụng và có một cú pháp rõ ràng, đẹp mắt để tuần tự hóa. Nó cũng cung cấp nhiều loại định dạng mà bạn có thể lưu vào (XML, Json, Binary (bao gồm cả phiên bản di động với sự tôn trọng endianess)). Nó hỗ trợ kế thừa và chỉ dành cho tiêu đề,


0

Trò chơi của bạn sẽ thỏa hiệp các cấu trúc dữ liệu (hy vọng?) Mà bạn cần chuyển đổi thành byte (tuần tự hóa) để bạn có thể lưu trữ chúng. Trong tương lai, bạn có thể tải lại các byte đó và chuyển chúng trở lại cấu trúc ban đầu của bạn (khử lưu huỳnh). Trong C ++, nó không quá khó vì phản xạ rất hạn chế. Tuy nhiên, một số thư viện có thể giúp bạn ở đây. Tôi đã viết một bài viết về nó: https://rubentorresbonet.wordpress.com/2014/08/25/an-overview-of-data-serialization-techniques-in-c/ Về cơ bản, tôi sẽ đề nghị bạn xem qua thư viện ngũ cốc nếu bạn có thể nhắm mục tiêu trình biên dịch C ++ 11. Không cần phải tạo các tệp trung gian như với protobuf, vì vậy bạn sẽ tiết kiệm được một chút thời gian ở đó nếu bạn muốn có kết quả nhanh chóng. Bạn cũng có thể chọn giữa nhị phân và JSON, vì vậy nó có thể giúp gỡ lỗi ở đây.

Trên hết, nếu bảo mật là mối quan tâm, bạn có thể muốn mã hóa / giải mã dữ liệu bạn đang lưu trữ, đặc biệt nếu bạn đang sử dụng các định dạng có thể đọc được của con người như JSON. Các thuật toán như AES rất hữu ích ở đây.


-5

Bạn cần sử dụng fstreamcho các tập tin đầu vào / đầu ra. Cú pháp đơn giản EX:

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

Hoặc là

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

Các biện pháp khác có thể xảy ra trong hồ sơ của bạn: append , nhị phân , trunc , vv Bạn sẽ sử dụng cú pháp tương tự như trên ios thay vì chúng ta đặt std ::: :( cờ), ví dụ:

  • ios::out cho hoạt động đầu ra
  • ios::in cho hoạt động đầu vào
  • ios::binary cho hoạt động IO nhị phân (byte thô), thay vì dựa trên ký tự
  • ios::app để bắt đầu viết ở cuối tập tin
  • ios::trunc vì nếu tập tin đã tồn tại thay thế xóa nội dung cũ và thay thế bằng mới
  • ios::ate - định vị con trỏ tệp "ở cuối" cho đầu vào / đầu ra

Vd

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

Dưới đây là một ví dụ đầy đủ hơn nhưng đơn giản.

#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 Đây là một câu trả lời rất tệ. Bạn nên định dạng và hiển thị chính xác mã và giải thích những gì bạn đang làm, không ai muốn giải mã một đoạn mã.
Vaillancourt

Cảm ơn bạn katu vì nhận xét của bạn là đúng. Tôi nên giải thích rõ hơn về mã của mình. Bạn có thể cho tôi biết cách tôi định dạng nguồn của mình từ trang web vì tôi mới biết loại này
Francisco Forcier 10/03/2016

Từ trang web này, hoặc cho trang web này? Để được trợ giúp trong việc định dạng bài viết, bạn có thể truy cập trang trợ giúp định dạng . Có một dấu chấm than bên cạnh tiêu đề của vùng văn bản bạn sử dụng để đăng để giúp bạn.
Vaillancourt

Cố gắng ghi lại những gì được hỏi; bạn không cần bình luận mọi thứ. Và bằng cách không giải thích những gì bạn đang làm, trong nhận xét của tôi, tôi có nghĩa là nói chung bạn giới thiệu chiến lược mà bạn đề xuất với ít nhất một đoạn ngắn. (ví dụ: "Một trong những kỹ thuật là sử dụng định dạng tệp nhị phân với toán tử luồng. Bạn phải cẩn thận để đọc và viết theo cùng một thứ tự, bla bla lba").
Vaillancourt

2
Và bằng cách sử dụng gotos, bạn sẽ nhận được ở nơi công cộng. Đừng dùng gotos.
Vaillancourt
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.