Pygame遊戲開發之太空射擊實戰子彈與碰撞處理篇

視頻

本教程的視頻

碰撞

碰撞是遊戲開發的基本組成部分。碰撞檢測就是要檢測遊戲中的一個對象是否正在接觸另一個對象。碰撞處理決定瞭當碰撞發生時你想要發生什麼。

在我們的遊戲中,我們目前有許多敵人的精靈沿著屏幕飛向玩傢,我們想知道其中一個精靈何時出現。對於我們遊戲的這個階段,我們隻會說敵人擊中玩傢意味著遊戲結束瞭。

邊界框

請記住,Pygame 中的每個精靈都有一個rect定義其坐標和大小的屬性。Pygame中的一個Rect對象的格式是[x, y, width, height],其中 xy表示矩形的左上角。此矩形的另一個詞是邊界框,因為它表示對象的邊界。

這種碰撞檢測稱為AABB,代表“軸對齊邊界框”,因為矩形與屏幕軸對齊 – 它們不會傾斜一定角度。AABB碰撞非常受歡迎,因為它速度很快 – 計算機可以非常快速地比較矩形的坐標,如果您要比較大量對象,這將非常有用。

要檢測碰撞,我們需要查看玩傢的rect碰撞,並將其與每個敵人的rect碰撞進行比較。現在,我們可以通過循環遍歷敵人並對執行此比較的每個敵人來執行此操作:

在這張圖片中,您可以看到隻有矩形#3與大黑色矩形相撞。#1 在 x 軸上重疊,但在 y 軸上不重疊; #2 在 y 中重疊,但在 x 中不重疊。為瞭使兩個矩形重疊,它們的邊界必須在每個軸上重疊。在代碼中編寫以下內容:

if mob.rect.right > player.rect.left and \
   mob.rect.left < player.rect.right and \
   mob.rect.bottom > player.rect.top and \
   mob.rect.top < player.rect.bottom:
       collide = True

對我們來說幸運的是,Pygame有一個內置的方式,通過使用這個spritecollide()函數來做到這一點。

敵人與玩傢碰撞

我們將此命令添加到遊戲循環的“更新”部分:

#Update
all_sprites.update()
#check to see if a mob hit the player
hits = pygame.sprite.spritecollide(player, mobs, False)
if hits:
    running = False

spritecollide()語法

#Find sprites in a group that intersect another sprite.
spritecollide(sprite, group, dokill, collided = None) -> Sprite_list

spritecollide()函數采用 3 個參數:要檢查的 sprite 的名稱、要與之進行比較的組的名稱,以及名為dokill 的 True/False 參數。

dokill參數如果設置為 True,則所有發生碰撞的sprite都將從組中刪除。例如,如果我們試圖查看玩傢是否撿到瞭一枚硬幣,我們希望將其設置為True硬幣消失。

collied參數是一個回調函數,用於計算兩個精靈是否發生沖突。它應該將兩個精靈作為值,並返回一個bool值,指示它們是否發生沖突。如果未傳遞collied參數,則所有精靈都必須具有“rect”值,該值是精靈區域的矩形,將用於計算碰撞。

spritecollide()命令的返回結果是被擊中的精靈列表(請記住,玩傢可能一次與多個敵人相撞)。我們將該列表分配給變量hits

如果hits列表不為空,則if語句將為True ,並且我們設置runningFalse遊戲將結束。

射擊

子彈精靈

現在我們準備添加一個新的精靈:子彈。這將是一個精靈,在我們射擊時生成,出現在玩傢精靈的頂部,並以相當高的速度向上移動。定義精靈現在應該開始看起來很熟悉瞭,所以下面是完整的Bullet類:

class Bullet(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((10, 20))
        self.image.fill(YELLOW)
        self.rect = self.image.get_rect()
        self.rect.bottom = y
        self.rect.centerx = x
        self.speedy = -10
    def update(self):
        self.rect.y += self.speedy
        # kill if it moves off the top of the screen
        if self.rect.bottom < 0:
            self.kill()

在子彈精靈的__init__()方法中,我們傳遞xy值,以便我們可以告訴精靈出現在哪裡。由於玩傢精靈可以移動,因此將設置為玩傢射擊時玩傢所在的位置。我們設置speedy為負值,以便它將向上移動。

最後,我們檢查子彈是否從屏幕頂部脫落,如果是這樣,我們將其刪除。

按鍵事件

為瞭保持簡單,我們將讓它每次玩傢按下空格鍵時,都會發射一顆子彈。我們需要將其添加到事件檢查中:

for event in pygame.event.get():
    # check for closing window
    if event.type == pygame.QUIT:
        running = False
    elif event.type == pygame.KEYDOWN:
        if event.key == pygame.K_SPACE:
            player.shoot()

我們的新代碼檢查事件,如果存在KEYDOWN事件,則檢查它是否是K_SPACE按鍵。如果是這樣,我們將運行玩傢精靈的shoot()方法。

生成子彈

首先,我們需要添加一個新組來保存所有項目符號:

bullets = pygame.sprite.Group()

現在,我們可以將以下方法添加到Player類中:

def shoot(self):
    bullet = Bullet(self.rect.centerx, self.rect.top)
    all_sprites.add(bullet)
    bullets.add(bullet)

shoot()方法所做的就是生成一顆子彈,使用玩傢的頂部中心作為生成點。然後,我們確保將子彈精靈添加到all_sprites(以便將其繪制和更新)和 bullets,我們將用於碰撞。

子彈碰撞

現在我們需要檢查子彈是否擊中瞭敵人。這裡的區別在於,我們有多個子彈(在bullets組中)和多個敵人(在mobs組中),所以我們不能像以前那樣使用spritecollide(),因為這隻會將一個精靈與一個組進行比較。相反,我們將使用groupcollide()

groupcollide()語法

# Find all sprites that collide between two groups.
groupcollide(group1, group2, dokill1, dokill2, collided = None) -> Sprite_dict

發現兩組中的所有精靈之間的沖突。通過比較每個 Sprite 的Sprite.rect屬性或使用collided函數(如果不是 None)來確定碰撞。

返回值是個字典,未發生碰撞時,字典為空。發生碰撞時,字典的keys是group1中發生碰撞的對象(一直以為字典的keys值是個字符串之類的東西,沒想到還可以是對象),values是一個列表,其元素是group2中與每一個group1發生碰撞的所有對象。

Groupcollide返回值的形態和使用是個難點,舉例說明:比如group1有三架飛機對象p1、p2、p3,group2有八個敵人對象b1、b2、b3、b4、b5、b6、b7、b8,假如p1與b1、b4、b7發生碰撞,p2與b2、b5、b8發生碰撞,

groupcollide(group1, group2, 1, 1)

返回值是如下的字典:

{p1:[b1, b4, b7], p2:[b2, b5, b8]}

group1 中的每個發生碰撞的Sprite 都會添加到返回字典中,作為鍵。每個項的值是組 2 中相交的精靈列表。

如果任一 dokill 參數為 True,則碰撞的精靈將從其各自的組中刪除。

collieded參數是一個回調函數,用於計算兩個精靈是否發生沖突。它應該將兩個精靈作為值,並返回一個 bool 值,指示它們是否正在碰撞。如果未傳遞collided,則所有精靈都必須具有“rect”值,該值是精靈區域的矩形,將用於計算碰撞。

# Update
all_sprites.update()
# check to see if a bullet hit a mob
hits = pygame.sprite.groupcollide(mobs, bullets, True, True)
for hit in hits:
    m = Mob()
    all_sprites.add(m)
    mobs.add(m)

groupcollide()函數類似於spritecollide() ,除瞭您命名兩個組進行比較,並且您將獲得的是被擊中的敵人列表。有兩個dokill選項,每個組一個。

如果我們隻是刪除敵人,我們將遇到一個問題:敵人用完瞭!因此,我們要做的就是hits循環,對於我們摧毀的每個敵人,將生成另一個新的敵人。

現在它實際上開始感覺像一個遊戲:

在下一課中,我們將學習如何向遊戲添加圖形,而不是使用那些純色矩形。

此部分的完整代碼

# KidsCanCode - Game Development with Pygame video series
# Shmup game - part 3
# Video link: https://www.youtube.com/watch?v=33g62PpFwsE
# Collisions and bullets
import pygame
import random
WIDTH = 480
HEIGHT = 600
FPS = 60
# define colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
# initialize pygame and create window
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Shmup!")
clock = pygame.time.Clock()
class Player(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((50, 40))
        self.image.fill(GREEN)
        self.rect = self.image.get_rect()
        self.rect.centerx = WIDTH / 2
        self.rect.bottom = HEIGHT - 10
        self.speedx = 0
    def update(self):
        self.speedx = 0
        keystate = pygame.key.get_pressed()
        if keystate[pygame.K_LEFT]:
            self.speedx = -8
        if keystate[pygame.K_RIGHT]:
            self.speedx = 8
        self.rect.x += self.speedx
        if self.rect.right > WIDTH:
            self.rect.right = WIDTH
        if self.rect.left < 0:
            self.rect.left = 0
    def shoot(self):
        bullet = Bullet(self.rect.centerx, self.rect.top)
        all_sprites.add(bullet)
        bullets.add(bullet)
class Mob(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((30, 40))
        self.image.fill(RED)
        self.rect = self.image.get_rect()
        self.rect.x = random.randrange(WIDTH - self.rect.width)
        self.rect.y = random.randrange(-100, -40)
        self.speedy = random.randrange(1, 8)
        self.speedx = random.randrange(-3, 3)
    def update(self):
        self.rect.x += self.speedx
        self.rect.y += self.speedy
        if self.rect.top > HEIGHT + 10 or self.rect.left < -25 or self.rect.right > WIDTH + 20:
            self.rect.x = random.randrange(WIDTH - self.rect.width)
            self.rect.y = random.randrange(-100, -40)
            self.speedy = random.randrange(1, 8)
class Bullet(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((10, 20))
        self.image.fill(YELLOW)
        self.rect = self.image.get_rect()
        self.rect.bottom = y
        self.rect.centerx = x
        self.speedy = -10
    def update(self):
        self.rect.y += self.speedy
        # kill if it moves off the top of the screen
        if self.rect.bottom < 0:
            self.kill()
all_sprites = pygame.sprite.Group()
mobs = pygame.sprite.Group()
bullets = pygame.sprite.Group()
player = Player()
all_sprites.add(player)
for i in range(8):
    m = Mob()
    all_sprites.add(m)
    mobs.add(m)
# Game loop
running = True
while running:
    # keep loop running at the right speed
    clock.tick(FPS)
    # Process input (events)
    for event in pygame.event.get():
        # check for closing window
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                player.shoot()
    # Update
    all_sprites.update()
    # check to see if a bullet hit a mob
    hits = pygame.sprite.groupcollide(mobs, bullets, True, True)
    for hit in hits:
        m = Mob()
        all_sprites.add(m)
        mobs.add(m)
    # check to see if a mob hit the player
    hits = pygame.sprite.spritecollide(player, mobs, False)
    if hits:
        running = False
    # Draw / render
    screen.fill(BLACK)
    all_sprites.draw(screen)
    # *after* drawing everything, flip the display
    pygame.display.flip()
pygame.quit()

第 4 部分:添加圖形

到此這篇關於Pygame遊戲開發之太空射擊實戰子彈與碰撞處理篇的文章就介紹到這瞭,更多相關Pygame子彈與碰撞處理內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: