背景部分
創建Pygame窗口以及響應用戶輸入
# invasion.py
import sys
import pygame
def run_game():
# 初始化遊戲並創建一個屏幕對象
pygame.init()
screen = pygame.display.set_mode((1200, 700))
pygame.display.set_caption("Thunder")
# 開始遊戲的主循環
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
# 讓最近繪製的屏幕可見
pygame.display.flip()
run_game()
- 首先我們導入模塊
pygame
和sys
。sys用於退出遊戲 - 遊戲以函數
run_game()
開頭。 pygame.init()
用於初始化遊戲背景。pygame.display.set_mode()
用於創建一個名爲screen的顯示窗口。實參(1200,700)是一個元組,指定遊戲窗口的尺寸。- 對象screen是一個surface。在Pygame中,surface是屏幕的一部分,用於顯示遊戲元素(比如外星人、飛船),遊戲中每個元素都是一個surface。激活遊戲的動畫循環後,每經過一次循環都將重新繪製這個surface。
- 爲訪問Pygame偵聽到的時間,我們使用方法
pygame.event.get()
。所有的鍵盤和鼠標事件都將促使for循環運行。比如玩家點擊窗口的關閉按鈕時,將檢測到pygame.QUIT事件,我們就調用sys.exit()
來退出遊戲。 pygame.display.flip()
命令Pygame讓最近繪製的屏幕可見。它在每次執行while循環時都會繪製一個空屏幕,並擦去舊屏幕。
繪製背景色
# 在while循環中加入如下語句:
screen.fill((230,230,230))
Pygame中,顏色是以RGB值表示的。
創建設置類
#settings.py
class Settings():
"""存儲遊戲所有設置的類"""
def __init__(self):
"""初始化遊戲的設置"""
# 屏幕設置
self.screen_width = 1200
self.screen_length = 700
self.bg_color = (230, 230, 230)
將所有遊戲的設置存儲在這個類中, 則invasion.py可修改:
import sys
import pygame
from settings import Settings
def run_game():
# 初始化遊戲並創建一個屏幕對象
pygame.init()
sett = Settings()
screen = pygame.display.set_mode(
(sett.screen_length, sett.screen_width)
)
pygame.display.set_caption("Thunder")
# 開始遊戲的主循環
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.fill(sett.bg_color)
# 讓最近繪製的屏幕可見
pygame.display.flip()
run_game()
飛船部分
添加飛船圖像
就選用書配套的素材吧
創建Ship類
import pygame
class Ship():
def __init__(self, screen):
self.screen = screen
self.image = pygame.image.load("ship.bmp")
self.rect = self.image.get_rect()
self.screen_rect = screen.get_rect()
self.rect.centerx = self.screen_rect.centerx
self.rect.bottom = self.screen_rect.bottom
def blitme(self):
"""在指定位置繪製飛船"""
self.screen.blit(self.image, self.rect)
在屏幕上繪製飛船
在invasion.py中創建飛船對象,並調用其方法blitme():
import sys
import pygame
from settings import Settings
from ship import Ship
def run_game():
# 初始化遊戲並創建一個屏幕對象
pygame.init()
sett = Settings()
screen = pygame.display.set_mode(
(sett.screen_width, sett.screen_length)
)
pygame.display.set_caption("Thunder")
ship = Ship(screen)
# 開始遊戲的主循環
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.fill(sett.bg_color)
ship.blitme()
# 讓最近繪製的屏幕可見
pygame.display.flip()
run_game()
運行後結果:
重構:game_function模塊
函數check_events()
我們把管理事件的代碼移到一個名爲check_events()的函數裏,以簡化run_game()
#game_function.py
import sys
import pygame
def check_events():
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
函數update_screen()
爲進一步簡化run_game(),將更新屏幕的代碼移到一個名爲update_screen()的函數裏,並將函數定義放在game_function中
def update_screen(sett, screen, ship):
screen.fill(sett.bg_color)
ship.blitme()
# 讓最近繪製的屏幕可見
pygame.display.flip()
飛船移動部分
響應按鍵
每當用戶按鍵時,都在Pygame裏註冊一個事件。事件都是通過方法pygame.event.get()
獲取的,因此在函數check_events()中,我們需要制定檢查哪些類型的事件。
每次按鍵都被註冊一個KEYDOWN
事件。檢測到該事件後,我們需要檢查是否按下了特定的鍵,執行特定的操作。比如按下右鍵後,要讓飛船向右移動。
def check_events(ship):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
ship.rect.centerx += 1
我們在參數列表里加入了ship,因爲需要能夠訪問到飛船內部的屬性。
允許不斷移動
玩家按住→鍵是希望飛船不停移動,直到鬆開爲止。
我們可以讓遊戲檢測pygame.KEYUP
事件,然後結合KEYUP和KEYDOWN事件實現持續移動。
# ship.py
def __init__(self, screen):
...
self.right_move = False
def update(self):
if self.right_move:
self.rect.centerx += 1
在飛船的類內初始化時多添了一個屬性移動標誌變量right_move
多添了一個方法update()
,用於檢查該標誌變量,實現飛船屬性更新:這個變量爲True時,飛船就會向右移動。
而這個標誌變量會因KEYDOWN變爲True,因KEYUP變爲False,以此來實現持續移動
同時,要在invasion.py的while循環裏調用update()方法:
# invasion.py
while True:
gf.check_events(ship)
ship.update()
gf.update_screen(sett, screen, ship)
左右移動
只需照着向右移動就能做出向左移動
調整飛行速度
每次執行while循環,飛船最多移動1像素。但可以在settings模塊里加入屬性ship_speed
,用於控制飛船的速度。
# settings.py
class Settings():
"""存儲遊戲所有設置的類"""
def __init__(self):
"""初始化遊戲的設置"""
# 屏幕設置
self.screen_width = 1200
self.screen_length = 700
self.bg_color = (230, 230, 230)
self.ship_speed = 0.7
同時在ship.py中修改:
class Ship():
def __init__(self, sett, screen):
...
self.center = float(self.rect.centerx)
...
...
def update(self):
if self.right_move:
self.center += sett.ship_speed
if self.left_move:
self.center -= sett.ship_speed
self.rect.centerx = self.center
- 在__init__()的形參中加入了setting類的sett,讓飛船的方法update()可以獲取其速度設置。
- rect只存儲整數,所以我們新建一個屬性
center
,用flota()
將rect.centerx
轉化成小數存儲到center
中。更新center之後,再根據它來更新控制飛船位置的rect.centerx(雖然centerx只存儲self.center的整數部分,但對於顯示飛船而言問題不大。)
限制飛船活動範圍
爲了防止飛船飛出屏幕外,我們在飛船位置變更前添加if語句判斷飛船是否將飛出框外。
# ship.py
def update(self):
if self.right_move and self.rect.right < self.screen_rect.right:
self.center += self.sett.ship_speed
if self.left_move and self.rect.left > 0:
self.center -= self.sett.ship_speed
如果rect的左/右邊緣沒有觸及屏幕左/右邊緣,纔可以移動。
重構check_event()
# game_function.py
def check_keydown(event, ship):
if event.key == pygame.K_RIGHT:
ship.right_move = True
elif event.key == pygame.K_LEFT:
ship.left_move = True
def check_keyup(event, ship):
if event.key == pygame.K_RIGHT:
ship.right_move = False
elif event.key == pygame.K_LEFT:
ship.left_move = False
def check_events(ship):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown(event, ship)
elif event.type == pygame.KEYUP:
check_keyup(event, ship)
子彈部分
添加子彈設置
在setting.py中添加新類Bullet所需的值:
def __init__(self):
"""初始化遊戲的設置"""
# 屏幕設置
...
# 子彈設置
self.bullet_speed = 1
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = (60, 60, 60)
創建Bullet類
# bullet.py
import pygame
from pygame.sprite import Sprite
class Bullet(Sprite):
"""一個對飛船的子彈管理的類"""
def __init__(self, sett, screen, ship):
"""在飛船處創建一個子彈對象"""
super(Bullet, self).__init__()
self.screen = screen
self.rect = pygame.Rect(0, 0, sett.bullet_width, sett.bullet_height)
self.rect.centerx = ship.rect.centerx
self.rect.top = ship.rect.top
self.y = float(self.rect.y)
self.color = sett.bullet_color
self.speed_factor = sett.bullet_speed
子彈並非基於圖像,因此我們必須使用pygame.Rect()
類從空白開始創建一個矩形。創建這個類的實例時,必須提供矩形左上角的x座標和y座標,還有寬度和高度。我們先在(0,0)處創建一個矩形,並在接下來放在正確的位置,這個位置取決於飛船的位置。
接下來編寫update()
和draw_bullet
方法
def update(self):
"""向上移動子彈"""
self.y -= self.speed
self.rect.y = self.y
def draw_bullet(self):
pygame.draw.rect(self.screen, self.color, self.rect)
將子彈存到編組中
在玩家每次按下空格時都射出一發子彈。首先我們在invasion.py中創建一個編組(Group)用於存儲所有子彈,以便能夠管理髮射出去的子彈。
這個編組是pygame.sprite.Group類的一個實例;Group類 類似於列表,但提供了有助於遊戲開發的功能。在主循環中,我們使用這個編組在屏幕上繪製子彈,更新每一個子彈的位置。
import sys
import pygame
import game_function as gf
from pygame.sprite import Group
from settings import Settings
from ship import Ship
def run_game():
# 初始化遊戲並創建一個屏幕對象
...
bullets = Group()
# 開始遊戲的主循環
while True:
gf.check_events(sett, screen, ship, bullets)
ship.update()
bullets.update()
gf.update_screen(sett, screen, ship, bullets)
run_game()
我們將bullets作爲實參傳遞給了check_events()和update_screen()。在check_event()中我們要用空格處理bullets;在update_screen中則要更新繪製到屏幕上的bullets。
當你對編組調用update()時,編組將自動對每一個"精靈"調用update(),即對每一個子彈。
開火
因爲只有在按下空格鍵時飛船纔會開火,所以我們只需修改check_keydown_events()
而不用修改keyup
# game_function.py
from bullet import Bullet
def check_keydown(event, sett, screen, ship, bullets):
if event.key == pygame.K_RIGHT:
ship.right_move = True
elif event.key == pygame.K_LEFT:
ship.left_move = True
elif event.key == pygame.K_SPACE:
new_bullet = Bullet(sett, screen, ship)
bullets.add(new_bullet)
def check_events(sett, screen, ship, bullets):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown(event, sett, screen, ship, bullets)
elif event.type == pygame.KEYUP:
check_keyup(event, ship)
def update_screen(sett, screen, ship, bullets):
screen.fill(sett.bg_color)
ship.blitme()
for bullet in bullets:
bullet.draw_bullet()
# 讓最近繪製的屏幕可見
pygame.display.flip()
刪除已經消失的子彈
我們需要將已經飛出屏幕的子彈刪除,減少內存負擔。
爲此,我們需要在每次更新子彈位置後,檢測rect的bottom屬性小於0的子彈,並刪除它們。
# invasion.py
while True:
gf.check_events(sett, screen, ship, bullets)
ship.update()
bullets.update()
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
print(len(bullets))
gf.update_screen(sett, screen, ship, bullets)
- 在for循環中,不應從列表或編組中刪除條目,因此必須是遍歷編組的副本,故需要調用方法
copy()
,返回一個編組的副本。 - 輸出編組的長度,即有效子彈的數量,是爲了顯示子彈的數量,覈實已消失的子彈確實被刪除了。
子彈效果如圖:
限制子彈數量
多數同類型遊戲裏面都會有對子彈數量的限制,鼓勵玩家有目標地射擊。
我們在此限制子彈最大數量爲4.
首先在Setting類裏設置允許的最大子彈數:
#setting.py
class Settings():
"""存儲遊戲所有設置的類"""
def __init__(self):
"""初始化遊戲的設置"""
# 屏幕設置
...
# 子彈設置
...
self.bullet_allowed = 4
在check_keydown_event()中檢測到空格前,添加if語句判斷子彈數量(羣組長度)是否已經超過最大限制。
# game_function.py
def check_keydown(event, sett, screen, ship, bullets):
if event.key == pygame.K_RIGHT:
ship.right_move = True
elif event.key == pygame.K_LEFT:
ship.left_move = True
elif event.key == pygame.K_SPACE:
if len(bullets) < sett.bullet_allowed:
new_bullet = Bullet(sett, screen, ship)
bullets.add(new_bullet)
重構bullet函數
我們可以把子彈更新函數和刪除子彈的代碼寫進一個函數update_bullet()
裏:
# game_function.py
def update_bullet(bullets):
bullets.update()
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
print(len(bullets))
故主循環裏的代碼可簡化:
# invasion.py
while True:
gf.check_events(sett, screen, ship, bullets)
ship.update()
gf.update_bullet(bullets)
gf.update_screen(sett, screen, ship, bullets)
同時,把檢查子彈數量是否超額的代碼已經添加新子彈的代碼整合進一個fire_bullet()
函數裏:
# game_function.py
def fire_bullet(sett, screen, ship, bullets):
if len(bullets) < sett.bullet_allowed:
new_bullet = Bullet(sett, screen, ship)
bullets.add(new_bullet)
def check_keydown(event, sett, screen, ship, bullets):
if event.key == pygame.K_RIGHT:
ship.right_move = True
elif event.key == pygame.K_LEFT:
ship.left_move = True
elif event.key == pygame.K_SPACE:
fire_bullet(sett, screen, ship, bullets)
外星人部分
創建Alien類
# alien.py
import pygame
from pygame.sprite import Sprite
class Alien(Sprite):
"""單個外星人的類"""
def __init__(self, sett, screen):
super().__init__()
self.screen = screen
self.setting = sett
# 加載外星人圖像,設置rect屬性
self.image = pygame.image.load('alien.bmp')
self.rect = self.image.get_rect()
# 每個外星人最初都在屏幕左上角
self.rect.x = self.rect.width
self.rect.y = self.rect.height
# 存儲外星人準確位置
self.x = float(self.rect.x)
def blitme(self):
self.screen.blit(self.image, self.rect)
創建Alien實例
# invasion.py
def run_game():
# 初始化遊戲並創建一個屏幕對象
...
alien = Alien(sett, screen)
...
# 開始遊戲的主循環
while True:
...
gf.update_screen(sett, screen, ship, alien, bullets)
# update_screen裏調用 alien.blitme()
run_game()
創建一羣外星人
確定一行可以容納多少外星人
我們要根據屏幕水平寬度確定一行可容納多少外星人。我們要在屏幕兩邊留下邊距,把它設置爲外星人圖像的寬度。所以放置外星人的水平空間爲:
available_space_x = sett.screen_width - (2 * alien_width)
外星人之間還得留下空間,設置爲一個外星人的寬度。因此一行可容納的外星人數量:
number_aliens_x = available_space_x / (2 * alien_width)
創建多行外星人
爲創建一行外星人,首先在invasion.py中創建一個名爲aliens的空編組,用於存儲全部外星人,再調用game_function.py中的創建外星人羣的函數:
# invasion.py
...
ship = Ship(sett, screen)
bullets = Group()
aliens = Group()
aliens = Group()
gf.create_fleet(sett, screen, aliens)
# 開始遊戲的主循環
while True:
...
gf.update_screen(sett, screen, ship, aliens, bullets)
# game_function.py
def create_fleet(sett, screen, aliens):
"""創建外星人羣"""
# 創建一個外星人,並計算一行可容納多少外星人
alien = Alien(sett, screen)
alien_width = alien.rect.width
available_space_x = sett.screen_width - 2 * alien_width
number_aliens_x = int(available_space_x / (2 * alien_width))
# 創建第一行外星人
for alien_number in range(number_aliens_x):
# 創建一個外星人並加入羣組
alien = Alien(sett, screen)
alien.x = alien_width + 2 * alien_width * alien_number
alien.rect.x = alien.x
aliens.add(alien)
def update_screen(sett, screen, ship, aliens, bullets):
...
...
aliens.draw(screen)
...
# 讓最近繪製的屏幕可見
...
效果如圖:
重構create_fleet()
爲create_fleet()新添兩個函數create_alien()
和get_number_aliens_x()
# game_function.py
def get_number_aliens_x(sett, alien_width):
available_space_x = sett.screen_width - 2 * alien_width
number_aliens_x = int(available_space_x / (2 * alien_width))
return number_aliens_x
def create_alien(sett, screen, aliens, alien_width, alien_number):
# 創建一個外星人並加入羣組
alien = Alien(sett, screen)
alien.x = alien_width + 2 * alien_width * alien_number
alien.rect.x = alien.x
aliens.add(alien)
def create_fleet(sett, screen, aliens):
"""創建外星人羣"""
# 創建一個外星人,並計算一行可容納多少外星人
alien = Alien(sett, screen)
alien_width = alien.rect.width
number_aliens_x = get_number_aliens_x(sett, alien_width)
# 創建第一行外星人
for alien_number in range(number_aliens_x):
create_alien(sett, screen, aliens, alien_width, alien_number)
添加行
要創建外星人羣,需要計算屏幕可容納多少行,並對創建一行外星人的循環重複相應的次數。爲計算可容納的行數,我們將屏幕高度減去第一行的外星人的上邊距(外星人高度)、飛船的高度以及最初外星人高度加上外星人邊距:
available_space_y = sett.screen_height - 3*alien_height - ship_height
這樣可以給飛船上方留出一定空白區域。
每行下方都要留出一定的空白區域,並將其設置爲外星人的高度。爲計算可容納的行數,我們將可用垂直空間除以外星人高度的兩倍:
number_rows = available_space_y /(2 * alien_height)
# game_funtion.py
def get_nuber_rows(sett, alien_height, ship_height):
available_space_y = sett.screen_length - 3 * alien_height - ship_height
number_rows = int(available_space_y / (2 * alien_height))
return number_rows
def create_alien(sett, screen, aliens, alien_width, alien_number, row_number):
# 創建一個外星人並加入羣組
...
alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_number
...
def create_fleet(sett, screen, ship, aliens):
"""創建外星人羣"""
# 創建一個外星人,並計算一行可容納多少外星人
...
number_rows = get_nuber_rows(sett, alien_height, ship.rect.height)
# 創建第一行外星人
for row_number in range(number_rows):
for alien_number in range(number_aliens_x):
create_alien(sett, screen, aliens, alien_width, alien_number, row_number)
移動外星人
讓外星人向右移動
爲移動外星人,我們將使用alien.py中的方法update(),且對外星人羣中的每個外星人都調用它。
首先添加一個外星人移動速度的設置:
# setting.py
class Settings():
"""存儲遊戲所有設置的類"""
def __init__(self):
"""初始化遊戲的設置"""
# 屏幕設置
...
# 子彈設置
...
self.alien_speed = 1
然後在Alien類裏實現update():
# alien.py
def update(self):
self.x += self.setting.alien_speed
self.rect.x = self.x
接着在game_function.py裏編寫update_aliens()
# game_function.py
def update_aliens(aliens):
aliens.update()
aliens編組將自動對每一個外星人調用update()。
在主循環裏調用update_aliens(aliens):
while True:
gf.check_events(sett, screen, ship, bullets)
ship.update()
gf.update_bullet(bullets)
gf.update_aliens(aliens)
gf.update_screen(sett, screen, ship, aliens, bullets)
創建表示外星人移動方向的設置
讓外星人在撞到屏幕右邊緣後會向下移動,再向左移動,代碼如下:
# setting.py
self.alien_speed = 1
self.alien_drop_speed = 10
# 下降速度
self.fleet_direction = 1
# 1表示向右,-1表示向左, 可以直接作爲速度的係數用於座標運算
檢查外星人是否撞到邊緣
檢查外星人是否撞到邊緣,爲類Alien編寫方法check_edges()
:
# alien.py
def check_edges(self):
screen_rect = self.screen.get_rect()
if self.rect.right >= screen_rect.right:
return True
elif self.rect.left <= screen_rect.left:
return True
同時修改update():
def update(self):
self.x += self.setting.alien_speed * self.setting.fleet_direction
self.rect.x = self.x
向下移動並改變移動方向
有一個外星人到達屏幕邊緣時,需要將整羣外星人下移並轉向。所以我們需要對game_function.py做大修改,因爲我們需要檢查每一個外星人是不是已經到了邊緣。爲此我們編寫check_fleet_edge()
和change_fleet_dir()
# game_function.py
def change_fleet_dir(sett, aliens):
"""將整羣外星人下移"""
for alien in aliens:
alien.rect.y += sett.alien_drop_speed
sett.fleet_direction *= -1
def check_fleet_edges(sett, aliens):
"""有一個外星人到達邊緣"""
for alien in aliens.sprites():
if alien.check_edges():
change_fleet_dir(sett, aliens)
break
def update_aliens(sett, aliens):
check_fleet_edges(sett, aliens)
aliens.update()
同時修改主循環中update_aliens()的參數:
gf.update_aliens(sett, aliens)
射殺外星人
檢測子彈與外星人的碰撞
子彈擊中外星人時,我們要讓外星人消失。爲此我們需要在更新子彈位置後判斷其是否碰撞。
我們用sprite.groupcollide()
方法來檢測兩個羣組的成員是否有碰撞。
它將每顆子彈的rect同每個外星人的rect進行比較,並返回一個字典,其中包含發生碰撞的子彈和外星人。在這個字典中,每個鍵都是一顆子彈,而對應的值都是被擊中的外星人。(這個字典在之後計分要用到)
# game_function.py
def update_bullets(aliens, bullets):
...
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
這行代碼先遍歷每顆子彈再遍歷每個外星人,每當有兩者rect重疊,它就在返回的字典中添加一對鍵值對。最後兩個實參告訴pygame刪除發生碰撞的子彈和外星人(第一個true表示子彈會被刪除,如果改爲false則子彈碰撞時不會被刪除,而是一直飛到屏幕外)
接着要在invasion.py中的update_bullets()參數中添加aliens。
生成新的外星人羣
當一個外星人羣被消滅後,應該再出現另一羣外星人。
我們先檢查編組aliens是否爲空,如果爲空,就調用create_fleet()。我們將在update_bullets()中進行這個檢查,因爲外星人都是在這裏被消滅的
# game_function.py
def update_bullets(sett, screen, ship, aliens, bullets):
...
if len(aliens) == 0:
create_fleet(sett, screen, ship, aliens)
同時要修改invasion.py中update_bullets的參數。
重構update_bullets()
# game_function.py
def check_bullet_collision(sett, screen, ship, bullets, aliens):
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if len(aliens) == 0:
create_fleet(sett, screen, ship, aliens)
def update_bullet(sett, screen, ship, bullets, aliens):
bullets.update()
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
print(len(bullets))
check_bullet_collision(sett, screen, ship, bullets, aliens)
結束遊戲
需要添加失敗事件:外星人撞到飛船,或者有外星人降到屏幕底端,飛船將會被摧毀,玩家用光生命樹後遊戲結束。
檢測飛船與外星人碰撞
# game_function.py
def update_aliens(sett, ship, aliens):
check_fleet_edges(sett, aliens)
aliens.update()
if pygame.sprite.spritecollideany(ship, aliens):
print("shit!")
方法spritecollideany()
接受兩個實參:一個精靈和一個編組。它檢查編組是否有其他成員與精靈發生了碰撞,並在找到與精靈發生碰撞的成員後停止遍歷,返回True. 如果沒有碰撞則返回None。
響應外星人與飛船碰撞
飛船與外星人碰撞後:飛船生命-1、全屏外星人和子彈清空並暫停一段時間後出現新的外星人羣。
尋找編寫一個用於跟蹤遊戲統計信息的新類–Gamestats, 並將其保存爲文件stats. py :
# stats.py
class Gamestats():
def __init__(self, sett):
self.sett = sett
self.reset_stats()
self.active = False
def reset_stats(self):
self.life = 1
同時在invasion.py中創建一個名爲stats的實例
sett = Settings()
stats = Gamestats(sett)
接着編寫飛船碰撞時的響應:
# game_function.py
def ship_hit(sett, stats, screen, ship, aliens, bullets):
stats.life -= 1
aliens.empty()
bullets.empty()
create_fleet(sett, screen, ship, aliens)
ship.center = screen.get_rect().centerx
# 將飛船調整至中心位置
sleep(0.5)
def update_aliens(sett, stats, screen, ship, aliens, bullets):
check_fleet_edges(sett, aliens)
aliens.update()
if pygame.sprite.spritecollideany(ship, aliens):
ship_hit(sett, stats, screen, ship, aliens, bullets)
同時要在invasion.py中修改update_aliens()參數列表
有外星人到達底部
爲此我們寫一個函數check_alien_bottom()
# game_function.py
def alien_bottom(sett, stats, screen, ship, aliens, bullets):
screen_rect = screen.get_rect()
for alien in aliens:
if alien.rect.bottom >= screen_rect.bottom:
ship_hit(sett, stats, screen, ship, aliens, bullets)
def update_aliens(sett, stats, screen, ship, aliens, bullets):
check_fleet_edges(sett, aliens)
aliens.update()
if pygame.sprite.spritecollideany(ship, aliens) or alien_bottom(sett, stats, screen, ship, aliens, bullets):
ship_hit(sett, stats, screen, ship, aliens, bullets)
遊戲結束
當life減爲0後,遊戲結束。我們在GameStats裏添加一個作爲標誌的屬性active,以便在玩家的飛船用完後結束遊戲:
# stats.py
class Gamestats():
def __init__(self, sett):
self.sett = sett
self.life = 3
self.active = True
def reset_stats(self):
self.life = 3
self.active = True
當玩家的生命減爲0時,該變量變爲false
# game_function.py
def ship_hit(sett, stats, screen, ship, aliens, bullets):
stats.life -= 1
if stats.life>0:
aliens.empty()
bullets.empty()
create_fleet(sett, screen, ship, aliens)
ship.center = screen.get_rect().centerx
sleep(0.5)
else:
stats.active = False
添加PLAY按鈕
添加PLAY按鈕,讓程序開始時處於非活動狀態,則要修改stats.py中的代碼
class Gamestats():
def __init__(self, sett):
self.sett = sett
self.life = 3
self.active = False
def reset_stats(self):
self.life = 3
創建Button類
由於pygame沒有內置創建按鈕的方法,所以我們創建一個Button類
# button.py
import pygame.font
class Button():
def __init__(self, sett, screen, msg):
self.screen = screen
self.screen_rect = screen.get_rect()
self.width, self.height = 200, 50
self.button_color = (0, 255, 0)
self.text_color = (255, 255, 255)
self.font = pygame.font.SysFont(None, 48)
# 指定字體字號來渲染文字
self.rect = pygame.Rect(0, 0, self.width, self.height)
self.rect.center = self.screen_rect.center
# 將字符串渲染成圖像
self.prep_msg(msg)
def prep_msg(self, msg):
"""將字符串渲染成圖像"""
# 第二個布爾參數是反鋸齒開關
self.msg_image = self.font.render(msg, True, self.text_color, self.button_color)
self.msg_image_rect = self.msg_image.get_rect()
self.msg_image_rect.center = self.rect.center
def draw_button(self):
self.screen.fill(self.button_color, self.rect)
self.screen.blit(self.msg_image, self.msg_image_rect)
在屏幕上繪製按鈕
只需要一個Play按鈕,故我們直接在invasion.py中創建
...
from button import Button
def run_game():
# 初始化遊戲並創建一個屏幕對象
...
pygame.display.set_caption("Thunder")
play_button = Button(sett, screen, 'PLAY')
...
# 開始遊戲的主循環
while True:
...
gf.update_screen(sett, screen, stats, ship, aliens, bullets, play_button)
run_game()
接着修改game_function.py的update_screen,以便在遊戲處於非活動狀態時顯示按鈕
def update_screen(sett, screen, stats, ship, aliens, bullets, button):
...
if not stats.active:
button.draw_button()
# 讓最近繪製的屏幕可見
pygame.display.flip()
一定要把draw放在flip前面,這樣才能讓繪製完所有其他元素之後再繪製按鈕,然後切換到新屏幕。
開始遊戲
在按下按鈕時開始新遊戲,需要對鼠標事件進行監視。
在game_function.py中添加如下代碼:
def check_play(stats, button, mouseX, mouseY):
if button.rect.collidepoint(mouseX, mouseY):
stats.active = True
def check_events(sett, screen, stats, button, ship, bullets):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown(event, sett, screen, ship, bullets)
elif event.type == pygame.KEYUP:
check_keyup(event, ship)
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_X, mouse_Y = pygame.mouse.get_pos()
check_play(stats, button, mouse_X, mouse_Y)
重置遊戲
遊戲結束後,會再顯示PLAY按鈕。每次單擊它都應該重置整個遊戲,重置統計信息,刪除現有的外星人和子彈,創建新的外星人,讓飛船居中。
def check_play(sett, screen, stats, button, ship, aliens, bullets, mouseX, mouseY):
if button.rect.collidepoint(mouseX, mouseY):
stats.reset_stats()
stats.active = True
aliens.empty()
bullets.empty()
create_fleet(sett, screen, ship, aliens)
ship.center = screen.get_rect().centerx
def check_events(sett, screen, stats, button, ship, aliens, bullets):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown(event, sett, screen, ship, bullets)
elif event.type == pygame.KEYUP:
check_keyup(event, ship)
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_X, mouse_Y = pygame.mouse.get_pos()
check_play(sett, screen, stats, button, ship, aliens, bullets, mouse_X, mouse_Y)
將Play按鈕切換到非活動狀態
有一個問題是,即使在遊戲活動狀態,按鈕圖形不會顯示,但是點擊其原來的位置依然會重置遊戲。所以要在監視鼠標事件時添加一個if條件:
def check_play(sett, screen, stats, button, ship, aliens, bullets, mouseX, mouseY):
if button.rect.collidepoint(mouseX, mouseY) and not stats.active:
...
隱藏光標
在點擊Play後的遊戲活動狀態,鼠標光標應該被隱藏
def check_play(sett, screen, stats, button, ship, aliens, bullets, mouseX, mouseY):
if button.rect.collidepoint(mouseX, mouseY) and not stats.active:
pygame.mouse.set_visible(False)
stats.reset_stats()
stats.active = True
aliens.empty()
bullets.empty()
create_fleet(sett, screen, ship, aliens)
ship.center = screen.get_rect().centerx
並在遊戲結束後重現它:
def ship_hit(sett, stats, screen, ship, aliens, bullets):
stats.life -= 1
if stats.life > 0:
...
else:
pygame.mouse.set_visible(True)
stats.active = False
提高難度
隨着遊戲的進行,遊戲的難度應當得到提升。
修改速度設置
我們要通過提高遊戲整體速度來提升難度,所以飛船、子彈、外星人的速度是在變的。爲此我們可以將settting裏的設置屬性分爲靜態和動態兩部分。
class Settings():
"""存儲遊戲所有設置的類"""
def __init__(self):
"""初始化遊戲的設置"""
# 屏幕設置
self.screen_width = 1200
self.screen_length = 700
self.bg_color = (230, 230, 230)
# 子彈設置
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = (60, 60, 60)
self.bullet_allowed = 4
self.speed_scale = 1.1
# 速度提升率
self.init_dynamic()
def init_dynamic(self):
self.alien_speed = 1
self.alien_drop_speed = 10
# 下降速度
self.fleet_direction = 1
# 1表示向右,-1表示向左, 可以直接作爲速度的係數用於座標運算
self.bullet_speed = 2
self.ship_speed = 1.5
接着編寫提升速度的方法increase_speed()
def increase_speed(self):
self.alien_speed *= self.speed_scale
self.bullet_speed *= self.speed_scale
self.ship_speed *= self.speed_scale
並在每消滅一羣外星人時調用一次這個方法:
def check_bullet_collision(sett, screen, ship, bullets, aliens):
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if len(aliens) == 0:
create_fleet(sett, screen, ship, aliens)
sett.increase_speed()
重置速度
每次開始新遊戲時,速度都要重置一次
def check_play(sett, screen, stats, button, ship, aliens, bullets, mouseX, mouseY):
if button.rect.collidepoint(mouseX, mouseY) and not stats.active:
...
sett.init_dynamic()
記分
在stats類裏添加一個屬性記錄得分
class Gamestats():
def __init__(self, sett):
self.sett = sett
self.reset_stats()
self.active = False
def reset_stats(self):
self.life = 1
self.score = 0
顯示得分
爲了在屏幕上顯示得分,我們首先創建一個新類scoreboard:
import pygame.font
class ScoreBoard():
def __init__(self, sett, screen, stats):
self.screen = screen
self.screen_rect = screen.get_rect()
self.sett = sett
self.stats = stats
self.text_color = (30, 30, 30)
self.font = pygame.font.SysFont(None, 48)
self.prep_score()
def prep_score(self):
score_str = str(self.stats.score)
self.score_image = self.font.render(score_str, True, self.text_color, self.sett.bg_color)
self.score_rect = self.score_image.get_rect()
self.score_rect.right = self.screen_rect.right - 20
self.score_rect.top = 20
def show_score(self):
self.screen.blit(self.score_image, self.score_rect)
創建記分牌實例
...
from scoreboard import ScoreBoard
def run_game():
# 初始化遊戲並創建一個屏幕對象
...
sb = ScoreBoard(sett, screen, stats)
...
# 開始遊戲的主循環
while True:
...
gf.update_screen(sett, screen, stats, sb, ship, aliens, bullets, play_button)
run_game()
同時要在update_screen()中調用show_score()
得分
擊殺外星人後要增加分數。只需要檢查子彈擊中外星人時返回的字典(collision)即可.
我們現在setting中設置一個外星人的得分。
def __init__(self):
"""初始化遊戲的設置"""
...
self.alien_score = 50
接着在check_bullet_collision()
中檢查字典。(這個字典的鍵是一顆子彈,值是被這顆子彈擊中的外星人列表)
def check_bullet_collision(sett, screen, stats, sb, ship, bullets, aliens):
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
...
if collisions:
for aliens in collisions.key():
stats.score += sett.alien_score * len(aliens)
sb.prep_score()
主循環中要修改update_bullets()的參數
提高點數
隨着遊戲難度提升,一個外星人的得分應當提高。
所以在setting中增加一個得分提升的幅度屬性
self.score_scale = 1.5
在遊戲難度提升時,即速度提高時,修改setting的屬性alien_score
(因爲alien_score會變動,所以要把這個屬性分類爲動態,使其在動態初始化方法中被賦初值)
class Settings():
"""存儲遊戲所有設置的類"""
def __init__(self):
"""初始化遊戲的設置"""
...
self.init_dynamic()
def init_dynamic(self):
...
self.alien_score = 50
def increase_speed(self):
...
self.alien_score = int(self.alien_score * self.score_scale)
將得分圓整
大部分遊戲都會將遊戲得分顯示爲10的整倍數。我們可以把得分圓整。
# scoreboard.py
def prep_score(self):
rounded_score = int(round(self.stats.score, -1))
score_str = "{:,}".format(rounded_score)
...
round()
的第二個參數爲精確到的小數位。
第二個參數爲負數,則round()
將圓整到最近的10、100、1000等整倍數。
"{:,}".format(rounded_score)
爲一個字符串格式設置指令,它讓Python將數值轉換成字符串時在其中插入逗號。
最高分
我們在stats中增加一個屬性最高分,並將其展示在屏幕頂端中央。
但是爲了讓數據保存,這個最高分存儲在外部文件中,所以每次都需要從外部文件讀入:
class Gamestats():
def __init__(self, sett):
self.sett = sett
self.reset_stats()
with open("highScore.txt", 'r') as hs:
self.high_score = int(hs.read())
每當一場遊戲結束後,都要更新最高分:
def ship_hit(sett, stats, screen, ship, aliens, bullets):
stats.life -= 1
if stats.life > 0:
...
else:
pygame.mouse.set_visible(True)
stats.active = False
if stats.score > stats.high_score:
stats.high_score = stats.score
每次關閉前都要在外部文件更新最高分:
def check_events(sett, screen, stats, button, ship, aliens, bullets):
for event in pygame.event.get():
if event.type == pygame.QUIT:
with open("highScore.txt", 'w') as hs:
hs.write(str(stats.high_score))
sys.exit()
...
接着要在最頂端顯示最高分:
# scoreboard.py
class ScoreBoard():
def __init__(self, sett, screen, stats):
...
self.prep_score()
self.prep_high()
def prep_score(self):
...
def prep_high(self):
high_score_str = "{:,}". format(self.stats.high_score)
print(high_score_str)
self.high_score_image = self.font.render(high_score_str, True, self.text_color, self.sett.bg_color)
self.high_score_rect = self.high_score_image.get_rect()
self.high_score_rect.centerx = self.screen_rect.centerx
self.high_score_rect.top = self.screen_rect.top
def show_score(self):
self.screen.blit(self.score_image, self.score_rect)
self.screen.blit(self.high_score_image, self.high_score_rect)
同時,在更新過最高分之後調用prep_high()
:
def ship_hit(sett, stats, screen, ship, sb, aliens, bullets):
stats.life -= 1
if stats.life > 0:
...
else:
pygame.mouse.set_visible(True)
stats.active = False
if stats.score > stats.high_score:
stats.high_score = stats.score
sb.prep_high()
用到ship_hit()的地方都要修改參數
獎勵子彈
我們新增一個機制,當玩家分數達到一定程度後,我們將接下來的3發子彈的寬度提高100倍,提高消滅外星人的效率。
首先,現在stats.py中設置獎勵標準:
class Gamestats():
def __init__(self, sett):
...
def reset_stats(self):
...
self.award_level = 1 # 獎勵等級
self.bullet_award = False # 獎勵狀態
self.award_b = 0 # 已用獎勵子彈數量
self.award_score = 1500 # 獎勵分數標準
因爲外星人的分數會隨着遊戲難度增加而增加,所以獎勵分數標準應該在每一次獎勵後增加。所以我們在setting.py中增加一個屬性award_score_scale
:
class Settings():
"""存儲遊戲所有設置的類"""
def __init__(self):
"""初始化遊戲的設置"""
# 屏幕設置
self.screen_width = 1200
self.screen_length = 700
self.bg_color = (230, 230, 230)
# 子彈設置
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = (60, 60, 60)
self.bullet_allowed = 4
self.speed_scale = 1.1
# 速度提升率
self.score_scale = 1.5
self.award_score_scale = 1.4
# 分數提升率
接着,在game_funciton.py中編寫判斷函數:
def award(sett, stats):
if stats.score >= stats.award_level * stats.award_score:
stats. bullet_award = True
sett.bullet_width = 300
stats.award_level += 1
stats.award_b = 0
stats.award_score *= sett.award_score_scale
每此獎勵完之後,獎勵等級(award_level)要提升,獎勵分數標準(award_score)要提升,已用獎勵子彈數(award_b)清零。
然後要讓獎勵狀態在三發子彈後變回False。因爲子彈是在按下空格後發射,所以我們可以在檢測空格事件的函數中實現:
def award_check(sett, stats):
if stats.bullet_award:
if stats.award_b == 3:
stats.bullet_award = False
sett.bullet_width = 3
stats.award_b += 1
def check_keydown(event, sett, screen, stats, ship, bullets):
if event.key == pygame.K_RIGHT:
ship.right_move = True
elif event.key == pygame.K_LEFT:
ship.left_move = True
elif event.key == pygame.K_SPACE:
if stats.bullet_award:
award_check(sett, stats)
fire_bullet(sett, screen, ship, bullets)
因爲獎勵狀態是隨着得分轉變的,所以我們在得分的函數裏調用award()
,即check_bullet_collision():
def check_bullet_collision(sett, screen, stats, sb, ship, bullets, aliens):
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if len(aliens) == 0:
create_fleet(sett, screen, ship, aliens)
sett.increase_speed()
if collisions:
for aliens in collisions.values():
stats.score += sett.alien_score * len(aliens)
sb.prep_score()
award(sett, stats)
左上角顯示剩餘生命
最後,我們來顯示玩家還剩多少艘飛船,但用的是圖形而不是數字。
首先,需要讓Ship繼承Sprite,以便創建飛船編組:
import pygame
from pygame.sprite import Sprite
class Ship(Sprite):
def __init__(self, sett, screen):
...
super().__init__()
接着在scoreboard. py 中,創建一個可供顯示的飛船編組。
def show_score(self):
self.screen.blit(self.score_image, self.score_rect)
self.screen.blit(self.high_score_image, self.high_score_rect)
self.ships.draw(self.screen)
def prep_ship(self):
"""顯示剩餘飛船"""
self.ships = Group()
for ship_num in range(self.stats.life):
ship = Ship(self.sett, self.screen)
ship.rect.x = 10 + ship_num * ship.rect.width
ship.rect.y = 10
self.ships.add(ship)
要在遊戲開始時顯示這個剩餘生命,所以我們在開始新遊戲時調用prep_ships()。這個將在check_play()中進行:
def check_play(sett, screen, stats, button, ship, sb, aliens, bullets, mouseX, mouseY):
if button.rect.collidepoint(mouseX, mouseY) and not stats.active:
...
sb.prep_score()
sb.prep_ship()
同時,當損失生命值時,左上角的生命牌需要更新,要在ship_hit()中還要調用prep_ship()
def ship_hit(sett, stats, screen, ship, sb, aliens, bullets):
stats.life -= 1
if stats.life > 0:
...
else:
....
sb.prep_ship()
- 別忘了對對相關函數的參數列表修改
最後的重構
- 將清屏和重新創建艦隊的代碼編寫爲一個函數
clear_recreate()
- 將點擊PLAY按鈕後的分數板和動態設置重置的函數整合爲
restart()
- 將scoreboard .py中的__init__()調用的prep方法整合
最終代碼:
# ship.py
import pygame
from pygame.sprite import Sprite
class Ship(Sprite):
def __init__(self, sett, screen):
self.screen = screen
self.sett = sett
self.image = pygame.image.load("ship.bmp")
self.rect = self.image.get_rect()
self.screen_rect = screen.get_rect()
self.rect.centerx = self.screen_rect.centerx
self.rect.bottom = self.screen_rect.bottom
self.center = float(self.rect.centerx)
self.right_move = False
self.left_move = False
super().__init__()
def blitme(self):
"""在指定位置繪製飛船"""
self.screen.blit(self.image, self.rect)
def update(self):
if self.right_move and self.rect.right < self.screen_rect.right:
self.center += self.sett.ship_speed
if self.left_move and self.rect.left > 0:
self.center -= self.sett.ship_speed
self.rect.centerx = self.center
# alien.py
import pygame
from pygame.sprite import Sprite
class Alien(Sprite):
"""單個外星人的類"""
def __init__(self, sett, screen):
super().__init__()
self.screen = screen
self.setting = sett
# 加載外星人圖像,設置rect屬性
self.image = pygame.image.load('alien.bmp')
self.rect = self.image.get_rect()
# 每個外星人最初都在屏幕左上角
self.rect.x = self.rect.width
self.rect.y = self.rect.height
# 存儲外星人準確位置
self.x = float(self.rect.x)
def blitme(self):
self.screen.blit(self.image, self.rect)
def check_edges(self):
screen_rect = self.screen.get_rect()
if self.rect.right >= screen_rect.right:
return True
elif self.rect.left <= screen_rect.left:
return True
def update(self):
self.x += self.setting.alien_speed * self.setting.fleet_direction
self.rect.x = self.x
# bullet.py
import pygame
from pygame.sprite import Sprite
class Bullet(Sprite):
"""一個對飛船的子彈管理的類"""
def __init__(self, sett, screen, ship):
super().__init__()
self.screen = screen
self.rect = pygame.Rect(0, 0, sett.bullet_width, sett.bullet_height)
self.rect.centerx = ship.rect.centerx
self.rect.top = ship.rect.top
self.y = float(self.rect.y)
self.color = sett.bullet_color
self.speed = sett.bullet_speed
def update(self):
"""向上移動子彈"""
self.y -= self.speed
self.rect.y = self.y
def draw_bullet(self):
pygame.draw.rect(self.screen, self.color, self.rect)
# setting.py
class Settings():
"""存儲遊戲所有設置的類"""
def __init__(self):
"""初始化遊戲的設置"""
# 屏幕設置
self.screen_width = 1200
self.screen_length = 700
self.bg_color = (230, 230, 230)
# 子彈設置
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = (60, 60, 60)
self.bullet_allowed = 4
self.speed_scale = 1.1
# 速度提升率
self.score_scale = 1.5
self.award_score_scale = 1.4
# 分數提升率
self.awared_width = 300
# 獎勵寬度
self.init_dynamic()
def init_dynamic(self):
self.alien_speed = 1
self.alien_drop_speed = 10
# 下降速度
self.fleet_direction = 1
# 1表示向右,-1表示向左, 可以直接作爲速度的係數用於座標運算
self.bullet_speed = 2
self.ship_speed = 1.5
self.alien_score = 50
def increase_speed(self):
self.alien_speed *= self.speed_scale
self.bullet_speed *= self.speed_scale
self.ship_speed *= self.speed_scale
self.alien_score = int(self.alien_score * self.score_scale)
# stats.py
class Gamestats():
def __init__(self, sett):
self.sett = sett
self.reset_stats()
with open("highScore.txt", 'r') as hs:
self.high_score = int(hs.read())
def reset_stats(self):
self.life = 3
self.score = 0
self.active = False
self.award_level = 1
self.bullet_award = False
self.award_b = 0
self.award_score = 1500
# button.py
import pygame.font
class Button():
def __init__(self, sett, screen, msg):
self.screen = screen
self.screen_rect = screen.get_rect()
self.width, self.height = 200, 50
self.button_color = (0, 255, 0)
self.text_color = (255, 255, 255)
self.font = pygame.font.SysFont(None, 48)
# 指定字體字號來渲染文字
self.rect = pygame.Rect(0, 0, self.width, self.height)
self.rect.center = self.screen_rect.center
# 將字符串渲染成圖像
self.prep_msg(msg)
def prep_msg(self, msg):
"""將字符串渲染成圖像"""
# 第二個布爾參數是反鋸齒開關
self.msg_image = self.font.render(msg, True, self.text_color, self.button_color)
self.msg_image_rect = self.msg_image.get_rect()
self.msg_image_rect.center = self.rect.center
def draw_button(self):
self.screen.fill(self.button_color, self.rect)
self.screen.blit(self.msg_image, self.msg_image_rect)
# scoreboard.py
import pygame.font
from pygame.sprite import Group
from ship import Ship
class ScoreBoard():
def __init__(self, sett, screen, stats):
self.screen = screen
self.screen_rect = screen.get_rect()
self.sett = sett
self.stats = stats
self.text_color = (30, 30, 30)
self.font = pygame.font.SysFont(None, 48)
self.prep_image()
def prep_image(self):
self.prep_score()
self.prep_high()
self.prep_ship()
def prep_score(self):
rounded_score = int(round(self.stats.score, -1))
score_str = "{:,}".format(rounded_score)
self.score_image = self.font.render(score_str, True, self.text_color, self.sett.bg_color)
self.score_rect = self.score_image.get_rect()
self.score_rect.right = self.screen_rect.right - 20
self.score_rect.top = 20
def prep_high(self):
high_score_str = "{:,}". format(self.stats.high_score)
self.high_score_image = self.font.render(high_score_str, True, self.text_color, self.sett.bg_color)
self.high_score_rect = self.high_score_image.get_rect()
self.high_score_rect.centerx = self.screen_rect.centerx
self.high_score_rect.top = self.screen_rect.top
def show_score(self):
self.screen.blit(self.score_image, self.score_rect)
self.screen.blit(self.high_score_image, self.high_score_rect)
self.ships.draw(self.screen)
def prep_ship(self):
"""顯示剩餘飛船"""
self.ships = Group()
for ship_num in range(self.stats.life):
ship = Ship(self.sett, self.screen)
ship.rect.x = 10 + ship_num * ship.rect.width
ship.rect.y = 10
self.ships.add(ship)
# game_function.py
import sys
import pygame
from bullet import Bullet
from alien import Alien
from time import sleep
def fire_bullet(sett, screen, ship, bullets):
if len(bullets) < sett.bullet_allowed:
new_bullet = Bullet(sett, screen, ship)
bullets.add(new_bullet)
def get_number_aliens_x(sett, alien_width):
available_space_x = sett.screen_width - 2 * alien_width
number_aliens_x = int(available_space_x / (2 * alien_width))
return number_aliens_x
def get_nuber_rows(sett, alien_height, ship_height):
available_space_y = sett.screen_length - 3 * alien_height - ship_height
number_rows = int(available_space_y / (2 * alien_height))
return number_rows
def create_alien(sett, screen, aliens, alien_width, alien_number, row_number):
# 創建一個外星人並加入羣組
alien = Alien(sett, screen)
alien.x = alien_width + 2 * alien_width * alien_number
alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_number
alien.rect.x = alien.x
aliens.add(alien)
def create_fleet(sett, screen, ship, aliens):
"""創建外星人羣"""
# 創建一個外星人,並計算一行可容納多少外星人
alien = Alien(sett, screen)
alien_width = alien.rect.width
alien_height = alien.rect.height
number_aliens_x = get_number_aliens_x(sett, alien_width)
number_rows = get_nuber_rows(sett, alien_height, ship.rect.height)
# 創建第一行外星人
for row_number in range(number_rows):
for alien_number in range(number_aliens_x):
create_alien(sett, screen, aliens, alien_width, alien_number, row_number)
def award_check(sett, stats):
if stats.bullet_award:
if stats.award_b == 3:
stats.bullet_award = False
sett.bullet_width = 3
stats.award_b += 1
def check_keydown(event, sett, screen, stats, ship, bullets):
if event.key == pygame.K_RIGHT:
ship.right_move = True
elif event.key == pygame.K_LEFT:
ship.left_move = True
elif event.key == pygame.K_SPACE:
if stats.bullet_award:
award_check(sett, stats)
fire_bullet(sett, screen, ship, bullets)
def check_keyup(event, ship):
if event.key == pygame.K_RIGHT:
ship.right_move = False
elif event.key == pygame.K_LEFT:
ship.left_move = False
def clear_recreate(sett, screen, ship, aliens, bullets):
"""清除屏幕重新開始"""
aliens.empty()
bullets.empty()
create_fleet(sett, screen, ship, aliens)
ship.center = screen.get_rect().centerx
def restart(sett, sb):
sett.init_dynamic()
sb.prep_score()
sb.prep_ship()
def check_play(sett, screen, stats, button, ship, sb, aliens, bullets, mouseX, mouseY):
if button.rect.collidepoint(mouseX, mouseY) and not stats.active:
pygame.mouse.set_visible(False)
stats.reset_stats()
stats.active = True
clear_recreate(sett, screen, ship, aliens, bullets)
restart(sett, sb)
def check_events(sett, screen, stats, button, ship, sb, aliens, bullets):
for event in pygame.event.get():
if event.type == pygame.QUIT:
with open("highScore.txt", 'w') as hs:
hs.write(str(stats.high_score))
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown(event, sett, screen, stats, ship, bullets)
elif event.type == pygame.KEYUP:
check_keyup(event, ship)
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_X, mouse_Y = pygame.mouse.get_pos()
check_play(sett, screen, stats, button, ship, sb, aliens, bullets, mouse_X, mouse_Y)
def check_bullet_collision(sett, screen, stats, sb, ship, bullets, aliens):
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if len(aliens) == 0:
create_fleet(sett, screen, ship, aliens)
sett.increase_speed()
if collisions:
for aliens in collisions.values():
stats.score += sett.alien_score * len(aliens)
sb.prep_score()
award(sett, stats)
def update_bullet(sett, screen, stats, sb, ship, bullets, aliens):
bullets.update()
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
check_bullet_collision(sett, screen, stats, sb, ship, bullets, aliens)
def update_screen(sett, screen, stats, sb, ship, aliens, bullets, button):
screen.fill(sett.bg_color)
ship.blitme()
sb.show_score()
aliens.draw(screen)
for bullet in bullets:
bullet.draw_bullet()
if not stats.active:
button.draw_button()
# 讓最近繪製的屏幕可見
pygame.display.flip()
def change_fleet_dir(sett, aliens):
"""將整羣外星人下移"""
for alien in aliens:
alien.rect.y += sett.alien_drop_speed
sett.fleet_direction *= -1
def check_fleet_edges(sett, aliens):
"""有一個外星人到達邊緣"""
for alien in aliens.sprites():
if alien.check_edges():
change_fleet_dir(sett, aliens)
break
def alien_bottom(sett, stats, screen, ship, sb, aliens, bullets):
screen_rect = screen.get_rect()
for alien in aliens:
if alien.rect.bottom >= screen_rect.bottom:
ship_hit(sett, stats, screen, ship, sb, aliens, bullets)
def ship_hit(sett, stats, screen, ship, sb, aliens, bullets):
stats.life -= 1
if stats.life > 0:
clear_restart(sett, screen, ship, aliens, bullets)
sleep(0.5)
else:
pygame.mouse.set_visible(True)
stats.active = False
if stats.score > stats.high_score:
stats.high_score = stats.score
sb.prep_high()
sb.prep_ship()
def update_aliens(sett, stats, screen, ship, sb, aliens, bullets):
check_fleet_edges(sett, aliens)
aliens.update()
if pygame.sprite.spritecollideany(ship, aliens) or alien_bottom(sett, stats, screen, ship, sb, aliens, bullets):
ship_hit(sett, stats, screen, ship, sb, aliens, bullets)
def award(sett, stats):
if stats.score >= stats.award_level * stats.award_score:
stats. bullet_award = True
sett.bullet_width = sett.awared_width
stats.award_level += 1
stats.award_b = 0
stats.award_score *= sett.award_score_scale
# invasion.py
import pygame
import game_function as gf
from pygame.sprite import Group
from settings import Settings
from ship import Ship
from stats import Gamestats
from button import Button
from scoreboard import ScoreBoard
def run_game():
# 初始化遊戲並創建一個屏幕對象
pygame.init()
sett = Settings()
stats = Gamestats(sett)
screen = pygame.display.set_mode(
(sett.screen_width, sett.screen_length)
)
pygame.display.set_caption("Thunder")
play_button = Button(sett, screen, 'PLAY')
ship = Ship(sett, screen)
bullets = Group()
aliens = Group()
sb = ScoreBoard(sett, screen, stats)
gf.create_fleet(sett, screen, ship, aliens)
# 開始遊戲的主循環
while True:
gf.check_events(sett, screen, stats, play_button, ship, sb, aliens, bullets)
ship.update()
gf.update_bullet(sett, screen, stats, sb, ship, bullets, aliens)
gf.update_aliens(sett, stats, screen, ship, sb, aliens, bullets)
gf.update_screen(sett, screen, stats, sb, ship, aliens, bullets, play_button)
run_game()