接下來學習怎麼製作障礙物,也就是樹和小旗。在這一部分,為了簡化起見,我們再次從零開始——沒有滑雪者,只有障礙物。我們會在最後將滑雪者的代碼和障礙物的代碼放到一起。
Skier 遊戲的窗口大小是 640×640 像素。為了簡化,也為了防止障礙物靠得太近,我們將窗口分割為一個 10×10 的網格。這樣,一共有 100 個格子,每個格子的大小是 64×64 像素。因為我們的障礙物尺寸並不是太大,所以即使兩個障礙物位於相鄰的格子中,它們之間也有一些空隙。
創建單個障礙物
首先我們需要創建單個障礙物。為此,我們創建了一個名為 ObstacleClass
的類。和滑雪者一樣,這也是一個 Pygame Sprite
類。
class ObstacleClass(pygame.sprite.Sprite): def __init__(self, image_file, location, type):pygame.sprite.Sprite.__init__(self)self.image_file = image_fileself.image = pygame.image.load(image_file)self.rect = self.image.get_rectself.rect.center = locationself.type = typeself.passed = False
創建障礙物地圖
現在我們來創建多個障礙物,以便填充在 640×640 像素的窗口中。我們將 10 個障礙物(小旗和樹)隨機地分佈在 100 個格子中,每個障礙物既可以是小旗也可以是樹。最終結果可能是 2 面小旗 8 棵樹,7 面小旗 3 棵樹,或者是總和為 10 的任意組合。總之,小旗和樹是隨機選擇的,它們的位置也是隨機的。
我們唯一需要當心的是,不要嘗試將兩個障礙物放在同一位置,所以我們需要知道哪些位置是已經使用過的。變量 locations
是一個用於記錄使用過的位置的列表。當想要在某個位置放置一個新的障礙物時,我們首先要查看這個位置是否已有一個障礙物。
好眼力!我們不希望遊戲開始時滿屏都是障礙物,而是希望它是空白的,然後障礙物從底部出現。所以,我們將障礙物的場景創建在窗口底部的「下方」。為此,我們需要為每個障礙物位置的 y 值增加 640 像素(窗口的高度)。
遊戲開始時,我們希望障礙物從底部滾動上來。為此,我們修改每個障礙物位置的 y 值。改動的大小取決於滑雪者滑下小山的速度。我們將它放入一個名為 update
的方法中,它是 ObstacleClass
的一部分。
def update(self): global speed self.rect.centery -= speed[1]
變量 speed
是滑雪者的速度,它是一個全局變量,包含了 x 和 y 方向的速度,所以我們使用索引 [1] 來獲取 y(垂直)方向的速度。
和創建的第一屏障礙物一樣,我們還需要在窗口下方創建另外一屏的障礙物。那怎麼知道應該在什麼時候創建呢?我們可以創建一個名為 map_position
的變量,由它來告訴我們場景已經向上滾動了多少。我們在主循環中像下面這樣處理。
我們用 animate
函數來重繪屏幕,就像在只有滑雪者的代碼中那樣。合在一起,只有障礙物的代碼看起來像下面這樣。
代碼清單 25-2 創建 Skier 遊戲——只有障礙物
如果運行以上代碼,你應該可以看到樹和小旗在屏幕上往上滾。
問得好。在當前的代碼中,我們讓它們繼續在窗口上邊界外向上滾動,這樣其位置的 y 值(負值)絕對值會越來越大。如果遊戲運行了比較長的時間,則會創建並積累大量的障礙物場景。這有可能會導致程序變慢或者在某個時間點發生內存不足的情況。所以我們需要做一點清理工作。
在障礙物類的 update
方法中,我們添加一個判斷邏輯,看看障礙物是否已經移出屏幕。如果是的話,就移除它。Pygame 有一個名為 kill
的原生方法可用來做這件事。新的 update
方法看起來像下面這樣。
現在我們可以將滑雪者和障礙物的代碼放到一起了。
我們需要
SkierClass
和ObstacleClass
。我們的
animate
函數需要同時繪製滑雪者和障礙物。我們的初始化代碼需要創建滑雪者和初始地圖。
主循環需要同時包括滑雪者的鍵盤事件綁定和障礙物場景的創建。
基本上,這組合了代碼清單 25-1 和代碼清單 25-2,結果如下所示。
代碼清單 25-3 滑雪者代碼與障礙物代碼相結合
如果運行以上代碼,你就能操作滑雪者滑下小山,並且會看到障礙物向上滾過。你也會注意到滑雪者向左右滑和向下滑的速度取決於他的轉向。至此,這個遊戲已經快要完成了。
最後我們需要處理的兩項是:
檢測滑雪者是否碰到了樹或者是否撿到了小旗
記錄並顯示分數
你已經在第 17 章中學到如何進行碰撞檢測了。代碼清單 25-3 已經將障礙物精靈放到了一個 sprite 組中,所以我們可以直接用 spritecollide
函數來檢測滑雪者是否碰到樹或者小旗。接下來我們需要知道這個障礙物是什麼(樹還是小旗),然後:
如果是樹,則將滑雪者的圖像切換為「碰撞」的圖像,並將分數減去 100。
如果是小旗,則將分數加 10,然後將小旗從屏幕中移除。
為此所需的代碼位於主循環中,看起來像下面這樣:
變量 hit
告訴我們滑雪者撞到了哪個障礙物。它是一個列表,但在這裡列表中只有一個條目,因為滑雪者一次只可能碰到一個障礙物,所以他碰到的障礙物是 hit[0]
。
passed
變量用於標記是否碰撞過樹。這能保證當滑雪者在撞樹之後繼續往下滑時,不會立刻運行撞到同一棵樹的邏輯。
現在我們需要顯示分數。這只需要再寫 3 行代碼。在初始化代碼中,我們創建一個 font
對象,它是 Pygame 的 Font
類的實例:
font = pygame.font.Font(None, 50)
在主循環中,我們使用一個新的分數文本來渲染 font 對像:
score_text = font.render(\"Score: \" +str(points), 1, (0, 0, 0))
在 animate
函數中,我們在左上角顯示分數:
screen.blit(score_text, [10, 10])
這就是這個遊戲的全部了。如果你把這些組合在一起,會發現這就是第 10 章的代碼,只是現在你理解得更加深入了。理解 Skier 遊戲的工作原理可以幫助你更好地策劃和開發自己的遊戲。
你學到了什麼
在這一章,你學到了以下內容。
Skier 遊戲的各個部分是如何工作的
怎樣創建一個滾動的背景
動手試一試
1. 嘗試修改 Skier 遊戲,使遊戲的難度隨著遊戲的進行逐漸增大。可以嘗試以下建議:
使遊戲的速度隨著遊戲的進行逐漸加快。
滑雪者滑下小山時樹越來越多。
添加障礙物「冰」,使得滑雪者轉向時更加困難。
2. Skier 遊戲的靈感來自一個名為 SkiFree 的遊戲,這個遊戲中有一個討厭的雪人會隨機出現並追趕滑雪者。如果你想挑戰一下,可以嘗試在 Skier 遊戲中添加一些類似的物體。你需要找到或者創建一個新的圖片,並且修改代碼以獲得你想要的行為。