Como crio um arquivo salvo para um jogo em C ++?

33

Estou codificando minha final para um curso de Programação de videogame e quero saber como criar um arquivo salvo para o meu jogo, para que um usuário possa jogar e depois voltar mais tarde. Qualquer idéia de como isso é feito, tudo que eu fiz antes foi em programas de execução única.

Tucker Morgan
fonte
2
Você também pode usar o SQLite
Nick Shvelidze
1
@Shvelo Embora você possa fazer isso, parece que adicionaria muita complexidade que não é necessariamente necessária.
Nate

Respostas:

38

Você precisa usar a serialização para salvar suas variáveis ​​na memória no disco rígido. Existem muitos tipos de serialização. O .NET XML é um formato comum, embora haja serializadores binários e JSON disponíveis. Não sou muito programador em C ++, mas uma pesquisa rápida mostrou um exemplo de serialização em C ++:

Existem bibliotecas que oferecem funcionalidades de serialização. Alguns são mencionados em outras respostas.

As variáveis ​​nas quais você estará interessado provavelmente estarão relacionadas ao estado do jogo. Por exemplo, você provavelmente desejará conhecer esse tipo de informação

  1. O jogador estava jogando nível 3
  2. O jogador estava em X, Y coordenadas mundiais
  3. O jogador tem três itens na mochila
    1. Arma
    2. armaduras
    3. Comida

Você realmente não se importa com as texturas que estão sendo usadas (a menos que seu jogador possa mudar sua aparência, esse é um caso especial), porque elas geralmente são as mesmas. Você precisa se concentrar em salvar dados importantes do estado do jogo.

Ao iniciar o jogo, você inicia normalmente um jogo "novo" (isso carrega suas texturas, modelos etc.), mas no momento apropriado você carrega os valores do seu arquivo salvo no objeto de estado do jogo, substituindo o novo "padrão" estado do jogo. Em seguida, você permite que o player continue a jogar.

Simplifiquei bastante aqui, mas você deve ter uma ideia geral. Se você tiver uma pergunta mais específica, faça uma nova pergunta aqui e podemos tentar ajudá-lo.

Nate
fonte
Eu entendo o que eu preciso para salvar, mas o que eu gostaria de saber qual é a maneira exata, você salve-o em um arquivo .txt no projeto, essas variáveis modificados, ou alguma outra forma
Tucker Morgan
Sim, se o seu jogo for simples, um arquivo de texto pode ser suficiente; você precisa ter em mente que qualquer um pode editar um arquivo de texto e, assim, fazer a sua própria poupança de jogos ...
Nate
Os arquivos de texto salvos não são apenas para jogos simples. O Paradox usou um formato de texto estruturado para salvar arquivos para todos os jogos que eles criaram usando o mesmo mecanismo que o principal motor Europa Universalis. Especialmente jogo atrasado, esses arquivos podem ser enormes.
Dan Neely 21/03
1
@DanNeely Um ponto justo, não há razão para que você não possa usar um formato de texto para armazenar muitos dados complicados, mas de um modo geral, quando seus dados são tão complicados, os benefícios de outro formato (binário, xml, etc.) se tornam mais pronunciados.
Nate
1
@NateBross concordou. Os jogos Paradox eram muito amigáveis ​​com mod e usavam um formato semelhante (idêntico?) Para os dados do cenário. Armazenar a maioria de seus dados como texto significava que eles não precisavam investir em ferramentas do editor de cenários para uso público.
Dan Neely 21/03
19

Normalmente, isso é específico para o seu jogo. Tenho certeza que você aprendeu a escrever e ler arquivos de suas aulas até agora. A ideia básica é:

  1. Ao sair do jogo, escreva os valores que deseja salvar em um arquivo.
  2. Ao carregar o jogo, verifique se existe um arquivo salvo, caso exista, carregue os valores lidos no seu programa. Se o arquivo não existir, continue como agora e defina os valores para os valores iniciais / padrão.

O que você escreve depende de você, depende do seu jogo. Uma maneira de escrever é escrever as variáveis ​​que você deseja em uma ordem específica como um fluxo de bytes. Em seguida, ao carregar, leia-os no seu programa na mesma ordem.

Por exemplo (no pseudo-código rápido):

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
fonte
1
Esse método é bom e pequeno, embora eu recomende colocar algumas tags simples para pedaços de dados. Dessa forma, se mais tarde você precisar alterar algo que normalmente está no meio do arquivo, poderá fazê-lo e a única "conversão do antigo" que você precisará fazer será dentro desse único bloco. Não é tão importante para uma tarefa única, mas se você continuar trabalhando depois que as pessoas começarem a obter arquivos salvos, será um pesadelo usar apenas bytes retos, com a posição sendo o único identificador.
Lunin
1
Sim, isso não gera arquivos salvos prontos para o futuro. Também não funciona para dados com tamanhos de bytes variáveis, como cadeias de caracteres. O último é fácil de corrigir, primeiro escrevendo o tamanho dos dados que estão prestes a serem gravados e depois usando-os ao carregar para ler o número correto de bytes.
MichaelHouse
6

Provavelmente, existe um grande número de maneiras de fazer isso, mas a mais simples que sempre achei e usei tanto pessoal quanto profissionalmente é criar uma estrutura que contenha todos os valores que desejo salvar.

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
}

Depois, escrevo / lemos os dados de e para um arquivo usando os valores básicos de IO do arquivo. O inventárioCont é o número de estruturas de itens que são salvas após a estrutura principal SaveGameData no arquivo, para que eu saiba quantas delas serão lidas após a busca desses dados. A chave aqui é que, quando eu quero salvar algo novo, a menos que seja uma lista de itens ou algo parecido, tudo o que tenho que fazer é adicionar um valor à estrutura em algum lugar. Se for uma lista de itens, terei que adicionar um passe de leitura, como já impliquei para os objetos Item, um contador no cabeçalho principal e depois as entradas.

Isso tem a desvantagem de tornar incompatíveis versões diferentes de um arquivo salvo sem tratamento especial (mesmo que sejam apenas valores padrão para cada entrada na estrutura principal). Mas, no geral, isso facilita a extensão do sistema, apenas adicionando um novo valor de dados e colocando um valor quando necessário.

Novamente, algumas maneiras de fazer isso e isso podem levar mais ao C do que ao C ++, mas ele já fez o trabalho!

James
fonte
1
Também vale a pena notar que isso não é independente de plataforma, não funcionará para cadeias de caracteres C ++, nem para objetos referidos por referências ou ponteiros, ou quaisquer objetos que contenham qualquer um dos itens acima!
Kylotan
Por que essa plataforma não é independente? Funcionou bem nos sistemas PC, PS * e no 360 .. fwrite (pToDataBuffer, sizeof (tipo de dados), countOfElements, pToFile); obras para todos aqueles objetos supondo que você pode obter um ponteiro para os seus dados e o tamanho do objeto e, em seguida, o número deles que você quer escrever .. e ler correspondências que ..
James
Ele é independente de plataforma, simplesmente não há garantia de que os arquivos salvos em uma plataforma pode ser carregado em um outro. O que é bastante irrelevante para, por exemplo, salvar dados de jogos. O material ponteiro para dados e tamanho de memória pode obviamente ser um pouco estranho, mas funciona.
leftaroundabout
3
Na verdade, não há garantia de que ele continuará funcionando para você para sempre - o que acontece se você lançar uma nova versão criada com um novo compilador ou mesmo novas opções de compilação que alterem o preenchimento da estrutura? Eu recomendaria fortemente, fortemente, o uso de raw-struct fwrite () apenas por esse motivo (estou falando da experiência neste, aliás).
Maçante
1
Não se trata de '32 bits de dados'. O pôster original está simplesmente perguntando "como faço para salvar minhas variáveis". Se você escrever a variável diretamente, perderá as informações nas plataformas. Se você tiver que pré-processar antes da escrita, você deixou de fora a parte mais importante da resposta, ou seja. como processar os dados para que sejam salvos corretamente e incluam apenas o bit trivial, ou seja. chamando fwrite para colocar algo em um disco.
Kylotan
3

Primeiro, você precisa decidir quais dados precisam ser salvos. Por exemplo, pode ser a localização do personagem, sua pontuação e o número de moedas. Obviamente, seu jogo provavelmente será muito mais complexo e, portanto, você precisará salvar dados adicionais, como o número do nível e a lista de inimigos.

Em seguida, escreva o código para salvá-lo em um arquivo (use ofstream). Um formato relativamente simples que você pode usar é o seguinte:

x y score coins

E assim o arquivo ficaria assim:

14 96 4200 100

O que significa que ele estava na posição (14, 96) com uma pontuação de 4200 e 100 moedas.

Você também precisa escrever um código para carregar este arquivo (use ifstream).


É possível salvar os inimigos incluindo sua posição no arquivo. Podemos usar este formato:

number_of_enemies x1 y1 x2 y2 ...

Primeiro, number_of_enemiesé lido e, em seguida, cada posição é lida com um loop simples.

Pubby
fonte
1

Uma adição / sugestão seria adicionar um nível de criptografia à sua serialização para que os usuários não possam editar seus valores em texto para "9999999999999999999". Uma boa razão para fazer isso seria evitar estouros de número inteiro (por exemplo).

Styler
fonte
0

Eu acho que sua melhor aposta é boost :: serialization porque é fácil de propagar na sua hierarquia. Você só precisa chamar a função de salvar / carregar do objeto superior e todas as suas classes são criadas facilmente.

Consulte: http://www.boost.org/doc/libs/1_49_0/libs/serialization/doc/index.html

Killrazor
fonte
0

Por uma questão de completude, quero mencionar uma biblioteca de serialização c ++, que eu pessoalmente uso e ainda não foi mencionada: cereal .
É fácil de usar e possui uma sintaxe agradável e limpa para serialização. Ele também oferece vários tipos de formatos nos quais você pode salvar (XML, Json, Binário (incluindo uma versão portátil com os respectivos aspectos endianess)). Ele suporta herança e é somente cabeçalho,

LukeG
fonte
0

Seu jogo comprometerá as estruturas de dados (espero?) Que você precisa transformar em bytes (serializar) para poder armazená-las. Em um futuro, você poderá carregar esses bytes de volta e transformá-los novamente em sua estrutura original (desserialização). Em C ++, não é tão complicado, pois a reflexão é muito limitada. Mas ainda assim, algumas bibliotecas podem ajudá-lo aqui. Eu escrevi um artigo sobre isso: https://rubentorresbonet.wordpress.com/2014/08/25/an-overview-of-data-serialization-techniques-in-c/ Basicamente, eu sugiro que você dê uma olhada biblioteca de cereais se você tiver como alvo os compiladores C ++ 11. Não é necessário criar arquivos intermediários, como o protobuf, para que você economize algum tempo se desejar resultados rápidos. Você também pode escolher entre binário e JSON, para ajudar bastante na depuração aqui.

Além disso, se a segurança for uma preocupação, convém criptografar / descriptografar os dados que você está armazenando, especialmente se você estiver usando formatos legíveis por humanos, como JSON. Algoritmos como o AES são úteis aqui.

Rubén Torres Bonet
fonte
-5

Você precisa usar fstreampara arquivos de entrada / saída. A sintaxe é simples EX:

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

Ou

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

Outras ações são possíveis no seu arquivo: append , binário , trunc etc. Você usaria a mesma sintaxe acima, em vez de colocarmos std :: ios: :( flags), por exemplo:

  • ios::out para operação de saída
  • ios::in para operação de entrada
  • ios::binary para operação IO binária (byte bruto), em vez de baseada em caracteres
  • ios::app para começar a escrever no final do arquivo
  • ios::trunc pois se o arquivo já existir, substitua, exclua o conteúdo antigo e substitua por novo
  • ios::ate - posicione o ponteiro do arquivo "no final" para entrada / saída

Ex:

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

Aqui está um exemplo mais completo, mas simples.

#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;
}
Francisco Forcier
fonte
4
-1 Esta é uma resposta muito ruim. Você deve formatar e exibir corretamente o código e explicar o que está fazendo, ninguém quer decifrar um pedaço de código.
Vaillancourt
Obrigado Katu para o comentário que você está certo que eu deveria explicar o meu código mais bem você pode me dizer como eu formatar meu fonte do website porque eu sou novo para esse tipo de coisa
Francisco Forcier
A partir deste site ou para este site? Para obter ajuda na formatação das postagens, você pode visitar a página de ajuda da formatação . Há um ponto de exclamação ao lado do cabeçalho da área de texto que você usa para postar para ajudá-lo também.
Vaillancourt
Tente documentar o que é pedido; você não precisa comentar tudo. E, ao não explicar o que você estava fazendo, no meu comentário, eu quis dizer que geralmente você introduz a estratégia que sugere com pelo menos um parágrafo curto. (por exemplo, "Uma das técnicas é usar um formato de arquivo binário com um operador de fluxo. Você deve ter o cuidado de ler e escrever na mesma ordem, bla bla lba").
Vaillancourt
2
E, usando gotos, você será linchado no local público. Não use gotos.
Vaillancourt