목차

JSON Save and Load

  

스타팅 프로젝트

필요성

게임에서 저장과 불러오기는 기본이다. 내 진행사항을 저장하지 않는다면 다시 처음부터 시작해야 하기 때문이다.

파일을 저장하는 방법은 여러가지가 있을 것이다.

가장 날 것으로 저장하는 방법은 파일을 이진(Binary)로 저장하는 것이다. Raylib는 바로 이 방법을 예제로 알려주고 있다.

그런데 위 예제로 변수들을 저장하다보면 첫째, 사람이 파일을 열어봐서 이해하는 것은 사실상 불가능하고, 둘째, 세이브해야 할 변수들이 많아진다면 조작해야 할 것들이 매우 많아진다.

Raylib에서 제안한 방식은 int 변수를 저장하는 것이다. 이를 float로 변경하여 저장해 보았는데, 수정한 Save Load예제의 소스를 보면 알겠지만 변수가 많아지면 코드가 많이 길어지게 될 것으로 보여서 보기만 해도 짜증난다.

그리고 무엇보다도, 일단 사람이 볼 수 있는 파일을 저장하는 것이 목표이므로1), 위와 같은 이진 파일 형식은 지양하기로 한다. 다만 참고로 나중에 사람들이 쉽게 고치지 못하는 세이브 파일을 만들 가능성도 있으므로 수정한 Save Load예제는 메모해 두도록 하자.

사람이 볼 수 있게 하는 방식은 windows 환경에서는 ini파일로 만드는 방법도 있지만, 나는 json 파일을 만드는 것을 목표로 하였다. 왜냐하면 ini파일은 세팅값을 저장하는 데에는 탁월하지만 타일맵과 같이 여러가지 변수를 저장하는데에는 조금 부족하다고 생각하기 때문이다.

보다 범용적으로 데이터 통신에 이용하는 json 파일을 만들도록 하자.

JSON 라이브러리 추가

여러가지 JSON라이브러리를 테스트 해봤는데, 현재 가장 사용하기 편한 라이브러리는 nlohmann Json Library였다.

라이브러리의 파일크기가 다소 커서 조금 마음에는 안 들었지만, 이를 대체할 만한 다른 가벼운 라이브러리는 json으로 변환한 데이터를 다시 파일로 변환하는 것을 지원하지 않았기 때문에 사용이 까다로웠다. 우리는 게임 만드는 것에 집중할 것이므로 JSON을 어떻게 Parsing할 것인가에 대하여는 깊게 파고들 여유가 없다. 따라서 다소 파일 크기는 크기만 nlohmann Json Library라이브러리를 사용하자.

singleinclude 폴더에 있는 json.hpp만 다운받아서 내 프로젝트 폴더에 추가하면 된다.

JSON파일 입출력 함수

1. config.h

환경변수를 저장할 것이므로 환경변수를 저자아고 있는 config 파일에서 JSON 파일 입출력을 수행할 예정이다.

nlohmann Json Library를 사용하려면 해당 라이브러리 외에 파일입출력을 담당하는 CPP 헤더파일도 선언해야 한다.

#include "utils/json.hpp"
#include <iostream>
#include <fstream>

이를 포함한 헤더파일의 예제는 다음과 같다.

config.h
#pragma once
#ifndef __CONFIG__
#define __CONFIG__
#include "utils/json.hpp"
#include <iostream>
#include <fstream>
// 로고, 메인메뉴, 게임 화면 등 각각의 씬
enum class Scene 
{
    logo = 0, titleMenu, game, scoreScene, settingScreen
};
 
// 전역적으로 사용할 싱글톤 클래스
class SceneManager 
{
    private:
        SceneManager() {} 
        static SceneManager* instance; 
 
    public:
 
        static SceneManager* GetInstance()
        {
            if (instance != nullptr)
            {
                return instance;
            }else
            {
                instance = new SceneManager();
                return instance;
            }
        }
 
        bool SaveJson();
        bool LoadJson();
        bool gameExit;
        Scene scene;
        Scene lastSce;
        float volume = 1.0f;
        bool isLog = false;
};
 
 #endif

2. config.cpp

다음에는 실제 JSON파일을 만들고 불러오는 코드를 보자.

config.cpp
#include "config.h"
 
// Save Setting to json file
bool SceneManager::SaveJson()
{
    using namespace nlohmann;
 
    json j;
    j["Name"] = "DK Flappy Bird!";
    j["SoundVolume"] = volume;
    j["bLog"] = isLog;
 
 
    // write prettified JSON to another file
    std::ofstream o("settings.json");
    if (o.bad())
    {
        return false;
    }
 
    o << std::setw(4) << j << std::endl;
    o.close();
 
    return true;
}
 
bool SceneManager::LoadJson()
{
    using namespace nlohmann;
 
    std::ifstream f("settings.json");
 
    if (f.bad())
    {
        return false;
    }
 
    json data = json::parse(f);
    volume = data["SoundVolume"];
    isLog = data["bLog"];
    f.close();
    return true;
 
}

위 예제만으로 바로 이해가 갈 것이다. 바로 이 이유 때문에 크기가 다소 크더라도 nlohmann Json Library를 선택하였다.

JSON을 만들 때에는 Json을 선언한 이후에, 배열과 같은 이름으로 각 엘리멘트를 저장할 수 있다.

파일을 만드는 것 역시 라이브러리가 자체적으로 파일 스트림에 json 데이터를 바로 저장할 수 있게 함수를 구현해줬으므로 이를 그대로 이용하면 된다. 이렇게 파일을 만들면 다음과 같은 settings.json 파일이 프로그램 실행 파일이 있는 폴더에 저장된다.

{
    "Name": "DK Flappy Bird!",
    "SoundVolume": 0.5074999928474426,
    "bLog": true
}

파일을 읽는 것 역시 위 예제를 참고하자

함수 호출

1. Setting Screen에서 호출

JSON으로 파일 입출력이 되면 Setting Screen에서 성공 혹은 실패 문구가 나오게 만들었다.

// Save Settings
void SettingScreen::Save()
{
    if(menuManager->SaveJson())
    {
        isDisplayTimer = true;
        DisplayTimer = 180;
        DisplayString = "Successfully Saved!\n" ;
    }else
    {
        isDisplayTimer = true;
        DisplayTimer = 180;
        DisplayString = "Save Failed!\n";
    }
}
 
// Load Settings
void SettingScreen::Load()
{
    if(menuManager->LoadJson())
    {
        isDisplayTimer = true;
        DisplayTimer = 180;
        DisplayString = "Successfully Loaded!\n";
    }else 
    {
        isDisplayTimer = true;
        DisplayTimer = 180;
        DisplayString = "Load Failed!\n";
    }
}

2. main.cpp에서 호출

프로그램을 시작할 때 이전에 저장한 JSON파일을 불러와서 환경 설정을 하도록 하였다.

    // InitWIndow() 이후에 선언해 줘야 한다.
    Logo logo = Logo();
    Game game = Game();
    Menu menu = Menu();
    SettingScreen setting = SettingScreen();
    menuManager->LoadJson();

결론

이런 식으로 하면 사람이 읽어보기 편한 JSON파일이 프로그램 실행 폴더에 만들어짐을 알 수 있다.

JSON Save and Load

이제 게임에 필요한 기능들은 거의 다 만들어 본 것 같다.

JSON라이브러리 및 CPP에서 제공하는 파일 입출력 라이브러리(fstream, iostream)을 같이 컴파일 하다보니 파일크기가 급격하게 커졌다.

실행파일이 무려 9MB로 되어서 조금 마음에 안들긴 하지만 향후 확장성을 위하여 컴파일 크기에 대한 집착은 버리자.

지금까지 만든 소스파일은 다음과 같다.

JSON파일로 저장하기와 불러오기

그 사이에 한글 비트맵 폰트를 완벽 지원하는 헬퍼 유틸리티도 만들었다.

한글비트맵폰트 로더

다음에는 다국어 지원하기를 만들어 볼 것이다. JSON을 응용해 볼 것이다.

1)
이는 향후 타일맵을 만들 것이기 때문에 그렇기도 하다