前言:python 除了生孩子 ,啥都會 。包括打飛機 !今天小詹的一位讀者就來教你如何用 python 打飛機 !
簡述
相信小詹是一個單純的孩子 ,也相信大家明白小詹說的打飛機是指啥意思 ,對吧 ?嗯 ,沒毛病 。就是 pygame 實現一個打飛機的遊戲 ,優秀的我啊 !
我們知道 pygame 框架可以用於管理圖形 、動畫聲音等 ,能夠利用它來輕鬆地開發複雜的遊戲 ,可以讓我們更加專注於面向對象編程 。此項目是基於 pygame 框架搭建的一個小遊戲 ,在此文中將實現此項目的 50% ,後續會有跟進 ,源代碼已經放在我的 GitHub 中 ,並在進行中將會不斷對代碼結構進行優化 ,對樣式進行優化 。在本例中將會接觸簡單的面向對象編程和繼承 。面向對象編程是提取項目中某種事物的關鍵屬性進行抽象 ,抽象模型中包括數據和行爲 ,類是對象的抽象 ,對象是類的實例 。
源代碼獲取方式見置頂留言 。
先送上一波效果圖(被壓縮的時間略短)
效果圖 遊戲簡介 :在遊戲《外星人入侵》中 ,玩家控制着一艘最初出現在屏幕底部中央的飛船 。玩家可以使用箭頭鍵左右移動飛船 ,還可使用空格鍵進行射擊 。遊戲開始時 ,一羣外星人出現在天空中 ,他們在屏幕中向下移動 。玩家的任務是射殺這些外星人 。玩家將所有外星人都消滅乾淨後 ,將出現一羣新的外星人 ,他們移動的速度更快 。只要有外星人撞到了玩家的飛船或到達了屏幕底部 ,玩家就損失一艘飛船 。玩家損失三艘飛船後 ,遊戲結束 。
遊戲用例圖(第一次畫用例圖,不是很準確……)
用例圖 分析該項目 ,飛船 、子彈 、外星人可以分別劃分爲具有共同屬性的一類 ,類中定義各自的屬性 ,包括圖像 、形狀 、位置 、更新位置 、繪製圖像等 ;在主函數中將對象實例化 ,對對象中的成員變量和函數進行調用實現移動 、擊殺等操作 ;本節實現飛船和子彈類和基本功能 。
在構建子彈類時 ,用到了繼承的概念 ,當在參與大型項目開發設計時 ,繼承是一個非常關鍵的概念 。繼承是對已有類的一種複用 ,子類繼承父類 ,可以對父類中的方法和數據進行重寫 ,也可以新定義只屬於子類的成員變量 。當項目中有許多類具有相同的基本屬性時 ,可以考慮將這些基本屬性抽象爲一個父類 ,子類通過繼承父類而擁有父類中的數據和方法 ,這會提高代碼的可讀性 ,也省去很多重複的代碼 。
當前新建以下幾個文件 :
- alien-invasion.py 主函數
- ship.py 飛船類
- bullet.py 子彈類
- game_functions.py 許多主函數會調用的函數
- settings.py 配置文件 ,常量
其目錄結構如下 :
代碼運行步驟 : 方法 1——
- git clone https://github.com/AlisaBen/easycoding
- powershell進入到工程目錄下(/fun_python>)
- cd ./alien_invasion
- python alien_invasion.py
方法2——
- 按照下面代碼部分新建文件 ,並複製代碼
- 在文件的同級目錄下新建
images
目錄 - 找飛船和子彈的圖片分別命名爲
外星飛船.png
和生氣.png
.emmm…畫風有點不對 ,怪我了 ,闊以自己找圖片替代 ,對應修改飛船類和子彈類的文件名就好 ~ - 進入到代碼的根目錄下
- python alien_invasion.py
代碼
alien-invasion.py
run_game()
定義了主函數 ,首先繪製屏幕 , 對象 screen 是一個 surface ,在 pygame 中,surface 是屏幕的一部分 ,顯示遊戲元素 。每個元素 ,外星人或者飛船 ,子彈都是一個surface 。ship = Ship(game_settings,screen)
就是一個對象是類的實例的例子 ,arguments 是類初始化需要傳入的參數 ,ship
就是Ship
類型的對象 ,可以訪問Ship
類中的數據和方法 。- 每個遊戲循環中 ,更新飛船位置 ,子彈位置 ,子彈編組 ,重新渲染遊戲界面 。
- 子彈編組用來管理屏幕中的所有子彈 ,其實用列表來管理子彈對象也是可以實現的 ,但是 pygame 自帶的 Group 已經定義了一些函數 ,更加方便 。
import pygame from settings import Settings from ship import Ship import game_functions as gf from pygame.sprite import Group def run_game(): pygame.init() # 獲取配置 game_settings = Settings() # 繪製屏幕 screen = pygame.display.set_mode((game_settings.screen_width,game_settings.screen_height)) backgound_color = (220,230,230) pygame.display.set_caption("Alien Invasion") # 飛船實例化 ship = Ship(game_settings,screen) # 子彈編組,管理屏幕中所有的子彈,保存繼承Sprite類的Bullet實例 bullets = Group() while True: # 檢測飛船事件:左移右移發射子彈 gf.check_events(ship,game_settings,screen,bullets) # 修改飛船移動標誌 ship.update() # 根據飛船移動標誌重新計算飛船中心位置 # 更新所有子彈位置 gf.update_bullets(bullets) # 繪製飛船子彈 gf.update_screen(game_settings,screen,ship,bullets) run_game()
ship.py
類
- 飛船類中主要涉及類初始化 ,飛船位置更新和渲染飛船surface 。
- 在初始化中主要定義一些代表飛船屬性的成員變量 ,如圖像 ,飛船矩形外形 ,飛船中心 ,飛船左移標誌和右移標誌等 。
- 涉及 pygame 的圖像
load()
方法和get_rect()
方法 ,主要是爲了之後飛船和外星人進行碰撞計算 ,其實可以直接self.image = pygame.image.load('images/外星飛船.png')
加載圖像 ,但是圖像太大 ,就pygame.transform.smoothscale
方法修改圖像大小 。
import pygame class Ship(object): """docstring for Ship""" def __init__(self, game_settings,screen): super(Ship, self).__init__() self.game_settings = game_settings self.screen = screen # screen遊戲界面 self.image = pygame.image.load('images/外星飛船.png').convert_alpha() # 加載飛船圖像 # 修改圖像大小 self.width,self.height = self.image.get_size() self.image = pygame.transform.smoothscale(self.image,(self.width//2,self.height//2)) self.rect = self.image.get_rect() self.screen_rect = screen.get_rect() # 獲取遊戲界面的中心x座標和底部位置便可確定飛船位置 self.rect.centerx = self.screen_rect.centerx self.rect.bottom = self.screen_rect.bottom - game_settings.screen_height / 5 self.center = float(self.rect.centerx) # centerx中只能表示整數 # 移動標誌 self.moving_right = False self.moving_left = False def update(self): """ 根據移動標誌調整飛船位置 更改爲根據self.center更新位置 """ if self.moving_right and self.rect.right < self.screen_rect.right: # self.rect.centerx += 1 self.center += self.game_settings.ship_speed_factor if self.moving_left and self.rect.left > 0: # self.rect.centerx -= 1 self.center -= self.game_settings.ship_speed_factor # 更新centerx,因爲繪製圖形是根據self.rect() # self.rect()定位需要根據centerx,bottom self.rect.centerx = self.center def blitme(self): """在self.rect位置繪製圖像""" self.screen.blit(self.image,self.rect)
bullet.py
在子彈類中尤爲需要說明的是該類繼承了Sprite精靈類 ,繼承精靈類中的方法 。
import pygame from pygame.sprite import Sprite # 繼承Sprite類,完善自己的代碼 class Bullet(Sprite): """docstring for Bullet""" def __init__(self, game_settings,screen,ship): super(Bullet, self).__init__() self.screen = screen # # 在0,0位置創建bullet,並設置寬高 # self.rect = pygame.Rect(0,0,game_settings.bullet_width,game_settings.bullet_height) # # 調整子彈位置到飛船所在位置,調整top相同,中心x座標相同 # self.rect.centerx = ship.rect.centerx # self.rect.top = ship.rect.top # # 將y座標存儲爲小數值,以便能夠微調子彈的速度 # self.y = float(self.rect.y) # self.color = game_settings.bullet_color # self.speed_factor = game_settings.bullet_speed_factor # 把子彈換成一個好玩的圖片,把圖片轉換成位圖 self.image = pygame.image.load('images/生氣.png').convert_alpha() # 修改圖像大小 self.width,self.height = self.image.get_size() self.image = pygame.transform.smoothscale(self.image,(self.width//10,self.height//10)) self.rect = self.image.get_rect() # 調整子彈位置到飛船所在位置,調整top相同,中心x座標相同 self.rect.centerx = ship.rect.centerx self.rect.top = ship.rect.top # 將y座標存儲爲小數值,以便能夠微調子彈的速度 self.y = float(self.rect.y) self.speed_factor = game_settings.bullet_speed_factor def update(self): """向上移動子彈""" # 更新一次座標y向上移動speed_factor個像素 self.y -= self.speed_factor # 更新子彈的rect位置 self.rect.y = self.y def draw_bullet(self): # """ # API:rect(Surface,color,Rect,width=0)->Rect # draw a rectangular shape on the Surgace # The width argument is the thickness to draw the outer edge. # If width is zero then the rectangle will be filled. # """ # pygame.draw.rect(self.screen,self.color,self.rect) """在self.rect位置繪製圖像""" self.screen.blit(self.image,self.rect)
game_functions.py
- 該文件的作用主要是集成主函數邏輯函數 ,判斷飛船事件 ,更新子彈編組和更新屏幕
fill
函數填充屏幕嚴肅 ,參數rgb- 需要說明的是 bullets 子彈編組需要更新判斷子彈是否超出屏幕 ,以從編組中刪除 ,否則會影響效率
- 鼠標和鍵盤監聽事件
pygame.event.get()
;事件鍵pygame.K_RIGHT
pygame.K_LEFT
pygame.K_SPACE
事件類型pygame.KEYDOWN
pygame.KEYUP
import pygame import sys from bullet import Bullet def check_keydown_events(event,ship,game_settings,screen,bullets): """ 按下鍵盤事件:右移,左移,發射子彈 """ if event.key == pygame.K_RIGHT: ship.moving_right = True elif event.key == pygame.K_LEFT: ship.moving_left = True elif event.key == pygame.K_SPACE: fire_bullet(game_settings,screen,ship,bullets) def fire_bullet(game_settings,screen,ship,bullets): """ 如果沒有超過當前屏幕顯示的最多子彈數,實例化子彈,添加到子彈編組中 """ if(len(bullets) < game_settings.bullet_allowed): bullet = Bullet(game_settings,screen,ship) bullets.add(bullet) def check_keyup_events(event,ship): """ 擡起鍵,擡起空格不發生任何事 """ if event.key == pygame.K_RIGHT: ship.moving_right = False elif event.key == pygame.K_LEFT: ship.moving_left = False def check_events(ship,game_settings,screen,bullets): for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() elif event.type == pygame.KEYDOWN: check_keydown_events(event,ship,game_settings,screen,bullets) elif event.type == pygame.KEYUP: check_keyup_events(event,ship) def update_screen(game_settings,screen,ship,bullets): screen.fill(game_settings.background_color) ship.blitme() """ API:pygame.sprite.Group.sprites sprites() -> sprite_list bullet迭代器 """ for bullet in bullets.sprites(): bullet.draw_bullet() pygame.display.flip() def update_bullets(bullets): bullets.update() # 超出屏幕邊界移除子彈 # 子彈編組副本 for bullet in bullets.copy(): if bullet.rect.bottom <= 0: bullets.remove(bullet) # 註釋掉,調試用,耗時 # print(len(bullets))
settings.py
配置文件
將常量變量繼承在配置文件中 ,當需要修改常量提高遊戲體驗時 ,直接修改該文件即可
class Settings(object): """docstring for Settings""" def __init__(self): super(Settings, self).__init__() self.screen_width = 1200 self.screen_height = 800 self.background_color = (230,230,230) self.ship_speed_factor = 1.5 # 飛船一個while循環走1.5像素 # 子彈配置 self.bullet_width = 3 self.bullet_height = 15 self.bullet_color = (120,120,120) self.bullet_speed_factor = 3 self.bullet_allowed = 5 # 最大子彈數
pygame接口文檔總結
API | 接口 | 描述 |
---|---|---|
pygame.init() | init() -> None | |
pygame.display.set_mode() | set_mode(resolution=(0,0), flags=0, depth=0) -> Surface | Initialize a window or screen for display |
pygame.display.set_caption() | set_caption(title, icontitle=None) -> None | |
pygame.event.get() | ||
pygame.image.load() | load(filename) -> Surface | |
get_rect() | ||
blit() | ||
pygame.transform.smoothscale() | smoothscale(Surface, (width, height), DestSurface = None) -> Surface | |
pygame.display.flip() | flip() -> None | Update the full display Surface to the screen |