iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 22
2

勘誤:昨天的loadMap函數內有一部份寫錯了,需做更改,請大家見諒!

fopen函數的第一個參數,原本為TITLE_MAP,這邊需要請大家將其改為mapPath,否則地圖會永遠load到TITLE_MAP... 筆者在自己嘗試下發現了這個大Bug,若大家發現了換不了地圖的問題,大概出自於此orz

void loadMap(char *mapPath)
{
    int i = 0, j = 0;
    FILE *map_file = NULL;

    if((map_file = fopen(mapPath, "r")) != NULL)  // 請大家將此處的TITLE_MAP改成mapPath!
    {
        for(i = 0; i < MAP_HEIGHT; i++)
        {
            for(j = 0; j < MAP_WIDTH; j++)
            {
                fscanf(map_file, "%c", &game_map[i][j]);
            }
        }

        fclose(map_file);
    }
    else
    {
        printf("地圖檔不存在或損壞...\n");
        system("pause");
        exit(1);
    }
}



引言

昨天我們完成了遊戲的開頭,再來就是要進入遊戲內容的部分。

這邊的計畫是這樣的,
在使用者按了任意鍵進入遊戲後,
我們會先load第一張地圖,此時玩家會出現在地圖中,
因此我們第一步要來處理玩家的走動,也就是人物控制的部分!


規格表

以下是今天的規格表,大家可以先下載下來:

項目 內容
地圖文字檔 level0.txt
開門音效 ドア03
勝利音效 ジングル01

下載後,將level0.txt直接放到maps資料夾中,並在loadMap.h中加上:

/* File: loadMap.h */

#define LEVEL0_MAP "maps/level0.txt"

開門音效先取名為"unlock.wav"、勝利音效取名為"victory.wav"(請先轉成.wav檔,再重新命名!),
都放入musics資料夾,並在SystemSetting.h中加上:

/* File: SystemSetting.h */

#define UNLOCK "musics/unlock.wav"
#define VICTORY "musics/victory.wav"

進入遊戲音效

這部分昨天沒有說明到,稍微補充一下:

/* File: main.c */

.
..
...
loadMap(TITLE_MAP);
showMap();
system("pause");

// 寫在system("pause");底下

// ------------新增部分-------------------
playMusic(MUSIC_STOP, BGM_MODE);  // 當玩家按下任意鍵,先停止BGM播放
playMusic(SYSTEM_OK, SE_MODE);  // 然後以音效模式(播放完才繼續執行)播放SYSTEM_OK的音效
// --------------------------------------


遊玩廣場PlayGround

在由開頭進入遊戲後,預計會執行的動作是:

  1. 將地圖Load進來
  2. 初始化玩家(在此之前要先定義「玩家」)
  3. 一個半無窮迴圈(在"isPlaying"狀態未解除時,會不斷重複),迴圈內容:
    • 不斷更新畫面
    • 不斷讀取按鍵,按鍵改變的內容會交給下一次畫面更新

如以上流程,每一關卡都會重複這樣的步驟,
關卡結束時會將"isPlaying"狀態解除,跳出迴圈,進入下一關。

我們可以選擇將這個流程包裝成一個函數,我們稱它為PlayGround
如同遊戲主要遊玩的廣場。

以下我們先建立一組PlayGround檔案:

/* File: PlayGround.h */

#ifndef __PLAYGROUND_H__
#define __PLAYGROUND_H__

#include "LoadMap.h"  // 需要用到LoadMap.h,先引入

#endif // __PLAYGROUND_H__
/* File: PlayGround.c */

#include "PlayGround.h"

玩家Player 定義

流程圖中,提到要定義玩家,我們首先來定義玩家(Player)是什麼樣子:

先構思一下,玩家需要哪些東西:

  1. skin,顯示在螢幕上的一個字元,例如用"@"來代表玩家
  2. x,玩家所在x座標
  3. y,玩家所在y座標

OK,暫時先定義這樣就行,之後有需要什麼新功能我們再補上。

這樣子的一組資料,我想用struct(結構)來實作最適合了~

/* File: PlayGround.h */

#ifndef __PLAYGROUND_H__
#define __PLAYGROUND_H__

#include "LoadMap.h"

// ------------新增部分-------------------
typedef struct player
{
    char skin;
    int x;
    int y;
}Player;  // 定義結構Player,包含skin, x, y等資訊

Player p;  // 宣告一Player型態的變數p,之後對於player的操作都會從這裡取用
           // (ex: p.x = 10; 將玩家x座標設為10)
// ---------------------------------------

#endif // __PLAYGROUND_H__


初始化玩家

老話一句,別忘了初始化~
我們宣告一個initPlayer函數來做初始化,並定義它。

/* File: PlayGround.h */

// 寫在Player p;底下

void initPlayer(char skin, int x, int y);  // 設定玩家skin,並給定一x, y座標當作初始位置
/* File: PlayGround.c */

#include "PlayGround.h"

void initPlayer(char skin, int x, int y)
{
    p.skin = skin;  // 玩家的skin會設為傳進來的skin
    p.x = x;  // 玩家的x位置會設為傳進來的x
    p.y = y;  // 玩家的y位置會設為傳進來的y

    game_map[y][x] = skin;  // 在地圖上該位置寫入玩家skin,相當於將玩家擺到地圖中
    
    isPlaying = 1;  // 將isPlaying狀態設為1,方可進入迴圈(見以下介紹)
}

PlayGround函數

裝入半無窮迴圈的函數,PlayGround,
而所謂「半」無窮迴圈就是因為有isPlaying狀態判斷是否繼續迴圈,
因此我們也要定義isPlaying變數。

/* File: PlayGround.h */

#ifndef __PLAYGROUND_H__
#define __PLAYGROUND_H__

#include "LoadMap.h"

typedef struct player
{
    char skin;
    int x;
    int y;
}Player;

Player p;

// ------------新增部分-------------------
int isPlaying;  // 設為1時PlayGround的迴圈繼續,設為0時跳出迴圈,進入下一關或遊戲結束
// ---------------------------------------

void initPlayer(char skin, int x, int y);

// ------------新增部分-------------------
void playGround(char *mapPath, int initX, int initY);  // PlayGround傳入要遊玩的地圖,
                                                       // 以及玩家初始位置
// ---------------------------------------

#endif // __PLAYGROUND_H__
/* File: PlayGround.c */

//寫在initPlayer底下

void playGround(char *mapPath, int initX, int initY)
{
    system("cls");  // 清空前一幕留下的所有訊息
    loadMap(mapPath);  // load mapPath中的地圖檔
    initPlayer('@', initX, initY);  // 玩家skin設為"@",初始位置在initX, initY

    while(isPlaying)  // 由isPlaying判斷迴圈是否繼續
    {
        // 迴圈內容稍後分析
    }

    system("cls");  // 跳出迴圈後「完全」清空畫面,
                    // 因為selfCls只能清空地圖部分,這個則會整個視窗都清空,方便進入下一關
}

移動玩家 movePlayer

由流程得知迴圈中需要:

  1. 不斷更新畫面
  2. 不斷讀取按鍵

其中讀取按鍵一部分是為了移動玩家,
我們把移動玩家這部分包成一個函數,叫做movePlayer:

/* File: PlayGround.h */

// 寫在playGround底下

void movePlayer(int x, int y);  // 簡單明瞭,想移動到哪個位置就傳入該座標
/* File: PlayGround.c */

// 寫在playGround底下

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] == 'E')  // 若打算移動的位置是大寫E(終點),
    {                          // 播放音效、isPlaying設為0
        playMusic(VICTORY, SE_MODE);  // 以音效模式播放VICTORY
        printf("抵達終點!\n");  // 提示玩家已抵達終點
        system("pause");
        isPlaying = 0;  // 離開這個PlayGround
    }
}

再來就可以補全PlayGround了!

/* File: PlayGround.c */

// 補全 while(isPlaying) 中的內容

while(isPlaying)
{
    selfCls();  // 
    showMap();  // 更新畫面
    
    // 按下各個方向鍵,移動玩家
    if(GetAsyncKeyState(VK_RIGHT) != 0)
    {
        movePlayer(p.x + 1, p.y);  // 例如p.x加一,就是往右移動一格
    }
    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);
    }
}


主函數呼叫

/* 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);  // 把level0.txt地圖讀取進來成為PlayGround
// ---------------------------------------

    return 0;
}

執行

都ok了就執行看看吧!
https://ithelp.ithome.com.tw/upload/images/20191006/20111429SyUS2Iwido.png
用方向鍵將玩家"@"移動到"E"的位置,
https://ithelp.ithome.com.tw/upload/images/20191006/20111429gFqm8u0RPn.png
就會聽到勝利的音效囉~然後按下任意鍵遊戲結束。

當然不只那麼簡單啦~ 明天我們會繼續加功能的!


上一篇
[11屆鐵人賽Day21] 2D遊戲—開頭畫面設計(讀檔應用)
下一篇
[11屆鐵人賽Day23] 2D遊戲—撿鑰匙開門!
系列文
若沒有遊戲引擎、合作夥伴...做得出遊戲嗎? 不試試看不知道吧? [使用C語言]30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言