用 PyGame 入門遊戲開發第三篇


3樓貓 發佈時間:2024-04-16 20:33:15 作者:韓大 Language

消除麻將
根據遊戲規則,兩張相同圖案的麻將,如果互相之間沒有其他麻將牌被直線阻隔(中間的距離可以無限),可以通過先後點擊選擇這兩張麻將,消除這兩張牌。
要實現以上功能,需要分步完成以下幾個能力:
  1. 要能實現“先後選中”的能力,因此要對鼠標點擊的操作做出響應。
  2. 需要能控制顯示、消失圖像,用以表現“選中”麻將,以及顯示“消除”的效果。
  3. 通過“桌子”的內置數據結構,對麻將牌的位置是否成直線、兩個選中的麻將判斷是否有阻隔。
選中麻將
對於麻將類 Mahjong 的 update() 方法,增加對於用戶輸入事件的檢測和處理,就能完成“選中麻將”的功能:
第一篇介紹的 Director 類,會在每一幀,都通過 pygame 把所有的用戶輸入事件,存放到 Director.events 屬性中,所以每個 Sprite 的子類對象,都可以在 update() 函數中去檢測判斷:用戶有什麼輸入。
由於 mahjong.MainScenario 類,在 start() 方法中,構建 Table 這個 Group 的子類對象時,傳入了 director 參數
table = Table(self.director)
因此每個 Mahjong 對象,都可以通過其 table 屬性獲得 director 對象,從而獲得每幀更新的用戶事件(們):self.table.director.events
桌上所有的 Mahjong 對象,由於存放在 Table 這個 Group 裡面,所以每幀其 update() 都會被調用。也就是說,每幀、每個麻將對象,都可以在 update() 裡檢測一遍:“我”有沒有被鼠標點中。用戶在此刻的所有操作,會被 pygame 放入 events 列表,需要我們通過循環迭代語句,獲取其中的每個事件。
通過 event.type 屬性,判斷 pygame.MOUSEBUTTONDOWN 就可以知道是否有鼠標按鈕按下的事件;隨後可以通過 pygame.mouse.get_pos() 可以獲得鼠標當前的位置;最後通過 Sprite.rect.collidepoint(pos) 可以判斷當前 Sprite 對象是否有“碰撞”到某個 pos 點位置。當前的 Sprite 就是麻將對象,所以我們就判斷鼠標是否“點擊”到了當前的麻將。
顯示選中特效
對於選中的麻將,我們希望是:
  1. 如果第一次選中麻將,在被選中的麻將上顯示一個“框框”
  2. 被選中的麻將,需要以某個方式記錄其座標
  3. 如果已經有一個麻將被選中,選中第二個麻將後,“框框”消失
所有需要控制顯示的對象,都繼承 Sprite 實現一個類,通過構造器來實現加載某個圖像數據。此對象的 image/rect 屬性通過加載一個圖片作為框框顯示,這個圖片需要是中間透明的,所以使用的 png 格式。

我們可以建立一個類 Edge,用來顯示“選中框”。此類有的 pos 屬性是一個數組,記錄選中的麻將牌的桌上座標。
Table 類添加一個屬性 edge,持有此 Edge 對象;另外一個屬性 is_show_edge 記錄框框是否已經顯示。
在每幀都調用的 Table.show() 方法裡面,根據 is_show_edge 屬性,來決定是否 add(self.edge),就可以實現根據 is_show_edge 來顯示/消失這個框框。
由於每次顯示 Edge 對象的位置不一樣,所以在 Table 上增加了一個 show_edge() 方法,用來修改 Table.edge 的位置:
其中 loc 參數表示被選中麻將的座標,會記錄到 Edge.pos 上,同時根據此座標計算並修改 edge.rect 的位置,並且對 is_show_edge 賦值為 True;當點擊事件觸發“點擊第二張牌”的時候,此屬性會被置為 False。
選中第二個牌的處理
點擊第二張牌後,需要判斷是否可以消除,代碼在 Mahjong.update():
由於 Table.show_edge() 會在 table.edge.pos 記錄被選中的第一張麻將的座標,所以第二張麻將被選中的時候,可以通過這個座標(i,j)從 table.heap 這個二維數組獲得被選中的麻將。
下面就是幾個情況,判斷是否可以消除,具體判斷:
  • 兩個牌直接是否有阻隔
  • 被選中的牌不能是空
  • 兩張牌的圖案是一樣的
  • 不能選中兩次是同一張牌
如果可以消除,通過對 heap[x][y] 的值賦值 None 就表示了消除。在 Table.show() 裡面,會跳過為 None 的 heap 成員,因此就可以作為消除牌的功能實現。
如果不能消除,這裡調用了一個 Table.show_text() 方法,用於顯示提示文字,後續會介紹如何顯示。
顯示爆炸效果
在上述邏輯中,通過了以下代碼實現“顯示”爆炸效果:
self.bomb.show(self.rect.left,self.rect.top)
selected.bomb.show(selected.rect.left,selected.rect.top)
由於在 MainSenario 的 start() 方法中,為每個麻將對象,都添加了一個爆炸對象屬性 Mahjong.bomb,所以被選中的兩個麻將對象,都可以調用 self.bomb.show() 這個方法,傳入了需要顯示的座標。一旦調用這個方法,Bomb 類就會自己通過 Bomb.update() 方法,顯示一段時間“爆炸”的圖片。如果想內存佔用的小一點,也可以在 MainSenario.start() 方法中只構造兩個 Bomb 對象,然後在需要爆炸的時候,再顯示到對應的位置。
具體的方法是:
  1. 修改自己的顯示位置,把自己 add 到“特效層”的 effect 組裡
  2. 設置一個倒計時屬性 counter,需要顯示多少幀時間,就設置為多少,這裡是 30,也就是一秒,因為 director.fps 設置了 30
  3. 通過 update() 方法,每幀對 counter 減一,如果為 0,則從 effect 組裡去掉(通過 Group.remove(Sprite) 方法),從而消失。由於 effect 組並不會每幀都清空所有成員,和 table 組不一樣,所以不需要每次 update() 都去 add() 一次自己
顯示文字提示
文字提示,實際上也是一種 Sprite 對象,也需要對 image/rect 進行賦值,和上面的圖像不同的是,文字的 image 需要通過選擇字體和文字內容進行繪製。如果要顯示一段文字在遊戲畫面上,只需要:
從上面的代碼可以看出,我們可以選擇文字的字體、顏色,還可以選擇和其他內容共同“畫”在一個圖形上。
由於本遊戲只需要在一個地方顯示文字,而且字體只需要一種,所以在 Table 對象的屬性中構造好字體對象 font、顯示文字對象這兩個對象 text_sprite。另外,這個提示文字需要自動消失,所以還需要兩個屬性來記錄文字顯示了幾秒 show_text_time,以及何時開始 start_ticks。這個自動消失的功能和上面的爆炸特效功能類似,但是這裡使用了不同方法,純粹為了學習。
然後寫一個 show_text() 的方法,用來在桌上顯示文字:
這裡需要注意的是 self.show_text_time = time 這句,是記錄了當前文字要顯示多少秒,這個值會在 update() 中逐漸減少,用以讓文字自動消失。下面是 Table.show() 的代碼段:
這裡可以看到,每次 update() 調用 show(),然後都會判斷一下 show_text_time,用以決定是否要顯示文字提示。由於 self.start_ticks 記錄了啟動顯示的時間,所以根據 pygame.time.get_ticks() 返回的當前時間(毫秒數),就能知道已經顯示了多久。顯示和消失也是用 add() 和 remove() 控制。由於 Table.show() 的第一行是 self.empty(),會清空所有在 table 這個 Group 裡的 Sprite,所以下面要顯示的內容,都必須要調用 self.add()。
從上面的代碼可以看到,遊戲程序的所有“動態能力”,基本實現思想都是:
  • 每個遊戲對象在構造器或者初始化函數中,構建好所需的各種對象
  • 通過每幀調用 update() 函數進行“驅動”
  • 在每幀的時刻,進行用戶操作檢測
  • 在每幀的時刻,計算出當前幀遊戲的內部邏輯的狀態
  • 根據當前幀的狀態,控制在屏幕上合適的位置,實現顯示、消失
因此,遊戲系統的動畫,也大多數是如此實現,是通過一幀幀的邏輯,來決定如何顯示下一個畫面,從而形成一個動畫。由於 udpate() 函數每幀都要調用,所以儘量減少在這個函數中構建新的對象,或者進行特別慢的操作如等待加載磁盤文件、等待網絡響應等。因為如果 update() 特別慢,整個遊戲的運行就會感覺特別卡。
下一篇介紹如何實現麻將的移動動畫,以及複雜的遊戲邏輯判斷。

© 2022 3樓貓 下載APP 站點地圖 廣告合作:asmrly666@gmail.com