讀古今文學網 > 父與子的編程之旅:與小卡特一起學Python > 17.2 崩 ! 碰撞檢測 >

17.2 崩 ! 碰撞檢測

大多數計算機遊戲中,你需要知道一個動畫精靈在什麼時候碰到另一個精靈。例如,可能需要知道保齡球何時碰到球瓶,或者導彈什麼時候擊中飛船。

你可能認為,如果我們知道每個動畫精靈的位置和大小,可以寫一些代碼對每一個其他動畫精靈的位置和大小進行檢查,看哪裡出現了重疊。不過,編寫 Pygame 的人已經為我們完成了這項工作。Pygame 中已經內置有這種碰撞檢測。

術語箱

簡單地說,碰撞檢測(collision detection)指的是瞭解兩個動畫精靈何時接觸或重疊。兩個移動的東西相互碰到一起,這就是一個碰撞(collision)。

Pygame 還提供了一種方法對動畫精靈分組。例如,在保齡球遊戲中,所有球瓶可能在一組,球則在另一組。

組和碰撞檢測密切相關。在保齡球例子中,你可能想檢測球何時擊倒某個瓶子,因此要尋找球精靈與球瓶組中所有精靈之間的碰撞。還可以檢測組內部的碰撞(如球瓶相互碰倒)。

下面來完成一個例子。以我們的反彈沙灘球為基礎,不過為了更容易地看出發生了什麼,這裡首先建立 4 個球而不是 9 個球。另外與上一個例子中建立球的列表不同,我們將會使用 Pygame 的 group 類。

這裡還要對代碼稍稍做些整理,把完成球動畫的部分(代碼清單 17-2 中的最後幾行)放在一個函數中,我們把這個函數命名為 animateanimate 函數還包括完成碰撞檢測的代碼。兩個球碰撞時,我們會讓它們反向。

代碼清單 17-3 顯示了相應的代碼。

代碼清單 17-3 使用一個動畫精靈組而不是列表

這裡最有意思的新內容是碰撞檢測如何工作。Pygame sprite 模塊有一個 spritecollide 函數,它會查找一個精靈與一個組中所有精靈之間的碰撞。要檢查同一個組中精靈之間的碰撞,必須通過 3 步來完成:

  • 從這個組中刪除這個精靈;

  • 檢查這個精靈與組中其他精靈之間的碰撞;

  • 再把這個精靈添加回原來的組中。

這些工作在第 21 行到第 29 行的 for 循環中(animate 函數的中間部分)完成。如果開始時沒有從組中刪除這個精靈,spritecollide 會檢測到這個精靈與它自身發生了碰撞,因為它也在這個組中。乍一看好像有些奇怪,不過如果再想想看就會發現這是有道理的。

如果加大動畫步,就能更容易地看到這一點。可以把速度從 2 增加到 5,另外把各步之間的延遲從 20 增加到50。

運行程序,看看有什麼結果。有沒有注意到一些奇怪的行為?我注意到兩點:

  • 球碰撞時,它們會「顫抖」或者發生兩次碰撞;

  • 有時球會「卡」在窗口邊界上,顫抖一段時間。

為什麼會出現這種情況?嗯,這與我們如何編寫 animate 函數有關。注意我們的做法是先移動一個球,檢查它的碰撞,然後移動另一個球,再檢查這個球的碰撞,依此類推。也許應該先完成所有移動,然後再完成全部碰撞檢測。

所以需要把第 28 行(ball.move)放在它自己的循環中,就像這樣:

試試看,效果是不是比原來好一些。

可以對這個代碼做些試驗,改變某些值,比如速度(time.delay 數)、球數、球原先的位置、隨機性等,來看球會有什麼變化。

矩形碰撞與像素完美碰撞

你會注意到,球「碰撞」時並不總是完全接觸。這是因為 spritecollide 沒有使用球的圓形輪廓來檢測碰撞。它使用了球的 rect,也就是球的外圍矩形。

如果想看看具體是怎樣的,可以畫一個矩形包圍球圖像,並且使用這個新圖像而不是原先常規的沙灘球圖像。我已經為你做好了這個新圖像,你可以試一試:

img_file = 「b_ball_rect.png」  

看上去就像右圖顯示的這樣:

如果希望球的圓形部分(而不是矩形邊界)真正接觸時球才會相互反彈,就必須使用一種稱為「像素完美碰撞檢測」的方法。spritecollide 函數沒有這樣做,而是使用了更簡單的「矩形碰撞檢測」。

它們的區別如下。使用矩形碰撞檢測,兩個球矩形區的任何部分相互接觸時就會「碰撞」。而使用像素完美碰撞檢測,兩個球本身接觸時才會碰撞。如下:

像素完美碰撞檢測更逼真。(在真正的沙灘球周圍你不會覺得有任何隱形的矩形,對吧?)但是在程序中實現時就沒這麼簡單了。

對於要在 Pygame 中完成的大多數工作來說,矩形碰撞檢測已經足夠了。像素完美碰撞檢測需要寫更多代碼,而且會讓遊戲運行得更慢,所以應該只有在確實有必要的情況下才使用這種方法。完成像素完美碰撞檢測有幾個模塊(我上次查看時,Pygame 網站上至少有兩個)。如果想嘗試進行像素完美碰撞檢測,只需在網上搜索一下就會找到這些模塊。