PyGame 入門遊戲開發最終篇


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

麻將移動動畫
根據遊戲邏輯,麻將被選中後,是可以再點擊桌面上的空位,進行移動的。要實現麻將的移動,需要有以下幾點功能需要實現:
  1. 檢測鼠標點擊事件,開始進入移動的邏輯。這一點通過 Table 上的“空格”對象進行“點擊判斷”就可以了。
  2. 判斷目標地點是否可以移動。如果沒有選中麻將,不能移動;如果目標地點與被選中的麻將,不在縱橫的直線上,就應該不可以移動。
  3. 修改麻將的位置,並且顯示一段從起點到終點的動畫。
點擊空格產生移動
為了實現上面第一點功能,我們可以為桌子上的“空格”構造一個 Sprite 子類對象,這裡設計叫 Point 類。你可以理解為桌子上鋪了一個桌布,這個桌布是一堆圓點構成的,每個格子的桌布就是一個 Point 對象。

通過在 Point 對象的 update() 中,編寫鼠標點擊事件判斷,就可以發起“移動”功能。這個與上一篇介紹“選中麻將”的做法是一樣的。每個 Point 對象,在每一幀,都會檢測一次,自己是否被鼠標點擊。
在其他的一些遊戲引擎中,往往會在更底層的框架裡,去實現鼠標點擊或者其他“碰撞檢測”的功能。用法一般是:在那些“可以”被玩家點擊的對象身上,添加一個“可點擊”的標記,然後在遊戲中,一旦這種“可點擊”的對象被創建出來,就會被底層代碼放入一個“點擊檢測”的列表,由底層引擎每幀去檢測它們是否有被點擊到。如果有點擊到,就會發起一次對這些對象的某個預設方法的調用。
實現移動動畫
麻將的動畫,實際上是通過每幀重繪“移動中”的麻將的圖像來實現的。也就是說,每個麻將,現在都需要有一個“移動中”的狀態,而不僅僅是直接根據 Table.heap 的座標,直接顯示在屏幕上了。因此我們需要設計一個管理和維持這個狀態的屬性:is_moving,當需要移動麻將的時候,調用 Table.move() 方法,為 is_moving 賦予 True 這個值,表示開始顯示移動動畫。然後,在 Table.update() 中,對於 Table.heap 中的所有 Mahjong 對象,都會調用 show() 方法。只需要在 Mahjong.show() 中不斷的修改 self.rect 的 x,y 屬性值,直到這兩個值等於移動目的地應該顯示的值,就停止修改,把 is_moving 改回 False 值。

每個麻將,在桌上的位置座標(2個元素的一個數組[x,y]),除了在 Table.heap 中記錄,我們也可以給 Mahjong 增加一個 pos 屬性用以記錄。我們可以通過 pos 數值中的座標,計算出麻將牌最後應該顯示的“目的地”位置;然後我們通過在 show() 方法中,不斷修改 Mahjong.rect.left/.top 的值去逼近這個目的位置,就可以實現動畫了。因為根據之前的設計,所有在 Table.heap 裡面的 Mahjong 對象,都會被顯示,我們只需要增加一個判斷:如果 Mahjong.is_move 為 True 的,就不去根據 heap 中的座標去調整 Mahjong.rect 的值,而是按原來那個對象的值去顯示就好了。

具體實現,首先增加一個 Table.move() 方法,具體功能就是設置麻將的新位置,關鍵是對 Mahjong.pos 進行賦值。這個 pos 屬性就是後續顯示移動動畫,關鍵的“目的座標”的計算依據。

上述方法的 src 和 dst 參數,代表了把桌上 src 座標的麻將,移動到 dst 座標去,這兩個參數都是“兩個元素的列表”類型,如 [3,2]
然後我們根據 Mahjong.pos 和 Mahjong.is_moving,在 Mahjong.show() 中添加不斷修改 Mahjong.rect.left/top 的代碼,從而實現移動。

上面的代碼,先計算出移動目的地的顯示座標:dst_left/dst_top,這兩個變量會用來判斷,是否應該停止移動。然後計算 move_left/move_top 這兩個變量,用來決定本幀(當前)此麻將對象應該顯示在什麼位置。注意計算的時候,由於移動速度 moving_speed 未必和麻將的寬度、高度是因數關係,所以可能出現:移動之後的位置 move_left/move_top 越過了 dst_left/dst_top 的情況,所以增加了上圖 14-17 行的代碼,確保不會出現這種情況。最後通過 += 操作,修改 rect 的 left/top 就可以了。
選擇整隊麻將
上面的代碼,只是實現了單個麻將的移動。但是本遊戲的邏輯,是需要實現整隊麻將的移動。因此我們需要有辦法,通過先點擊一個麻將,然後點擊一個空位,來實現選中一隊麻將。之後再對這對麻將裡面的每個對象,進行移動操作即可。


由於此過程必須先選中一個麻將,所以對於“選擇整隊麻將”的功能,適合放到 Mahjong 類中,所以我們定義了 Mahjong.search_deck() 方法:

此方法從被選中的麻將開始,按照點擊的空白點的方向,依次從 Table.heap 中取出麻將,最後放在 deck 變量中返回。這裡由於有水平、垂直兩個方向,所以有兩端類似的代碼,是可以想辦法重構成只有一段的。

這裡需要注意的是,此 search_deck() 返回 None 表示選擇的方法不合法,需要調用處代碼進行判斷處理。如果有更復雜的“不合法”操作的處理方案,就不應該僅僅通過返回 None 來表達了,就可能需要專門做一個包含錯誤的返回值了。在複雜的遊戲開發中,我們可能使用異常、錯誤碼返回值等手段來實現各種“錯誤”的傳遞和處理。這裡由於是入門項目,所以沒有做的更復雜。
模擬移動後檢查是否可消除
由於遊戲的設計,並不允許隨意移動,而是要求移動的一堆麻將中,必須要有可以消除的,才能移動。所以我們不能在移動麻將之後,再一個個判斷“是否有可以消除”,而是應該在移動之前,就遍歷移動的整隊麻將,挨個檢查到達目的地之後,是否可以消除。

如圖,“二條”和“八條”這一隊,就可以往上移動,直到“八條”碰到上面的“八萬”。因為移動到這個位置之後,移動了的“二條”可以和右側的“二條”可以消除。
計算移動後的位置
要實現上述的功能,我們需要分幾步來實現這個功能:
  1. 計算整隊麻將移動後,每個麻將“應該”到達的位置
  2. 在新的位置上,判斷是否可以消除
  3. 在垂直於移動方向的 +1 方向(往下、往右)判斷
  4. 在垂直於移動方向的 -1 方向(往上、往左)判斷
在 Point 類上添加 move_deck_check() 方法,用這個方法進行上面的判斷。

上面這段代碼,重點是對於 dst_x 和 dst_y 的計算。deck 參數存放了所有需要移動的麻將牌,而 self 這個 Point 對象,就是 deck 裡面的多個麻將要移動到的目的地。根據 deck 裡面的第一個麻將的 pos 屬性,以及目的空位 pos 屬性的值,就可以計算出:
  1. 水平還是垂直方向移動
  2. 是 +1 還是 -1 方向移動
有了上面的兩個方向,剩下的就是根據 deck 裡面的順序,從第一個麻將牌開始,依次從目的地位置,倒排過去即可。

上圖就是以往左移動為例,說明了 dst_x 的計算過程。
判斷是否可以消除
一旦獲得了 dst_x/dst_y 作為移動後的位置,以及將要移動的麻將對象的圖案,以及移動的方向,我們就可以編寫一個函數,用以檢查,是否這張麻將牌在新的位置上,有可以與之消除的其他麻將。我們在 Table 類上添加 can_erase() 方法,用來完成這個功能:

這段代碼看似很長,實際處理的過程很簡單:
  1. 根據 direct 的不同進行計算,只判斷垂直於移動方向上的牌
  2. 從 [dst_x, dst_y] 出發,在 +1/-1 的方向上分別進行檢查
  3. 獲取從 [dst_x, dst_y] 出發,遍歷檢查方向上的每一張牌,直到碰到座標邊界:[0,0] 或者 [Table.cols,Table.rows]
  4. 如果是檢查的位置沒有牌,則檢查下一個位置
  5. 如果檢查的位置有牌,圖案相同則返回 True,不同則退出此方向的檢查。


如果此函數返回 True,就可以對選擇的整堆牌,調用 Point.move_deck() 方法,讓整個牌桌呈現新的狀態即可。

而 Point.move_deck() 方法,就是對 deck 中的每個麻將,調用 Table.move() 方法。其中 self.pos 表示隊尾的麻將最終移動到的位置,其他麻將,根據所在隊列中的位置,依照移動方向挨個計算新位置。

總結
  1. 遊戲的主要程序結構:主循環+每幀 update()
  2. pygame 的功能:
  3. 畫圖:Groups/Sprite/Surface
  4. 輸入:evens
  5. 使用類、數組等數據結構進行遊戲邏輯的存放和計算,最後根據這些數據結構用以顯示
至此,整個遊戲的核心玩法開發就完成了。雖然現在還沒有遊戲難度控制、標題畫面和 GameOver 畫面等。但是這些,都不會比遊戲玩法更難實現。
在這個遊戲的開發過程中,使用 pygame 的能力其實並不複雜,最複雜的還是遊戲邏輯的實現。使用什麼樣的數據結構,去表達遊戲邏輯,是一個遊戲程序的核心問題。
最後發一下最終完成版的小遊戲版本,已經上傳機核


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