iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 28
3

引言

前幾天我們把遊戲的要素加得差不多了,再來我想就是存檔了~

對於我們的遊戲來說,現階段無法做到「讀取已儲存的檔案後,遊戲回到存檔時的地圖位置、連地圖元素都回復」的功能。但我們能做的是,以「關卡」為單位,一次跳一關,但只能跳到該關卡的最初始位置。

例如:我過了第一關,遊戲自動存檔。然後我在第二關玩到一半,遊戲關掉了,此時再次打開遊戲,會回到第二關開始的狀態,人物的數值也是只到第一關結束時的數值。也就是玩家必須能撐到一個關卡結束再關閉遊戲最不會有損失~

為了更方便展示存檔功能,我們先新增第二關Level2,以下是規格表:

項目 內容
地圖文字檔 level2.txt

大家下載後,將檔案放到maps資料夾。
然後一樣在LoadMap.h中加上這一行:

/* File: LoapMap.h */

#define LEVEL2_MAP "maps/level2.txt"

並在main.c中加上PlayGround:

/* File: main.c */

playGround(LEVEL2_MAP, 29, 2);  // 設定的位置就是level1結束時的對應位置

紀錄接下來的關卡編號 nextLevel

如果我們想做到「以關卡為單位的存檔、讀檔」,就必須可以跳關
但目前的設計是在main函數流水式的往下執行而已,也就是一定要一關關來,不能中途從第三關開始之類的。

所以我的想法是,創立一個紀錄「下一關即將去哪關」的變數nextLevel,並在main函數的PlayGround函數呼叫前先判斷目前的nextLevel值是多少,決定將要執行的關卡。

在PlayGround.h中加上這個宣告:

/* File: PlayGround.h */

int nextLevel;  // 下一關是哪一關

然後在initPlayerStatus中,加上初始化:

/* File: PlayGround.c */

void initPlayerStatus()
{
    p.key_a_num = 0;
    p.key_b_num = 0;
    p.key_c_num = 0;
    p.hp = 1000;
    p.atk = 10;
    p.def = 10;
    p.money = 0;

    shopPrice = 10;
    
// ------------新增部分-------------------
    nextLevel = 0;  // 一開始先去第0關
// --------------------------------------

}

在碰到"E"終點時,關卡數加1:

/* File: PlayGround.c */

// 請更改movePlayer中,終點的判斷式

if(game_map[y][x] == 'E')
{
    nextLevel += 1;  // 下一關

    playMusic(VICTORY, SE_MODE);
    printf("抵達終點!\n");
    system("pause");
    isPlaying = 0;
}

如此一來我們也能顯示目前的關卡數:

/* File: PlayGround.c */

void playerStatus()
{
    printf("[道具欄] 鑰匙A: %02d支  鑰匙B: %02d支  鑰匙C: %02d支\n", p.key_a_num, p.key_b_num, p.key_c_num);
    printf("[ 狀態 ] 生命值: %5d  攻擊力: %5d  防禦值: %5d\n", p.hp, p.atk, p.def);
    
    printf("[ 金錢 ] %8d        <Level %d>\n", p.money, nextLevel);  // 在金錢後面加上顯示關卡數
}

最後在main函數中,加上對關卡的判斷:

/* File: main.c */

// 在原先的PlayGround中,分別加上對應的判斷

if(nextLevel == 0)
{
    playGround(LEVEL0_MAP, 28, 4);
}
if(nextLevel == 1)
{
    playGround(LEVEL1_MAP, 28, 27);
}
if(nextLevel == 2)
{
    playGround(LEVEL2_MAP, 29, 2);
}

這邊筆者稍微解釋一下,例如我在遊戲一開始,將nextLevel設為1,那麼一開始if(nextLevel == 0)的判斷式就會被跳過,直接進入到level1,這樣就達成跳關的功能了。


存檔 saveData

這樣就準備萬全了,可以來設計存檔功能囉!

我打算設計自動存檔,在每關通過後將目前的數值以及目前關卡數通過檔案存起來。
相對應的,在遊戲一開始也會尋找儲存的檔案是否存在,存在的話就把數值匯到遊戲中。

/* File: PlayGround.h */

void saveData();  // 存檔函數
void loadData();  // 讀檔函數

我們先把savaData跟loadData放到合適呼叫的地方:

首先是saveData,要放在PlayGround.c中:

/* File: PlayGround.c */

// 一樣在終點的判斷式添加

if(game_map[y][x] == 'E')
{
    nextLevel += 1;  // 下一關

// ------------新增部分-------------------
    saveData();
    printf("存檔完成!\n");
// --------------------------------------

    playMusic(VICTORY, SE_MODE);
    printf("抵達終點!\n");
    system("pause");
    isPlaying = 0;
}

再來loadData要放在main.c中:

/* File: main.c */

// 放在準備呼叫PlayGround的判斷式之前

.
..
...
loadData();  // 讀檔後,再進入關卡

if(nextLevel == 0)
{
    playGround(LEVEL0_MAP, 28, 4);
}
if(nextLevel == 1)
{
    playGround(LEVEL1_MAP, 28, 27);
}
if(nextLevel == 2)
{
    playGround(LEVEL2_MAP, 29, 2);
}

return 0;


實作函數

再來就是函數的實作,我們利用FILE指標與fopen來建立存檔讀取存檔

/* File: PlayGround.c */

void saveData()
{
    FILE *saveFile = NULL;

    saveFile = fopen("sav", "w");  // 以「寫入」模式開啟sav檔
                                   // (sav是自己取的名字,不存在的話系統會自己創建一個)

    fprintf(saveFile, "%d\n%d\n%d\n%d\n%d\n%d\n%d\n%d\n%d\n",
            nextLevel,
            p.key_a_num,
            p.key_b_num,
            p.key_c_num,
            p.hp,
            p.atk,
            p.def,
            p.money,
            shopPrice);  // 將以上資訊存入檔案,諸如鑰匙、生命之類的,還有目前關卡數

    fclose(saveFile);  // 寫檔完成,關閉檔案
}
/* File: PlayGround.c */

void loadData()
{
    FILE *loadFile = NULL;

    if((loadFile = fopen("sav", "r")) != NULL)  // 讀檔有點不太一樣,首先是以「讀取」模式開啟,
    {                                           // 然後必須先判斷是否能開啟,若檔案不存在,不執行
        fscanf(loadFile, "%d%d%d%d%d%d%d%d%d",
            &nextLevel,
            &p.key_a_num,
            &p.key_b_num,
            &p.key_c_num,
            &p.hp,
            &p.atk,
            &p.def,
            &p.money,
            &shopPrice);  // 以跟sav檔同順序來讀取資料,並分別存入個變數中

        fclose(loadFile);  // 讀檔完成關閉檔案
    }
}

存檔、讀檔就是這樣的概念來實作了,這邊可以讓大家練習基本檔案的操作~

還有一點是這個存檔幾乎沒有安全性可言(笑

如果你手動去更改sav檔案,是真的可以外掛成功的XD

真的要有安全性的話,必須做加密,或是檔案放在伺服端,諸如此類,
但在這次教學中就不贅述了~


執行

為了測試存檔是否有真正儲存到數值,我們稍微更改一下level1的地圖元素:
https://ithelp.ithome.com.tw/upload/images/20191012/201114295dHCnAPOqh.png
我們在地圖上方加上一些藥水與怪物,和一個商店

那麼我們執行看看吧!

首先先完成第0關,遊戲會顯示「存檔完成」:
https://ithelp.ithome.com.tw/upload/images/20191012/201114297MZu0mEkKl.png
https://ithelp.ithome.com.tw/upload/images/20191012/20111429Glu5ecuwEv.png

然後正常地進入第2關:
https://ithelp.ithome.com.tw/upload/images/20191012/20111429uaEIqVFONx.png

這時候把遊戲關掉!重新打開:
https://ithelp.ithome.com.tw/upload/images/20191012/20111429dHbFyHj5jS.png

按下任意鍵進入關卡,你會發現:
https://ithelp.ithome.com.tw/upload/images/20191012/20111429eDqNlxgfgv.png
直接出現level1的畫面!

這時候我們來專案資料夾一探究竟,可以看到多了sav檔案,因此檔案的讀取就是從這邊來的:
https://ithelp.ithome.com.tw/upload/images/20191012/20111429aeqoWrREnf.jpg

因此如果把檔案刪掉,遊戲就只能從第0關開始囉~


測試2

我們這次完成level1來測試數值是否有存檔成功:

首先進入到level1上半段,這次有新增很多怪物與藥水,我們來試試看數值是否能被記錄:
https://ithelp.ithome.com.tw/upload/images/20191012/20111429BMTFY97fRE.png
https://ithelp.ithome.com.tw/upload/images/20191012/20111429eBSlEZQ06T.png

將怪物與藥水處理完後,進入商店:
https://ithelp.ithome.com.tw/upload/images/20191012/20111429Re6Tbkg0vs.png

這邊筆者選擇購買防禦值,def加了4,錢也花光了:
https://ithelp.ithome.com.tw/upload/images/20191012/201114297ZpnKB76jd.png

進入終點,存檔完成:
https://ithelp.ithome.com.tw/upload/images/20191012/20111429AB9fnM6lyD.png

關掉遊戲重開吧!
https://ithelp.ithome.com.tw/upload/images/20191012/20111429Diycq6L6yC.png
可以看到直接由level2開始,且數值也是維持剛剛的(例如def是24),證明存檔沒問題!


上一篇
[11屆鐵人賽Day27] 2D遊戲—查看怪物資訊
下一篇
[11屆鐵人賽Day29] 2D遊戲—遊戲圖標Icon
系列文
若沒有遊戲引擎、合作夥伴...做得出遊戲嗎? 不試試看不知道吧? [使用C語言]30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言