20220709 大作業二講解

Sprout 2022

評分方式

送分嗎?(20%)

好像有BUG(10%)

大AI時代(20% + bonus 10%)

四人遊戲比較好玩(10%)

新增子彈種類(20%)

新增道具種類(20%)

bonus(10%)

送分嗎 - 成功跑起遊戲(10%):

照著spec(或discord討論區)的說明,安裝套件、編譯、執行,應該就可以ㄌ。

送分嗎 - 更改各種 icon(5%):

在 image 資料夾裡,把其中兩個圖做修改即可。

例如:把子彈從黑的改成白的。

送分嗎 - 更改各種 icon(5%):

在 image 資料夾裡,把其中兩個圖做修改即可。

例如:把子彈從黑的改成白的。

遊戲畫面

送分嗎 - 讓 P1、P2 各勝利一次(5%):

在遊戲裡面,讓P1(P2)瘋狂攻擊對方,直到對方死掉就可以了。

好像有 BUG - 把遊戲結束時的小 BUG 修正(10%):

從你的 shell / terminal,可以看出 bug 大概長怎樣:
    object ... pointer being freed was not allocated.

    這表示... 原本的程式碼釋放了不該釋放的資源。

好像有 BUG - 把遊戲結束時的小 BUG 修正(10%):

先看一下程式架構:

在 main.cpp (主程式)中,最後一段要釋放所有場景的資源,其中有一行是 delete MainGame。

先看一下程式架構:

delete MainGame 會執行如下的程式碼。

這串程式碼在 mainGame.cpp 中。

好像有 BUG - 把遊戲結束時的小 BUG 修正(10%):

先看一下程式架構:

其中,obj->destroy() 和 delete obj ,

會執行的內容如右圖。(這段程式在 object.cpp 中)

好像有 BUG - 把遊戲結束時的小 BUG 修正(10%):

BUG 就出現在這裡!!

好像有 BUG - 把遊戲結束時的小 BUG 修正(10%):

左圖中,依序會執行 obj->destroy() 和 delete obj。

    其中,delete obj (即 ~Object()) 又會執行 obj->destroy()

  (如右圖)

這表示,obj->destroy 被執行了兩次!!

同樣的資源被釋放了兩次,當然就會出問題了。

好像有 BUG - 把遊戲結束時的小 BUG 修正(10%):

所以,修改方式很簡單:

    把 obj->destroy() 和 delete obj 的其中之一刪掉,就 OK 了~

原本(錯誤的)

新的(正確的)

這樣就不會跳錯誤通知了~

大 AI 時代 - 讓遊戲透過 Model class 中,update 回傳的值控制對手(10%)、繼承 Model(5%)

想把鍵盤控制的版本,改成 AI 的版本,

我們可以先觀察,鍵盤是如何去控制的:

在原本的程式之中,是用一個叫 key_state 的陣列,

來決定飛機要如何移動。(key_state 會由鍵盤控制

大 AI 時代 - 讓遊戲透過 Model class 中,update 回傳的值控制對手(10%)、繼承 Model(5%)

在這裡,我們可以用同樣的方式:開另一個陣列來決定對手的行動,該陣列會由 Model 的 update 控制

大 AI 時代 - 讓遊戲透過 Model class 中,update 回傳的值控制對手(10%)、繼承 Model(5%)

update 會以目前的場況當作參數,

修改 board 陣列的值。(如下圖,是讓對手隨機行動)

(下圖的程式碼是寫在我另外新增的 model.cpp 裡)

大 AI 時代 - 讓遊戲透過 Model class 中,update 回傳的值控制對手(10%)、繼承 Model(5%)

只要能讓對手自行移動(即使是完全隨機的),

就能夠拿到這十五分。

大 AI 時代 - 四人遊戲中,兩個對手都是 AI 。(5%)

把兩個對手的行動都用剛剛的方式:開陣列、用 update 依據場況更新值,套用上去即可。

大 AI 時代 - 打敗講師做的 AI。(bonus 10%)

要打敗講師的 AI,其實可以有很多種策略可以發揮。

例如:想增加你的 AI 的攻擊力,可以讓飛機試圖追蹤對手、在對手面前時發射子彈等。

例如:想增加你的 AI 的防禦力,可以讓飛機躲子彈、躲隕石等。

例如:用一些策略性的玩法,像是先多吃藥水再全力攻擊等等。

大 AI 時代 - 打敗講師做的 AI。(bonus 10%)

講師的 AI,原則如下:

int *Model::update(std::list<Object*> object_list, int board[], Player *player){
    // init
    for (int i=0 ; i<ALLEGRO_KEY_MAX ; i++) board[i] = 0;
    int distance;
    
    for (auto it = object_list.begin() ; it != object_list.end() ; it ++){
        // if an opponent is in the front, then attack.
        if (dynamic_cast <Player*> (*it)){
            auto py = dynamic_cast<Player*> (*it);
            if (player == py) continue;
            if ((player->y - height / 2) * (py->y >= height / 2) < 0 && abs(player->x - py->x) <= 1) board[ALLEGRO_KEY_SPACE] = 1;
            distance = player->x - py->x;
        }
        
        // if a bullet is dangerous, then dodge.
        if (dynamic_cast <Bullet*> (*it)){
            auto bu = dynamic_cast<Bullet*> (*it);
            if ((player->y - bu->y)*(bu->speedY) > 0 && abs(player->y - bu->y) <= 3){
                if (player->x <= bu->x && player->x >= bu->x-3) board[ALLEGRO_KEY_A] = 1;
                if (player->x >= bu->x && player->x <= bu->x+3) board[ALLEGRO_KEY_D] = 1;
            }
        }
        
        // if an asteroid is dangerous, then dodge.
        if (dynamic_cast <Asteroid*> (*it)){
            auto as = dynamic_cast<Asteroid*> (*it);
            if ((player->y - as->y)*(as->speedY) > 0 && abs(player->y+1 - as->y) <= 2){
                if (player->x+1 <= as->x && player->x+2 >= as->x) board[ALLEGRO_KEY_A] = 1;
                if (player->x+1 > as->x && player->x <= as->x) board[ALLEGRO_KEY_D] = 1;
                if (player->x+1 == as->x) board[ALLEGRO_KEY_SPACE] = 1;
            }if ((player->x - as->x)*(as->speedX) > 0 && abs(player->x+1 - as->x) <= 2){
                if (player->y+1 <= as->y && player->y+2 >= as->y) board[ALLEGRO_KEY_W] = 1;
                if (player->y+1 > as->y && player->y <= as->y) board[ALLEGRO_KEY_S] = 1;
            }
        }
    }
    
    // Randomly keep the airplanes moving.
    if (board[ALLEGRO_KEY_W] == 0 && board[ALLEGRO_KEY_S] == 0){
        int temp = rand()%5;
        if (temp == 0) board[ALLEGRO_KEY_W] = 1;
        if (temp == 1) board[ALLEGRO_KEY_S] = 1;
    }
    if (board[ALLEGRO_KEY_A] == 0 && board[ALLEGRO_KEY_D] == 0){
        int temp = rand()%20;
        if (temp%5 == 0) board[ALLEGRO_KEY_A] = 1;
        if (temp%5 == 1) board[ALLEGRO_KEY_D] = 1;
        if (temp == 2 && distance < 0) board[ALLEGRO_KEY_D] = 1;
        if (temp == 2 && distance > 0) board[ALLEGRO_KEY_A] = 1;
    }
    // I want to keep the airplanes shooting.
    if (rand()%5 == 0) board[ALLEGRO_KEY_SPACE] = 1;
    
    return board;
}

    有對手在前方,就打

    有子彈或隕石來了,就躲

    最後用一點亂數增加隨機性 (和被打到的機會)

大 AI 時代 - 打敗講師做的 AI。(bonus 10%)

解答範例:

程式邏輯:有對手在前方,就打。

                   有子彈或隕石來了,就躲。

                   基本上只要躲得夠好,就能贏過講師了

int *Model::update(std::list<Object*> object_list, int board[], Player *player){
    // init
    for (int i=0 ; i<ALLEGRO_KEY_MAX ; i++) board[i] = 0;
    
    for (auto it = object_list.begin() ; it != object_list.end() ; it ++){
        // if an opponent is in the front, then attack.
        if (dynamic_cast <Player*> (*it)){
            auto py = dynamic_cast<Player*> (*it);
            if (player == py) continue;
            if ((player->y - height / 2) * (py->y >= height / 2) < 0 && abs(player->x - py->x) <= 1) board[ALLEGRO_KEY_SPACE] = 1;
        }
        
        // if a bullet is dangerous, then dodge.
        if (dynamic_cast <Bullet*> (*it)){
            auto bu = dynamic_cast<Bullet*> (*it);
            if ((player->y - bu->y)*(bu->speedY) > 0 && abs(player->y - bu->y) <= 3){
                if (player->x <= bu->x && player->x >= bu->x-3) board[ALLEGRO_KEY_A] = 1;
                if (player->x >= bu->x && player->x <= bu->x+3) board[ALLEGRO_KEY_D] = 1;
            }
        }
        
        // if an asteroid is dangerous, then dodge.
        if (dynamic_cast <Asteroid*> (*it)){
            auto as = dynamic_cast<Asteroid*> (*it);
            if ((player->y - as->y)*(as->speedY) > 0 && abs(player->y+1 - as->y) <= 2){
                if (player->x+1 <= as->x && player->x+2 >= as->x) board[ALLEGRO_KEY_A] = 1;
                if (player->x+1 > as->x && player->x <= as->x) board[ALLEGRO_KEY_D] = 1;
                if (player->x+1 == as->x) board[ALLEGRO_KEY_SPACE] = 1;
            }if ((player->x - as->x)*(as->speedX) > 0 && abs(player->x+1 - as->x) <= 2){
                if (player->y+1 <= as->y && player->y+2 >= as->y) board[ALLEGRO_KEY_W] = 1;
                if (player->y+1 > as->y && player->y <= as->y) board[ALLEGRO_KEY_S] = 1;
            }
        }
    }
    // randomly shoot
    if (rand()%20 == 0) board[ALLEGRO_KEY_SPACE] = 1;
    return board;
}

大 AI 時代 - 打敗講師做的 AI。(bonus 10%)

當然,如果不賦予 AI 一些隨機性,遊戲可能就不是那麼好玩(雖然我們評分是不評這個),如果你想做的是一個「好玩」的 AI,也許可以考慮加些隨機的要素進去,並且把難度做適當調整,就可以藉由 AI 讓這個遊戲更好玩。

我們可以先想想,把現有的兩人遊戲改成四人,

    會需要修改什麼東西。

飛機從兩架變四架

四人遊戲 - 把遊戲從兩人改成四人(5%):

四架飛機要能移動、發射子彈

四位玩家的資訊要即時更新

遊戲畫面要顯示四位玩家的血量、能量等資訊

其他還有...?

飛機從兩架變四架(以下都是我個人的做法,僅供參考)

四人遊戲 - 把遊戲從兩人改成四人(5%):

在 mainGame.hpp 中,新增 P3、P4 兩個屬性。

在 mainGame.cpp 中,initail() 裡面,多生成兩架飛機(如下)。

事實上,這個小題只要在 mainGame.cpp 裡面,找到所有有關 P1 和 P2 的程式碼,再自己補上 P3、P4 的對應程式碼,應該就不會有太大問題。

四人遊戲 - 把遊戲從兩人改成四人(5%):

以下列出幾個例子:

四位玩家的資訊要即時更新:

四人遊戲 - 把遊戲從兩人改成四人(5%):

玩家資訊的更新寫在 update() 裡面(如上圖)。

只要仿造上圖,寫出下圖的程式碼,就能更新 P3 與 P4 的資訊。

遊戲畫面上要印出四位玩家的血量、能量等資訊:

四人遊戲 - 把遊戲從兩人改成四人(5%):

在 draw() 裡面,仿造印出 P1、P2 資訊的那段程式碼的樣子,就可以也印出 P3、P4 的資訊(如下是 P3 資訊的程式)

四位玩家都要能移動、發射子彈:

四人遊戲 - 把遊戲從兩人改成四人(5%):

在 update() 裡面,有一段程式碼,負責 P1 的移動與發射。(左)

仿造這段程式碼,就可以寫出 P3 (或P4) 的版本。(右)

    // if up fght is pressed then player3 moves.
    // player3 will move from (P3->x, P3->y) to (P3->x + P3->speedX, P3->y + P3->speedY)
    if(key_state[ALLEGRO_KEY_H]){
        this->P3->speedX += speed;
    }
    if(key_state[ALLEGRO_KEY_F]){
        this->P3->speedX -= speed;
    }
    if(key_state[ALLEGRO_KEY_T]){
        this->P3->speedY -= speed;
    }
    if(key_state[ALLEGRO_KEY_G]){
        this->P3->speedY += speed;
    }
    
    // We cannot let (P3->x + P3->speedX, P3->y + P3->speedY) be out of bound.
    if(this->P3->x + this->P3->speedX > width - 3 || this->P3->x + this->P3->speedX < 0 ){
        this->P3->speedX = 0;
    }
    if(this->P3->y + this->P3->speedY > height - 3 || this->P3->y + this->P3->speedY < height / 2 ){
        this->P3->speedY = 0;
    }
    
    // when player3 shoot (press space), create new bullet object in front of player3
    if(key_state[ALLEGRO_KEY_B] && this->P3->energy >= 20 && this->P3->bullet_cool == 0){
        this->P3->bullet_cool = 5;
        this->P3->energy -= 20;
        ALLEGRO_BITMAP *tmp = al_clone_bitmap(this->bullet_img);
        Object *bullet = new Bullet(this->P3->x + 1, this->P3->y - 1, 0, -1, tmp, 3);
        this->object_list.push_back(bullet);
    }
    // if asdw is pressed then player1 moves.
    // player1 will move from (P1->x, P1->y) to (P1->x + P1->speedX, P1->y + P1->speedY)
    if(key_state[ALLEGRO_KEY_D]){
        this->P1->speedX += speed;
    }
    if(key_state[ALLEGRO_KEY_A]){
        this->P1->speedX -= speed;
    }
    if(key_state[ALLEGRO_KEY_W]){
        this->P1->speedY -= speed;
    }
    if(key_state[ALLEGRO_KEY_S]){
        this->P1->speedY += speed;
    }
	
    // We cannot let (P1->x + P1->speedX, P1->y + P1->speedY) be out of bound.
    if(this->P1->x + this->P1->speedX > width - 3 || this->P1->x + this->P1->speedX < 0 ){
        this->P1->speedX = 0;
    }
    if(this->P1->y + this->P1->speedY > height - 3 || this->P1->y + this->P1->speedY < height / 2 ){
        this->P1->speedY = 0;
    }
    
    // when player1 shoot (press space), create new bullet object in front of player1
    if(key_state[ALLEGRO_KEY_SPACE] && this->P1->energy >= 20 && this->P1->bullet_cool == 0){
        this->P1->bullet_cool = 5;
        this->P1->energy -= 20;
        ALLEGRO_BITMAP *tmp = al_clone_bitmap(this->bullet_img);
        Object *bullet = new Bullet(this->P1->x + 1, this->P1->y - 1, 0, -1, tmp, 1);
        this->object_list.push_back(bullet);
    }

同隊不互撞:把前兩頁投影片中,負責玩家移動的程式碼片段(上圖),新增一個與隊友互撞的判定(下圖)即可。

四人遊戲 - 同隊玩家不互撞、子彈會打到隊友(5%):

    // We cannot let (P1->x + P1->speedX, P1->y + P1->speedY) be out of bound.
    int p1x = this->P1->x + this->P1->speedX, p1y = this->P1->y + this->P1->speedY;
    if(p1x > width - 3 || pix->speedX < 0 ){
        this->P1->speedX = 0;
    }
    if(p1y > height - 3 || p1y < height / 2 ){
        this->P1->speedY = 0;
    }
    p1x = this->P1->x + this->P1->speedX, p1y = this->P1->y + this->P1->speedY;
    // We cannot let (P1->x + P1->speedX, P1->y + P1->speedY) be out of bound.
    int p1x = this->P1->x + this->P1->speedX, p1y = this->P1->y + this->P1->speedY;
    if(p1x > width - 3 || pix->speedX < 0 ){
        this->P1->speedX = 0;
    }
    if(p1y > height - 3 || p1y < height / 2 ){
        this->P1->speedY = 0;
    }
    if (p1x+2 >= p3x && p1x <= p3x+2 && p1y+2 >= p3y && p1y <= p3y+2){
        this->P1->speedX = 0;
        this->P1->speedY = 0;
    }
    p1x = this->P1->x + this->P1->speedX, p1y = this->P1->y + this->P1->speedY;

這段程式碼的邏輯是,如果我這架飛機確實按照鍵盤的狀態來移動,會超出邊界、或是會撞到隊友的話,就不能讓飛機確實按照鍵盤狀態來移動。

四人遊戲 - 同隊玩家不互撞、子彈會打到隊友(5%):

    // We cannot let (P1->x + P1->speedX, P1->y + P1->speedY) be out of bound.
    int p1x = this->P1->x + this->P1->speedX, p1y = this->P1->y + this->P1->speedY;
    if(p1x > width - 3 || pix->speedX < 0 ){
        this->P1->speedX = 0;
    }
    if(p1y > height - 3 || p1y < height / 2 ){
        this->P1->speedY = 0;
    }
    if (p1x+2 >= p3x && p1x <= p3x+2 && p1y+2 >= p3y && p1y <= p3y+2){
        this->P1->speedX = 0;
        this->P1->speedY = 0;
    }
    p1x = this->P1->x + this->P1->speedX, p1y = this->P1->y + this->P1->speedY;

子彈打到隊友:在 update() ,check collision 的 for 迴圈中(就最大的那個 for 迴圈),有子彈撞玩家的判定(左)。

四人遊戲 - 同隊玩家不互撞、子彈會打到隊友(5%):

將左圖改成右圖,即可達到我們要的效果。

(在 P3、P4 生成子彈時,要記得把子彈的 type 設定對)

到 image 資料夾,做出四張子彈圖片。

四人遊戲 - 新增子彈(20%):

在 mainGame.hpp 中,新增 bullet3_img 和 bullet4_img 兩個屬性。

在 mainGame.cpp,MainGame::MainGame() 中,有處理子彈圖片的程式碼。將其由兩種子彈的版本,改為四種子彈的版本。(如下圖)

四人遊戲 - 新增子彈(20%):

如此一來,四位玩家就分別都有一種長得不一樣的子彈了。

四人遊戲 - 新增子彈(20%):

再來,我們可以來幫這些子彈加點效果,例如:

子彈有 20% 機率爆擊。

子彈打到隕石獲得的經驗值加倍。

子彈移動速度加倍。

子彈打到敵方可以讓敵方 energy 歸零。

...等等。這裡可以盡情發揮你的創意~

四人遊戲 - 新增子彈(20%):

子彈有 20% 機率爆擊。(假設是玩家一)

在 MainGame.cpp,update() 的 check collision 的 for 迴圈中,對子彈撞玩家的判定做修改:

四人遊戲 - 新增子彈(20%):

子彈打到隕石經驗值加倍。(假設是玩家二)

在 MainGame.cpp,update() 的 check collision 的 for 迴圈中,對子彈撞隕石的判定做修改:

四人遊戲 - 新增子彈(20%):

子彈移動速度加倍。(假設是玩家三)

在 MainGame.cpp,update() 中,玩家三發射子彈時的參數做修改。(由上圖改成下圖)

其中,被由 -1 改成 -2 是子彈的 speedY 參數,詳情可以查看 bullet.cpp。

四人遊戲 - 新增子彈(20%):

子彈可讓敵方 energy 歸零。(假設是玩家四)

在 MainGame.cpp,update() 的 check collision 的 for 迴圈中,對子彈撞玩家的判定做修改:

到 image 資料夾,做出四張道具圖片。

四人遊戲 - 新增道具(20%):

在 mainGame.cpp,update() 中,有一段程式在處理道具的生成。(上圖)

四人遊戲 - 新增道具(20%):

既然現在總共有七種道具,那我就把 type 的範圍設成 0 到 6。(下圖)

如此一來,遊戲畫面上就會出現更多種道具了!

四人遊戲 - 新增道具(20%):

至於道具的效果...這裡也是發揮你們的創意了!

四人遊戲 - 新增道具(20%):

例如:

吃到後會無敵一段時間。

吃到後打到隕石所得到的經驗值加 10。

吃到後攻擊力加倍一段時間。

吃到後發射子彈所需的 energy 減 5。

在這裡,我們可以在 player.hpp 中添加屬性,來儲存玩家與道具之間的狀態。以前面四種效果為例:

四人遊戲 - 新增道具(20%):

吃到後會無敵一段時間。

吃到後打到隕石所得到的經驗值加 10。

吃到後攻擊力加倍一段時間。

吃到後發射子彈所需的 energy 減 5。

然後,在 player.cpp 中,記得對這些添加的屬性做正確的初始化。(下圖)

四人遊戲 - 新增道具(20%):

吃到後會無敵一段時間:(假設是 type 3)

(potion[3] 設定:平常是0,如果吃到對應道具會變 500。每 tick 減 1,如果非 0 就表示在無敵狀態)

四人遊戲 - 新增道具(20%):

吃到後攻擊力加倍一段時間:(假設是 type 4)

(potion[4] 設定:平常是0,如果吃到對應道具會變 500。每 tick 減 1,如果非 0 的話子彈威力加倍)

吃到後會無敵一段時間,和吃到後攻擊力加倍一段時間,兩者比較接近,所以我們放在一起講。

四人遊戲 - 新增道具(20%):

從 potion[3]、potion[4] 的設定中,我們大概可以知道我們可能需要修改幾個地方:

吃到道具時要改成 500。

每個 tick 要減 1。

被打到時,要根據 potion[3] 的值,決定是否受到傷害。

被打到時,要根據 potion[4] 的值,決定是否受到兩倍傷害。

四人遊戲 - 新增道具(20%):

吃到道具時要改成 500:

    在判斷碰撞的 for 迴圈中,作下列修改:

四人遊戲 - 新增道具(20%):

每個 tick 要減 1:

    在 update() 處理玩家資訊的程式部分,做以下修改:

四人遊戲 - 新增道具(20%):

被打到時,要根據 potion[3] 和 potion[4] 的值,決定受到的傷害:

    在判斷碰撞的 for 迴圈中,作下列修改:

原本:

修改過後:

四人遊戲 - 新增道具(20%):

吃到後打到隕石所得到的經驗值加 10、吃到後發射子彈所需的 energy 減 5,這兩者我們也放在一起講:

打到隕石所得到的經驗值加 10:(假設是 type 5)

(potion[5] 設定:開場時是 20,如果吃到對應道具就加 10。玩家打到隕石會加 potion[5] 經驗值。

發射子彈所需的 energy 減 5:(假設是 type 6)

(potion[6] 設定:開場時是 20,如果吃到對應道具就減 5。玩家發射子彈會需要 potion[6] 的 energy。

四人遊戲 - 新增道具(20%):

因此,我們一樣要在判斷碰撞的 for 迴圈中,作下列修改:

四人遊戲 - 新增道具(20%):

在該 for 迴圈中,子彈撞隕石的部分要做修改:

原本的:

修改過的:

四人遊戲 - 新增道具(20%):

在處理玩家發射子彈的程式部分也要做修改:

原本的:(以player 1 為例子)

修改過的:

四人遊戲 - 新增道具(20%):

以上就是前面幾種道具的例子。

因為你們做的道具(和子彈)應該會跟我們不一樣,

所以要針對你自己需要的部分來對程式碼修改哦~

謝謝大家

20220709 大作業二講解

By allen522019

20220709 大作業二講解

  • 240