C ++ 게임용 저장 파일을 어떻게 만듭니 까?


33

비디오 게임 프로그래밍 과정을 위해 결승을 코딩하고 있는데 사용자가 게임을하고 나중에 다시 올 수 있도록 게임용 저장 파일을 만드는 방법을 알고 싶습니다. 이것이 어떻게 이루어 졌는지, 내가 전에 한 모든 일은 단일 실행 프로그램이었습니다.



2
SQLite를 사용할 수도 있습니다
Nick Shvelidze

1
@Shvelo 그렇게 할 수는 있지만 반드시 필요하지 않은 많은 복잡성을 추가하는 것처럼 보입니다.
Nate

답변:


38

직렬화를 사용해야합니다변수를 메모리의 하드 드라이브에 저장 를 합니다. 사용 가능한 바이너리 및 JSON 직렬 변환기가 있지만 .NET XML은 일반적인 형식으로 많은 직렬화 유형이 있습니다. 나는 C ++ 프로그래머가 아니지만 빠른 검색은 C ++의 직렬화에 대한 예를 제시했습니다.

직렬화 기능을 제공하는 라이브러리가 있습니다. 일부는 다른 답변에 언급되어 있습니다.

관심있는 변수는 게임 상태와 관련이있을 것입니다. 예를 들어이 유형의 정보를 알고 싶을 것입니다.

  1. 플레이어가 레벨 3을하고있었습니다
  2. 플레이어는 X, Y 월드 좌표에있었습니다
  3. 플레이어는 배낭에 3 개의 아이템을 가지고 있습니다
    1. 무기
    2. 갑옷
    3. 식품

어떤 텍스처가 사용되고 있는지는 신경 쓰지 않아도됩니다 (플레이어가 모양을 바꿀 수 없다면 특별한 경우입니다). 중요한 게임 상태 데이터 저장에 집중해야합니다.

게임을 시작할 때 "새로운"게임 (텍스처, 모델 등이로드 됨)에 대해 정상적으로 시작하지만 적절한 시간에 저장 파일의 값을 게임 상태 오브젝트로 다시로드하여 "기본"을 바꿉니다. 게임 상태. 그런 다음 플레이어가 재생을 다시 시작하도록 허용합니다.

여기에서 크게 단순화했지만 일반적인 아이디어를 얻어야합니다. 보다 구체적인 질문이 있으시면 여기에 새로운 질문을하시면 도와 드리겠습니다.


내가 저장해야하는 것을 이해하지만 정확한 방법이 무엇인지 알고 싶은가, 프로젝트의 .txt 파일, 수정 된 변수 또는 다른 방법으로 저장합니까?
Tucker Morgan

예, 게임이 단순하다면 텍스트 파일이면 충분합니다. 당신은 누구나 텍스트 파일을 편집하여 그들 자신의 세이브 게임을 만들 수 있다는 것을 명심해야합니다.
Nate

텍스트 파일 저장은 단순한 게임을위한 것이 아닙니다. Paradox는 플래그십 Europa Universalis 엔진과 동일한 엔진을 사용하여 생성 한 모든 게임의 파일을 저장하기 위해 구조화 된 텍스트 형식을 사용했습니다. 특히 게임이 늦었을 때이 파일들은 엄청날 수 있습니다.
Dan Neely

1
@DanNeely 공정한 요점, 많은 복잡한 데이터를 저장하기 위해 텍스트 형식을 사용할 수는 없지만, 일반적으로 데이터가 복잡 할 때 다른 형식 (이진, xml 등)의 이점이 더욱 두드러집니다.
Nate

1
@NateBross 동의합니다. Paradox 게임은 매우 친숙하고 시나리오 데이터에 대해 유사한 (동일?) 형식을 사용했습니다. 대부분의 데이터를 텍스트로 저장한다는 것은 공개 용도로 시나리오 편집기 도구에 투자 할 필요가 없음을 의미했습니다.
Dan Neely

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
이 방법은 훌륭하고 작지만 데이터 덩어리에 간단한 태그를 넣는 것이 좋습니다. 나중에 나중에 파일 중간에있는 것을 변경해야하는 경우 그렇게 할 수 있으며 "이전에서의 변환"만 해당 블록 내에 있습니다. 일회성 과제에는 중요하지 않지만 사람들이 파일 저장을 시작한 후에 작업을 계속하면 위치가 유일한 식별자 인 직선 바이트를 사용하는 것은 약간의 악몽입니다.
Lunin

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 값을 사용하여 파일에 데이터를 fwrite / fread합니다. inventoryCount는 파일의 기본 SaveGameData 구조 이후에 저장되는 Item 구조의 수이므로 해당 데이터를 가져온 후 읽을 구조 수를 알고 있습니다. 여기서 핵심은 항목 목록 등이 아닌 한 새로운 것을 저장하고 싶을 때 구조에 값을 추가하는 것입니다. 항목 목록 인 경우 이미 Item 개체, 기본 헤더의 카운터 및 항목에 대해 암시 한 것처럼 읽기 패스를 추가해야합니다.

이것은 주 버전 구조의 각 항목에 대한 기본값 일지라도 특별한 처리없이 서로 다른 버전의 저장 파일을 서로 호환되지 않는 단점이 있습니다. 그러나 전반적으로 새로운 데이터 값을 추가하고 필요할 때 값을 입력하면 시스템을 쉽게 확장 할 수 있습니다.

다시 말하지만,이 작업을 수행하는 몇 가지 방법이 있으며 C ++보다 C로 더 많이 이끌 수 있지만 작업이 완료되었습니다!


1
또한 이것은 플랫폼에 독립적이지 않으며 C ++ 문자열이나 참조 또는 포인터를 통해 참조되는 객체 또는 위의 항목을 포함하는 객체에는 작동하지 않습니다!
Kylotan

이 플랫폼이 왜 독립적이지 않습니까? PC, PS * 시스템 및 360에서 제대로 작동했습니다. fwrite (pToDataBuffer, sizeof (datatype), countOfElements, pToFile); 데이터에 대한 포인터를 얻을 수 있다고 가정하고 해당 객체의 크기와 작성하려는 객체의 수 및 그 개수를 일치한다고 가정합니다.
James

그것은 이다 하나 개의 플랫폼에 저장된 파일이 다른 하나를로드 할 수있는 단지 보장이 없다, 플랫폼 독립적. 예를 들어 게임 데이터 저장과 관련이 없습니다. 포인터 대 데이터 및 크기 memcpy 항목은 분명히 조금 어색 할 수 있지만 작동합니다.
leftaroundabout

3
실제로 그것이 영원히 계속 작동한다고 보장 할 수는 없습니다. 새로운 컴파일러 또는 빌드 패딩을 변경하는 새로운 컴파일 옵션으로 빌드 된 새 버전을 출시하면 어떻게됩니까? 이 이유만으로 raw-struct fwrite () 사용을 강력히 권장합니다 (이것에 대한 경험에서 우연히 말하고 있습니다).
솜털 같은

1
그것은 '32 비트의 데이터 '에 관한 것이 아닙니다. 원래 포스터는 단순히 "변수를 저장하는 방법"을 묻습니다. 변수를 직접 작성하면 여러 플랫폼에서 정보가 손실됩니다. fwrite 전에 사전 처리를해야한다면 답의 가장 중요한 부분을 생략했습니다. 데이터가 올바르게 저장되고 사소한 비트 만 포함되도록 데이터를 처리하는 방법. fwrite를 호출하여 디스크에 무언가를 넣습니다.
Kylotan

3

먼저 어떤 데이터를 저장해야하는지 결정해야합니다. 예를 들어, 이것은 캐릭터의 위치, 점수 및 동전 수일 수 있습니다. 물론 게임은 훨씬 더 복잡 할 수 있으므로 레벨 번호 및 적 목록과 같은 추가 데이터를 저장해야합니다.

다음으로 이것을 파일에 저장하는 코드를 작성하십시오 (스트림 사용). 사용할 수있는 비교적 간단한 형식은 다음과 같습니다.

x y score coins

따라서 파일은 다음과 같습니다.

14 96 4200 100

이것은 그가 4200과 100 동전의 점수로 위치 (14, 96)에 있다는 것을 의미합니다.

또한이 파일을로드하는 코드를 작성해야합니다 (ifstream 사용).


파일에 위치를 포함시켜 적을 저장할 수 있습니다. 이 형식을 사용할 수 있습니다 :

number_of_enemies x1 y1 x2 y2 ...

먼저 number_of_enemies를 읽은 다음 간단한 루프로 각 위치를 읽습니다.


1

한 가지 추가 / 제안은 직렬화에 암호화 수준을 추가하여 사용자가 "9999999999999999999"로 값을 텍스트 편집 할 수 없도록합니다. 이를 수행하는 좋은 이유 중 하나는 정수 오버플로를 방지하는 것입니다 (예 :



0

완전성을 위해 개인적으로 사용하고 아직 언급하지 않은 c ++ 직렬화 라이브러리에 대해 언급하고 싶습니다 : cereal .
사용하기 쉽고 직렬화를위한 훌륭하고 깔끔한 구문이 있습니다. 또한 XML, Json, Binary (엔디안 관련 휴대용 버전 포함)에 저장할 수있는 여러 유형의 형식을 제공합니다. 상속을 지원하며 헤더 전용입니다.


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") ;

파일에서 append , binary , trunc 등의 다른 작업을 수행 할 수 있습니다. 위와 동일한 구문을 사용하는 대신 std :: ios : :( flags)를 입력합니다. 예를 들면 다음과 같습니다.

  • ios::out 출력 동 작용
  • ios::in 입력 조 작용
  • ios::binary 문자 기반 대신 이진 (원시 바이트) IO 작업
  • 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

당신은 당신이 바로 내가 당신이 내가 이런 종류의 일에 새로운 오전 원인 내가 웹 사이트에서 내 소스를 포맷하는 방법을 말해 줄 수 더 잘 내 코드를 설명해야한다있는 코멘트 katu 감사합니다
시스코 Forcier

에서 이 사이트 또는 에 대한 사이트? 게시물 형식에 대한 도움말을 보려면 형식 도움말 페이지를 방문 하십시오 . 텍스트 영역 헤더 옆에 느낌표가있어 도움을주기 위해 게시하는 데 사용됩니다.
Vaillancourt

요청 된 내용을 문서화하십시오. 모든 것을 언급 할 필요는 없습니다. 그리고 당신이하고있는 일을 설명하지 않으면 서, 나는 일반적으로 적어도 짧은 단락으로 제안하는 전략을 소개한다는 것을 의미했습니다. (예 : "기술 중 하나는 스트림 연산자와 함께 이진 파일 형식을 사용하는 것입니다. 같은 순서로 읽고 쓰는 데주의해야합니다. bla bla lba").
Vaillancourt

2
그리고 gotos 를 사용 하면 공공 장소에 갇히게됩니다. gotos를 사용하지 마십시오 .
Vaillancourt
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.