C言語入門|STEP19:セーブ・ロード処理の実装-ゲーム状態構造体とファイル操作

はじめに:途中でやめられるRPGは親切

STEP18では、マップ探索とイベント分岐の仕組みを解説しました。
STEP19では、RPGを「最後まで安心して遊べるもの」にするための
セーブ・ロード機能とイベント処理 を扱います。

このSTEPのポイントは、

  • ゲームの状態をまとめて保存する。
  • 再開時に正しく復元する。
  • イベント進行を一度きりに管理する。

という点です。
RPGでは、この仕組みがあるかどうかで完成度が大きく変わります。

セーブ対象となるゲーム状態とは

まず、セーブ対象を整理してみましょう。
このRPGでは、次の情報をまとめて保存します。

要素内容
パーティキャラのHP・MP・レベルなど
所持アイテムインベントリ配列
プレイヤー位置マップ上の座標
マップ状態敵や宝箱の消費状況
進行フラグボス討伐など

これらをまとめたものが、
GameState 構造体 です。

GameState 構造体の役割

ゲーム全体の状態は、
次のような構造体で管理されています。

typedef struct {
    Character party[PARTY_SIZE];
    int inv[ITEM_COUNT];
    int px, py;
    char map[MAP_H][MAP_W];
    int game_clear;
} GameState;

この設計のポイント

  • ゲームに必要な情報を1つに集約
  • セーブ・ロード処理が簡単
  • 関数引数として扱いやすい。

ゲーム全体=1つの構造体
という考え方は、とても重要です。

セーブ処理の基本構造

セーブ処理では、
GameState をそのままファイルに書き込みます。

static void save_game(const GameState* gs) {
    FILE* fp = fopen("save.dat", "wb");
    if (!fp) return;

    fwrite(gs, sizeof(GameState), 1, fp);
    fclose(fp);
}

何をしている処理か

  1. バイナリ書き込みモードでファイルを開く。
  2. GameState を一括で書き込む。
  3. ファイルを閉じる。

構造体を丸ごと保存するため、
処理がとてもシンプルです。

バイナリ保存を使う理由

テキストではなく、
バイナリ形式で保存している理由は次のとおりです。

理由内容
実装が簡単fwrite 1回で完了
読み書きが高速データ変換不要
学習向き仕組みが分かりやすい。

学習用RPGとしては、
最も分かりやすいセーブ方式 です。

ロード処理の基本構造

ロード処理は、
セーブの逆を行います。

static int load_game(GameState* gs) {
    FILE* fp = fopen("save.dat", "rb");
    if (!fp) return 0;

    fread(gs, sizeof(GameState), 1, fp);
    fclose(fp);
    return 1;
}

ロード処理の流れ

  1. ファイルが存在するか確認
  2. GameState を一括で読み込み。
  3. 読み込み成功を返す。

失敗時には、
新規ゲームに進む設計です。

セーブ・ロードをメニュー化する理由

セーブとロードは、
マップ探索中に選択できます。

printf("s:セーブ  l:ロード  移動キー\n");

この設計のメリット

  • いつでも中断できる。
  • 状況に応じて使い分けできる。
  • テストプレイがしやすい。

操作性の良さ=遊びやすさ
につながっています。

マップ状態も保存している理由

GameState には、
マップ配列も含まれています。

char map[MAP_H][MAP_W];

これが重要な理由

  • 倒した敵が復活しない。
  • 開けた宝箱が再出現しない。
  • 探索済み状態が保持される。

単に位置だけを保存するのではなく、
世界の状態そのものを保存 しているのがポイントです。

イベントを一度きりにする仕組み

イベント処理では、
実行後にマップを書き換えます。

g_map[y][x] = '.';

この方法のメリット

  • フラグ管理が不要
  • マップが進行状況を記憶する。
  • 実装が非常にシンプル

イベントを「消す」ことで、
再発を防いでいます。

宿・回復イベントの処理例

回復イベントでは、
パーティ全体を回復します。

for (int i = 0; i < PARTY_SIZE; i++) {
    party[i].hp = party[i].max_hp;
    party[i].mp = party[i].max_mp;
}

なぜ全回復なのか

  • プレイヤーに安心感を与える。
  • 難易度を緩和する役割
  • 探索継続を後押しする。

イベントは、
ゲームバランス調整の重要な要素 です。

ゲームクリアフラグの役割

ボスを倒すと、
次のフラグが立ちます。

gs->game_clear = 1;

フラグ管理の意味

  • エンディング表示の分岐
  • 再戦を防ぐ
  • ゲーム終了条件を明確化

状態をフラグで管理することで、
処理がとても分かりやすくなります。

セーブデータが壊れた場合への配慮

ロード失敗時には、
次のように分岐します。

if (!load_game(&gs)) {
    printf("セーブデータがありません\n");
}

この処理がある理由

  • ファイル未存在に対応
  • ゲームが止まらない。
  • 安心して操作できる。

失敗を前提にした設計 は、
実用的なプログラムの基本です。

STEP19で押さえておきたいポイントまとめ

STEP19の重要ポイントはこちらです。

  • ゲーム状態は1つの構造体で管理する。
  • セーブは構造体を丸ごと保存する。
  • マップ状態も保存対象に含める。
  • イベントは一度きりで処理する。
  • フラグで進行状態を明確にする。