---
# System prepended metadata

title: 計算機程式專題
tags: [other]

---

# 計算機程式專題
## 個人分工 B12502166 劉昆泰
我個人主要是負責遊戲整體架構的程式以及遊戲邏輯、運行功能設計，主要寫了整體的遊戲框架、環境的建置，以及遊戲的整體邏輯與角色邏輯，這邊可以提一下，因為我們是使用clion在windows上進行編輯，不是像課程中教學使用code block或是vscode，在環境建置的時候，也是花費了不少心思在編輯cmakeList以及抓取動態函式庫，最後也有成功將環境建置出來並且公開倒github上給大家使用。
在這裡附上環境連結: https://github.com/Ktliu-Tyler/SDL_env_clion
遊戲連結: https://github.com/Ktliu-Tyler/GOBLIN_GAME
### 程式架構設計
關於程式的架構設計，我要求必須將檔案分類於include和src這兩個與main位於相同根目鱸的資料夾中，裡面放置我們的.h和.c檔案，方便後續進行debug時的程式跳轉，架構如下:
Goblin_game
├── cmake_modules, CMakeLists.txt, SDL_lib<span style="color:blue"># CMake 配置文件</span>
├── imgs, musics, myFont, record <span style="color:blue"># 資料存放</span>
├── include
│ ├── constants.h <span style="color:blue"># 定義常量</span>
│ ├── Enemy.h <span style="color:blue"># 定義敵人相關的類和方法</span>
│ ├── engine.h <span style="color:blue"># 定義遊戲引擎相關的類和方法</span>
│ ├── menu.h <span style="color:blue"># 定義菜單相關的類和方法</span>
│ ├── Player.h <span style="color:blue"># 定義玩家相關的類和方法</span>
│ ├── playground.h <span style="color:blue"># 定義遊戲場景相關的類和方法</span>
│ ├── test.h <span style="color:blue"># 定義測試相關的類和方法</span>
│ └── tool.h <span style="color:blue"># 定義工具類和方法</span>
├── src
│ ├── Enemy.cpp <span style="color:blue"># 實現敵人相關的功能</span>
│ ├── engine.cpp <span style="color:blue"># 實現遊戲引擎相關的功能</span>
│ ├── menu.cpp <span style="color:blue"># 實現菜單相關的功能</span>
│ ├── Player.cpp <span style="color:blue"># 實現玩家相關的功能</span>
│ ├── playground.cpp <span style="color:blue"># 實現遊戲場景相關的功能</span>
│ ├── test.cpp <span style="color:blue"># 實現測試相關的功能 主要是我在除錯exe無法運行的問題</span>
│ └── tool.cpp <span style="color:blue"># 實現工具相關的功能</span>
└── main.cpp <span style="color:blue"># 主程序文件</span>
<div style="page-break-after:always"></div>

### 遊戲參數設置
我將所有我們會用到的常數，包含會需要引用到的路徑名稱、圖片音訊位置都放在一個constant.h中，這樣使我們在進行debug的時候可以透過簡單的更改參數來進行調整，甚至是在更改角色圖片或式音效時都能透過這裡快速進行更改，不過這也是造成我們使用const和static較少的原因，因為我的程式習慣會將不更動的參數用define的方式寫成宏，並且可以當作全域變數使用，這樣在書寫時選字顏色比較容易區分，基本上所有的程式都會引用constant.h的常數，方便們進行管理，以下為constant.h的宣告程式以及其代表意義(圖片的話因為太長我就不全放了):

```cpp=
// constant.h
#ifndef CONSTANTS
#define CONSTANTS
#define TRUE 1 // 沒什麼用的宣告 只是比較習慣python的樣子
#define FALSE 0 // 
#define WINDOW_HEIGHT 720 // 螢幕高
#define WINDOW_WIDTH 1280 // 螢幕寬
#define WINDOW_M_HEIGHT 336 // 地面移動背景的高度x
#define SCOREBOARD_X 20 // 記分板位置
#define SCOREBOARD_Y 10 
#define SCOREBOARD_BET 50 // 記分板的間隔
#define FPS (16 * 60) // 幀率 單位有點錯，但這是目前運行最順暢的情況，詳細可見engine
#define FRAME_TARGET_TIME (1000 / FPS) //寫在engine的update地方，計算deltatime
#define PLAYER_W 100 //玩家大小
#define PLAYER_h 100 //玩家大小
// #define PLAYER_HIT_W 80 玩家碰撞大小
const int PLAYER_HIT_W =  80;
// #define PLAYER_HIT_H 80 玩家碰撞大小
const int PLAYER_HIT_H =  80;
// #define ENEMY_HIT_RATE 0.8 //原本define寫法也是互通的
const float ENEMY_HIT_RATE =  0.8; //這邊是因為我們都沒甚麼用到const所以在這裡用
#define PLAYER_SHOOT_T 30 // 玩家基礎射擊間隔
#define PLAYER_HP_MAX 20 // 玩家基礎血量
#define PLAYER_NP_MAX 40 // 玩家基礎能量
#define LEVEL_MAX 50 // 進度條最大值
#define PLAYER_SPEED 300 //玩家速度
#define BULLET_SPEED 300 //子彈速度
#define ENEMYNUMMAX 5 // 最大場上存在敵人數
#define ENEMYHP_MAX 10 // 敵人最高血量
#define ENEMPTYPE 3 // 敵人總類，每個遊戲模式各三個，其實後續會隨意更動
#define MENUID 0 // Menu編號
#define PLAYGROUNDID 1 // 遊戲編號
#define PLAYGROUNDID_RESTART 2 // 切換畫面的癲號
#define PLAYGROUNDID2 3 // 特殊遊戲模式編號
#define PLAYGROUNDID_RESTART2 4 // 切換特殊遊戲模式編號
#define BDSPEED -200 // 背景移動速度
#define BDTIME 10 // 背景移動週期
#define MENUBGM "../musics/BGM1.mp3" // 背景音樂
#define PLAYGROUNDBGM "../musics/bg.mp3"// 遊戲背景音樂
#define PLAYGROUNDBGM2 "../musics/GAMEOVER.mp3" // 遊戲背景音樂2
#define GAMEOVERBGM "../musics/BGM2.mp3" // 遊戲結束音樂
```
```cpp=
#define SHOOT_SOUND "../musics/goblin/shoot.wav" //攻擊音樂
#define SHOOT1_SOUND "../musics/goblin/shoot1.wav" // 備用攻擊音樂
#define WALK_SOUND "../musics/goblin/walk.wav" // 走路聲音
#define HURT_SOUND "../musics/goblin/hurt.wav" // 受傷的聲音
#define GIRL_SOUND "../musics/girl.wav" // 女生受傷的聲音(百合)
#define CLASSMATE_SOUND "../musics/classmate.wav" // 帥潮受傷聲音
#define BOMB_SOUND "../music/bomb.wav" //爆炸聲
#define GAMERECORD_FILE "../record/Gamerecoder.txt"// 遊戲紀錄的檔案位置
#define FONT "../myFont/Conther/Conther-2.ttf" // 字體位置
#define PLAYER_WALK_IMAGES {"../im...//哥布林走路
#define PLAYER_SHOOT_IMAGES { "... //射擊圖片
#define ARROW_NORM {"../imgs/arrow/arrow.png"}// 弓箭1圖片
#define ARROW_FAST {"../imgs/arrow/fastarrow.png"}//弓箭2圖片
#define ARROW_POISON {"../imgs/arrow/poisonarrow.png"}//弓箭3圖片
#define ARROW_POWER {"../imgs/arrow/powerarrow.png"}//弓箭4圖片
#define ENEMY1_IMAGES {"...//敵人圖片
#define ENEMY1_DIE {"...//敵人死亡圖片
#define MONSTER_IMAGES {"...//怪獸特瓦林塗圖片
#define HUMAN_IMAGES {"...//學生圖片
#define HUMAN2_IMAGES {"...//徐生圖片
#define HUMAN3_IMAGES {"...//學生圖片
#define EXPLODE_IMAGES {"...//爆炸圖片
#define SKAKE_HEAD {"...// 搖頭女孩圖片
#define STOP_PIC "../imgs/stop.png"// 停止圖片
#define CHANGE_BACKGROUND "../imgs/backgrounds/bg_change.png"// 黑幕位置
#define MENU_BACKGROUND "../imgs/backgrounds/menu.png" // Menu 介面背景
#define MENU_ANIME {"...//MENU的動畫圖片
#define PLAYGROUND_BACKGROUND {"... // 遊戲背景"../imgs/backgrounds/bg_1.jpg",
#define PLAYGROUND_BACKGROUND_MOVE {"...// 移動地板圖片

// 這邊的圖片和音檔路徑引用，因為太多了就先省略
// 基本上就是名稱對應到的圖片或音訊路徑
#endif
```
在這邊的常數設置基本上都跟const的用法類似，如果捨去掉改成設置一般的常數，很容易在編寫程式時不小心改動到定值而造成debug困難，其實define和const還是有很大的差別，一個是用預處理器先編好，但我這邊使用的功能比較類似而已，本來是有打算用define去做一些寫程式上的變化，像是把重複使用的function直接用define寫成段落貼入，不過最後時間不太夠就沒有額外的增加功能了。
下面的 static和 const 宣告在很多地方較分散，使用如下:

| 宣告                             |  功能  | 存放位置                       |
|-------------------------------------|-----|--------------------------------|
| static GameRecorder *game_recorder |計分器 | engine.cpp, line 12       |
| static int s = 60;                       |單次位移| Enemy.cpp, line 151       |
| static menu *MenuPage = nullptr;        |主畫面 | engine.cpp, line 14       |
| static playground *PlayPage = nullptr;  |遊戲畫面 | engine.cpp, line 15       |
| static MusicPlayer *BGMmusic = nullptr; |音樂播放器 | SDL_bgi.cpp, line 42      |
| static int fgWidth = static_cast<int>((static_cast<float>(value) / max_value) * w);|文字框寬度 | tool.cpp, line 31         |
| static int n = ENEMYNUMMAX;             | 敵人計數| playground.cpp, line 410  |
| static int PAGE_ID = 0;                  |當前頁面紀錄| engine.cpp, line 21       |

這些static的變數能確保區域變數不受到外部存取的影響而改變其值，並且在重複呼叫該區塊時能保有其值，作為計數器使用，裡如上面倒數第二個static int n，若是拿掉static，會導致程式每次進入都重新宣告並賦值，而無法計算每次的當前敵人數而造成錯誤。

| 宣告方式                 |功能                                                                                     | 存放位置                         |
|---------|---------------|---------|
| const std::vector\<std::string> *playbg = PLAYGROUND_BACKGROUND;  |遊戲背景路徑                                             | engine.cpp, line 17              |
| const std::vector\<std::string> Wimages = PLAYER_WALK_IMAGES;      |角色圖片路徑                                  | Player.h, line 28                |
| const std::vector\<std::string> playbgMove = PLAYGROUND_BACKGROUND_MOVE; |移動背景路徑                                      | engine.cpp, line 18              
| const Uint8* keystate = (SDL_GetKeyboardState(NULL)); |讀取keyboard狀態                                                        | engine.cpp, line 19              |
| const std::vector\<std::string> Simages = PLAYER_SHOOT_IMAGES;|射擊圖片路徑                                                 | Player.h, line 29                |
| const int PLAYER_HIT_W = 80;                                             |角色碰撞rect                                     | constants.h, line 23             |
| const std::vector\<std::string> Dimages = PLAYER_DIED_IMAGES;              |角色死亡圖片路徑                     | Player.h, line 30                |
| const int PLAYER_HIT_H = 80;                |角色碰撞rect                                                                  | constants.h, line 25             |
| const float ENEMY_HIT_RATE = 0.8;                |敵人碰撞rect                                                             | constants.h, line 28             |

    
const 的宣告能夠固定數值而不被他者更動，對於程式中的一些常數，用const宣告能避免錯誤更動，或是const指標，讓指標固定指向某一位址，如上面的keyboard輸入只能使用const，因為該function會回傳數值固定到該指標，因此必須用紙const指標來儲存，否則的話會因指標指向錯誤而無法讀取或是報錯。
    
<div style="page-break-after:always"></div>
    
### main.cpp / engine.cpp
這次遊戲引擎的架構我有用了一些小巧思，由於在剛開始設計的時候，考量到我們會有多個視窗切換，並且要讓組員之間容易分工，因此我思考可以利用模組化的方式，將每個介面分類成一個class，並且透過我們的engine程式來進行呼叫，這樣容易將各個頁面分配給組員進行編輯，也容易整合在一起。
首先，我們將整個程式運行的主架構分成以下部分，寫成engine的幾個function。而main.cpp程式只需要寫簡單的迴圈，並且呼叫engine的程式便可以運行。

```cpp=
//main.cpp
int main(int argc, char *argv[]) {
    game_is_running = initialize_window();
    setup();
    while (game_is_running) {
        // SDL_Log("Game is running...");
        process_input();
        game_is_running = update();
        render();
    }
    destroy_window();
    return 0;
}
```
可以注意到這邊有一個蠻重要的全域變數game_is_running，我們會持續監控此變數的狀態，以作為程式是否關閉的運行判斷。

```cpp=
//engine.h
extern SDL_Window *mywindow; // 視窗指標
extern SDL_Renderer *renderer; // 選染器
extern int game_is_running;

int initialize_window(); // 初始化
void setup();  // 設定各class
void process_input(); // 處理輸入
int update(); // 更新角色資料
void render(); // 渲染
void change_page(); // 畫面切換
void destroy_window(); //清除視窗
```
initialize_window()中會初始化SDL環境，和set_up差別在於一個是環境的初始化，一個是物件的初始化。並且分別都用SDL_Log對偵錯進行輸出，方便我們進行debug。還有這裡的destroy_window也有對所有建立好的指標進行記憶體釋放，優化我們的程式。

<div style="page-break-after:always"></div>

接下來這裡提一下我們的change_page以及一些engine邏輯

```cpp= 
// engine.cpp 的宣告
static GameRecorder *game_recorder = nullptr; // 遊戲紀錄存檔模組
static menu *MenuPage = nullptr; // 菜單介面
static playground *PlayPage = nullptr; // 遊戲介面
static MusicPlayer *BGMmusic = nullptr; // 背景音樂播放器
const std::vector<std::string> playbg = PLAYGROUND_BACKGROUND; //背景圖的路徑
const std::vector<std::string> playbgMove = PLAYGROUND_BACKGROUND_MOVE;
const Uint8* keystate = (SDL_GetKeyboardState(NULL)); //用來獲取鍵盤狀態
static int  PAGE_ID = 0; // 用來判斷當前畫面
static int gametype = 1; //判斷遊戲模式
static int prePage_ID = 0; //跟PAGE_ID比較判斷畫面是否更改
static int last_frame_time = 0; //幀數紀錄時前一次的時間
static float delta_time = 0; // 用來存幀率對應的時間
```
上面介紹了這些參數的用途，應該很容易理解到，我主要想解釋的是我們切換畫面的方法，我使用Page_ID來進行頁面切換的判斷，對應執行該狀態介面的update、render、input_proccess，這樣有一個很大的優勢是，如果我今天想要新增新的介面，我只要確保此介面的class中，包含了我所需的render、input_proccess，和update，便可以直接在ID添加此介面，快速更新整個程式，並且，我也能透過這樣同時指運行一種介面的方式，來減少當前正在運行的資料量，達到更通順的運行。
```cpp=
void change_page() {
    if (PAGE_ID != prePage_ID) {
        prePage_ID = PAGE_ID;
        if(PAGE_ID == PLAYGROUNDID2) {
            PAGE_ID = PLAYGROUNDID;
            gametype =2;
        }else if(PAGE_ID == PLAYGROUNDID){
            gametype = 1;
        }
        if (PAGE_ID == PLAYGROUNDID || PAGE_ID == PLAYGROUNDID_RESTART) {
            prePage_ID = PLAYGROUNDID;
            PAGE_ID = PLAYGROUNDID;
            SDL_Log("Play Game 1");
            if(gametype == 1){
                PlayPage = new playground(playbg[1], playbgMove[1]
                                          , renderer, game_recorder, BGMmusic);
            }else if (gametype == 2) {
                PlayPage = new playground2(playbg[0], playbgMove[0]
                                           , renderer, game_recorder, BGMmusic);
            }
        }else if (PAGE_ID == MENUID) {
            SDL_Log("MENU");
            MenuPage = new menu(MENU_BACKGROUND, renderer
                                , game_recorder, BGMmusic);}}}
```
可以注意到我在裡面有兩個static變數，讓我判斷切換畫面，成功切換後直接更新playground或是menu物件，便能成功刷新或是切換畫面了。

<div style="page-break-after:always"></div>

## **class playground & playground2**
### 程式架構 *這區講的程式都在playgrdoun.h 和 playground.cpp中
此程式主要用於建立遊戲中的遊戲畫面page，這個class 用到了繼承、組合、複製建構子、解構子等功能，其中scoreboard為其子物件，而playground2又由他進行繼承並設置virtual函式調整了新增敵人的方法，player、enemy和bullet將其設為friend，可以取用這些class的private宣告。

```cpp=
// playground.h
class playground { // 遊戲介面 class
public:
    int update(float deltatime);
    int process_input(SDL_Event *event, const Uint8* keystate);
    void render(SDL_Renderer *renderer); 
    void movebd(float deltatime);// 移動背景
    void bdrender(); // 背景渲染
    int gameSTOP(); // 遊戲結束時的判斷
    void changebd(); //畫面切換的黑幕
    playground(std::string path,std::string pathMove, SDL_Renderer* renderer,
               GameRecorder *recorder, MusicPlayer *music_player); // 物件建構子
    playground(const playground & p); // 複製建構子 
    ~playground(); // 物件解構子
    virtual void new_Enemy(); //新增敵人的程式
    virtual void bullet_update(float deltatime); // 子彈更新程式
    virtual void enemy_update(float deltatime); //敵人更新程式
    virtual int gameOVER_ANIME(); // gameover的while迴圈  
protected:
    float backgroundX;
    int timecounter = 0; // 痾好這個後來沒用到
    int enemyNUM = 0; // 敵人當前數量
    int chbgX = -300; // chang background的黑幕座標
    int gameStart = false; // 判斷黑幕跑完沒
    int gametype = 1; //判斷遊戲模式
    SDL_Texture *background = nullptr; //背景圖
    SDL_Texture *backgroundMove = nullptr;//背景圖
    SDL_Texture *backgroundChange = nullptr;//黑幕圖 我用小畫家塗的
    SDL_Renderer *renderer = nullptr; // 渲染氣 來自engine
    GameRecorder *recorder = nullptr; // 分數紀錄器 來自engine
    MusicPlayer *musicPlayer = nullptr; // 背景音樂 來自engine
    Player *player; // 玩家
    Scoreboard scoreboard; // 子物件 記分板
    std::vector<Bullet *> bullets; // 子彈指標陣列
    std::vector<Enemy *> enemys; // 敵人指標陣列
};
```
<div style="page-break-after:always"></div>

### 參數宣告
變數我主要都用 protected 進行宣告，以防不當的編寫意外更改導致難以除錯，同時也讓繼承其的 playground2 容易調整參數，而其他會被engine調用的程式我放在public，以利撰寫時直接調用。
* virtual的使用是因為後面繼承的特殊遊戲介面playground2 會需要更新不同的角色生成方式或是角色攻擊方式，這樣可以使後續程式能夠覆蓋這裡的函式內容。

### 程式運行邏輯
運行程式時，由其上游的 engine 對應到這些 page 的基礎程式:
進入 playground -> process_input -> update -> render 而其中又分別對應
|程式|      process_input     | update         | render         |
|----|--------------|--------------|---------------|
|功能| 進行輸入讀取，有角色移動操作的輸入，子彈發射的輸入。遊戲畫面切換的輸入。| 遊戲資料更新，包含碰撞判斷、角色死亡判斷，以及分數紀錄更新等等    |   最重要的畫面渲染，會呼叫playground中，各個物件進行渲染，像是enemy、player、scoreboard等等 |
|程式| 主要處理物件內的程式 | new_Enemy() bullet_update() enemy_update() movebd()  |   bdrender() 和一些角色 render()|

結構都依循著主架構的 engine，主要特別的是，遊戲有一個過場畫面的黑幕，因此設定了參數 gamestart 為 false ，待黑幕移動到指定位置後，將 gamestart 設為 true 才開始，還有在遊戲結束的畫面是於engine之中的while迴圈，自成一個架構的 gameOVER_ANIME() 其中等待使用者輸入後才決定跳轉至何畫面。接下來將根據個 function 依序介紹功能:

#### 建構子 & 複製建構子
此 class 初始化需要提供 
| path          | pathMove         | renderer         | recorder    |music_player |
|---------------|--------------|---------------|-----|-----|
| string  | string     |    SDL_Renderer*   |GameRecorder*  (tool.h)| MusicPlayer* (tool.h)  |
| 背景圖片路徑  |   移動背景    |    渲染器       |分數紀錄器|  背景音樂播放器  |

這裡的 playground 是一個組合，包含了 scoreboard 的物件，在建立 playground 時一起初始化，用來記錄遊戲中的分數、血量、能量等等，並且進行記分板的畫面渲染以及遊戲狀態的紀錄，因此其將 playground 設為 friend 方便進行調用。

複製建構子程式如下
```cpp=
// playground.cpp
playground::playground(const playground & c):scoreboard(c.renderer, font) {
    SDL_Log("Creating playground...");
    this -> background = c.background;
    this -> backgroundMove = c.backgroundMove;
    this -> backgroundChange  = c.backgroundChange;
    this -> renderer = c.renderer;
    this -> recorder = c.recorder;
    this -> musicPlayer = c.musicPlayer;
    this -> backgroundX = 0; // 計算畫面滾動的座標
    this -> chbgX = -300; // 一開始畫面的位置
    srand( time(NULL) );  // 設定random的參數隨機
    font =  TTF_OpenFont(FONT, 24); // 小字體
    fontBIG =  TTF_OpenFont(FONT, 100); // Gameover的字體
    this->musicPlayer->stop(); // 在切換畫面時音樂停止
    player = new Player(PLAYER_W+30, WINDOW_HEIGHT-WINDOW_M_HEIGHT, 
                        PLAYER_W,PLAYER_h, PLAYER_SPEED, renderer, 
                        WINDOW_HEIGHT,WINDOW_HEIGHT-WINDOW_M_HEIGHT);
    scoreboard.setHealth(PLAYER_HP_MAX);// 初始化scoreboard
    scoreboard.setNP(PLAYER_NP_MAX);
    scoreboard.setScore(0);
    scoreboard.setLevel(0);
}
```

#### 繼承 & 組裝
前面有提過了playground 有一個繼承的 playground2 一樣在 playground.cpp中，而 playground 為一個組裝並包含scoreboard的物件，這邊使用組裝是因為scoreboard不需要被傳遞給engine，而是跟著playground一起被創建和delete，因此此直接把他當作playground的子物件來進行初始化也比較方便。而scoreboard又將playground設為friend方便調用，除此之外，內容中使用到的所有Enemy是由最初蝙蝠的enemy的class去進行繼承的，這樣方便我們在加入到enemy* 的 vector 中也能順利運行，並且方便修改整體的創建、判斷程式。

#### 特殊使用 (指標處理)
這邊想補充我在 enemy 和 bullet 新增的方法，也算是指標的使用，因為程式碼蠻長的就不貼上來，內容都在playground,cpp中可以找到new_enemy、enemy_update、bullet_update等，可以注意到我一開始設定的用來儲存的型別為指標陣列 (引用vector)
```cpp=
// playground.h的class中
std::vector<Bullet *> bullets;
std::vector<Enemy *> enemys;
```
<div style="page-break-after:always"></div>
    
不得不說 vector 是個很好用的東西，透過他我可以用 pushback 的方式輕鬆處理動態陣列，還有在 bullet 和 enemy 的碰撞判斷等等，直接調用物件指標，直接存取各物件的 hitrect 來進行判斷，當角色血量判斷死亡時，讓我們播放動畫及音效(這是我花很多心思的功能，後面會提到)，並且可以用 erase 將物件從 vector 中去除，大幅提升了我在程式編寫上的效率，這邊的物件大多是使用指標宣告，方便對物件進行傳遞和取值，在物件被刪除時，我也都有使用delete將物件記憶體釋放。

還有一點是因為我們的敵人角色都是利用繼承的方式進行編寫，所以都能夠放入這邊的enemy指標陣列中，使得在切換不同種敵人生成時很方便。

####  polymorphism & virtual
這邊利用 virtual 達到 polymorphism 在繼承類別的實作，用意是讓特殊模式生成的敵人為其他繼承enemy類別的角色，方便我們對於不同種類的敵人記形客製化或是更改參數。
```cpp=
// 原virtual
void playground::new_Enemy() {
    int n = ENEMYNUMMAX-enemyNUM;
    if (scoreboard.state == 'n') {
        for (int i = 0; i < n; i++) {// 判斷當前場上還缺幾個敵人就直接隨機生成
            int enemyType = rand()%ENEMPTYPE;
            int hp = rand()%6+3;
            if(enemyType == 0) {//辨別隨機敵人對應的生成數字
                enemys.push_back(new Enemy(rand()%WINDOW_WIDTH+WINDOW_WIDTH, 
                   (rand()%WINDOW_M_HEIGHT-30-hp*15)+
                   (WINDOW_HEIGHT-WINDOW_M_HEIGHT-100+hp*15),
                   rand()%200,hp, renderer));
            }else if (enemyType == 1) {
                enemys.push_back(new Human(rand()%WINDOW_WIDTH+WINDOW_WIDTH,
                    (rand()%WINDOW_M_HEIGHT-30-hp*15)+
                    (WINDOW_HEIGHT-WINDOW_M_HEIGHT-100+hp*15),
                   rand()%200, hp, renderer));
            }else if (enemyType == 2) {
                enemys.push_back(new Human(rand()%WINDOW_WIDTH+WINDOW_WIDTH,
                    (rand()%WINDOW_M_HEIGHT-30-hp*15)+
                    (WINDOW_HEIGHT-WINDOW_M_HEIGHT-100+hp*15), 
                    rand()%200, hp, renderer));
            }
            enemyNUM++;
        }
    }else  if (scoreboard.state == 'g'){// Boss生成
        Monster *e = new Monster(rand()%WINDOW_WIDTH+WINDOW_WIDTH
                                 , WINDOW_HEIGHT/4+30, -100, 30, renderer);
        e->type = 'B';
        enemys.push_back(e);
        enemyNUM = 0;
        scoreboard.state = 'B';
    }
}
```
可以對比下面的內容，程式中都是使用random的方式來隨機生成敵人的血量，並根據血量去生成角色大小和攻擊力，主要差別就是使用的enemy繼承物件不一樣，而這也就是在初始化時對應到的動畫和音效不一樣。
```cpp=
// playground2 中的 new_Enemy()
void playground2::new_Enemy() {
    static int n = ENEMYNUMMAX;
    n = ENEMYNUMMAX-enemyNUM;//計算要生成的敵人數量
    for (int i = 0; i < n; i++) {
        int enemyType = rand()%ENEMPTYPE;
        int hp = rand()%10+1;
        if(enemyType == 0) {//判斷隨機數字對應
            enemys.push_back(new Human3(rand()%WINDOW_WIDTH+
                    WINDOW_WIDTH, (rand()%WINDOW_M_HEIGHT-30-hp*15)
                    +(WINDOW_HEIGHT-WINDOW_M_HEIGHT-100+hp*15),
                    rand()%200,hp, renderer));
        }else if (enemyType == 1) {
            enemys.push_back(new Human1(rand()%WINDOW_WIDTH+
                    WINDOW_WIDTH, (rand()%WINDOW_M_HEIGHT-30-hp*15)
                    +(WINDOW_HEIGHT-WINDOW_M_HEIGHT-100+hp*15), 
                    rand()%200, hp, renderer));
        }else if (enemyType == 2) {
            enemys.push_back(new Human2(rand()%WINDOW_WIDTH+WINDOW_WIDTH, 
                    (rand()%WINDOW_M_HEIGHT-30-hp*15)+
                    (WINDOW_HEIGHT-WINDOW_M_HEIGHT-100+hp*15), 
                    rand()%200, hp, renderer));
        }
        // SDL_Log("Enemy born at %f, %f", enemys[i]->x, enemys[i]->y);
        enemyNUM++;
    }
}
```
    
<!-- <div style="page-break-after:always"></div> -->
    
#### enemy更新處理(指標處理)
在playground的許多函式中，我都使用到大量的指標來進行物件之間的交互，像是這邊對enemy的更新處理，我利用for迴圈遍歷指標陣列，並取出其記憶體位址針對其物件下的hitrect進行碰撞判斷，以及後續遊戲邏輯更新處理。
```cpp=
void playground::enemy_update(float deltatime) {
    for (auto it = enemys.begin(); it != enemys.end(); ) {//遍歷指標
        Enemy* enemy = *it;
        enemy->kinetic(deltatime);
        for (auto bullet: bullets) {
            if(SDL_HasIntersection(bullet->rect, enemy->hitrect)//碰撞判斷
               && !enemy->destroyed) {
                enemy->hurted(bullet->att);// 敵人受傷
                bullet->destroyed = true; // 子彈消滅
                scoreboard.getLevel(1); // level增加
                enemy->animW->playSound(4);// 播放音效
                if (bullet->type == 'p') { // 子彈種類判斷
                    enemy->speed =BDSPEED+10; // 敵人中毒減速
                    if(enemy->type == 'B') { // 判斷是否為BOSS
                        enemy->speed = BDSPEED+120; //減速
                        SDL_Log("slow %d", enemy->speed);
                    }// nextpage
```
```cpp=

                }
            }
        }//這邊基本上和前面是相同的概念
        if(SDL_HasIntersection(enemy->hitrect, player->hitrect) 
           && !enemy->attacked && !enemy->destroyed) {
            scoreboard.getHurt(enemy->getAttack());
            enemy->attacked = true;
            player->animD->playSound(5);
            SDL_Log("Hurt");
        }
        int test = enemy->ifdied();
        //判斷敵人死亡並進行分數與獎勵計算
        if (enemy->destroyed && enemy->animD->finish) {
            enemy->destroy();
            if(test == 1 && enemy->dieANIMcount == 0) {
                scoreboard.getScore(enemy->getAttack());
                scoreboard.getNP(enemy->getAttack()/3+2);
                scoreboard.getLevel(enemy->getAttack());
                enemy->animD->finish = false;
                enemy->state = 'D';

                enemy->speed = BDSPEED;
                enemy->dieANIMcount++;//這是敵人死亡數的計數
                //可以用來後續判斷是否要生成新敵人

            }
            if(enemy->animD->finish) {//判斷敵人遭擊殺且死亡動畫播放完畢
                delete enemy;
                enemyNUM--;
                if(enemy->type=='B') {
                    scoreboard.state = 'n';
                    scoreboard.setLevel(0);
                    enemyNUM = 0;
                }
                it = enemys.erase(it);
            }

        } else {
            ++it;
        }
    }
}
```

#### 其他
其實還有很多內容，像是我的滾動背景設計，場景疊圖，物件碰撞程式(考量兩個rect重疊情況)，角色死亡時的動畫呈現和清除的順序也是我花很多時間修整的地方，但礙於篇幅，這邊就不多贅述。
(程式碼一樣都位於playground中)

<div style="page-break-after:always"></div>

## class Player & Bullet
接戲來就是遊戲的核心角色，我在設計上為了調用的方便，一樣幫只要是會渲染的物件添加move、kinetic、render，在playground中，這兩個式子會被添加到input_progress，update和render中，分別用來處理角色的速度方向控制、位移計算、畫面渲染，這部分的渲染會使用到我寫的Animation class，在動畫顯示上提供很大程度的自由性。
### 程式架構
```cpp=
//player.cpp
class Player {// 玩家 class
public:
    friend class playground; 
    Player(float x, float y, float width ,
           float height, float speed, SDL_Renderer* renderer
           ,float yMax=WINDOW_HEIGHT,float yMin=0);//建構子
    ~Player();//解構子
    void init(float x, float y); //初始化，其實沒用到 因為角色遊戲結束會刪除
    void render(SDL_Renderer* renderer); // 渲染
    void move(char dir); // 改變移動方向
    void kinetic(float dt); // 計算位置
    void changeArrow(char type); //改變弓箭
private:
    SDL_Rect *rect = nullptr;//圖片rect
    SDL_Rect *hitrect = nullptr;//碰撞rect
    const std::vector<std::string> Wimages = PLAYER_WALK_IMAGES;// 走路圖片路徑
    const std::vector<std::string> Simages = PLAYER_SHOOT_IMAGES;// 射擊圖片路徑
    const std::vector<std::string> Dimages = PLAYER_DIED_IMAGES;// 死亡圖片路徑
    Animation* animW = nullptr;//動畫
    Animation* animS = nullptr;//動畫
    Animation* animD = nullptr;//動畫
    float x, y, vx=100, vy=100;//位置和速度
    float speed;//速度
    float width;//寬度
    float height;//高度
    float yMax;//向下最大位置
    float yMin;//向上最大位置
    char direction = 's'; // 角色方向 有's'靜止'u'向上'd'向下
    char state = 'W'; // 角色狀態
    char type = 'N'; // 角色弓箭模式
};
```
#### 參數宣告
private主要是儲存player自己本身的動畫，碰撞位置、大小、方向等等，public主要為會被調用的函式，而將playgraound設為friend，讓其可以調用角色狀態，來判斷當前遊戲是否結束，還有scoreboard要如何顯示。

```cpp=
class Bullet {
    friend class Player;
    friend class playground;
public:
    Bullet(float posx, float posy, char type, SDL_Renderer* renderer);
    ~Bullet();
    void init(float x, float y);//初始化
    void render(SDL_Renderer* renderer);//渲染
    void kinetic(float dt);//計算位置
    void destroy();// 子彈銷毀
    void set_arrow(char t, SDL_Renderer* renderer); // 設定弓箭
private:
    Animation* anim = nullptr;//動畫
    float x, y;// 位置
    bool destroyed = false;//是否被摧毀
    int att = 1; // 攻擊力
    char type = 'N'; // 種類 有四種弓箭
    int np = 0;//消耗的np
    SDL_Rect *rect;//圖片、碰撞
    const std::vector<std::string> Nimages = ARROW_NORM;// 弓箭圖片 種類1
    const std::vector<std::string> Fimages = ARROW_FAST;// 2
    const std::vector<std::string> PNimages = ARROW_POISON;// 3
    const std::vector<std::string> PRimages = ARROW_POWER;// 4
    float vx=100, vy=100;// 速度
    float speed=BULLET_SPEED; // 子彈速度
    float width=40, height=40;// 大小
};
```
#### 參數宣告
private主要是儲存Bullet自己本身的動畫，碰撞位置、大小等等，public主要為會被調用的函式，將playgraound和player設為friend，讓其可以調用弓箭狀態，來判斷敵人的扣血狀態和碰撞情況，還有角色的NP扣取量。

#### 建構子
對player和bullet進行初始化，都要提供位置大小等參數和速度，以定位腳色於我們的畫面上，而bullet發射的判斷是依附於player身上的座標進行移動。

#### Player 的移動
Player的移動主要要考慮當前動畫的幀率，透過在engine寄宿的delta time 去計算我們真正的位移量是多少，在加回原本的位置，能達到較順暢的移動。
```cpp
void Player::move(char dir) {
    direction = dir;// 更改當前的方向
}
void Player::kinetic(float dt) {
    if(direction=='u') {
        y -= speed * vy/abs(vy)*dt;//考慮方向和deltatime
    }else if(direction=='d') {
        y += speed * vy/abs(vy)*dt;
    }
    if (y >= yMax-height) {//防止角色跑出陸地
        y = yMax-height;
    } else if (y <= yMin-height) {
        y = yMin-height;
    }
}
```
在engine有時間的計算:並且把未達到deltatime的時間用while迴圈補齊，以達到每一幀都能等速的目的，不至於畫面忽快忽慢。
```cpp=
while (!SDL_TICKS_PASSED(SDL_GetTicks(), last_frame_time + FRAME_TARGET_TIME));
    float delta_time = (SDL_GetTicks() - last_frame_time) / 1000.0f;
    last_frame_time = SDL_GetTicks();
```


    
## tool.cpp中的其他class和模組
在我的分類中，除了遊戲角色外的特舒功能，我都歸類到tool.cpp和tool.h中，包含Animation、Scoreboard、GameRecorder、MusicPlayer、healthbar之類的，以及一些非class的工具函式，例如:loadTexture、enderProgressBar、createTextTexture、renderText，還有顏色常數宣告(雖然大部分都沒用到)
                               
```cpp=
                               
void renderProgressBar(int x, int y, int w, int h, int value, 
    int max_value,SDL_Color fgColor, SDL_Color bgColor, SDL_Renderer *renderer);

SDL_Texture* createTextTexture(const std::string& text, SDL_Color color,
               TTF_Font* font, SDL_Renderer* renderer);

void renderText(const std::string& text, int x, int y, SDL_Color color,
                TTF_Font* font, SDL_Renderer* renderer, char type); 
```
                               
例如這邊這邊的三個函式，主要都是在 tool 內部被調用，屬於最基層的工作函式，負責直接對介面或式檔案進行處理及渲染，邏輯的概念比較少。
                               
<div style="page-break-after:always"></div>

## Animation 
這是我寫得泛用性最廣的class，在整個程式裡，所有的動畫和音樂特效都是利用這個class進行操作的，基本上每個角色和敵人都有初始化自己的Animtion物件指標，用起來也很方便，還能彈性的調整動畫長度，算是我這裡面最得意的class之一。也是我花費很多時間進行編寫的成果。
### 程式架構
```cpp=
class Animation {
private:
    std::vector<SDL_Texture*> frames;// 用來儲存已經轉成texture的圖檔
    int currentFrameIndex = 0;// 當前的圖片id
    int frameMax = 0;//最大圖片數
    int timeCounter = 0; //計算每張圖的間隔時間
    Mix_Chunk* sound = nullptr;//引入音效
    void addAnimationFrame(const std::string& path, SDL_Renderer* renderer);
    //用來添加圖片
public:
    bool stop = false;//動畫是否暫停
    int updateTime = 0;//更新時間
    int finish = false;//動畫是否播完一輪了
    char id;// 動畫名稱
    void init(); // 動畫初始化
    void playSound(int channel); //播放音效
    Animation(char id, int updateTime, 
              const std::vector<std::string>& imagePaths,
              SDL_Renderer* renderer,const std::string& path="");//建構子
    SDL_Texture* getCurrentFrame();//讓外部獲取當前的動畫圖
    ~Animation();//解構子
    bool update(SDL_Renderer* renderer, SDL_Rect *rect);//更新動畫
};
```
### 參數宣告
pruvate將內建的時間計算、圖片列表、圖片讀取等包裝，使其不被外在class調用更改，同時public提供能夠調整更新時間、暫停動畫等等的參數，讓我們能對動畫的播放進行調整，像是弓箭在切換時射速改變，在player.cpp77行的change arrow就有使用updateTime的改變來讓快速弓箭發射變快。

### 圖片動畫處理
為了讓新增動畫時更方便，不需要考慮圖片數量都能製成動畫，這裡的圖片輸入我特別用vector來進行圖片路徑的讀取，這樣可以讓多張圖片被讀取並轉成SDL_Texture* 的形別，並存入我們的frames中，同時還能計算最大圖片數，以利後續的動畫處理。
```cpp=
void Animation::addAnimationFrame(const std::string &path,
                                  SDL_Renderer* renderer) {
    SDL_Texture* frame = loadTexture(path.c_str(), renderer);
    this->frames.push_back(frame);
    this->frameMax++;
}
```
載入圖片是用以下程式讀入: (加上SDL_Log()有助於圖片錯誤時進行debug) 
```cpp=
SDL_Texture* loadTexture(const std::string& path, SDL_Renderer* renderer) {
    SDL_Texture* newTexture = nullptr;
    SDL_Surface* loadedSurface = IMG_Load(path.c_str());
    if (loadedSurface == nullptr) {
         SDL_Log("Unable to load image %s", path.c_str());
    } else {
        newTexture = SDL_CreateTextureFromSurface(renderer, loadedSurface);
        if (newTexture == nullptr) {
            SDL_Log("Unable to create texture from %s", IMG_GetError);
        }
        SDL_FreeSurface(loadedSurface);
    }
    return newTexture;
}
```
在建構式的地方將所有圖片讀入，以及讀入音訊:
```cpp=
Animation::Animation(char id,int t, 
                    const std::vector<std::string>& imagePaths,
                    SDL_Renderer* renderer, const std::string& soundPath){
    this->id = id;
    this->updateTime = t;
    this->finish = true;
    if(soundPath !="") {
        this->sound = Mix_LoadWAV(soundPath.c_str());
    }
    for (const std::string& path : imagePaths) {
        addAnimationFrame(path ,renderer);
    }//對圖片陣列遍歷並添加
}
```
在使用時，利用getCurrentFrame()抓取圖片並回傳，同時對圖片的counter進行更新，counter是根據我一開始初始化時給的updateTime來進行判斷，因此updateTime愈高，這動畫就會播放得越慢可以和幀率之間進行配合調整。
```cpp=
SDL_Texture *Animation::getCurrentFrame() {
    timeCounter++;
    if (timeCounter >= updateTime && !stop) {
        currentFrameIndex++;
        if (currentFrameIndex >= frameMax-3) {
            currentFrameIndex = 0;
            finish = true;
        }
        timeCounter = 0;

    }
    return frames[currentFrameIndex];
}
```
在我們的遊戲中，各個物件中都可以看到Animation的存在，從MENU的封面動畫到角色的射擊、移動、死亡動畫等等都是。只要呼叫 anim->update() 就能對rect的位置進行部分渲染。
```cpp=
bool Animation::update(SDL_Renderer* renderer, SDL_Rect *rect) {
    SDL_RenderCopy(renderer, getCurrentFrame(), nullptr, rect);
    return 0;
}
```

## Musicplayer
這個就比較簡單了，主要是用來處理背景音樂的程式，至於為甚麼音效和背景音樂要分開呢，主要是因為背景音樂讀入的是mp3檔案，而且使用背景音樂是不能進行混音的，他同時只能播放同一種音檔並循環播放，音次我將它和需要有即時性的音效分開，把音效加入Animation中配合動畫播出，而背景音樂主要在engine就被宣告，因為他是一個跨越頁面的音樂，需要考量到轉場間的音效不同。
而即時性的音效需要能夠堆疊混音，並且還要選擇分配channel，因此寫在Animation中，可以固定每個角色的channel。
### 程式架構
```cpp=
class MusicPlayer {
public:
    MusicPlayer(const std::vector<std::string>& Paths);
    void playMenu();//播主畫面聲音
    void playGamining(int type=1);//播遊戲場景聲音
    void playGameOver();//播遊戲結束聲音
    void stop();
    ~MusicPlayer();
private:
    Mix_Music *musicMenu = nullptr;//音檔
    Mix_Music *musicPlayground = nullptr;
    Mix_Music *musicPlayground2 = nullptr;
    Mix_Music *musicGameOver = nullptr;
    Mix_Chunk *bombsound = nullptr;
};
```
### 參數宣告
音檔都以指標型式儲存於class的private中，而會被外部調用的直接設為public

## healthBar
一個簡單的渲染功能，用來為敵人的血條分配大小與血量，並根據敵人當前血量反映出扣血的狀態，主要是由三個不同顏色的長方體堆疊渲染，行程外框底色以及血條，並控制血條部分的寬度。
### 程式架構
```cpp=
class healthBar {
public:
    healthBar(int max_hp,int w, int h, SDL_Renderer* renderer);
    void render(int x, int y,int width,  int health, SDL_Renderer* renderer);
private:
    int width, height;
    int max_health;
    SDL_Renderer *renderer;
};
```

## 報告後額外心得分享
我自己一直都是很喜歡寫遊戲的人，過去也寫過pygame、godot、unity和一些c\++的小遊戲，這次期末專題剛好能夠有機會自由發揮，並讓我對於 c\++程式更加熟悉，這次花了相當多的時間投入在遊戲製作上，再加上我們組員人數只有兩個，即使早早就開始進行程式編寫，也一直調適到期末前才完成，很高興最後能看到在報告時大家有熱烈的反饋，讓我成就感十足。不過這次有點感覺奇怪的是，評分標準給的蠻晚的，其實最後趕工的時間，很多都是為了把原本設計的程式修出符合分數要求的程式，變得有點心煩意亂，畢竟原本完成的程式，又要更改很多地方，或又出現原本沒有的報錯之類的，不過所幸最後也是順利的完成了。還有報告編寫好麻煩呵呵，比起寫遊戲，報告對我來說根本就是一大負擔，不過把它上傳到hackmd和github上後又是另一種心情了啦，這學期真的收穫很多在c、c++中的特殊用法，課程的內容是真的紮實，很慶幸自己能選上這堂課。
P.S. 希望期末報告能高分!
附上hackmd連結: https://hackmd.io/@B-m0H4fLQ6WdHC924pQUYw/rk3EBw_Skl