《Python編程:從入門到實踐》筆記。python
本章主要學習如何使用pygame編寫一個簡單的小飛機打外星人的遊戲,因爲本人對用python寫遊戲並非特別感興趣,因此主要是看代碼的構建和一些編程規範,代碼會有所簡略。程序員
Python標準庫中並無自帶pygame
模塊,因此須要自行安裝,能夠在控制檯(Windows下是cmd)上使用命令行安裝:pip install pygame
。若是你是用的PyCharm,也能夠在設置中安裝:編程
點擊右邊的加號,在彈出的窗口中輸入pygame,而後安裝便可。微信
該項目中須要使用一些書中的圖片,這些圖片均可以在 http://www.ituring.com.cn/book/1861 中下載到。併發
首先須要新建一個項目,筆者取名爲 "alien_invasion" ,並在該項目的根目錄下新建一個 images 文件夾,用於存放項目中用到的圖片。在本節中,咱們將先建立4個文件:框架
alien_invasion.py
:遊戲主程序函數
settings.py
:遊戲的配置文件學習
game_functions.py
:存放遊戲的控制函數,好比響應鼠標、鍵盤等網站
ship.py
:飛船類spa
該模塊通過重構後的代碼以下:
import pygame
import game_functions as gf
from settings import Settings
from ship import Ship
def run_game():
# 初始化遊戲並建立一個屏幕對象
pygame.init() # 初始化背景設置,讓pygame能正常工做
ai_settings = Settings() # 實例化一個遊戲配置類
# 返回一個遊戲窗口
screen = pygame.display.set_mode(
(ai_settings.screen_width, ai_settings.screen_height))
pygame.display.set_caption("Alien Invasion") # 給這個遊戲窗口設置一個標題
ship = Ship(ai_settings, screen) # 實例化一個飛船類,傳入了參數ai_settings和屏幕對象screen
# 開始遊戲的主循環
while True:
gf.check_events(ship) # 用於響應遊戲事件
ship.update() # 更新飛船狀態
gf.update_screen(ai_settings, screen, ship) # 重繪screen
run_game()
複製代碼
①代碼第1行導入pygame
模塊,它包含開發遊戲所需的基本功能;
②代碼3到5行導入的是自行編寫且通過重構的模塊;
③第9行代碼執行遊戲的初始化工做,好比初始化遊戲背景等;
④第10行實例化一個遊戲配置類,用於配置遊戲參數,該類的具體實現見本篇後面的內容;
⑤代碼第12-13行用於生成一個名爲screen
的顯示窗口,長寬從配置對象ai_settings
中讀出;display.set_mode()
返回的是一個surface
,在pygame中,surface
是屏幕的一部分,用於顯示遊戲元素,這裏的screen
表示的是整個遊戲窗口。咱們激活遊戲的循環後,每通過一次循環pygame都將重繪這個screen
。
⑥代碼第20行的check_events()
函數用於響應遊戲中發生的時間,好比鼠標,鍵盤,關閉窗口等。
⑦代碼第21行用於更新飛船的信息,如飛船位置
⑧最後一行用於啓動遊戲,即初始化遊戲,並開始主循環。
該文件主要是遊戲的配置信息,存放遊戲的各類參數。
class Settings:
"""存儲《外星人入侵》的全部設置的類"""
def __init__(self):
"""初始化遊戲的設置"""
# 屏幕設置
self.screen_width = 1200 # 遊戲窗口寬度
self.screen_height = 800 # 遊戲窗口高度
self.bg_color = (230, 230, 230) # 遊戲背景顏色
self.ship_speed_factor = 1.5 # 飛船的移動速度
複製代碼
這裏故意將飛船的速度設置爲浮點數,也能夠是整數。在設置遊戲元素的位置時,若是直接使用浮點數,則只會截取整數部分。
該模塊描述了一個飛船類的基本內容:
import pygame
class Ship:
def __init__(self, ai_settings, screen):
"""初始化飛機並設置其初始位置"""
self.screen = screen
self.ai_settings = ai_settings
# 加載飛機圖片並獲取其外接矩形
self.image = pygame.image.load("images/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
# 自定義一個能存儲浮點數的臨時變量,x座標
self.center = float(self.rect.centerx)
# 標誌,用於表示是否正在向某個方向移動
self.moving_right = False
self.moving_left = False
def update(self):
"""根據移動標誌調整飛船的位置"""
if self.moving_right and self.rect.right < self.screen_rect.right:
self.center += self.ai_settings.ship_speed_factor
if self.moving_left and self.rect.left > 0:
self.center -= self.ai_settings.ship_speed_factor
# 用臨時變量更新rect的centerx,截取截取整數部分
self.rect.centerx = self.center
def blitme(self):
"""在指定位置繪製飛船"""
self.screen.blit(self.image, self.rect)
複製代碼
①__init__()
中的self.center
屬性,代碼將self.rect.centerx
即飛船的中心x座標轉換成浮點數,並將其存儲在self.cente
r中。之因此轉換成浮點數,是由於在settings.py
文件中,咱們將飛船移動速度設置成了浮點數。
②self.moving_right
和self.moving_left
標誌,用於表示飛船是否正在移動,用於實現飛船在不鬆開按鍵下連續移動。
③udpate()
方法,用於增減飛船的中心位置x
座標(由於飛船隻能在底部移動,因此不用改y
座標),並防止飛船移動出遊戲窗口。
④重寫了blitme()
函數,用於繪製飛船
該模塊主要是集中處理遊戲中發生的各類事件。
import sys
import pygame
def check_keydown_event(event, ship):
"""響應按下按鍵"""
if event.key == pygame.K_RIGHT:
ship.moving_right = True
if event.key == pygame.K_LEFT:
ship.moving_left = True
def check_keyup_event(event, ship):
"""響應鬆開按鍵"""
if event.key == pygame.K_RIGHT:
ship.moving_right = False
if event.key == pygame.K_LEFT:
ship.moving_left = 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(event, ship)
elif event.type == pygame.KEYUP:
check_keyup_event(event, ship)
def update_screen(ai_settings, screen, ship):
"""更新屏幕上的圖像,並切換到新屏幕"""
# 每次循環時都重繪屏幕
screen.fill(ai_settings.bg_color)
ship.blitme()
# 讓最近繪製的屏幕可見
pygame.display.flip()
複製代碼
①在pygame中,用K_RIGHT
,K_LEFT
表示方向按鍵,其實鍵盤上每一個鍵在pygame中都有所對於,以K_
開頭。check_keydown_event()
函數和check_keyup_event()
函數都是對下面的check_event()
的進一步簡化,這兩個函數的代碼都可以放在check_event()
中,但這樣代碼將會很臃腫,結構不清晰。
②check_event()
函數用於監聽遊戲的事件,好比pygame.QUIT
,它表示遊戲推出事件;pygame.KEYDOWN
和pygame.KEYUP
分別表示鍵盤按下與鬆開事件。本次大循環中(外層的while
循環)發生的全部事件都存儲在pygame.event
中,咱們使用get()
方法得到這些事件。
③在update_screen()
函數中,咱們使用screen
的fill()
方法填充窗體的背景色,調用blitme()
方法來在窗體中繪製飛船,最後,調用pygame.display.flip()
方法讓最近的繪製在窗體中可見。
如今咱們運行alien_invasion.py
文件,咱們將獲得以下窗體:
目前功能還比較簡單,只能實現飛船的左右移動。
爲了添加射擊功能,須要先添加一個子彈類。
import pygame
from pygame.sprite import Sprite
class Bullet(Sprite): # 使用精靈
"""一個對飛船發射的子彈進行管理的類"""
def __init__(self, ai_settings, screen, ship):
"""在飛船所處的位置建立一個子彈對象"""
super(Bullet, self).__init__()
self.screen = screen
# 在(0,0)處建立一個表示子彈的矩形,再設置正確的位置
self.rect = pygame.Rect(0, 0, ai_settings.bullet_width,
ai_settings.bullet_height)
self.rect.centerx = ship.rect.centerx # 從飛機的中央位置射出
self.rect.top = ship.rect.top # 從飛機的頂部射出
# 存儲用浮點數表示的子彈位置,由於子彈只在y軸上運動,因此不須要x座標
self.y = float(self.rect.y)
self.color = ai_settings.bullet_color # 子彈顏色
self.speed_factor = ai_settings.bullet_speed_factor # 子彈速度
def update(self):
"""向上移動子彈"""
# 更新表示子彈位置的浮點數值
self.y -= self.speed_factor
# 更新表示子彈的rect的位置
self.rect.y = self.y
def draw_bullet(self):
"""在屏幕上繪製子彈"""
pygame.draw.rect(self.screen, self.color, self.rect)
複製代碼
首先咱們須要導入pygame模塊以及其中的Sprite
類(直譯的話叫作「精靈類」,然而這名字叫的真的很尷尬),它可讓咱們在後面方便批量處理相同類型的同一操做,子彈類繼承自Sprite
類。該子彈類並無使用圖片,而是直接在screen上繪製矩形用於表示子彈。update()
方法用於更新子彈的位置。pygame.draw.rect()
用於在screen
上繪製子彈。
在該模塊中添加子彈類的參數:
class Settings:
def __init__(self):
-- snip --
# 子彈設置
self.bullet_speed_factor = 1
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = 60, 60, 60
# 表示窗口中最多容許存在的子彈數,固然你也能夠將其去掉
self.bullets_allowed = 3
複製代碼
遊戲中咱們按空格鍵發射子彈,併發射子彈的過程單獨寫在一個函數fire_bullet()
中。爲了響應空格鍵,須要修改check_event()
函數和check_keydown_event()
函數,前者只修改了參數,後者在判斷結構中添加了一個判斷。有了子彈類,那咱們還須要在screen
中繪製子彈,因此還須要修改update_screen()
函數,而子彈自身信息(好比子彈的移動)的修改則放在了一個新的函數update_bullets()
中。
import sys
import pygame
from Bullet import Bullet
# 新增函數!
def fire_bullet(ai_settings, screen, ship, bullets):
"""若是尚未到達限制,就發射一顆子彈"""
# 建立新子彈,並將其加入到編組bullets中
# 若是你想讓飛船能無限發射子彈,能夠將判斷語句刪除
if len(bullets) < ai_settings.bullets_allowed:
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
# 修改了參數!
def check_keydown_event(event, ship, ai_settings, screen, bullets):
-- snip --
# 按空格鍵發射子彈
elif event.key == pygame.K_SPACE:
fire_bullet(ai_settings, screen, ship, bullets)
# 修改了參數!
def check_events(ai_settings, screen, ship, bullets):
"""響應按鍵和鼠標事件"""
for event in pygame.event.get():
-- snip --
elif event.type == pygame.KEYDOWN:
# 增長了參數
check_keydown_event(event, ship, ai_settings, screen, bullets)
-- snip --
# 修改了函數!
def update_screen(ai_settings, screen, ship, bullets):
-- snip --
# 繪製子彈
for bullet in bullets.sprites():
bullet.draw_bullet()
-- snip --
# 新增函數
def update_bullets(bullets):
"""更新子彈的位置,並刪除已消失的子彈"""
# 更新子彈的位置
# bullets是個Group對象,調用一次update()就會調用其中全部Sprite對象的update()
# 至關於你不用本身寫for循環了
bullets.update()
# 刪除已消失的子彈
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
複製代碼
當子彈從窗口中消失時,它並無從內存中消失,若是對於已經從屏幕中消失的子彈不作處理的話,時間一長,子彈數一多,光子彈一項的內存佔用就會愈來愈多(土豪請忽略),雖然只是線性增加,但做爲一個合格的程序員,應該避免這種無謂的浪費。
最後,咱們修改主程序,在其中添加一個pygame.sprite
中的Group
對象用於表示子彈集合,以及對該對象的操做代碼。
import pygame
from pygame.sprite import Group # 導入一個新類
import game_functions as gf
from settings import Settings
from ship import Ship
def run_game():
-- snip --
bullets = Group()
# 開始遊戲的主循環
while True:
gf.check_events(ai_settings, screen, ship, bullets)
ship.update()
gf.update_bullets(bullets)
gf.update_screen(ai_settings, screen, ship, bullets)
複製代碼
如下是運行截圖:
自此,咱們建立了一個能開火的小飛機,在下一篇文章中咱們將向其中添加外星人。
本篇中的代碼都是通過了重構後的代碼,可是,當咱們本身在編程時,若是對某一框架仍是小白,搞不清楚該如何組織代碼,那就把全部代碼都寫在一個或幾個文件裏(雖然這種習慣很很差),也暫時不用考慮代碼結構之類的問題,由於 你的任務是造東西,而不是寫漂亮代碼,用精巧結構,用別人沒看過的語法。 二者能兼備固然更好,但每一個人都有當小白的時期,有必定熟練度後,再來考慮代碼重構的問題。
迎你們關注個人微信公衆號"代碼港" & 我的網站 www.vpointer.net ~