Breakout

《Breakout》是一款由雅達利開發及發佈的街機遊戲。此遊戲是由諾蘭·布殊內爾和斯蒂夫·布里斯托構思,並且是參考1972年雅達利街機遊戲《乓》創作,於1976年4月發佈,並且已洐生了不少打磚塊作品,如《Gee Bee》和《快打磚塊》。

img

0. 本章重點

  1. 向量(Vector)

  2. 列表(list)

  3. 函數(function)

  4. 邏輯布林運算

  5. 程式常用技巧: 一鍵restart遊戲

1. 繪畫一個會彈牆的球

跟上一次一樣,我們先建立一個空間,畫一個會動的球,這個球在撞到左右兩邊的牆時會反彈。

螢幕截圖 2022-09-24 下午3.05.24

1.1 Vector(向量)

上述的程式碼沒有甚麼問題,一切都可以運作起來,但你想想,如果是2D遊戲,每個球的座標會有兩個變數,如果是3D的遊戲,每個球的座標就會是3個變數;除了變數較多外,這些變數也不好運作。

這裡介紹一個很好用的數學工具叫Vector(向量)。詳細的數學方面的教學可看看這裡

 

就是宣告一個空的Vector

 

跟上面分開宣告x和y一樣,但這裡只要集中x和y宣告一次就可以了。

 

之前是ballPosX加上ballVecXballPosY加上ballVecY,這裡其實都是做同一樣的事情,只是用向量的方式進行。

如果要指定一個向量(vector)的獨立軸的值(例如x軸),可以用這樣表示: ballPos.x

2. 製作會反彈球的彈板

螢幕截圖 2022-09-24 下午4.01.05

宣告一個叫beam的向量。

 

setup()中預返beam的位置,將rectMode()設定為CENTER

 

draw()中,每幀都指定beam的位置為(mouseX, height-15),跟上一次的Pong一樣,當球撞到板後,就反彈球。而今次我們最後將beam畫出來。

3. 美化一下更加像原版

 

加入一個變數beamWidth用來控製球拍的寬度。

 

原本球拍寬度為50時,球拍一半就是25,所以要用ballPos.x >= beam.x - 25 and ballPos.x <= beam.x + 25,現在設定了球拍的寬度為beamWidth,所以要改成為ballPos.x >= beam.x - beamWidth/2 and ballPos.x <= beam.x + beamWidth/2

 

最後將球和拍畫出來。顏色跟隨原版,沒有框線。

4. 製作磚頭

image-20220926140951895

setup()之前,宣告所有磚的位置、狀況(是出現還是消失)、寬度、長度和每一行磚的顏色。值得注意的是brickPosbrickState,由於有6x16塊磚,而每塊磚都有自己的狀態,所以我們不可能用6x16個不同命名的變數去裝起它們,這裡我們用一個list去裝起它們。甚麼是list,可以參考這裡

 

按著在setup()中,先定義每塊磚的長和寬。利用for 去重覆,每次更新每塊磚的位置。由於rectMode()是用CENTER,所以每塊磚的x座標要再加上半塊磚的距離。

 

最後在draw()中,將所有的磚畫出來。每行的磚都有自己的顏色,這時就要用之前事前已經用list存好的brickColor[]

5. 令磚頭消失

image-20220927091919614

setup()中,加入另一個for loop,初始化brickState變成6x16的2d list,並將全部內容都設為True

 

setup()draw()之外,加入第3個自定的函數(function)。這個函數(function)你可以隨意命名,不一定要跟我一樣,函數(function)有2個參數(parameter),python不需要跟其他程式一樣事前定義參數(parameter)的數據格式,你導入的引數(Argument)格式是甚麼就是甚麼,一般為了方便驅分,參數的命名如果跟全域變數(global varaiable)很像的話,都會在名字前面加入_

exp1

 

每次在繪畫磚塊之時,檢查一下球有否撞過磚塊,如果有的話, 就將其狀態(brickState)設定為False,在繪畫時就不繪畫它。

6. 判斷贏或是輸

Screenshot 2022-09-29 124643

 

首先開一個函數叫runGame(),將所有原本在draw()之中的內容都剪下貼上到這個函數之中。留意一下,在這裡的第16行,被加入了一句ballVec.x += random(-5,5),令到每次當球反彈時,不會根據物理原理跟隨法線反彈,而是會隨機加一點亂數,放便消除所有的磚,否則有一些磚可能要花十分多時間才會消除。

 

在程式最開始的宣告區,開一個變數叫gameOver,在setup()中設定它為False

 

在原本的draw()中,修改一下,如果gameOverFalse的話,即代表遊戲還沒有結束,所以就要runGame()即繼續遊戲,之後額外加多2個函數,分別去檢查遊戲是贏了還是輸了。

 

要檢查是否輸就很簡單,如果球飛出了畫面,就即是輸了,所以檢查ballPos.y,如果大於height的話即出了界,就判定其為輸。

 

要檢查是否贏就一點複雜,首先要參考一下下表:

truth-table

在邏輯運算中,or 的邏輯運算,兩個輸入只要有一個是True,就會得出結果為True。所以一開始我們設定一個叫hasTrue的變數,一開始的值是False,之後將所有的brickState[][]都和這個變數or一次,如果其中一個(例如是第2個)是True的話,那麼結果就會變成True,之後即使再or其他的brickState[][]brickState[][]的狀態即使是False,之前的結果也已經轉成了True,所以再or也是True。除非全部的brickState[][]都是False,那麼跟hasTrue全部or一次的結果才會是False。(這是一個寫遊戲很常用的技巧,可以記一下,之後寫遊戲會經常用得著)

7. 考考你

請自行為遊戲加入:

  1. 按下鍵盤的r鍵後會自動重啟這個遊戲;

  2. 加入生命值,如果出了界的話,不會立即gameover,而是會減少生命值,例如有3次機會,出界3次後少gameover。再將生命值用文字顯示在最上方正中,像原版一樣;

  3. 加入分數(score),每當消除一塊磚就加1分,將分數顯示在遊戲左上角;

  4. 加入回合制,完成了一關之後,可以開始下一回合,下一回合的磚頭數量(例如行數加多一行)會有變化,每兩至三關球的速度會加速,消滅磚塊的分數也會跟據回合倍增變成第1關1分, 第2關2分等等。