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