引言
昨天我們實作了玩家的移動,
能讓玩家在超簡單的地圖直接走到終點XD
堪稱最簡單遊戲(X
所以今天我們來為遊戲增添點難度,
讓遊戲多了門的要素,讓玩家找鑰匙來開門!
不過只有一種門也很無聊吧!
我們來設計三種門與三種鑰匙,玩家必須找到對應鑰匙才能開門~
這樣應該會比較豐富。
以下是今天的規格表:
項目 | 內容 |
---|---|
地圖文字檔 | level1.txt |
大家下載下來後一樣把它放到maps資料夾裡,然後在LoadMap.h中加上:
#define LEVEL1_MAP "maps/level1.txt"
鑰匙數量與種類
鑰匙我們一共會設計三種,分別叫a, b, c鑰匙,
門也有三種,分別叫A, B, C門,就是以大小寫分開~
我們先設計鑰匙,因為鑰匙是玩家帶著的,我們在Player結構裡加上鑰匙欄位:
/* File: PlayGround.h */
typedef struct player
{
char skin;
int x;
int y;
// ------------新增部分-------------------
int key_a_num; // 三個整數分別記錄a, b, c鑰匙的數量
int key_b_num; // 取用方式就是直接 p.key_a_num += 1; 之類的就行了
int key_c_num;
// --------------------------------------
}Player;
有了鑰匙的定義,
因為很重要講三遍XD
就先初始化吧:
/* File: PlayGround.c */
void initPlayer(char skin, int x, int y)
{
isPlaying = 1;
p.skin = skin;
p.x = x;
p.y = y;
// ------------新增部分-------------------
p.key_a_num = 0; // 一開始都是0支
p.key_b_num = 0;
p.key_c_num = 0;
// --------------------------------------
game_map[y][x] = skin;
}
狀態欄
喔喔,新的概念來了~
這會是為什麼要將遊戲視窗高度加10格的原因之一。
為了顯示玩家目前的狀態(攜帶的物品之類的),
我們需要有個地方可以展示各種遊戲數值目前是多少。
那就是狀態欄啦,狀態欄跟地圖分開顯示,
大概如下圖所示:
那在實際程式顯示上呢,
會遵守以下順序:
以下我們來實作狀態欄吧:
/* File: PlayGround.h */
// 寫在PlayGround函數之下
void playerStatus();
/* File: PlayGround.c */
// 寫在PlayGround函數之下
void playerStatus()
{
printf("[道具欄] 鑰匙A: %02d支 鑰匙B: %02d支 鑰匙C: %02d支\n", p.key_a_num, p.key_b_num, p.key_c_num); // 分別表示出a, b, c鑰匙的數量
}
定義好之後,在PlayGround函數的半無窮迴圈中中呼叫playerStatus,代表每一循環都更新一次狀態欄:
/* File: PlayGround.c */
// PlayGround函數中的半無窮迴圈
while(isPlaying)
{
selfCls();
showMap();
// ------------新增部分-------------------
playerStatus(); // 接在showMap底下,印出狀態欄
// --------------------------------------
// ------------新增部分-------------------
if(GetAsyncKeyState(VK_ESCAPE) != 0) // 這邊我們順便新增一個按鍵,當Esc被按下時,離開遊戲
{
playMusic(SYSTEM_OK, SE_MODE);
exit(0);
}
// --------------------------------------
if(GetAsyncKeyState(VK_RIGHT) != 0)
{
movePlayer(p.x + 1, p.y);
}
if(GetAsyncKeyState(VK_LEFT) != 0)
{
movePlayer(p.x - 1, p.y);
}
if(GetAsyncKeyState(VK_UP) != 0)
{
movePlayer(p.x, p.y - 1);
}
if(GetAsyncKeyState(VK_DOWN) != 0)
{
movePlayer(p.x, p.y + 1);
}
}
等待玩家觀看結果 & 詢問玩家問題
在進入鑰匙與門的單元前,我們必須先定義兩個常用的功能:等待 以及 詢問,
遊戲總是會有要等待玩家看目前結果的時刻,以及詢問玩家選擇的時刻,
我們把這兩個函數命名為:waitForUser, askUser。
/* File: PlayGround.h */
// 寫在playerStatus底下
void waitForUser(int t); // 傳入一個時間t,代表等待時間
int askUser(char *question); // 傳入將要問的問題,並回傳int型態的結果(是或否)
/* File: PlayGround.c */
// 寫在playerStatus底下
void waitForUser(int t)
{
Sleep(t); // Sleep函數定義在windows.h,系統會暫停t秒
system("cls"); // 清空全畫面
}
int askUser(char *question)
{
printf("%s\n(按下F1確定,F2取消)", question); // 輸出傳入的問題外,
// 再換一行輸出"(按下F1確定,F2取消)"
while(1)
{
if(GetAsyncKeyState(VK_F1) != 0) // 按下F1,就表示"是",回傳1
{
return 1;
}
else if(GetAsyncKeyState(VK_F2) != 0) // 按下F2,就表示"否",回傳0
{
return 0;
}
}
}
開門 — 地圖物件判斷
我們前面寫的movePlayer函數只判斷了兩種情況:空白 以及 出口,
現在我們新增了 門 與 鑰匙 的要素,也就是地圖上會出現門與鑰匙,
這個我們必須把狀況加進movePlayer中。
/* File: PlayGround.c */
void movePlayer(int x, int y)
{
if(game_map[y][x] == ' ')
{
game_map[p.y][p.x] = ' ';
game_map[y][x] = p.skin;
p.x = x;
p.y = y;
}
// ------------新增部分-------------------
if(game_map[y][x] == 'a') // 下一步如果是鑰匙a(撿到鑰匙a的情況)
{
game_map[p.y][p.x] = ' ';
game_map[y][x] = p.skin;
p.x = x;
p.y = y; // 到這邊為止,是讓玩家走到下一格
printf("發現A鑰匙!\n"); // 提醒玩家撿到開A門的鑰匙了
playMusic(SYSTEM_OK, SE_MODE); // 播放音效
waitForUser(500); // 等待0.5秒並清空全畫面
p.key_a_num += 1; // 玩家擁有的a鑰匙加了1支
}
if(game_map[y][x] == 'b')
{
game_map[p.y][p.x] = ' ';
game_map[y][x] = p.skin;
p.x = x;
p.y = y;
printf("發現B鑰匙!\n");
playMusic(SYSTEM_OK, SE_MODE);
waitForUser(500);
p.key_b_num += 1;
}
if(game_map[y][x] == 'c')
{
game_map[p.y][p.x] = ' ';
game_map[y][x] = p.skin;
p.x = x;
p.y = y;
printf("發現C鑰匙!\n");
playMusic(SYSTEM_OK, SE_MODE);
waitForUser(500);
p.key_c_num += 1;
}
if(game_map[y][x] == 'A') // 下一步如果是A門
{
if(p.key_a_num > 0) // 先看玩家有沒有至少1支a鑰匙,沒有的話沒有任何反應
{
if(askUser("遇到A門,是否開啟?")) // 如果有,詢問玩家是否要花費鑰匙開啟
{
game_map[p.y][p.x] = ' ';
game_map[y][x] = p.skin;
p.x = x;
p.y = y;
printf("使用鑰匙a開啟A門!\n"); // 提醒玩家已經開啟了
playMusic(UNLOCK, SE_MODE); // 播放解鎖音效
waitForUser(500); // 等待0.5秒並清空全畫面
p.key_a_num -= 1; // 玩家擁有的a鑰匙少了1支
}
}
}
if(game_map[y][x] == 'B')
{
if(p.key_b_num > 0)
{
if(askUser("遇到B門,是否開啟?"))
{
game_map[p.y][p.x] = ' ';
game_map[y][x] = p.skin;
p.x = x;
p.y = y;
printf("使用鑰匙b開啟B門!\n");
playMusic(UNLOCK, SE_MODE);
waitForUser(500);
p.key_b_num -= 1;
}
}
}
if(game_map[y][x] == 'C')
{
if(p.key_c_num > 0)
{
if(askUser("遇到C門,是否開啟?"))
{
game_map[p.y][p.x] = ' ';
game_map[y][x] = p.skin;
p.x = x;
p.y = y;
printf("使用鑰匙c開啟C門!\n");
playMusic(UNLOCK, SE_MODE);
waitForUser(500);
p.key_c_num -= 1;
}
}
}
// --------------------------------------
if(game_map[y][x] == 'E')
{
playMusic(VICTORY, SE_MODE);
printf("抵達終點!\n");
system("pause");
isPlaying = 0;
}
}
main函數呼叫
這部分其實也是很容易忘記,大家記得函數定義完後,要回到main函數做呼叫啊~
/* File: main.c */
#include "SystemSetting.h" // 設定標題、視窗、音效、錯誤處理、遊戲初始化等
#include "LoadMap.h" // 載入地圖、頁面
#include "PlayGround.h" // 遊戲遊玩頁面、玩家設定
int main(int argc, char *argv[])
{
playMusic(BGM, BGM_MODE);
setGameTitle(GAME_TITLE);
setGameWindow(GAME_WIDTH, GAME_HEIGHT);
initMap();
loadMap(TITLE_MAP);
showMap();
system("pause");
playMusic(MUSIC_STOP, BGM_MODE);
playMusic(SYSTEM_OK, SE_MODE);
playGround(LEVEL0_MAP, 28, 4);
// ------------新增部分-------------------
playGround(LEVEL1_MAP, 28, 27); // 加了level1關卡
// --------------------------------------
return 0;
}
如果在其他檔案把函數寫好了,在main函數就會像這樣非常乾淨,要加新的一關只要加一行code就好囉!因此也希望大家學習這種方式,初學者通常會把所有東西都放在單一個main函數裡,程式一大起來其實非常不好維護~
所以分檔寫很重要哦!
執行
都完成後,就可以執行啦!
第0關抵達終點後
就到達第1關啦!
撿到a鑰匙了~
開A門,系統詢問要不要開啟?(這邊就是askUser的結果)
鏗!門打開了!
大家可以過到終點試試看,筆者有設計過地圖,是可以玩的XD
明天我們再來加一些功能吧~