URLhttps://learnscript.net/zh-hant/pygame/sprite/collision/custom/
    複製連結移至說明  範例

    解決精靈碰撞偵測不精確的問題,以及自訂碰撞偵測區域和函式

    閱讀 5:08·字數 1541·發佈 
    Youtube 頻道
    訂閱 375

    前言

    在 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物件設定了透明色彩鍵,那麽將由透明色彩鍵決定像素是否參與碰撞偵測。

    像素級碰撞偵測的效能問題

    像素級碰撞偵測能有效消除「幽靈碰撞」,但其效能開銷較大,並且碰撞效果可能仍不夠「真實」(如子彈消失過快)。

    在下面的範例中,精靈物件lasercandy進行了像素級碰撞偵測。

    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模組的spritecollidegroupcollide函式,支援通過collided參數指定碰撞偵測函式,因此,與碰撞偵測相關的程式碼可以變得更為靈活。

    # 縮放碰撞偵測區域
    pygame.sprite.spritecollide(door, lasers, True, collided=crr)
    
    # 自訂碰撞偵測區域 pygame.sprite.groupcollide(candies, lasers, True, True, collided=collide_rects)

    講解影片

    Pygame 自訂碰撞偵測區域和函式,改善不精確的 Sprite 精靈碰撞偵測·YouTube