解決精靈碰撞偵測不精確的問題,以及自訂碰撞偵測區域和函式
訂閱 375
Pygame 自訂碰撞偵測區域和函式,改善不精確的 Sprite 精靈碰撞偵測影片示範 YouTube
前言
在 Pygame 中,sprite
模組提供了方便的碰撞偵測函式,但直接使用這些函式,可能會導致不夠精確的碰撞效果(比如「幽靈碰撞」)。本文將探討這個問題的原因,並介紹三種改進碰撞偵測精確度的方法。
「幽靈碰撞」問題
「幽靈碰撞」,即視覺上看起來沒有接觸的精靈,卻被判定為發生了碰撞。造成「幽靈碰撞」的原因在於,精靈的碰撞偵測區域超出了精靈所展示的形象。
改進方法一:像素級碰撞偵測
像素級碰撞偵測(又稱像素完美碰撞偵測,或遮罩碰撞偵測)通過遮罩(Mask
物件)來決定影像中哪些像素參與碰撞偵測。
sprite
模組提供了collide_mask
函式,用於對精靈進行像素級碰撞偵測,如果參與檢測的精靈物件未包含遮罩資訊,collide_mask
會根據精靈影像自動建置。
collide_mask(left, right)
- left,right 參數
參與檢測的兩個精靈物件。
- 傳回值
在未發生碰撞時,傳回空值
None
,否則傳回表示第一個被碰撞像素的座標的整數元組,其 X,Y 座標相對於left
參數所對應的精靈影像的左上角。
當然,如果需要的話,你也可以通過mask
模組的from_surface
函式手動建立遮罩。
from_surface(surface, threshold=127)
- surface 參數
繪製了影像的
Surface
物件。- threshold 參數
透明度閾值,只有透明度大於該值的像素才會參與檢測。如果
Surface
物件設定了透明色彩鍵,那麽將由透明色彩鍵決定像素是否參與碰撞偵測。
像素級碰撞偵測的效能問題
像素級碰撞偵測能有效消除「幽靈碰撞」,但其效能開銷較大,並且碰撞效果可能仍不夠「真實」(如子彈消失過快)。
在下面的範例中,精靈物件laser
和candy
進行了像素級碰撞偵測。
for laser in lasers:
for candy in candies:
p = pygame.sprite.collide_mask(laser, candy)
if p:
print(f"碰撞像素座標: {p}")
laser.kill()
candy.kill()
改進方法二:按比例縮小碰撞偵測區域
sprite
模組提供了collide_rect_ratio
類別,可用於縮放碰撞偵測區域,但這不會改變精靈的rect
變數所對應的矩形的大小,縮放效果僅在碰撞偵測時有效。
collide_rect_ratio(ratio)
- ratio 參數
小於
1
表示縮小,大於1
表示放大。
你可以像呼叫函式一樣,呼叫collide_rect_ratio
類別的執行個體,以檢測兩個精靈是否發生了碰撞。
collide_rect_ratio_instance(left, right)
- left,right 參數
參與檢測的兩個精靈物件。
- 傳回值
傳回表示是否發生碰撞的布林值。
等比例縮放帶來的問題
collide_rect_ratio
類別實作的等比例縮放,可能導致某些期望參與碰撞的區域,被排除在碰撞偵測之外。
在下面的範例中,我們將碰撞偵測區域縮小至70%
。
from pygame.sprite import collide_rect_ratio
crr = collide_rect_ratio(0.7) # 縮小到 70%
if crr(laser, candy):
# 處理碰撞
改進方法三:自訂碰撞偵測區域
對於結構複雜的精靈,可以定義多個碰撞偵測區域來實作更精確的控製,比如,精確控製哪些區域參與碰撞,或實作對角色不同部位造成不同傷害等。
對於自訂的檢測區域,不應該儲存在精靈類別的rect
變數中,因為該變數還決定了精靈的位置。
在下面的範例中,我們為精靈類別Candy
定義了多個小矩形,作為其碰撞偵測區域,並定義了實作碰撞偵測的函式collide_rects
,這裏假設了其參數left
對應的精靈擁有rects
變數。
class Candy(pygame.sprite.Sprite):
def __init__(self, *groups):
super().__init__(*groups)
# …
self.rects = [ # 定義多個小矩形作為碰撞區域
pygame.Rect(24, 4, 2, 2),
pygame.Rect(20, 8, 4, 4),
pygame.Rect(16, 12, 4, 4),
pygame.Rect(12, 16, 4, 4),
pygame.Rect(8, 20, 4, 4),
pygame.Rect(4, 24, 2, 2)
]
def collide_rects(left, right):
for rect in left.rects:
adjusted_rect = rect.move(left.rect.topleft)
if adjusted_rect.colliderect(right.rect):
return True
return False
# …
if collide_rects(candy, laser):
# 處理碰撞
指定碰撞偵測函式
sprite
模組的spritecollide
和groupcollide
函式,支援通過collided
參數指定碰撞偵測函式,因此,與碰撞偵測相關的程式碼可以變得更為靈活。
# 縮放碰撞偵測區域
pygame.sprite.spritecollide(door, lasers, True, collided=crr)
# 自訂碰撞偵測區域
pygame.sprite.groupcollide(candies, lasers, True, True, collided=collide_rects)