tft每日頭條

 > 遊戲

 > pygame開發的小遊戲

pygame開發的小遊戲

遊戲 更新时间:2024-06-30 21:33:01

pygame開發的小遊戲(從零開發一個小遊戲)1

編程派codingpy

編程派堅信,這個時代人人都學點編程,不論對自身成長還是日常工作,都将有巨大益處。因此,選擇了通過本公号與大家分享最易學的編程語言 Python 的教程和資源,希望對你有幫助。

今天分享的是 Python 翻譯組 最新譯文,原文來自real python,是一篇比較詳細的 PyGame 遊戲開發入門指南。

譯者:haiyuqiao,華中科技大學(在讀研究生),正在使用 Python 做數據分析。Fighting from now!校對:EarlGrey,編程派主頁君。

以下是正文,一起來學習吧。

下文中下劃線文字,可閱讀原文後點擊訪問。另外,本文代碼略有删減,完整代碼可閱讀原文。

PyGame是 SDL 庫的 Python 包裝器(wrapper)。SDL 是一個跨平台庫,支持訪問計算機多媒體硬件(聲音、視頻、輸入等)。SDL 非常強大,但美中不足的是,它是基于 C 語言的,而 C 語言比較難懂,因此我們采用 PyGame 。

在本教程中,我們将介紹 PyGame 的基本邏輯和沖突檢測,以及如何在屏幕上繪圖和将外部文件導入到遊戲中。

提示:教程假定你對 Python 的語法、文件結構和面向對象的程序設計已經有了基本的了解。

準備工作

打開PyGame下載頁面,根據你的操作系統和 Python 版本下載合适的 PyGame 安裝包。如果你使用的是 Python 3,那麼請下載 1.9.2 版.

EarlGrey:在下載頁面找不到 1.9.2 版的下載鍊接,但是 pip install 可以安裝。

新建一個 .py 文件,然後輸入以下代碼:

pygame開發的小遊戲(從零開發一個小遊戲)2

與其他 Python 程序一樣,我們首先導入想要使用的模塊。這裡,我們将導入 pygamepygame.locals ,後續我們将使用其中的一些常量。最後一行會初始化所有導入的 PyGame 模塊,在做其他操作之前必須執行調用該函數。

基礎對象

屏幕對象

首先,我們需要一張畫布,我們稱之為“屏幕”,它是我們繪畫的平台。為了創建一個屏幕,我們需要調用pygame.display 中的 set_mode 方法,然後向 set_mode() 傳遞包含屏幕窗口寬度和高度的元組(本教程中使用 800x600 尺寸)。

screen = pygame.display.set_mode((800,600))

運行上述代碼,将會彈出一個窗口,然後當程序退出後又立即消失。一點都不酷嘛,對吧?下一節,我們将介紹遊戲的主循環,它将确保隻有在我們給它正确的輸入時程序才會退出。

遊戲主循環

遊戲主循環/事件循環是所有操作發生的地方。在遊戲過程中,它不斷的更新遊戲狀态,渲染遊戲畫面和收集輸入指令。創建循環時,需要确保我們有辦法跳出循環,退出應用。為此,我們将同時介紹一些基本的用戶輸入指令。所有的用戶輸入(和我們稍稍後提到的其他事件)都會進入 PyGame 的事件隊列,通過調用 pygame.event.get() 可以訪問該隊列。這将返回一個包含隊列裡所有事件的列表,我們将循環這個列表,并根針對相應的事件類型做出反應。現在我們隻關心 KEYDOWNQUIT 事件:

pygame開發的小遊戲(從零開發一個小遊戲)3

将上述代碼添加到之前的代碼下,并運行。你應該看到一個空的窗口。隻有你按下 ESC 鍵 或者觸發一個 QUIT 事,否則這個窗口不會消失。

Surface 和 Rects

SurfaceRects是 PyGame 中的基本構件。可以将 Surface 看作一張白紙,你可以在上面随意繪畫。我們的屏幕對象也是一個 Surface 。它們可以包含圖片。Rects 是 Surface 中矩形區域的表示。

讓我們創建一個 50x50 像素的 Surface,然後給它塗色。由于屏幕是黑色的,所以我們使用白色。 我們然後調用 get_rect() 在 Surface上 得到一個矩形區域和 Surface 的 x 軸 和 y 軸。

pygame開發的小遊戲(從零開發一個小遊戲)4

Blit 和 Flip

僅僅隻是創建了 Surface 并不能在屏幕上看到它。為此我們需要将這個 Surface 繪制(Blit)到另一個 Surface 上。Blit 是一個專業術語,意思就是繪圖。你僅僅隻能從一個Surface Blit 到另一個Surface,我們的屏幕就是一個 Surface 對象。以下是我們如何将 surf 畫到屏幕上:

# 這一行表示:将surf畫到屏幕 x:400.y:300的坐标上

screen.blit(surf,(400,300)) pygame.display.flip()

blit() 有兩個參數:要畫的 Surface 和 在源 Surface 上的坐标。此處我們使用屏幕的中心,但是當你運行代碼時,你會發現我們的 surf 并沒有出現在屏幕的中心。這是因為 blit() 是從左上角開始畫 surf 。

注意在 blit 之後的 pygame.display.filp() 的調用。Flip将會更新自上次 flip 後的整個屏幕,兩次 flip 之間發生的修改都将在屏幕上顯示。沒有調用flip()那就什麼也不會出現。

Sprites

什麼是 Sprites ?從編程術語來講,Sprites 是屏幕上事物的二維表達。本質上來講,Sprite 就是一個圖片。Pygame 提供一個叫做 Sprites 的基礎類,它就是用來擴展的,可以包含想要在屏幕上呈現的對象一個或多個圖形表示。我們将會擴展Sprite 類,這樣可以使用它的内建方法。我們稱這個新的對象為 PlayerPlyaer 将擴展 Sprite,現在隻有兩個屬性:surfrect。我們也會給 surf 塗色(本教程使用白色),如之前 surface 例子,隻是現在 Surface 屬于 Player :

pygame開發的小遊戲(從零開發一個小遊戲)5

現在我們将上述代碼整合在一起:

EarlGrey:此處代碼省略。

運行上述代碼,你将會在屏幕中心看到一個白色的矩形:

pygame開發的小遊戲(從零開發一個小遊戲)6

如果将 screen.blit(player.surf,(400,300)) 改成 screen.blit(player.surf,player.rect) ,你覺得會發生什麼?修改之後,試着在控制台中打印 player.rectrect 的前兩個屬性分别是 rect 左上角的 x 和 y 軸坐标。當你将 rect 傳遞給 blit ,它将會根據這個坐标畫 surface 。我們後續将使用它控制 player 移動。

用戶輸入

現在開始才是有趣的部分。我們要把 Player 變得可控制!之前我們提過,按鍵事件 pygame.event.get() 将把最新的事件從事件堆(event stack)中移除。Pygame 還有另外一個事件方法,pygame.event.get_pressed()get_pressed()方法返回一個隊列,其中包含了所有按鍵事件組成的字典,我們将把它放在主循環中,這樣我們将在每一幀上的按鍵。

pressed_keys = pygame.event.get_presssed()

現在我們将寫一個方法,接收上面那個字典,并且根據按下的鍵定義 sprite 的行為,代碼如下:

pygame開發的小遊戲(從零開發一個小遊戲)7

K_UPK_DOWNK_LEFTK_RIGHT 對應鍵盤上的上、下、左 右方向鍵。我們判斷這些鍵是否按下,如果它為真,那麼我們就朝相應的方向移動 rect()。Rects 有兩個内建的移動方法,此處我們使用 move in place move_ip() ,因為我們希望移動 rect 并且不用複制它。

将上述方法添加到 Player 類,将 get_pressed() 調用放在主循環中。整體代碼現在應該是這樣的:

EarlGrey:此處代碼省略。

現在你可以使用方向鍵移動矩陣塊了。也許你注意到了,你可以将矩形塊移出屏幕,這可能并不是你想要的。所以我們我們需要往 update 方法中添加一些邏輯,檢測矩形的坐标是否移出了 800x600 的屏幕邊界;如果出了邊界,那麼就将它放回在邊界上:

pygame開發的小遊戲(從零開發一個小遊戲)8

上述代碼沒有使用 move 方法,我們隻要修改 上下左右的相應坐标即可。

現在我們添加一些敵人!

首先我們創建一個新的 sprite 類,命名為 Enemy。依照創建 player 的格式創建:

pygame開發的小遊戲(從零開發一個小遊戲)9

以上有幾點需要說明。首先,當我們在 surface 上調用 get_rect 時,我們将核心屬性 x 設為 820 ,y 的坐标為一個随機數,由 random.randint() 生成。

在最終的代碼中,我們會在文件的開頭導入 Random 庫(import random)。為什麼選擇随機數?因為我們希望敵人從屏幕右邊(820)的随機位置(0-600)上出現。 我們還将使用 random 設置敵人的速度屬性,這樣敵人就會有快有慢。

敵人的 update() 方法沒有參數限制(我們不關心敵人的輸入),隻要讓它向着屏幕左邊以一定的速度移動就可以了。update 方法中的最後一個 if 語句檢測敵人右側是否通過了屏幕左邊邊界(要确保它們不會一碰到屏幕的邊界就消失)。當他們通過屏幕的邊界後,我們調用 Sprite 的内建方法 kill(),從 sprite 組中删除它們,這樣它們就不會再被渲染出來。kill 不會釋放被它們占用的内存, 需要你确保你不再引用它們,以便 Python 的垃圾回收器回收。

Groups

Pygame 提供的另一個很有用的對象是 Sprite 的 Groups。誠如其名,是 Sprite 的集合。為什麼我們要使用 sprite.Group 而不是列表呢? 因為 sprite.Group 有一些内建的方法,有助于解決沖突和更新問題。那現在就創建一個 Group,用來包含遊戲中的所有 Sprites 。創建完 Group 後,我們要将 Player 添加到裡面,因為它是我們目前唯一的 Sprite 。我們也可以為敵人創建一個 group 。 當我們調用 Sprite 的 kill() 方法時,sprite 将會從其所在的全部 group 中删除。

enemies = pygame.sprite.Group()

all_sprites = pygame.sprite.Group()

all_sprites.add(player)

現在有了 all_sprites 的 group ,我們接着改變對象渲染方式,隻要渲染 group 中的所有對象即可:

pygame開發的小遊戲(從零開發一個小遊戲)10

現在,任何放到 all_sprites 中的對象都會被渲染出來。

自定義事件

現在我們為敵人創建了一個 sprite.Group ,但是并沒有實際的敵人。那怎樣才能在屏幕上出現敵人呢?我們當然可以在剛開始的時候創建一堆的敵人,但是這樣遊戲玩不了幾秒。為此,我們創建一個自定義事件,它隔幾秒鐘就會觸發創建一批敵人。我們要監聽該事件,方式和監聽按鍵或退出事件一樣。創建自定義事件十分容易,隻要命名即可:

ADDENEMY = pygame.USEREVENT 1

這樣就可以了!現在,我們有了一個叫做 ADDENEMY的事件,可以在主程序中監聽它。這裡我們隻需要注意一點,即自定義事件需要有一個獨特的值,要比 USEREVENT 的值大,這就是我們為什麼設定它為 USEREVENT 1。這裡說明一點:自定義事件本質上就是整數常量。又因為比 USEREVENT 小的數值已經被内置函數占據,所以創建的任何自定義事件都要比 USEREVENT大。

定義好事件之後,我們需要将它插入事件隊列中。因為整個遊戲過程中都要創建它們,所以将設置一個計時器。可以通過 PyGame 的 time() 對象實現。

pygame.time.set_timer(ADDENEMY,250)

這行代碼告訴 PyGame 每隔 250 毫秒(四分之一秒) 觸發一次 ADDENEMY 事件。這是在主遊戲循環之外執行的,不過在整個遊戲中都處于執行狀态。現在我們添加一些監聽事件的代碼:

pygame開發的小遊戲(從零開發一個小遊戲)11

謹記:set_timer() 隻能用來将事件插入到 PyGame 事件隊列中,不做其他任何事情。

現在我們會監聽ADDENEMY事件,當它觸發時,将創建一個 Enemy類的實例。然後我們将實例添加到enemies 這個 Sprite Group(後續用它來檢測沖突)和 all_sprites Group(這樣它會和其他對象一起渲染)。

沖突

這才是 PyGame 的魅力所在!寫沖突代碼(collision code)很難,但是 PyGame 提供了很多沖突檢測方法,你可以在這裡查看其中一部分。本次教程使用 spritecollideany。spritecollideany() 接受一個 Sprite 對象和一個 Sprite.Group ,檢測 Sprite 對象是否和 Sprite Group 中的其他 Sprites 沖突。這樣,我們可以拿 Player 和敵人所在的 Sprite Group 對比,檢測 player 是否被敵人擊中。代碼實現如下:

pygame開發的小遊戲(從零開發一個小遊戲)12

檢測 player 是否 enemies 中的 Sprites 沖突,如果發生沖突了,那麼調用 player Sprite 的 kill() 方法。因為我們隻渲染了 all_sprites Group 中的 sprites ,kill() 方法将從其所在的全部 Groups 中移出 Sprite ,player就不再出現,算是“殺死它了”。目前的完整代碼如下:

EarlGrey:此處代碼省略。

測試一下!

pygame開發的小遊戲(從零開發一個小遊戲)13

圖片

現在遊戲可以玩了,但是長得挺醜的。接下來,我們将白色方塊變成有意思的圖片,讓遊戲看上去有遊戲的樣子。

前面的代碼示例中,我們使用了塗色的 Surface 對象表示遊戲裡的所有事物。雖然這樣有助于理解什麼是 Surface 和它如何工作,但是卻讓遊戲變得很醜!現在我們要給 player 和 enemy 添加一些圖片。我喜歡自己畫圖,我把 player 畫成小飛機,enemy 是導彈,這些可以從我的代碼庫中下載。歡迎你使用我的作品,自己畫或者者下載一些免費遊戲素材。

修改對象的構造函數

下面是我們現在的 player 構造函數:

pygame開發的小遊戲(從零開發一個小遊戲)14

新的構造函數将會是這個樣子的:

pygame開發的小遊戲(從零開發一個小遊戲)15

我們想用一張圖片替代 Surface 對象。我們将使用 pygame.image.load() 導入圖片的路徑。load() 方法将會返回一個 Surface 對象。我們然後在這個 Surface 對象上調用 convert() 創建副本,這樣可以更快地将它畫在屏幕上。

接下來,我們在圖片上調用 set_colorkey() 方法。set_colorkey用于設置圖片的顔色,如果不設置 Pygame 會将圖片設置為透明。這裡我選用白色,因為和飛機的背景色一緻。RLEACCEL 是一個可選參數,它有助于 PyGame 在非加速顯示器上更快地渲染。

最後,我們和之前一樣調用 rect() 對象:在圖片上調用 get_rect()

謹記:圖片仍然是一個 surface 對象,隻不過它上面畫了一張圖。

對 enemy 構造函數做同樣的操作:

pygame開發的小遊戲(從零開發一個小遊戲)16

現在的遊戲雖然和以前一樣,但是比之前漂亮多啦!但是我仍然覺得它少了點什麼東西。讓我們加點不斷漂浮的白雲,這樣會有飛機劃過藍天的感覺。為此,我們需要遵循之前用過的一些原則。首先,我們創建 cloud 對象,畫上白雲的照片,其 update() 方法讓它不停地向着屏幕左邊移動。然後,我們需要添加一個自定義事件,每隔一段時間就生成白雲(我們還要将添加白雲到 all_spritesgroup)。白雲對象的實現如下:

pygame開發的小遊戲(從零開發一個小遊戲)17

上面的代碼看起來都很熟悉,下面這個事件創建代碼也一樣,我們将它放在 enemy 事件代碼下面:

ADDCLOUD = pygame.USEREVENT 2

pygame.time.set_timer(ADDCLOUD,1000)

現在為它們創建一個新的 Sprite.Group:

clouds = pygame.sprite.Group()

現在,在主循環中,我們需要開始監聽ADDCLOUD事件。

下面的代碼:

pygame開發的小遊戲(從零開發一個小遊戲)18

将會變成這樣:

pygame開發的小遊戲(從零開發一個小遊戲)19

我們還要把 clouds 添加到 all_sprites Group 和新的 clouds Group 中。我們這麼做,是因為我們使用 all_sprites 來渲染,使用 clouds 調用它們的 update 函數。也許你很好奇,為什麼我們将它不添加到 enemies Group中;畢竟我們調用的是一樣的 update 函數。原因在于,我們不想檢測白雲和飛機之間的沖突。我們的飛機要順暢地通過所有的雲層。現在剩下的就是調用 clouds Group 的 update() 方法了!

結語

大功告成!讓我們測試一下,效果應該是這樣的:

pygame開發的小遊戲(從零開發一個小遊戲)20

完整代碼可以在 GitHub 上的代碼庫獲取!希望本教程對你有用!

點此查看原文

Python 翻譯組是EarlGrey@編程派發起成立的一個專注于 Python 技術内容翻譯的小組,目前已有近 30 名 Python 技術愛好者加入。

翻譯組出品的内容(包括教程、文檔、書籍、視頻)将在編程派微信公衆号首發,歡迎各位 Python 愛好者推薦相關線索。

推薦線索,直接在編程派微信公衆号推文下留言即可。

,

更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!

查看全部

相关遊戲资讯推荐

热门遊戲资讯推荐

网友关注

Copyright 2023-2024 - www.tftnews.com All Rights Reserved