第12章 ファイル操作

12-2 ハイスコア情報を保存するには?

スコアがつくゲームでは、最高得点(ハイスコア)やランキング情報を保存するケースが多い。ゲーム実行中に表示するだけであれば問題ないが、ゲームを終わってもスコアを残し、次にゲームを始めるときに、過去の最高得点を表示させるには、そのスコアをファイルとして保存しておく必要がある。
ここでは、一番シンプルな例として、ハイスコアのみを保存・表示するサンプルを紹介する。

プログラム概要

ゲームをクリアするたびに、保存してある得点よりも高い得点(サンプルプログラムでは、より早いタイム)であれば、ハイスコアを更新するプログラムを考える。
インストール直後は得点が登録されていないため、一番最初にクリアしたときの得点を無条件に保存する。それ以降は、保存してある得点と、新たにクリアしたときの得点を比べる。

ハイスコア表示にもいろいろなパターンが考えられるが、今回は、スタート画面とゲーム画面に、常にハイスコアが表示されるようなプログラムにする。

《フローチャート》

変更1:ファイル操作用ソースファイルの作成

ファイル処理はゲームによっていろいろ変わり、しかもどの場面で行うかも変わるため、ファイル処理用のソースを作成したほうがよい(と思う)。よって、「File.cpp」と「File.h」を作成する。

《File.cpp》

//=============================================================================
//	ファイル処理関係の自作関数群
//=============================================================================
#include "common.h"

//-----------------------------------------------------------------------------
// グローバル変数
//-----------------------------------------------------------------------------
static char FileName[] = "HiScore.txt";	// ハイスコアを格納するファイル名

//-----------------------------------------------------------------------------
// 関数名 : HiScore_Load() 
// 機能概要: ハイスコアを読み込む
//-----------------------------------------------------------------------------
int HiScore_Load(void)
{
    FILE *fp;
    int HiScore;

    if ((fp = fopen(FileName, "r")) == NULL)
        HiScore = 999999;
    else
    {
        fscanf(fp, "%d", &HiScore);
        fclose(fp);
    }

    return (HiScore);

}

//-----------------------------------------------------------------------------
// 関数名 : HiScore_Save() 
// 機能概要: ハイスコアを保存する
//-----------------------------------------------------------------------------
void HiScore_Save(int NewScore)
{
    FILE *fp;
    char mes[80];

    if ((fp = fopen(FileName, "w")) == NULL)
    {
        wsprintf(mes, "データファイルのオープンに失敗しました。保存を中止しました。\n");
        OutputDebugString(mes);
        return;
    }

    fprintf(fp, "%d", NewScore);
    fclose(fp);

}
  1. HiScore_Load関数は、データファイルからハイスコアを読み込み、変数に格納する。ファイルが存在しない(fopenに失敗した)場合は、一度もクリアされていないとして、一番大きな数を入れておく(クリア時間の速さを競うため、数値が小さければ小さいほど高得点になる)。
  2. HiScore_Save関数は、引数で与えられたスコアをデータファイルに書き込む。

これらの関数は、他のソースで使うため、ヘッダ・ファイルを作成する。

《File.h》

//-----------------------------------------------------------------------------
// File.cppの関数のうち、他のソースで利用される関数のプロトタイプ宣言
//-----------------------------------------------------------------------------
int HiScore_Load(void);
void HiScore_Save(int);

ヘッダ・ファイルを作成しただけでは他のソースからは使えないため、common.hでインクルードする。

《Common.h(修正分)》

//-----------------------------------------------------------------------------
// インクルード・ファイル
//-----------------------------------------------------------------------------
#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include <ddraw.h>
#include "myDraw.h"
#include "File.h"
#include "Start.h"
#include "Game.h"
 ・
 ・
 ・

変更2:ハイスコアの読み込みと表示

ハイスコアはゲーム起動時に読み込み、スタート画面でもゲーム画面でも表示させるので、(この場合は)WinMain.cppで行うのがよいと思われる。

まず、ハイスコア格納用変数をWinMain.cppで宣言する。

//-----------------------------------------------------------------------------
// 外部変数(本体)
//-----------------------------------------------------------------------------
FRAMENO g_FrameNo = START_INIT;  // フレーム選択用
BYTE KeyTbl[256];  // キー情報
RECT ScreenRect = {0, 0, 640, 480};  // ウィンドウの矩形
DWORD nowTickCount;  // 時間制御用
int HiScore;  // ハイスコア格納用
 ・
 ・
 ・

この変数は、他のソースでも使うため、外部変数として宣言しなければならない。よって、common.hに追加する。

//-----------------------------------------------------------------------------
// 外部変数(本体は別ソース)
//-----------------------------------------------------------------------------
/* DirectDrawオブジェクト関係 */
 ・
 ・
 ・

/* 共通 */
extern FRAMENO g_FrameNo;  // フレーム選択用
extern BYTE KeyTbl[256];  // キー情報
extern RECT ScreenRect;  // ウィンドウの矩形
extern DWORD nowTickCount;  // 時間制御用
extern int HiScore;  // ハイスコア保存用
 ・
 ・
 ・

次に、ゲーム起動時にハイスコアを読み込むよう、WinMain関数を次のように修正する。

//=============================================================================
//	ウィンドウメイン関数(WinMain)
//=============================================================================
int WINAPI WinMain(HINSTANCE hThisInst, HINSTANCE hPrevInst, LPSTR lpszArgs, int nWinMode)
{
    HWND    hWnd;
    MSG     msg;

    /* 表示するウィンドウの定義、登録、表示 */
    if (!(hWnd = InitApp(hThisInst, nWinMode)))
        return FALSE;

    /* Direct Draw Object の初期化 */
    if (InitializeDraw(hWnd) != DD_OK)
        return FALSE;

    /* ハイスコアをロードする */
    HiScore = HiScore_Load();

   /* ゲーム・ループ */
    while (TRUE)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))  // メッセージがあるかどうか
        {
            if (!GetMessage(&msg, NULL, 0, 0))  // メッセージを取得し、WM_QUITかどうか
                break;
            TranslateMessage(&msg);		//キーボード利用を可能にする
            DispatchMessage(&msg);		//制御をWindowsに戻す
        }
        else  // ゲームメイン処理
        {
            UpdateFrame(hWnd);
            Sleep(1);
        }
    }

    return msg.wParam;

}

次に、ハイスコアを表示する処理をUpdateFrame関数に追加する。

//-----------------------------------------------------------------------------
// 関数名 : UpdateFrame()
// 機能概要: 画面更新処理
//-----------------------------------------------------------------------------
static void UpdateFrame(HWND hWnd)
{
    static int fps = 0, wfps = 0;  // FPSカウント
    char buff[80]; // 文字列表示用バッファ

    /* 現在の時間を取得 */
    nowTickCount = timeGetTime(); 

    /* 現在のキー情報を取得 */
    if (!GetKeyboardState(KeyTbl))
        return;

    /* 処理の振り分け */
    switch (g_FrameNo)
    {
        case START_INIT:
            StartInit(hWnd);
        case START_FRAME:
            StartFrame(hWnd);
            break;
        case GAME_INIT:
            GameInit(hWnd);
        case GAME_STARGE_INIT:
            GameStargeInit(hWnd);
        case GAME_FRAME:
            GameFrame(hWnd);
            break;
        case GAME_OVER:
            GameOverFrame(hWnd);
            break;
        case GAME_CLEAR:
            GameClearFrame(hWnd);
            break;
        default:
            OutputDebugString("g_FrameNoの値が例外です。\n");
    }

    /* ハイスコアを表示する */
    wsprintf(buff, "HISCORE %06d", HiScore);
    if (!StringDraw(buff, 500, 0, 1))
        return;
    
    /* FPSを求めて表示する */
    fps++;
    if (nowTickCount - backTickCount >= 1000)
    {
        wfps = fps;
        backTickCount = nowTickCount;
        fps = 0;
    }
    wsprintf(buff, "%04d FPS", wfps);
    if (!StringDraw(buff, 0, 0, 1))
        return;

    /* フリップ処理 */
    g_pDDSPrimary->Flip(NULL, 0);

}

変更3:ハイスコアの更新

ゲームクリア時、ハイスコアとクリア時間を調べ、クリア時間のほうが小さければハイスコアを更新しなければならない。
ゲームクリア時初期化処理用関数GameClearInit関数を、次のように修正する。

//-----------------------------------------------------------------------------
// 関数名 : GameClearInit()
// 機能概要: ゲームクリア処理前初期化処理
//-----------------------------------------------------------------------------
void GameClearInit(HWND hWnd)
{
    EndTime = nowTickCount;
    if ((int)(EndTime - MyChara.StartTime) < HiScore) // ハイスコアを更新
    {
        HiScore = (int)(EndTime - MyChara.StartTime);
        HiScore_Save(HiScore);
    }
    g_FrameNo = GAME_CLEAR;

}

補足

ハイスコアを格納するデータファイル(HiScore.txt)は、プロジェクトフォルダ内に作成される。
現在保存されているハイスコアをクリアしたければ、データファイルを削除すればよい。

第12章2練習問題1(必須)

上記修正を行い、ハイスコアが表示・更新されるかを確かめなさい。

第12章2練習問題2(自由)

ハイスコアを更新した場合、更新されたことが分かるようなメッセージを表示するようプログラムを修正しなさい(ビットマップファイルを編集してもよい)。


[ TOP ]