引言
前幾天我們把遊戲的要素加得差不多了,再來我想就是存檔了~
對於我們的遊戲來說,現階段無法做到「讀取已儲存的檔案後,遊戲回到存檔時的地圖位置、連地圖元素都回復」的功能。但我們能做的是,以「關卡」為單位,一次跳一關,但只能跳到該關卡的最初始位置。
例如:我過了第一關,遊戲自動存檔。然後我在第二關玩到一半,遊戲關掉了,此時再次打開遊戲,會回到第二關開始的狀態,人物的數值也是只到第一關結束時的數值。也就是玩家必須能撐到一個關卡結束再關閉遊戲最不會有損失~
為了更方便展示存檔功能,我們先新增第二關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的地圖元素:
我們在地圖上方加上一些藥水與怪物,和一個商店
那麼我們執行看看吧!
首先先完成第0關,遊戲會顯示「存檔完成」:
然後正常地進入第2關:
這時候把遊戲關掉!重新打開:
按下任意鍵進入關卡,你會發現:
直接出現level1的畫面!
這時候我們來專案資料夾一探究竟,可以看到多了sav檔案,因此檔案的讀取就是從這邊來的:
因此如果把檔案刪掉,遊戲就只能從第0關開始囉~
測試2
我們這次完成level1來測試數值是否有存檔成功:
首先進入到level1上半段,這次有新增很多怪物與藥水,我們來試試看數值是否能被記錄:
將怪物與藥水處理完後,進入商店:
這邊筆者選擇購買防禦值,def加了4,錢也花光了:
進入終點,存檔完成:
關掉遊戲重開吧!
可以看到直接由level2開始,且數值也是維持剛剛的(例如def是24),證明存檔沒問題!