中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁(yè) > news >正文

spring boot 網(wǎng)站開發(fā)網(wǎng)站編輯

spring boot 網(wǎng)站開發(fā),網(wǎng)站編輯,做網(wǎng)站用的圖標(biāo),仿同程網(wǎng) 連鎖酒店 網(wǎng)站模板十九、碰撞檢測(cè) 原文:inventwithpython.com/invent4thed/chapter19.html 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 碰撞檢測(cè)涉及確定屏幕上的兩個(gè)物體何時(shí)相互接觸(即發(fā)生碰撞)。碰撞檢測(cè)對(duì)于游戲非常有用。例如,如…

十九、碰撞檢測(cè)

原文:inventwithpython.com/invent4thed/chapter19.html

譯者:飛龍

協(xié)議:CC BY-NC-SA 4.0

外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳

碰撞檢測(cè)涉及確定屏幕上的兩個(gè)物體何時(shí)相互接觸(即發(fā)生碰撞)。碰撞檢測(cè)對(duì)于游戲非常有用。例如,如果玩家觸碰到敵人,他們可能會(huì)失去生命值?;蛘呷绻婕矣|碰到硬幣,他們應(yīng)該自動(dòng)撿起它。碰撞檢測(cè)可以幫助確定游戲角色是否站在堅(jiān)實(shí)的地面上,或者他們腳下只有空氣。

在我們的游戲中,碰撞檢測(cè)將確定兩個(gè)矩形是否重疊。本章的示例程序?qū)⒑w這種基本技術(shù)。我們還將看看我們的pygame程序如何通過鍵盤和鼠標(biāo)接受玩家的輸入。這比我們?yōu)槲谋境绦蛩龅恼{(diào)用input()函數(shù)要復(fù)雜一些。但在 GUI 程序中使用鍵盤要更加互動(dòng),而在我們的文本游戲中甚至無法使用鼠標(biāo)。這兩個(gè)概念將使您的游戲更加令人興奮!

本章涵蓋的主題

  • Clock對(duì)象

  • pygame中的鍵盤輸入

  • pygame中的鼠標(biāo)輸入

  • 碰撞檢測(cè)

  • 在迭代列表時(shí)不修改列表

碰撞檢測(cè)程序的示例運(yùn)行

在這個(gè)程序中,玩家使用鍵盤的箭頭鍵在屏幕上移動(dòng)一個(gè)黑色的方塊。較小的綠色方塊代表食物,出現(xiàn)在屏幕上,方塊觸碰到它們時(shí)會(huì)“吃”掉它們。玩家可以在窗口的任何地方點(diǎn)擊以創(chuàng)建新的食物方塊。此外,按 ESC 鍵退出程序,按 X 鍵將玩家傳送到屏幕上的隨機(jī)位置。

圖 19-1 顯示了程序完成后的樣子。

image

圖 19-1: pygame 碰撞檢測(cè)程序的屏幕截圖

碰撞檢測(cè)程序的源代碼

開始一個(gè)新文件,輸入以下代碼,然后將其保存為collisionDetection.py。如果在輸入此代碼后出現(xiàn)錯(cuò)誤,請(qǐng)使用在線 diff 工具將您輸入的代碼與本書代碼進(jìn)行比較,網(wǎng)址為www.nostarch.com/inventwithpython#diff

image

collision Detection.py

import pygame, sys, random
from pygame.locals import *# Set up pygame.
pygame.init()
mainClock = pygame.time.Clock()# Set up the window.
WINDOWWIDTH = 400
WINDOWHEIGHT = 400
windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT),0, 32)
pygame.display.set_caption('Collision Detection')# Set up the colors.
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)
WHITE = (255, 255, 255)# Set up the player and food data structures.
foodCounter = 0
NEWFOOD = 40
FOODSIZE = 20
player = pygame.Rect(300, 100, 50, 50)
foods = []
for i in range(20):foods.append(pygame.Rect(random.randint(0, WINDOWWIDTH - FOODSIZE),random.randint(0, WINDOWHEIGHT - FOODSIZE), FOODSIZE, FOODSIZE))# Set up movement variables.
moveLeft = False
moveRight = False
moveUp = False
moveDown = FalseMOVESPEED = 6# Run the game loop.
while True:# Check for events.for event in pygame.event.get():if event.type == QUIT:pygame.quit()sys.exit()if event.type == KEYDOWN:# Change the keyboard variables.if event.key == K_LEFT or event.key == K_a:moveRight = FalsemoveLeft = Trueif event.key == K_RIGHT or event.key == K_d:moveLeft = FalsemoveRight = Trueif event.key == K_UP or event.key == K_w:moveDown = FalsemoveUp = Trueif event.key == K_DOWN or event.key == K_s:moveUp = FalsemoveDown = Trueif event.type == KEYUP:if event.key == K_ESCAPE:pygame.quit()sys.exit()if event.key == K_LEFT or event.key == K_a:moveLeft = Falseif event.key == K_RIGHT or event.key == K_d:moveRight = Falseif event.key == K_UP or event.key == K_w:moveUp = Falseif event.key == K_DOWN or event.key == K_s:moveDown = Falseif event.key == K_x:player.top = random.randint(0, WINDOWHEIGHT -player.height)player.left = random.randint(0, WINDOWWIDTH -player.width)if event.type == MOUSEBUTTONUP:foods.append(pygame.Rect(event.pos[0], event.pos[1],FOODSIZE, FOODSIZE))foodCounter += 1if foodCounter >= NEWFOOD:# Add new food.foodCounter = 0foods.append(pygame.Rect(random.randint(0, WINDOWWIDTH -FOODSIZE), random.randint(0, WINDOWHEIGHT - FOODSIZE),FOODSIZE, FOODSIZE))# Draw the white background onto the surface.windowSurface.fill(WHITE)# Move the player.if moveDown and player.bottom < WINDOWHEIGHT:player.top += MOVESPEEDif moveUp and player.top > 0:player.top -= MOVESPEEDif moveLeft and player.left > 0:player.left -= MOVESPEEDif moveRight and player.right < WINDOWWIDTH:player.right += MOVESPEED# Draw the player onto the surface.pygame.draw.rect(windowSurface, BLACK, player)# Check whether the player has intersected with any food squares.for food in foods[:]:if player.colliderect(food):foods.remove(food)# Draw the food.for i in range(len(foods)):pygame.draw.rect(windowSurface, GREEN, foods[i])# Draw the window onto the screen.pygame.display.update()mainClock.tick(40)

導(dǎo)入模塊

pygame 碰撞檢測(cè)程序?qū)肓伺c第 18 章中的動(dòng)畫程序相同的模塊,還有random模塊:

import pygame, sys, random
from pygame.locals import *

使用時(shí)鐘來控制程序的節(jié)奏

第 5 到 17 行大部分做的事情與動(dòng)畫程序相同:它們初始化了pygame,設(shè)置了WINDOWHEIGHTWINDOWWIDTH,并分配了顏色和方向常量。

然而,第 6 行是新的:

mainClock = pygame.time.Clock()

在動(dòng)畫程序中,調(diào)用time.sleep(0.02)會(huì)減慢程序的運(yùn)行速度,以防止它運(yùn)行得太快。雖然這個(gè)調(diào)用在所有計(jì)算機(jī)上都會(huì)暫停 0.02 秒,但程序的其余部分的速度取決于計(jì)算機(jī)的速度。如果我們希望這個(gè)程序在任何計(jì)算機(jī)上以相同的速度運(yùn)行,我們需要一個(gè)函數(shù),在快速計(jì)算機(jī)上暫停時(shí)間更長(zhǎng),在慢速計(jì)算機(jī)上暫停時(shí)間更短。

pygame.time.Clock對(duì)象可以在任何計(jì)算機(jī)上暫停適當(dāng)?shù)臅r(shí)間。第 110 行在游戲循環(huán)內(nèi)調(diào)用了mainClock.tick(40)。對(duì)Clock對(duì)象的tick()方法的調(diào)用等待足夠的時(shí)間,以便它以大約 40 次迭代每秒的速度運(yùn)行,無論計(jì)算機(jī)的速度如何。這確保游戲永遠(yuǎn)不會(huì)比您預(yù)期的速度更快。對(duì)tick()的調(diào)用應(yīng)該只出現(xiàn)一次在游戲循環(huán)中。

設(shè)置窗口和數(shù)據(jù)結(jié)構(gòu)

第 19 到 22 行設(shè)置了一些用于在屏幕上出現(xiàn)的食物方塊的變量:

# Set up the player and food data structures.
foodCounter = 0
NEWFOOD = 40
FOODSIZE = 20

foodCounter變量將從值0開始,NEWFOOD40FOODSIZE20。稍后我們將看到這些變量在創(chuàng)建食物時(shí)如何使用。

第 23 行設(shè)置了玩家位置的pygame.Rect對(duì)象:

player = pygame.Rect(300, 100, 50, 50)

player變量有一個(gè)pygame.Rect對(duì)象,表示方塊的大小和位置。玩家的方塊將像動(dòng)畫程序中的方塊一樣移動(dòng)(參見“移動(dòng)每個(gè)方塊”在第 280 頁(yè)),但在這個(gè)程序中,玩家可以控制方塊的移動(dòng)方向。

接下來,我們?cè)O(shè)置了一些代碼來跟蹤食物方塊:

foods = []
for i in range(20):foods.append(pygame.Rect(random.randint(0, WINDOWWIDTH - FOODSIZE),random.randint(0, WINDOWHEIGHT - FOODSIZE), FOODSIZE, FOODSIZE))

程序?qū)⑹褂?code>foods列表來跟蹤每個(gè)食物方塊的Rect對(duì)象。第 25 和 26 行在屏幕周圍隨機(jī)放置了 20 個(gè)食物方塊。您可以使用random.randint()函數(shù)來生成隨機(jī)的 x 和 y 坐標(biāo)。

在第 26 行,程序調(diào)用pygame.Rect()構(gòu)造函數(shù)來返回一個(gè)新的pygame.Rect對(duì)象。它將表示一個(gè)新食物方塊的位置和大小。pygame.Rect()的前兩個(gè)參數(shù)是左上角的 x 和 y 坐標(biāo)。您希望隨機(jī)坐標(biāo)在0和窗口大小減去食物方塊大小之間。如果將隨機(jī)坐標(biāo)設(shè)置在0和窗口大小之間,那么食物方塊可能會(huì)被推到窗口之外,就像圖 19-2 中一樣。

pygame.Rect()的第三個(gè)和第四個(gè)參數(shù)是食物方塊的寬度和高度。寬度和高度都是FOODSIZE常量中的值。

image

圖 19-2:對(duì)于 400×400 窗口中的 100×100 方塊,將左上角設(shè)置為 400 會(huì)將矩形放在窗口外。要在內(nèi)部,左邊緣應(yīng)該設(shè)置為 300。

pygame.Rect()的第三個(gè)和第四個(gè)參數(shù)是食物方塊的寬度和高度。寬度和高度都是FOODSIZE常量中的值。

設(shè)置跟蹤移動(dòng)的變量

從第 29 行開始,代碼設(shè)置了一些變量,用于跟蹤玩家方塊的移動(dòng)方向:

# Set up movement variables.
moveLeft = False
moveRight = False
moveUp = False
moveDown = False

這四個(gè)變量具有布爾值,用于跟蹤哪個(gè)箭頭鍵被按下,并最初設(shè)置為False。例如,當(dāng)玩家按下鍵盤上的左箭頭鍵移動(dòng)方塊時(shí),moveLeft被設(shè)置為True。當(dāng)他們松開鍵時(shí),moveLeft被設(shè)置回False

第 34 到 43 行幾乎與以前的pygame程序中的代碼相同。這些行處理游戲循環(huán)的開始以及玩家退出程序時(shí)的操作。我們將跳過對(duì)此代碼的解釋,因?yàn)槲覀冊(cè)谏弦徽轮幸呀?jīng)涵蓋過了。

處理事件

pygame模塊可以根據(jù)鼠標(biāo)或鍵盤的用戶輸入生成事件。以下是pygame.event.get()可以返回的事件:

QUIT 當(dāng)玩家關(guān)閉窗口時(shí)生成。

KEYDOWN 當(dāng)玩家按下鍵時(shí)生成。具有key屬性,告訴按下了哪個(gè)鍵。還有一個(gè)mod屬性,告訴按下該鍵時(shí)是否按下了 SHIFT、CTRL、ALT 或其他鍵。

KEYUP 當(dāng)玩家釋放鍵時(shí)生成。具有與KEYDOWN類似的keymod屬性。

MOUSEMOTION:每當(dāng)鼠標(biāo)在窗口上移動(dòng)時(shí)生成。具有pos屬性(縮寫為position),返回窗口中鼠標(biāo)位置的元組(x, y)。rel屬性還返回一個(gè)(x, y)元組,但它給出自上一個(gè)MOUSEMOTION事件以來的相對(duì)坐標(biāo)。例如,如果鼠標(biāo)從(200, 200)向左移動(dòng) 4 像素到(196, 200),那么rel將是元組值(-4, 0)。button屬性返回一個(gè)三個(gè)整數(shù)的元組。元組中的第一個(gè)整數(shù)是左鼠標(biāo)按鈕,第二個(gè)整數(shù)是中間鼠標(biāo)按鈕(如果存在),第三個(gè)整數(shù)是右鼠標(biāo)按鈕。如果鼠標(biāo)移動(dòng)時(shí)它們沒有被按下,則這些整數(shù)將為0,如果它們被按下,則為1

MOUSEBUTTONDOWN:當(dāng)鼠標(biāo)在窗口中按下按鈕時(shí)生成。此事件具有pos屬性,它是鼠標(biāo)按下按鈕時(shí)鼠標(biāo)位置的(x, y)元組。還有一個(gè)button屬性,它是從15的整數(shù),告訴哪個(gè)鼠標(biāo)按鈕被按下,如表 19-1 中所述。

MOUSEBUTTONUP:當(dāng)鼠標(biāo)按鈕釋放時(shí)生成。這與MOUSEBUTTONDOWN具有相同的屬性。

當(dāng)生成MOUSEBUTTONDOWN事件時(shí),它具有button屬性。button屬性是與鼠標(biāo)可能具有的不同類型的按鈕相關(guān)聯(lián)的值。例如,左鍵的值為1,右鍵的值為3。表 19-1 列出了鼠標(biāo)事件的所有button屬性,但請(qǐng)注意,鼠標(biāo)可能沒有這里列出的所有button值。

表 19-1: button屬性值

button的值鼠標(biāo)按鈕
1左鍵
2中鍵
3右鍵
4滾輪向上滾動(dòng)
5滾輪向下滾動(dòng)

我們將使用這些事件來讓玩家使用KEYDOWN事件和鼠標(biāo)按鈕點(diǎn)擊來控制框。

處理 KEYDOWN 事件

處理按鍵和釋放事件的代碼從第 44 行開始;它包括KEYDOWN事件類型:

        if event.type == KEYDOWN:

如果事件類型是KEYDOWN,則Event對(duì)象具有一個(gè)key屬性,指示按下了哪個(gè)鍵。當(dāng)玩家按下箭頭鍵或 WASD 鍵(發(fā)音為wazz-dee,這些鍵與箭頭鍵的布局相同,但位于鍵盤左側(cè))時(shí),我們希望移動(dòng)框。我們將使用if語(yǔ)句來檢查按下的鍵,以便確定框應(yīng)該移動(dòng)的方向。

第 46 行將key屬性與K_LEFTK_a進(jìn)行比較,它們是表示鍵盤上左箭頭鍵和 WASD 中 A 的pygame.locals常量。第 46 至 57 行檢查每個(gè)箭頭和 WASD 鍵:

            # Change the keyboard variables.if event.key == K_LEFT or event.key == K_a:moveRight = FalsemoveLeft = Trueif event.key == K_RIGHT or event.key == K_d:moveLeft = FalsemoveRight = Trueif event.key == K_UP or event.key == K_w:moveDown = FalsemoveUp = Trueif event.key == K_DOWN or event.key == K_s:moveUp = FalsemoveDown = True

當(dāng)按下這些鍵之一時(shí),代碼告訴 Python 將相應(yīng)的移動(dòng)變量設(shè)置為True。Python 還會(huì)將相反方向的移動(dòng)變量設(shè)置為False。

例如,當(dāng)按下左箭頭鍵時(shí),程序執(zhí)行第 47 和 48 行。在這種情況下,Python 將moveLeft設(shè)置為TruemoveRight設(shè)置為False(即使moveRight可能已經(jīng)是False,Python 也會(huì)將其設(shè)置為False,以確保)。

在第 46 行,event.key可以等于K_LEFTK_a。如果按下左箭頭鍵,則event.key中的值將設(shè)置為與K_LEFT相同的值,如果按下 A 鍵,則設(shè)置為與K_a相同的值。

通過執(zhí)行第 47 和 48 行的代碼,如果按鍵是K_LEFTK_a,則左箭頭鍵和 A 鍵將執(zhí)行相同的操作。W、A、S 和 D 鍵用作更改移動(dòng)變量的替代鍵,讓玩家可以使用左手而不是右手。您可以在圖 19-3 中看到這兩組鍵的示例。

image

圖 19-3:WASD 鍵可以編程為與箭頭鍵執(zhí)行相同的操作。

字母和數(shù)字鍵的常量很容易找到:A 鍵的常量是K_a,B 鍵的常量是K_b,依此類推。3 鍵的常量是K_3。表 19-2 列出了其他鍵盤鍵的常用常量變量。

**表 19-2:**鍵盤鍵的常量變量

pygame 常量變量鍵盤鍵
K_LEFT左箭頭
K_RIGHT右箭頭
K_UP上箭頭
K_DOWN下箭頭
K_ESCAPEESC
K_BACKSPACE退格鍵
K_TABTAB
K_RETURNRETURN 或 ENTER
K_SPACE空格鍵
K_DELETEDEL
K_LSHIFT左 SHIFT
K_RSHIFT右 SHIFT
K_LCTRL左 CTRL
K_RCTRL右 CTRL
K_LALT左 ALT
K_RALT右 ALT
K_HOMEHOME
K_ENDEND
K_PAGEUPPGUP
K_PAGEDOWNPGDN
K_F1F1
K_F2F2
K_F3F3
K_F4F4
K_F5F5
K_F6F6
K_F7F7
K_F8F8
K_F9F9
K_F10F10
K_F11F11
K_F12F12
處理 KEYUP 事件

當(dāng)玩家釋放他們按下的鍵時(shí),將生成一個(gè)KEYUP事件:

        if event.type == KEYUP:

如果玩家釋放的鍵是 ESC,則 Python 應(yīng)終止程序。請(qǐng)記住,在pygame中,您必須在調(diào)用sys.exit()函數(shù)之前調(diào)用pygame.quit()函數(shù),我們?cè)诘?59 到 61 行中這樣做:

            if event.key == K_ESCAPE:pygame.quit()sys.exit()

第 62 到 69 行如果釋放了該方向鍵,則將移動(dòng)變量設(shè)置為False

            if event.key == K_LEFT or event.key == K_a:moveLeft = Falseif event.key == K_RIGHT or event.key == K_d:moveRight = Falseif event.key == K_UP or event.key == K_w:moveUp = Falseif event.key == K_DOWN or event.key == K_s:moveDown = False

通過KEYUP事件將移動(dòng)變量設(shè)置為False會(huì)使框停止移動(dòng)。

傳送玩家

您還可以將傳送添加到游戲中。如果玩家按下 X 鍵,則第 71 和 72 行將玩家框的位置設(shè)置為窗口上的隨機(jī)位置:

            if event.key == K_x:player.top = random.randint(0, WINDOWHEIGHT -player.height)player.left = random.randint(0, WINDOWWIDTH -player.width)

第 70 行檢查玩家是否按下了 X 鍵。然后,第 71 行設(shè)置一個(gè)隨機(jī)的 x 坐標(biāo),將玩家傳送到窗口的高度減去玩家矩形的高度之間。第 72 行執(zhí)行類似的代碼,但是針對(duì) y 坐標(biāo)。這使玩家可以通過按 X 鍵在窗口周圍傳送,但他們無法控制將傳送到哪里——這是完全隨機(jī)的。

添加新的食物方塊

玩家可以通過兩種方式向屏幕添加新的食物方塊。他們可以單擊窗口中希望新食物方塊出現(xiàn)的位置,或者他們可以等到游戲循環(huán)迭代NEWFOOD次數(shù),這樣新的食物方塊將在窗口上隨機(jī)生成。

我們首先看一下如何通過玩家的鼠標(biāo)輸入添加食物:

        if event.type == MOUSEBUTTONUP:foods.append(pygame.Rect(event.pos[0], event.pos[1],FOODSIZE, FOODSIZE))

鼠標(biāo)輸入與鍵盤輸入一樣通過事件處理。當(dāng)玩家在單擊鼠標(biāo)后釋放鼠標(biāo)按鈕時(shí),將發(fā)生MOUSEBUTTONUP事件。

第 75 行中,x 坐標(biāo)存儲(chǔ)在event.pos[0]中,y 坐標(biāo)存儲(chǔ)在event.pos[1]中。第 75 行創(chuàng)建一個(gè)新的Rect對(duì)象來表示一個(gè)新的食物方塊,并將其放置在MOUSEBUTTONUP事件發(fā)生的地方。通過向foods列表添加新的Rect對(duì)象,代碼在屏幕上顯示一個(gè)新的食物方塊。

除了可以由玩家自行添加外,食物方塊還可以通過第 77 到 81 行的代碼自動(dòng)生成:

    foodCounter += 1if foodCounter >= NEWFOOD:# Add new food.foodCounter = 0foods.append(pygame.Rect(random.randint(0, WINDOWWIDTH -FOODSIZE), random.randint(0, WINDOWHEIGHT - FOODSIZE),FOODSIZE, FOODSIZE))

變量foodCounter跟蹤應(yīng)添加食物的頻率。每次游戲循環(huán)迭代時(shí),foodCounter在第 77 行增加1。

一旦foodCounter大于或等于常量NEWFOODfoodCounter將被重置,并且通過第 81 行生成一個(gè)新的食物方塊。您可以通過調(diào)整第 21 行上的NEWFOOD來改變添加新食物方塊的速度。

84 行只是用白色填充窗口表面,我們?cè)凇疤幚硗婕彝顺鰰r(shí)”和第 279 頁(yè)中已經(jīng)討論過了,所以我們將繼續(xù)討論玩家如何在屏幕上移動(dòng)。

在窗口中移動(dòng)玩家

我們已將移動(dòng)變量(moveDownmoveUpmoveLeftmoveRight)設(shè)置為TrueFalse,具體取決于玩家按下了哪些鍵?,F(xiàn)在我們需要移動(dòng)玩家的方框,該方框由存儲(chǔ)在player中的pygame.Rect對(duì)象表示。我們將通過調(diào)整player的 x 和 y 坐標(biāo)來實(shí)現(xiàn)這一點(diǎn)。

    # Move the player.if moveDown and player.bottom < WINDOWHEIGHT:player.top += MOVESPEEDif moveUp and player.top > 0:player.top -= MOVESPEEDif moveLeft and player.left > 0:player.left -= MOVESPEEDif moveRight and player.right < WINDOWWIDTH:player.right += MOVESPEED

如果moveDown設(shè)置為True(并且玩家的方框底部不在窗口的底部之下),則第 88 行將通過將MOVESPEED添加到玩家當(dāng)前的top屬性來向下移動(dòng)玩家的方框。第 89 到 94 行對(duì)其他三個(gè)方向執(zhí)行相同的操作。

在窗口上繪制玩家

第 97 行在窗口上繪制玩家的方框:

    # Draw the player onto the surface.pygame.draw.rect(windowSurface, BLACK, player)

在移動(dòng)方框之后,第 97 行將其繪制在新位置。傳遞給第一個(gè)參數(shù)的windowSurface告訴 Python 在哪個(gè)Surface對(duì)象上繪制矩形。存儲(chǔ)在BLACK變量中的(0, 0, 0)告訴 Python 繪制黑色矩形。存儲(chǔ)在player變量中的Rect對(duì)象告訴 Python 要繪制的矩形的位置和大小。

檢查碰撞

在繪制食物方塊之前,程序需要檢查玩家的方框是否與任何方塊重疊。如果是,則需要從foods列表中刪除該方塊。這樣,Python 就不會(huì)繪制任何盒子已經(jīng)吃掉的食物方塊。

我們將在第 101 行使用所有Rect對(duì)象都具有的碰撞檢測(cè)方法colliderect()

    # Check whether the player has intersected with any food squares.for food in foods[:]:if player.colliderect(food):foods.remove(food)

在每次for循環(huán)迭代中,將foods(復(fù)數(shù))列表中的當(dāng)前食物方塊放入變量food(單數(shù))中。pygame.Rect對(duì)象的colliderect()方法將玩家矩形的pygame.Rect對(duì)象作為參數(shù),并在兩個(gè)矩形發(fā)生碰撞時(shí)返回True,如果它們沒有發(fā)生碰撞,則返回False。如果為True,第 102 行將從foods列表中移除重疊的食物方塊。

不要在迭代列表時(shí)更改列表

請(qǐng)注意,這個(gè)for循環(huán)與我們以前看到的任何其他for循環(huán)略有不同。如果您仔細(xì)看第 100 行,它并不是在foods上進(jìn)行迭代,而是在foods[:]上進(jìn)行迭代。

記住切片的工作原理。foods[:2]將計(jì)算列表的副本,其中包含從開頭到(但不包括)索引2的項(xiàng)目。foods[:]將為您提供包含從開頭到結(jié)尾的項(xiàng)目的列表的副本。基本上,foods[:]創(chuàng)建一個(gè)新列表,其中包含foods中所有項(xiàng)目的副本。這是復(fù)制列表的一種更簡(jiǎn)潔的方法,比如在第 10 章的井字棋游戲中getBoardCopy()函數(shù)所做的。

在迭代列表時(shí),您不能添加或刪除項(xiàng)目。如果 foods 列表的大小始終在變化,Python 可能會(huì)丟失 food 變量的下一個(gè)值應(yīng)該是什么。想象一下,當(dāng)有人添加或刪除果凍豆時(shí),要數(shù)出罐子里果凍豆的數(shù)量會(huì)有多困難。

但是,如果您迭代列表的副本(并且副本永遠(yuǎn)不會(huì)更改),則從原始列表中添加或刪除項(xiàng)目將不會(huì)成為問題。

在窗口上繪制食物方塊

第 105 行和 106 行的代碼類似于我們用來為玩家繪制黑色方框的代碼:

    # Draw the food.for i in range(len(foods)):pygame.draw.rect(windowSurface, GREEN, foods[i])

第 105 行循環(huán)遍歷foods列表中的每個(gè)食物方塊,第 106 行將食物方塊繪制到windowSurface上。

現(xiàn)在玩家和食物方塊都在屏幕上,窗口已準(zhǔn)備好更新,因此我們?cè)诘?109 行調(diào)用update()方法,并通過在我們之前創(chuàng)建的Clock對(duì)象上調(diào)用tick()方法來完成程序:

    # Draw the window onto the screen.pygame.display.update()mainClock.tick(40)

程序?qū)⒗^續(xù)通過游戲循環(huán)并保持更新,直到玩家退出。

總結(jié)

本章介紹了碰撞檢測(cè)的概念。在圖形游戲中,檢測(cè)兩個(gè)矩形之間的碰撞是如此普遍,以至于pygamepygame.Rect對(duì)象提供了自己的碰撞檢測(cè)方法,名為colliderect()。

這本書中的前幾個(gè)游戲都是基于文本的。程序的輸出是打印在屏幕上的文本,輸入是玩家在鍵盤上輸入的文本。但是圖形程序也可以接受鍵盤和鼠標(biāo)輸入。

此外,這些程序可以在玩家按下或釋放單個(gè)鍵時(shí)響應(yīng)單個(gè)按鍵。玩家不必輸入整個(gè)響應(yīng)并按下 ENTER 鍵。這樣可以實(shí)現(xiàn)即時(shí)反饋和更加互動(dòng)的游戲。

這個(gè)交互式程序很有趣,但讓我們超越繪制矩形。在第 20 章中,你將學(xué)習(xí)如何使用pygame加載圖像和播放音效。

二十、使用聲音和圖像

原文:inventwithpython.com/invent4thed/chapter20.html

譯者:飛龍

協(xié)議:CC BY-NC-SA 4.0

image

在第 18 章和第 19 章中,您學(xué)習(xí)了如何制作具有圖形并可以接受鍵盤和鼠標(biāo)輸入的 GUI 程序。您還學(xué)會(huì)了如何繪制不同的形狀。在本章中,您將學(xué)習(xí)如何向游戲中添加聲音、音樂和圖像。

本章涵蓋的主題

  • 聲音和圖像文件

  • 繪制和調(diào)整精靈的大小

  • 添加音樂和聲音

  • 切換聲音開關(guān)

使用精靈添加圖像

sprite是屏幕上用作圖形的一部分的單個(gè)二維圖像。圖 20-1 顯示了一些示例 sprite。

image

圖 20-1:一些精靈的示例

精靈圖像繪制在背景上。您可以水平翻轉(zhuǎn)精靈圖像,使其面向另一邊。您還可以在同一窗口上多次繪制相同的精靈圖像,并且可以調(diào)整精靈的大小,使其比原始精靈圖像大或小。背景圖像也可以被視為一個(gè)大精靈。圖 20-2 顯示了精靈一起使用。

外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳

圖 20-2:一個(gè)完整的場(chǎng)景,精靈繪制在背景上

下一個(gè)程序?qū)⒀菔救绾问褂?code>pygame播放聲音和繪制精靈。

聲音和圖像文件

精靈存儲(chǔ)在計(jì)算機(jī)上的圖像文件中。pygame可以使用幾種圖像格式。要了解圖像文件使用的格式,請(qǐng)查看文件名的末尾(最后一個(gè)句點(diǎn)之后)。這稱為文件擴(kuò)展名。例如,文件player.png是 PNG 格式。pygame支持的圖像格式包括 BMP、PNG、JPG 和 GIF。

您可以從網(wǎng)絡(luò)瀏覽器下載圖像。在大多數(shù)網(wǎng)絡(luò)瀏覽器上,您可以通過右鍵單擊網(wǎng)頁(yè)中的圖像,然后從出現(xiàn)的菜單中選擇“保存”來這樣做。記住您保存圖像文件的硬盤位置,因?yàn)槟枰獙⑾螺d的圖像文件復(fù)制到與 Python 程序的*.py*文件相同的文件夾中。您還可以使用諸如 Microsoft Paint 或 Tux Paint 之類的繪圖程序創(chuàng)建自己的圖像。

pygame支持的聲音文件格式為 MIDI、WAV 和 MP3。您可以像下載圖像文件一樣從互聯(lián)網(wǎng)下載音效文件,但音頻文件必須是這三種格式之一。如果您的計(jì)算機(jī)有麥克風(fēng),您還可以錄制聲音并制作自己的 WAV 文件以在游戲中使用。

Sprites and Sounds 程序的示例運(yùn)行

本章的程序與第 19 章的碰撞檢測(cè)程序相同。但是,在本程序中,我們將使用精靈而不是普通的方塊。我們將使用一個(gè)人的精靈來代表玩家,而不是黑色方塊,以及櫻桃的精靈,而不是綠色的食物方塊。我們還將播放背景音樂,并在玩家精靈吃掉櫻桃時(shí)添加聲音效果。

在這個(gè)游戲中,玩家精靈將吃掉櫻桃精靈,并且在吃掉櫻桃時(shí),它會(huì)變大。當(dāng)您運(yùn)行程序時(shí),游戲?qū)⒖雌饋硐駡D 20-3。

image

圖 20-3:Sprites and Sounds 游戲的屏幕截圖

Sprites and Sounds 程序的源代碼

開始一個(gè)新文件,輸入以下代碼,然后將其保存為spritesAndSounds.py。你可以從本書的網(wǎng)站www.nostarch.com/inventwithpython/下載我們?cè)诒境绦蛑惺褂玫膱D像和聲音文件。將這些文件放在與spritesAndSounds.py程序相同的文件夾中。

image

如果在輸入此代碼后出現(xiàn)錯(cuò)誤,請(qǐng)使用在線的 diff 工具比較你輸入的代碼和書中的代碼,網(wǎng)址為www.nostarch.com/inventwithpython#diff。

spritesAnd Sounds.py

import pygame, sys, time, random
from pygame.locals import *# Set up pygame.
pygame.init()
mainClock = pygame.time.Clock()# Set up the window.
WINDOWWIDTH = 400
WINDOWHEIGHT = 400
windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT),0, 32)
pygame.display.set_caption('Sprites and Sounds')# Set up the colors.
WHITE = (255, 255, 255)# Set up the block data structure.
player = pygame.Rect(300, 100, 40, 40)
playerImage = pygame.image.load('player.png')
playerStretchedImage = pygame.transform.scale(playerImage, (40, 40))
foodImage = pygame.image.load('cherry.png')
foods = []
for i in range(20):foods.append(pygame.Rect(random.randint(0, WINDOWWIDTH - 20),random.randint(0, WINDOWHEIGHT - 20), 20, 20))foodCounter = 0
NEWFOOD = 40# Set up keyboard variables.
moveLeft = False
moveRight = False
moveUp = False
moveDown = FalseMOVESPEED = 6# Set up the music.
pickUpSound = pygame.mixer.Sound('pickup.wav')
pygame.mixer.music.load('background.mid')
pygame.mixer.music.play(-1, 0.0)
musicPlaying = True# Run the game loop.
while True:# Check for the QUIT event.for event in pygame.event.get():if event.type == QUIT:pygame.quit()sys.exit()if event.type == KEYDOWN:# Change the keyboard variables.if event.key == K_LEFT or event.key == K_a:moveRight = FalsemoveLeft = Trueif event.key == K_RIGHT or event.key == K_d:moveLeft = FalsemoveRight = Trueif event.key == K_UP or event.key == K_w:moveDown = FalsemoveUp = Trueif event.key == K_DOWN or event.key == K_s:moveUp = FalsemoveDown = Trueif event.type == KEYUP:if event.key == K_ESCAPE:pygame.quit()sys.exit()if event.key == K_LEFT or event.key == K_a:moveLeft = Falseif event.key == K_RIGHT or event.key == K_d:moveRight = Falseif event.key == K_UP or event.key == K_w:moveUp = Falseif event.key == K_DOWN or event.key == K_s:moveDown = Falseif event.key == K_x:player.top = random.randint(0, WINDOWHEIGHT -player.height)player.left = random.randint(0, WINDOWWIDTH -player.width)if event.key == K_m:if musicPlaying:pygame.mixer.music.stop()else:pygame.mixer.music.play(-1, 0.0)musicPlaying = not musicPlayingif event.type == MOUSEBUTTONUP:foods.append(pygame.Rect(event.pos[0] - 10,event.pos[1] - 10, 20, 20))foodCounter += 1if foodCounter >= NEWFOOD:# Add new food.foodCounter = 0foods.append(pygame.Rect(random.randint(0, WINDOWWIDTH - 20),random.randint(0, WINDOWHEIGHT - 20), 20, 20))# Draw the white background onto the surface.windowSurface.fill(WHITE)# Move the player.if moveDown and player.bottom < WINDOWHEIGHT:player.top += MOVESPEEDif moveUp and player.top > 0:player.top -= MOVESPEEDif moveLeft and player.left > 0:player.left -= MOVESPEEDif moveRight and player.right < WINDOWWIDTH:player.right += MOVESPEED# Draw the block onto the surface.windowSurface.blit(playerStretchedImage, player)# Check whether the block has intersected with any food squares.for food in foods[:]:if player.colliderect(food):foods.remove(food)player = pygame.Rect(player.left, player.top,player.width + 2, player.height + 2)playerStretchedImage = pygame.transform.scale(playerImage,(player.width, player.height))if musicPlaying:pickUpSound.play()# Draw the food.for food in foods:windowSurface.blit(foodImage, food)# Draw the window onto the screen.pygame.display.update()mainClock.tick(40)

設(shè)置窗口和數(shù)據(jù)結(jié)構(gòu)

這個(gè)程序中的大部分代碼與第 19 章中的碰撞檢測(cè)程序相同。我們只關(guān)注添加精靈和聲音的部分。首先,在第 12 行,讓我們將標(biāo)題欄的標(biāo)題設(shè)置為描述這個(gè)程序的字符串:

pygame.display.set_caption('Sprites and Sounds')

為了設(shè)置標(biāo)題,你需要將字符串'Sprites and Sounds'傳遞給pygame.display.set_caption()函數(shù)。

添加一個(gè)精靈

現(xiàn)在我們已經(jīng)設(shè)置好標(biāo)題,我們需要實(shí)際的精靈。我們將使用三個(gè)變量來表示玩家,而不是之前程序中只使用一個(gè)。

# Set up the block data structure.
player = pygame.Rect(300, 100, 40, 40)
playerImage = pygame.image.load('player.png')
playerStretchedImage = pygame.transform.scale(playerImage, (40, 40))
foodImage = pygame.image.load('cherry.png')

第 18 行的player變量將存儲(chǔ)一個(gè)Rect對(duì)象,用于跟蹤玩家的位置和大小。player變量不包含玩家的圖像。在程序開始時(shí),玩家的左上角位于(300, 100),玩家的初始高度和寬度為 40 像素。

表示玩家的第二個(gè)變量是第 19 行的playerImage。pygame.image.load()函數(shù)傳遞了要加載的圖像文件的文件名字符串。返回值是一個(gè)Surface對(duì)象,其中包含圖像文件中的圖形。我們將這個(gè)Surface對(duì)象存儲(chǔ)在playerImage中。

改變精靈的大小

在第 20 行,我們將使用pygame.transform模塊中的一個(gè)新函數(shù)。pygame.transform.scale()函數(shù)可以縮小或放大精靈。第一個(gè)參數(shù)是一個(gè)帶有圖像的Surface對(duì)象。第二個(gè)參數(shù)是一個(gè)元組,表示第一個(gè)參數(shù)中圖像的新寬度和高度。scale()函數(shù)返回一個(gè)帶有以新尺寸繪制的圖像的Surface對(duì)象。在本章的程序中,當(dāng)玩家吃更多櫻桃時(shí),我們將使玩家精靈拉伸變大。我們將原始圖像存儲(chǔ)在playerImage變量中,而拉伸后的圖像存儲(chǔ)在playerStretchedImage變量中。

在第 21 行,我們?cè)俅握{(diào)用load()來創(chuàng)建一個(gè)帶有櫻桃圖像的Surface對(duì)象。確保player.pngcherry.png文件與spritesAndSounds.py文件在同一個(gè)文件夾中;否則,pygame將無法找到它們并報(bào)錯(cuò)。

設(shè)置音樂和聲音

接下來需要加載聲音文件。pygame中有兩個(gè)用于聲音的模塊。pygame.mixer模塊可以在游戲過程中播放短聲音效果。pygame.mixer.music模塊可以播放背景音樂。

添加聲音文件

調(diào)用pygame.mixer.Sound()構(gòu)造函數(shù)來創(chuàng)建一個(gè)pygame.mixer.Sound對(duì)象(簡(jiǎn)稱為Sound對(duì)象)。這個(gè)對(duì)象有一個(gè)play()方法,當(dāng)調(diào)用時(shí)會(huì)播放聲音效果。

# Set up the music.
pickUpSound = pygame.mixer.Sound('pickup.wav')
pygame.mixer.music.load('background.mid')
pygame.mixer.music.play(-1, 0.0)
musicPlaying = True

第 39 行調(diào)用pygame.mixer.music.load()來加載背景音樂,第 40 行調(diào)用pygame.mixer.music.play()來開始播放它。第一個(gè)參數(shù)告訴pygame在第一次播放后播放背景音樂的次數(shù)。因此,傳遞5會(huì)導(dǎo)致pygame播放背景音樂六次。在這里,我們傳遞參數(shù)-1,這是一個(gè)特殊值,使背景音樂永遠(yuǎn)重復(fù)播放。

play()的第二個(gè)參數(shù)是開始播放聲音文件的時(shí)間點(diǎn)。傳遞0.0將從開頭播放背景音樂。傳遞2.5將從開頭開始播放背景音樂 2.5 秒。

最后,musicPlaying變量具有一個(gè)布爾值,告訴程序是否應(yīng)該播放背景音樂和聲音效果。給玩家選擇在沒有聲音的情況下運(yùn)行程序是很好的。

切換聲音的開關(guān)

按 M 鍵可以打開或關(guān)閉背景音樂。如果musicPlaying設(shè)置為True,則當(dāng)前正在播放背景音樂,我們應(yīng)該通過調(diào)用pygame.mixer.music.stop()來停止它。如果musicPlaying設(shè)置為False,則當(dāng)前沒有播放背景音樂,我們應(yīng)該通過調(diào)用play()來開始播放。第 79 到 84 行使用if語(yǔ)句來實(shí)現(xiàn)這一點(diǎn):

            if event.key == K_m:if musicPlaying:pygame.mixer.music.stop()else:pygame.mixer.music.play(-1, 0.0)musicPlaying = not musicPlaying

無論音樂是否正在播放,我們都希望切換musicPlaying中的值。切換布爾值意味著將值設(shè)置為其當(dāng)前值的相反值。musicPlaying = not musicPlaying這一行將變量設(shè)置為False,如果它當(dāng)前為True,或者如果它當(dāng)前為False,則將其設(shè)置為True。想象一下切換就像你打開或關(guān)閉燈開關(guān)時(shí)發(fā)生的事情:切換燈開關(guān)會(huì)將其設(shè)置為相反的設(shè)置。

在窗口上繪制玩家

請(qǐng)記住,存儲(chǔ)在playerStretchedImage中的值是一個(gè)Surface對(duì)象。第 110 行使用blit()將玩家的精靈繪制到窗口的Surface對(duì)象上(存儲(chǔ)在windowSurface中):

    # Draw the block onto the surface.windowSurface.blit(playerStretchedImage, player)

blit()方法的第二個(gè)參數(shù)是一個(gè)Rect對(duì)象,指定在Surface對(duì)象上繪制精靈的位置。程序使用存儲(chǔ)在player中的Rect對(duì)象,它跟蹤玩家在窗口中的位置。

檢查碰撞

這段代碼與以前的程序中的代碼類似,但有幾行新代碼:

        if player.colliderect(food):foods.remove(food)player = pygame.Rect(player.left, player.top,player.width + 2, player.height + 2)playerStretchedImage = pygame.transform.scale(playerImage,(player.width, player.height))if musicPlaying:pickUpSound.play()

當(dāng)玩家精靈吃掉櫻桃之一時(shí),其大小增加兩個(gè)像素的高度和寬度。在第 116 行,一個(gè)比舊的Rect對(duì)象大兩個(gè)像素的新Rect對(duì)象將被分配為player的新值。

雖然Rect對(duì)象表示玩家的位置和大小,但玩家的圖像存儲(chǔ)在playerStretchedImage中,作為Surface對(duì)象。在第 117 行,程序通過調(diào)用scale()創(chuàng)建一個(gè)新的拉伸圖像。

拉伸圖像通常會(huì)使圖像略微失真。如果你不斷地重新拉伸已經(jīng)拉伸過的圖像,失真會(huì)迅速累積。但通過每次將原始圖像拉伸到新的尺寸——通過傳遞playerImage,而不是playerStretchedImage,作為scale()的第一個(gè)參數(shù)——你只會(huì)使圖像失真一次。

最后,第 119 行調(diào)用存儲(chǔ)在pickUpSound變量中的Sound對(duì)象上的play()方法。但只有當(dāng)musicPlaying設(shè)置為True時(shí)才會(huì)這樣做(這意味著聲音已打開)。

在窗口上繪制櫻桃

在以前的程序中,你調(diào)用pygame.draw.rect()函數(shù)為foods列表中存儲(chǔ)的每個(gè)Rect對(duì)象繪制一個(gè)綠色的正方形。然而,在這個(gè)程序中,你想要繪制櫻桃精靈。調(diào)用blit()方法并傳遞存儲(chǔ)在foodImage中的Surface對(duì)象,上面繪制了櫻桃圖像:

    # Draw the food.for food in foods:windowSurface.blit(foodImage, food)

food變量包含foods中的每個(gè)Rect對(duì)象,在每次for循環(huán)中告訴blit()方法在哪里繪制foodImage。

總結(jié)

你已經(jīng)為你的游戲添加了圖像和聲音。這些稱為精靈的圖像看起來比以前的程序中使用的簡(jiǎn)單繪制的形狀要好得多。精靈可以被縮放(即拉伸)到更大或更小的尺寸,因此我們可以以任何我們想要的尺寸顯示精靈。本章介紹的游戲還有一個(gè)背景并播放聲音效果。

現(xiàn)在我們知道如何創(chuàng)建窗口,顯示精靈,繪制基本圖形,收集鍵盤和鼠標(biāo)輸入,播放聲音,并實(shí)現(xiàn)碰撞檢測(cè),我們準(zhǔn)備在pygame中創(chuàng)建一個(gè)圖形游戲。第 21 章將所有這些元素結(jié)合起來,打造我們迄今為止最先進(jìn)的游戲。

二十一、有聲音和圖像的《躲避者》游戲

原文:inventwithpython.com/invent4thed/chapter21.html

譯者:飛龍

協(xié)議:CC BY-NC-SA 4.0

前面的四章介紹了 pygame 模塊,并演示了如何使用它的許多功能。在本章中,我們將利用這些知識(shí)創(chuàng)建一個(gè)名為《躲避者》的圖形游戲。

本章涵蓋的主題

  • pygame.FULLSCREEN 標(biāo)志

  • move_ip() Rect 方法

  • 實(shí)現(xiàn)作弊碼

  • 修改《躲避者》游戲

在《躲避者》游戲中,玩家控制一個(gè)精靈(玩家角色),必須躲避從屏幕頂部掉落的一大堆壞人。玩家能夠躲避壞人的時(shí)間越長(zhǎng),他們的得分就會(huì)越高。

只是為了好玩,我們還將在這個(gè)游戲中添加一些作弊模式。如果玩家按住 X 鍵,每個(gè)壞人的速度都會(huì)降低到超慢的速度。如果玩家按住 Z 鍵,壞人將會(huì)改變方向,向上而不是向下移動(dòng)。

基本 pygame 數(shù)據(jù)類型的回顧

在我們開始制作《躲避者》之前,讓我們回顧一下 pygame 中使用的一些基本數(shù)據(jù)類型:

pygame.Rect

Rect 對(duì)象表示矩形空間的位置和大小。位置由 Rect 對(duì)象的 topleft 屬性(或 topright、bottomleftbottomright 屬性)確定。這些角屬性是 x 和 y 坐標(biāo)的整數(shù)元組。大小由 widthheight 屬性確定,這些屬性是指示矩形有多長(zhǎng)或多高的整數(shù)像素。Rect 對(duì)象有一個(gè) colliderect() 方法,用于檢查它們是否與另一個(gè) Rect 對(duì)象發(fā)生碰撞。

pygame.Surface

Surface 對(duì)象是有色像素區(qū)域。Surface 對(duì)象表示一個(gè)矩形圖像,而 Rect 對(duì)象只表示一個(gè)矩形空間和位置。Surface 對(duì)象有一個(gè) blit() 方法,用于將一個(gè) Surface 對(duì)象上的圖像繪制到另一個(gè) Surface 對(duì)象上。pygame.display.set_mode() 函數(shù)返回的 Surface 對(duì)象是特殊的,因?yàn)樵谠?Surface 對(duì)象上繪制的任何東西在調(diào)用 pygame.display.update() 時(shí)會(huì)顯示在用戶的屏幕上。

pygame.event.Event

pygame.event 模塊在用戶提供鍵盤、鼠標(biāo)或其他輸入時(shí)生成 Event 對(duì)象。pygame.event.get() 函數(shù)返回這些 Event 對(duì)象的列表。您可以通過檢查其 type 屬性來確定 Event 對(duì)象的類型。QUITKEYDOWNMOUSEBUTTONUP 是一些事件類型的示例(有關(guān)所有事件類型的完整列表,請(qǐng)參見“處理事件”第 292 頁(yè))。

pygame.font.Font

pygame.font 模塊使用 Font 數(shù)據(jù)類型,表示 pygame 中文本使用的字體。傳遞給 pygame.font.SysFont() 的參數(shù)是字體名稱的字符串(通常傳遞 None 作為字體名稱以獲取默認(rèn)系統(tǒng)字體)和字體大小的整數(shù)。

pygame.time.Clock

pygame.time 模塊中的 Clock 對(duì)象有助于防止我們的游戲運(yùn)行得比玩家能看到的更快。Clock 對(duì)象有一個(gè) tick() 方法,可以傳遞我們希望游戲運(yùn)行的每秒幀數(shù)(FPS)。FPS 越高,游戲運(yùn)行得越快。

《躲避者》的示例運(yùn)行

當(dāng)您運(yùn)行這個(gè)程序時(shí),游戲?qū)?huì)看起來像圖 21-1。

image

圖 21-1:《躲避者》游戲的屏幕截圖

《躲避者》的源代碼

在一個(gè)新文件中輸入以下代碼,并將其保存為 dodger.py。您可以從 www.nostarch.com/inventwithpython/ 下載代碼、圖像和聲音文件。將圖像和聲音文件放在與 dodger.py 相同的文件夾中。

image

如果在輸入此代碼后出現(xiàn)錯(cuò)誤,請(qǐng)使用在線 diff 工具將你輸入的代碼與本書代碼進(jìn)行比較,網(wǎng)址為 www.nostarch.com/inventwithpython#diff。

dodger.py

import pygame, random, sys
from pygame.locals import *WINDOWWIDTH = 600
WINDOWHEIGHT = 600
TEXTCOLOR = (0, 0, 0)
BACKGROUNDCOLOR = (255, 255, 255)
FPS = 60
BADDIEMINSIZE = 10
BADDIEMAXSIZE = 40
BADDIEMINSPEED = 1
BADDIEMAXSPEED = 8
ADDNEWBADDIERATE = 6
PLAYERMOVERATE = 5def terminate():pygame.quit()sys.exit()def waitForPlayerToPressKey():while True:for event in pygame.event.get():if event.type == QUIT:terminate()if event.type == KEYDOWN:if event.key == K_ESCAPE: # Pressing ESC quits.terminate()returndef playerHasHitBaddie(playerRect, baddies):for b in baddies:if playerRect.colliderect(b['rect']):return Truereturn Falsedef drawText(text, font, surface, x, y):textobj = font.render(text, 1, TEXTCOLOR)textrect = textobj.get_rect()textrect.topleft = (x, y)surface.blit(textobj, textrect)# Set up pygame, the window, and the mouse cursor.
pygame.init()
mainClock = pygame.time.Clock()
windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
pygame.display.set_caption('Dodger')
pygame.mouse.set_visible(False)# Set up the fonts.
font = pygame.font.SysFont(None, 48)# Set up sounds.
gameOverSound = pygame.mixer.Sound('gameover.wav')
pygame.mixer.music.load('background.mid')# Set up images.
playerImage = pygame.image.load('player.png')
playerRect = playerImage.get_rect()
baddieImage = pygame.image.load('baddie.png')# Show the "Start" screen.
windowSurface.fill(BACKGROUNDCOLOR)
drawText('Dodger', font, windowSurface, (WINDOWWIDTH / 3),(WINDOWHEIGHT / 3))
drawText('Press a key to start.', font, windowSurface,(WINDOWWIDTH / 3) - 30, (WINDOWHEIGHT / 3) + 50)
pygame.display.update()
waitForPlayerToPressKey()topScore = 0
while True:# Set up the start of the game.baddies = []score = 0playerRect.topleft = (WINDOWWIDTH / 2, WINDOWHEIGHT - 50)moveLeft = moveRight = moveUp = moveDown = FalsereverseCheat = slowCheat = FalsebaddieAddCounter = 0pygame.mixer.music.play(-1, 0.0)while True: # The game loop runs while the game part is playing.score += 1 # Increase score.for event in pygame.event.get():if event.type == QUIT:terminate()if event.type == KEYDOWN:if event.key == K_z:reverseCheat = Trueif event.key == K_x:slowCheat = Trueif event.key == K_LEFT or event.key == K_a:moveRight = FalsemoveLeft = Trueif event.key == K_RIGHT or event.key == K_d:moveLeft = FalsemoveRight = Trueif event.key == K_UP or event.key == K_w:moveDown = FalsemoveUp = Trueif event.key == K_DOWN or event.key == K_s:moveUp = FalsemoveDown = Trueif event.type == KEYUP:if event.key == K_z:reverseCheat = Falsescore = 0if event.key == K_x:slowCheat = Falsescore = 0if event.key == K_ESCAPE:terminate()if event.key == K_LEFT or event.key == K_a:moveLeft = Falseif event.key == K_RIGHT or event.key == K_d:moveRight = Falseif event.key == K_UP or event.key == K_w:moveUp = Falseif event.key == K_DOWN or event.key == K_s:moveDown = Falseif event.type == MOUSEMOTION:# If the mouse moves, move the player to the cursor.playerRect.centerx = event.pos[0]playerRect.centery = event.pos[1]# Add new baddies at the top of the screen, if needed.if not reverseCheat and not slowCheat:baddieAddCounter += 1if baddieAddCounter == ADDNEWBADDIERATE:baddieAddCounter = 0baddieSize = random.randint(BADDIEMINSIZE, BADDIEMAXSIZE)newBaddie = {'rect': pygame.Rect(random.randint(0,WINDOWWIDTH - baddieSize), 0 - baddieSize,baddieSize, baddieSize),'speed': random.randint(BADDIEMINSPEED,BADDIEMAXSPEED),'surface':pygame.transform.scale(baddieImage,(baddieSize, baddieSize)),}baddies.append(newBaddie)# Move the player around.if moveLeft and playerRect.left > 0:playerRect.move_ip(-1 * PLAYERMOVERATE, 0)if moveRight and playerRect.right < WINDOWWIDTH:playerRect.move_ip(PLAYERMOVERATE, 0)if moveUp and playerRect.top > 0:playerRect.move_ip(0, -1 * PLAYERMOVERATE)if moveDown and playerRect.bottom < WINDOWHEIGHT:playerRect.move_ip(0, PLAYERMOVERATE)# Move the baddies down.for b in baddies:if not reverseCheat and not slowCheat:b['rect'].move_ip(0, b['speed'])elif reverseCheat:b['rect'].move_ip(0, -5)elif slowCheat:b['rect'].move_ip(0, 1)# Delete baddies that have fallen past the bottom.for b in baddies[:]:if b['rect'].top > WINDOWHEIGHT:baddies.remove(b)# Draw the game world on the window.windowSurface.fill(BACKGROUNDCOLOR)# Draw the score and top score.drawText('Score: %s' % (score), font, windowSurface, 10, 0)drawText('Top Score: %s' % (topScore), font, windowSurface,10, 40)# Draw the player's rectangle.windowSurface.blit(playerImage, playerRect)# Draw each baddie.for b in baddies:windowSurface.blit(b['surface'], b['rect'])pygame.display.update()# Check if any of the baddies have hit the player.if playerHasHitBaddie(playerRect, baddies):if score > topScore:topScore = score # Set new top score.breakmainClock.tick(FPS)# Stop the game and show the "Game Over" screen.pygame.mixer.music.stop()gameOverSound.play()drawText('GAME OVER', font, windowSurface, (WINDOWWIDTH / 3),(WINDOWHEIGHT / 3))drawText('Press a key to play again.', font, windowSurface,(WINDOWWIDTH / 3) - 80, (WINDOWHEIGHT / 3) + 50)pygame.display.update()waitForPlayerToPressKey()gameOverSound.stop()

導(dǎo)入模塊

Dodger 游戲?qū)肓伺c之前的 pygame 程序相同的模塊:pygame、random、syspygame.locals。

import pygame, random, sys
from pygame.locals import *

pygame.locals 模塊包含了 pygame 使用的幾個(gè)常量變量,比如事件類型(QUITKEYDOWN 等)和鍵盤按鍵(K_ESCAPEK_LEFT 等)。通過使用 from pygame.locals import * 語(yǔ)法,你可以在源代碼中直接使用 QUIT 而不是 pygame.locals.QUIT。

設(shè)置常量變量

第 4 到 7 行設(shè)置了窗口尺寸、文本顏色和背景顏色的常量:

WINDOWWIDTH = 600
WINDOWHEIGHT = 600
TEXTCOLOR = (0, 0, 0)
BACKGROUNDCOLOR = (255, 255, 255)

我們使用常量變量是因?yàn)樗鼈儽任覀兪謩?dòng)輸入的值更具描述性。例如,windowSurface.fill(BACKGROUNDCOLOR) 這一行比 windowSurface.fill((255, 255, 255)) 更容易理解。

你可以通過改變常量變量來輕松改變游戲。通過改變第 4 行的 WINDOWWIDTH,你會(huì)自動(dòng)改變代碼中所有使用 WINDOWWIDTH 的地方。如果你使用的是值 600,那么你需要在代碼中每次出現(xiàn) 600 的地方都進(jìn)行修改。改變常量的值一次會(huì)更容易。

在第 8 行,你設(shè)置了 FPS 的常量,即每秒幀數(shù),你希望游戲運(yùn)行的幀數(shù)。

FPS = 60

是通過游戲循環(huán)的單次迭代繪制的屏幕。你將 FPS 傳遞給第 186 行的 mainClock.tick() 方法,以便函數(shù)知道暫停程序的時(shí)間。這里 FPS 設(shè)置為 60,但你可以將 FPS 更改為更高的值以使游戲運(yùn)行更快,或者更改為更低的值以減慢游戲速度。

第 9 到 13 行設(shè)置了更多的壞蛋下落的常量變量:

BADDIEMINSIZE = 10
BADDIEMAXSIZE = 40
BADDIEMINSPEED = 1
BADDIEMAXSPEED = 8
ADDNEWBADDIERATE = 6

壞蛋的寬度和高度將在 BADDIEMINSIZEBADDIEMAXSIZE 之間。壞蛋在屏幕上下落的速度將在 BADDIEMINSPEEDBADDIEMAXSPEED 之間,每次游戲循環(huán)迭代的像素?cái)?shù)。并且每經(jīng)過 ADDNEWBADDIERATE 次游戲循環(huán)迭代,一個(gè)新的壞蛋將被添加到窗口頂部。

最后,PLAYERMOVERATE 存儲(chǔ)了玩家角色在游戲循環(huán)的每次迭代中在窗口中移動(dòng)的像素?cái)?shù)(如果角色正在移動(dòng)):

PLAYERMOVERATE = 5

通過增加這個(gè)數(shù)字,你可以增加角色移動(dòng)的速度。

定義函數(shù)

你將為這個(gè)游戲創(chuàng)建幾個(gè)函數(shù)。terminate()waitForPlayerToPressKey() 函數(shù)將分別結(jié)束和暫停游戲,playerHasHitBaddie() 函數(shù)將跟蹤玩家與壞蛋的碰撞,drawText() 函數(shù)將在屏幕上繪制得分和其他文本。

結(jié)束和暫停游戲

pygame 模塊要求你同時(shí)調(diào)用 pygame.quit()sys.exit() 來結(jié)束游戲。第 16 到 18 行將它們都放入一個(gè)名為 terminate() 的函數(shù)中。

def terminate():pygame.quit()sys.exit()

現(xiàn)在你只需要調(diào)用 terminate() 而不是同時(shí)調(diào)用 pygame.quit()sys.exit()。

有時(shí)你會(huì)希望暫停程序,直到玩家按下一個(gè)鍵,比如在游戲開始時(shí)出現(xiàn) Dodger 標(biāo)題文本或者在結(jié)束時(shí)顯示 Game Over 時(shí)。第 20 到 24 行創(chuàng)建了一個(gè)名為 waitForPlayerToPressKey() 的新函數(shù):

def waitForPlayerToPressKey():while True:for event in pygame.event.get():if event.type == QUIT:terminate()

在這個(gè)函數(shù)內(nèi)部,有一個(gè)無限循環(huán),只有在接收到 KEYDOWNQUIT 事件時(shí)才會(huì)中斷。在循環(huán)開始時(shí),pygame.event.get() 返回一個(gè) Event 對(duì)象列表供檢查。

如果玩家在程序等待玩家按鍵時(shí)關(guān)閉了窗口,pygame 將生成一個(gè) QUIT 事件,在第 23 行通過 event.type 進(jìn)行檢查。如果玩家退出,Python 將在第 24 行調(diào)用 terminate() 函數(shù)。

如果游戲收到KEYDOWN事件,它應(yīng)首先檢查是否按下了 ESC 鍵:

            if event.type == KEYDOWN:if event.key == K_ESCAPE: # Pressing ESC quits.terminate()return

如果玩家按下 ESC,則程序應(yīng)該終止。如果不是這種情況,那么執(zhí)行將跳過第 27 行的if塊,直接到return語(yǔ)句,退出waitForPlayerToPressKey()函數(shù)。

如果沒有生成QUITKEYDOWN事件,代碼將繼續(xù)循環(huán)。由于循環(huán)什么也不做,這將使游戲看起來像已經(jīng)凍結(jié),直到玩家按下鍵。

跟蹤壞人碰撞

如果玩家的角色與壞人之一發(fā)生碰撞,則playerHasHitBaddie()函數(shù)將返回True

def playerHasHitBaddie(playerRect, baddies):for b in baddies:if playerRect.colliderect(b['rect']):return Truereturn False

baddies參數(shù)是壞人字典數(shù)據(jù)結(jié)構(gòu)的列表。這些字典中的每一個(gè)都有一個(gè)’rect’鍵,該鍵的值是表示壞人大小和位置的Rect對(duì)象。

playerRect也是一個(gè)Rect對(duì)象。Rect對(duì)象有一個(gè)名為colliderect()的方法,如果Rect對(duì)象與傳遞給它的Rect對(duì)象發(fā)生碰撞,則返回True。否則,colliderect()返回False。

第 31 行的for循環(huán)遍歷baddies列表中的每個(gè)壞人字典。如果任何這些壞人與玩家的角色發(fā)生碰撞,則playerHasHitBaddie()返回True。如果代碼成功遍歷baddies列表中的所有壞人而沒有檢測(cè)到碰撞,則playerHasHitBaddie()返回False

向窗口繪制文本

在窗口上繪制文本涉及一些步驟,我們通過drawText()來完成。這樣,當(dāng)我們想要在屏幕上顯示玩家得分或游戲結(jié)束文本時(shí),只需要調(diào)用一個(gè)函數(shù)。

def drawText(text, font, surface, x, y):textobj = font.render(text, 1, TEXTCOLOR)textrect = textobj.get_rect()textrect.topleft = (x, y)surface.blit(textobj, textrect)

首先,第 37 行的render()方法調(diào)用創(chuàng)建了一個(gè)Surface對(duì)象,以特定字體呈現(xiàn)文本。

接下來,您需要知道Surface對(duì)象的大小和位置。您可以使用get_rect() Surface方法獲取包含此信息的Rect對(duì)象。

從第 38 行的get_rect()返回的Rect對(duì)象中復(fù)制了Surface對(duì)象的寬度和高度信息。第 39 行通過為其topleft屬性設(shè)置一個(gè)新的元組值來更改Rect對(duì)象的位置。

最后,第 40 行將渲染文本的Surface對(duì)象繪制到傳遞給drawText()函數(shù)的Surface對(duì)象上。在pygame中顯示文本比簡(jiǎn)單調(diào)用print()函數(shù)需要更多步驟。但是,如果將此代碼放入名為drawText()的單個(gè)函數(shù)中,那么您只需要調(diào)用此函數(shù)即可在屏幕上顯示文本。

初始化 pygame 和設(shè)置窗口

現(xiàn)在常量變量和函數(shù)已經(jīng)完成,我們將開始調(diào)用設(shè)置窗口和時(shí)鐘的pygame函數(shù):

# Set up pygame, the window, and the mouse cursor.
pygame.init()
mainClock = pygame.time.Clock()

第 43 行通過調(diào)用pygame.init()函數(shù)設(shè)置了pygame。第 44 行創(chuàng)建了一個(gè)pygame.time.Clock()對(duì)象,并將其存儲(chǔ)在mainClock變量中。這個(gè)對(duì)象將幫助我們防止程序運(yùn)行得太快。

第 45 行創(chuàng)建了一個(gè)用于窗口顯示的新Surface對(duì)象:

windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))

請(qǐng)注意,pygame.display.set_mode()只傳遞了一個(gè)參數(shù):一個(gè)元組。pygame.display.set_mode()的參數(shù)不是兩個(gè)整數(shù),而是一個(gè)包含兩個(gè)整數(shù)的元組。您可以通過傳遞一個(gè)包含WINDOWWIDTHWINDOWHEIGHT常量變量的元組來指定此Surface對(duì)象(和窗口)的寬度和高度。

pygame.display.set_mode()函數(shù)有第二個(gè)可選參數(shù)。您可以傳遞pygame.FULLSCREEN常量以使窗口填滿整個(gè)屏幕??匆幌聦?duì)第 45 行的修改:

windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT),pygame.FULLSCREEN)

WINDOWWIDTHWINDOWHEIGHT參數(shù)仍然用于窗口的寬度和高度,但圖像將被拉伸以適應(yīng)屏幕。嘗試在全屏模式和非全屏模式下運(yùn)行程序。

第 46 行將窗口的標(biāo)題設(shè)置為字符串’Dodger’:

pygame.display.set_caption('Dodger')

此標(biāo)題將顯示在窗口頂部的標(biāo)題欄中。

在 Dodger 中,鼠標(biāo)光標(biāo)不應(yīng)該可見。您希望鼠標(biāo)能夠移動(dòng)玩家角色在屏幕上移動(dòng),但鼠標(biāo)光標(biāo)會(huì)妨礙角色圖像。我們可以用一行代碼使鼠標(biāo)不可見:

pygame.mouse.set_visible(False)

調(diào)用pygame.mouse.set_visible(False)告訴pygame使光標(biāo)不可見。

設(shè)置字體、聲音和圖像對(duì)象

由于我們?cè)谶@個(gè)程序中在屏幕上顯示文本,我們需要為文本提供一個(gè)Font對(duì)象給pygame模塊使用。第 50 行通過調(diào)用pygame.font.SysFont()創(chuàng)建了一個(gè)Font對(duì)象:

# Set up the fonts.
font = pygame.font.SysFont(None, 48)

傳遞None使用默認(rèn)字體。傳遞48給字體一個(gè) 48 點(diǎn)的大小。

接下來,我們將創(chuàng)建Sound對(duì)象并設(shè)置背景音樂:

# Set up sounds.
gameOverSound = pygame.mixer.Sound('gameover.wav')
pygame.mixer.music.load('background.mid')

pygame.mixer.Sound()構(gòu)造函數(shù)創(chuàng)建一個(gè)新的Sound對(duì)象,并將對(duì)此對(duì)象的引用存儲(chǔ)在gameOverSound變量中。在您自己的游戲中,您可以創(chuàng)建任意數(shù)量的Sound對(duì)象,每個(gè)對(duì)象都有不同的聲音文件。

pygame.mixer.music.load()函數(shù)加載一個(gè)聲音文件用于背景音樂。這個(gè)函數(shù)不返回任何對(duì)象,一次只能加載一個(gè)背景音樂文件。背景音樂將在游戲期間持續(xù)播放,但Sound對(duì)象只會(huì)在玩家撞到壞人而輸?shù)粲螒驎r(shí)播放。

您可以為這個(gè)游戲使用任何 WAV 或 MIDI 文件。一些聲音文件可以從本書的網(wǎng)站www.nostarch.com/inventwithpython/下載。您也可以為這個(gè)游戲使用自己的聲音文件,只要您將文件命名為gameover.wavbackground.mid,或者更改第 53 和 54 行使用的字符串以匹配您想要的文件名。

接下來,您將加載用于玩家角色和壞人的圖像文件:

# Set up images.
playerImage = pygame.image.load('player.png')
playerRect = playerImage.get_rect()
baddieImage = pygame.image.load('baddie.png')

角色的圖像存儲(chǔ)在player.png中,壞人的圖像存儲(chǔ)在baddie.png中。所有壞人看起來都一樣,所以你只需要一個(gè)圖像文件。您可以從本書的網(wǎng)站www.nostarch.com/inventwithpython/下載這些圖像。

顯示開始畫面

游戲剛開始時(shí),Python 應(yīng)該在屏幕上顯示 Dodger 標(biāo)題。您還希望告訴玩家他們可以通過按任意鍵開始游戲。這個(gè)畫面出現(xiàn)是為了讓玩家在運(yùn)行程序后有時(shí)間準(zhǔn)備開始玩。

在 63 和 64 行,我們編寫代碼調(diào)用drawText()函數(shù):

# Show the "Start" screen.
windowSurface.fill(BACKGROUNDCOLOR)
drawText('Dodger', font, windowSurface, (WINDOWWIDTH / 3),(WINDOWHEIGHT / 3))
drawText('Press a key to start.', font, windowSurface,(WINDOWWIDTH / 3) - 30, (WINDOWHEIGHT / 3) + 50)
pygame.display.update()
waitForPlayerToPressKey()

我們將向此函數(shù)傳遞五個(gè)參數(shù):

  1. 您希望出現(xiàn)的文本字符串

  2. 您希望字符串出現(xiàn)的字體

  3. 文本將被渲染到的Surface對(duì)象

  4. Surface對(duì)象上的 x 坐標(biāo),用于繪制文本

  5. Surface對(duì)象上的 y 坐標(biāo),用于繪制文本

這可能看起來是一個(gè)很多參數(shù)的函數(shù)調(diào)用,但請(qǐng)記住,每次調(diào)用此函數(shù)調(diào)用將替換五行代碼。這縮短了程序,并使查找錯(cuò)誤變得更容易,因?yàn)橐獧z查的代碼更少。

waitForPlayerToPressKey()函數(shù)通過循環(huán)暫停游戲,直到生成KEYDOWN事件。然后執(zhí)行中斷循環(huán),程序繼續(xù)運(yùn)行。

開始游戲

現(xiàn)在所有函數(shù)都已定義,我們可以開始編寫主游戲代碼。第 68 行及以后將調(diào)用我們之前定義的函數(shù)。程序首次運(yùn)行時(shí),topScore變量的值為0。每當(dāng)玩家輸?shù)粲螒虿⑶业梅执笥诋?dāng)前最高分時(shí),最高分將被替換為這個(gè)更大的分?jǐn)?shù)。

topScore = 0
while True:

從第 69 行開始的無限循環(huán)在技術(shù)上不是游戲循環(huán)。游戲循環(huán)處理游戲運(yùn)行時(shí)的事件和繪制窗口。相反,這個(gè)while循環(huán)在每次玩家開始新游戲時(shí)迭代。當(dāng)玩家輸?shù)粲螒虿⑶矣螒蛑刂脮r(shí),程序的執(zhí)行會(huì)循環(huán)回到第 69 行。

一開始,您還希望將baddies設(shè)置為空列表:

    # Set up the start of the game.baddies = []score = 0

baddies變量是一個(gè)包含以下鍵的字典對(duì)象列表:

'rect’描述了壞人的位置和大小的Rect對(duì)象。

'speed’壞人下落的速度。這個(gè)整數(shù)表示每次游戲循環(huán)迭代的像素。

'surface’擁有縮放的壞人圖像繪制在上面的Surface對(duì)象。這是繪制到pygame.display.set_mode()返回的Surface對(duì)象的Surface。

第 72 行將玩家的分?jǐn)?shù)重置為0。

玩家的起始位置在屏幕中央,距離底部 50 像素,由第 73 行設(shè)置:

    playerRect.topleft = (WINDOWWIDTH / 2, WINDOWHEIGHT - 50)

第 73 行元組的第一個(gè)項(xiàng)目是左邊緣的 x 坐標(biāo),第二個(gè)項(xiàng)目是頂邊緣的 y 坐標(biāo)。

接下來,我們?cè)O(shè)置玩家移動(dòng)和作弊的變量:

    moveLeft = moveRight = moveUp = moveDown = FalsereverseCheat = slowCheat = FalsebaddieAddCounter = 0

移動(dòng)變量moveLeft、moveRight、moveUpmoveDown都設(shè)置為False。reverseCheatslowCheat變量也設(shè)置為False。只有當(dāng)玩家按住 Z 和 X 鍵啟用這些作弊時(shí),它們才會(huì)被設(shè)置為True。

baddieAddCounter變量是一個(gè)計(jì)數(shù)器,告訴程序何時(shí)在屏幕頂部添加一個(gè)新的壞人。baddieAddCounter的值每次游戲循環(huán)迭代時(shí)增加 1。(這類似于“添加新食物方塊”中的代碼在第 295 頁(yè)。)

當(dāng)baddieAddCounter等于ADDNEWBADDIERATE時(shí),baddieAddCounter重置為0,并在屏幕頂部添加一個(gè)新的壞人。(這個(gè)檢查稍后在第 130 行進(jìn)行。)

背景音樂在第 77 行開始播放,調(diào)用了pygame.mixer.music.play()函數(shù):

    pygame.mixer.music.play(-1, 0.0)

因?yàn)榈谝粋€(gè)參數(shù)是-1pygame會(huì)無限重復(fù)播放音樂。第二個(gè)參數(shù)是一個(gè)浮點(diǎn)數(shù),表示音樂開始播放的秒數(shù)。傳遞0.0意味著音樂從頭開始播放。

游戲循環(huán)

游戲循環(huán)的代碼不斷更新游戲世界的狀態(tài),改變玩家和壞人的位置,處理由pygame生成的事件,并在屏幕上繪制游戲世界。所有這些都會(huì)在幾十次每秒發(fā)生,使游戲?qū)崟r(shí)運(yùn)行。

第 79 行是主游戲循環(huán)的開始:

    while True: # The game loop runs while the game part is playing.score += 1 # Increase score.

第 80 行在游戲循環(huán)的每次迭代中增加玩家的分?jǐn)?shù)。玩家能夠在不失去的情況下走得越久,他們的分?jǐn)?shù)就越高。循環(huán)只有在玩家輸?shù)粲螒蚧蛲顺龀绦驎r(shí)才會(huì)退出。

處理鍵盤事件

程序?qū)⑻幚硭姆N類型的事件:QUIT、KEYDOWNKEYUPMOUSEMOTION。

第 82 行是事件處理代碼的開始:

        for event in pygame.event.get():if event.type == QUIT:terminate()

它調(diào)用pygame.event.get(),返回一個(gè)Event對(duì)象列表。每個(gè)Event對(duì)象表示自上次調(diào)用pygame.event.get()以來發(fā)生的事件。代碼檢查Event對(duì)象的type屬性,看看它是什么類型的事件,然后相應(yīng)地處理它。

如果Event對(duì)象的type屬性等于QUIT,那么用戶已經(jīng)關(guān)閉了程序。QUIT常量變量是從pygame.locals模塊導(dǎo)入的。

如果事件的類型是KEYDOWN,玩家已經(jīng)按下了一個(gè)鍵:

            if event.type == KEYDOWN:if event.key == K_z:reverseCheat = Trueif event.key == K_x:slowCheat = True

第 87 行檢查事件是否描述了按下Z鍵,條件為event.key == K_z。如果條件為True,Python 將reverseCheat變量設(shè)置為True以激活反向作弊。類似地,第 89 行檢查是否按下X鍵以激活減速作弊。

第 91 到 102 行檢查事件是否由玩家按下箭頭或 WASD 鍵生成。這段代碼類似于前幾章的與鍵盤相關(guān)的代碼。

如果事件的類型是KEYUP,玩家已經(jīng)釋放了一個(gè)鍵:

            if event.type == KEYUP:if event.key == K_z:reverseCheat = Falsescore = 0if event.key == K_x:slowCheat = Falsescore = 0

第 105 行檢查玩家是否釋放了 Z 鍵,這將停用反向作弊。在這種情況下,第 106 行將reverseCheat設(shè)置為False,第 107 行將分?jǐn)?shù)重置為0。分?jǐn)?shù)重置是為了阻止玩家使用作弊。

第 108 行到第 110 行對(duì) X 鍵和慢速作弊做了同樣的事情。釋放 X 鍵時(shí),slowCheat設(shè)置為False,玩家的分?jǐn)?shù)重置為0。

在游戲進(jìn)行期間,玩家可以隨時(shí)按 ESC 鍵退出:

                if event.key == K_ESCAPE:terminate()

第 111 行通過檢查event.key == K_ESCAPE來確定釋放的鍵是否是 ESC。如果是,第 112 行調(diào)用terminate()函數(shù)退出程序。

第 114 行到第 121 行檢查玩家是否停止按住箭頭或 WASD 鍵之一。在這種情況下,代碼將相應(yīng)的移動(dòng)變量設(shè)置為False。這類似于第 19 章和第 20 章程序中的移動(dòng)代碼。

處理鼠標(biāo)移動(dòng)

現(xiàn)在你已經(jīng)處理了鍵盤事件,讓我們處理可能生成的任何鼠標(biāo)事件。《躲避球》游戲如果玩家點(diǎn)擊了鼠標(biāo)按鈕,不會(huì)有任何反應(yīng),但是當(dāng)玩家移動(dòng)鼠標(biāo)時(shí)會(huì)有反應(yīng)。這給玩家在游戲中控制角色的兩種方式:鍵盤或鼠標(biāo)。

MOUSEMOTION事件在鼠標(biāo)移動(dòng)時(shí)生成:

            if event.type == MOUSEMOTION:# If the mouse moves, move the player to the cursor.playerRect.centerx = event.pos[0]playerRect.centery = event.pos[1]

type設(shè)置為MOUSEMOTIONEvent對(duì)象還有一個(gè)名為pos的屬性,用于存儲(chǔ)鼠標(biāo)事件的位置。pos屬性存儲(chǔ)了鼠標(biāo)光標(biāo)在窗口中移動(dòng)的 x 和 y 坐標(biāo)的元組。如果事件的類型是MOUSEMOTION,玩家的角色將移動(dòng)到鼠標(biāo)光標(biāo)的位置。

第 125 行和第 126 行將玩家角色的中心 x 和 y 坐標(biāo)設(shè)置為鼠標(biāo)光標(biāo)的 x 和 y 坐標(biāo)。

添加新的壞蛋

在游戲循環(huán)的每次迭代中,代碼將baddieAddCounter變量增加一:

        # Add new baddies at the top of the screen, if needed.if not reverseCheat and not slowCheat:baddieAddCounter += 1

只有在作弊未啟用時(shí)才會(huì)發(fā)生。請(qǐng)記住,只要按住 Z 和 X 鍵,reverseCheatslowCheat就會(huì)設(shè)置為True。在按住 Z 和 X 鍵時(shí),baddieAddCounter不會(huì)增加。因此,新的壞蛋不會(huì)出現(xiàn)在屏幕頂部。

當(dāng)baddieAddCounter達(dá)到ADDNEWBADDIERATE中的值時(shí),是時(shí)候在屏幕頂部添加一個(gè)新的壞蛋了。首先,將baddieAddCounter重置為0

        if baddieAddCounter == ADDNEWBADDIERATE:baddieAddCounter = 0baddieSize = random.randint(BADDIEMINSIZE, BADDIEMAXSIZE)newBaddie = {'rect': pygame.Rect(random.randint(0,WINDOWWIDTH - baddieSize), 0 - baddieSize,baddieSize, baddieSize),'speed': random.randint(BADDIEMINSPEED,BADDIEMAXSPEED),'surface':pygame.transform.scale(baddieImage,(baddieSize, baddieSize)),}

第 132 行生成了壞蛋的像素大小。大小將是BADDIEMINSIZEBADDIEMAXSIZE之間的隨機(jī)整數(shù),這些常量分別在第 9 行和第 10 行設(shè)置為1040。

第 133 行是創(chuàng)建新壞蛋數(shù)據(jù)結(jié)構(gòu)的地方。請(qǐng)記住,baddies的數(shù)據(jù)結(jié)構(gòu)只是一個(gè)帶有鍵'rect'、'speed''surface'的字典。'rect'鍵保存對(duì)存儲(chǔ)壞蛋位置和大小的Rect對(duì)象的引用。對(duì)pygame.Rect()構(gòu)造函數(shù)的調(diào)用有四個(gè)參數(shù):區(qū)域頂部邊緣的 x 坐標(biāo)、區(qū)域左邊緣的 y 坐標(biāo)、像素寬度和像素高度。

壞蛋需要出現(xiàn)在窗口頂部的隨機(jī)位置,因此將random.randint(0, WINDOWWIDTH - baddieSize)傳遞給壞蛋左邊緣的 x 坐標(biāo)。之所以傳遞WINDOWWIDTH - baddieSize而不是WINDOWWIDTH,是因?yàn)槿绻麎牡暗淖筮吘壧坑?#xff0c;那么壞蛋的一部分將超出窗口邊緣,不會(huì)在屏幕上可見。

壞蛋的底邊應(yīng)該位于窗口頂邊的上方。窗口頂邊的 y 坐標(biāo)是0。為了將壞蛋的底邊放在那里,將頂邊設(shè)置為0 - baddieSize

壞蛋的寬度和高度應(yīng)該相同(圖像是一個(gè)正方形),因此將baddieSize傳遞給第三個(gè)和第四個(gè)參數(shù)。

壞人在屏幕上移動(dòng)的速度設(shè)置在'speed'鍵中。將其設(shè)置為BADDIEMINSPEEDBADDIEMAXSPEED之間的隨機(jī)整數(shù)。

然后,在第 138 行,將新創(chuàng)建的壞人數(shù)據(jù)結(jié)構(gòu)添加到壞人數(shù)據(jù)結(jié)構(gòu)列表中:

            baddies.append(newBaddie)

程序使用這個(gè)列表來檢查玩家是否與任何壞人發(fā)生了碰撞,并確定在窗口上繪制壞人的位置。

移動(dòng)玩家角色和壞人

四個(gè)移動(dòng)變量moveLeft、moveRight、moveUpmoveDownpygame生成KEYDOWNKEYUP事件時(shí)分別設(shè)置為True和`False。

如果玩家的角色向左移動(dòng),并且玩家角色的左邊緣大于0(即窗口的左邊緣),那么playerRect應(yīng)該向左移動(dòng):

        # Move the player around.if moveLeft and playerRect.left > 0:playerRect.move_ip(-1 * PLAYERMOVERATE, 0)

move_ip()方法將Rect對(duì)象的位置水平或垂直移動(dòng)一定數(shù)量的像素。move_ip()的第一個(gè)參數(shù)是將Rect對(duì)象向右移動(dòng)的像素?cái)?shù)(要向左移動(dòng),傳遞一個(gè)負(fù)整數(shù))。第二個(gè)參數(shù)是將Rect對(duì)象向下移動(dòng)的像素?cái)?shù)(要向上移動(dòng),傳遞一個(gè)負(fù)整數(shù))。例如,playerRect.move_ip(10, 20)將使Rect對(duì)象向右移動(dòng) 10 個(gè)像素,向下移動(dòng) 20 個(gè)像素,playerRect.move_ip(-5, -15)將使Rect對(duì)象向左移動(dòng) 5 個(gè)像素,向上移動(dòng) 15 個(gè)像素。

move_ip()末尾的ip代表“原地”。這是因?yàn)樵摲椒ǜ淖兞?code>Rect對(duì)象本身,而不是返回具有更改的新Rect對(duì)象。還有一個(gè)move()方法,它不會(huì)改變Rect對(duì)象,而是在新位置創(chuàng)建并返回一個(gè)新的Rect對(duì)象。

你總是會(huì)移動(dòng)playerRect對(duì)象的像素?cái)?shù)為PLAYERMOVERATE。要得到一個(gè)整數(shù)的負(fù)形式,將其乘以-1。在第 142 行,由于PLAYERMOVERATE中存儲(chǔ)了5,表達(dá)式-1 * PLAYERMOVERATE的值為-5。因此,調(diào)用playerRect.move_ip(-1 * PLAYERMOVERATE, 0)將使playerRect的位置向左移動(dòng) 5 個(gè)像素。

第 143 到 148 行對(duì)其他三個(gè)方向進(jìn)行了相同的操作:右、上和下。

        if moveRight and playerRect.right < WINDOWWIDTH:playerRect.move_ip(PLAYERMOVERATE, 0)if moveUp and playerRect.top > 0:playerRect.move_ip(0, -1 * PLAYERMOVERATE)if moveDown and playerRect.bottom < WINDOWHEIGHT:playerRect.move_ip(0, PLAYERMOVERATE)

在第 143 到 148 行的三個(gè)if語(yǔ)句中,檢查其移動(dòng)變量是否設(shè)置為True,并且玩家的Rect對(duì)象的邊緣是否在窗口內(nèi)。然后調(diào)用move_ip()來移動(dòng)Rect對(duì)象。

現(xiàn)在,代碼循環(huán)遍歷baddies列表中的每個(gè)壞人數(shù)據(jù)結(jié)構(gòu),使它們向下移動(dòng)一點(diǎn):

        # Move the baddies down.for b in baddies:if not reverseCheat and not slowCheat:b['rect'].move_ip(0, b['speed'])

如果沒有激活任何作弊碼,那么壞人的位置向下移動(dòng)與其速度(存儲(chǔ)在'speed'鍵中)相等的像素?cái)?shù)。

實(shí)現(xiàn)作弊碼

如果反向作弊被激活,那么壞人應(yīng)該向上移動(dòng) 5 個(gè)像素:

            elif reverseCheat:b['rect'].move_ip(0, -5)

move_ip()的第二個(gè)參數(shù)傳遞為-5將使Rect對(duì)象向上移動(dòng) 5 個(gè)像素。

如果慢速作弊被激活,那么壞人仍然應(yīng)該向下移動(dòng),但速度為每次游戲循環(huán)迭代 1 個(gè)像素:

            elif slowCheat:b['rect'].move_ip(0, 1)

當(dāng)慢速作弊被激活時(shí),壞人的正常速度(同樣存儲(chǔ)在壞人數(shù)據(jù)結(jié)構(gòu)的'speed'鍵中)將被忽略。

移除壞人

任何掉到窗口底部以下的壞人都應(yīng)該從baddies列表中移除。記住,不應(yīng)該在迭代列表時(shí)添加或移除列表項(xiàng)。不要使用for循環(huán)迭代baddies列表,而是使用baddies列表的副本進(jìn)行迭代。要?jiǎng)?chuàng)建這個(gè)副本,使用空切片操作符[:]

        # Delete baddies that have fallen past the bottom.for b in baddies[:]:

第 160 行的for循環(huán)使用變量b來遍歷baddies[:]中的當(dāng)前項(xiàng)。如果壞人在窗口的底部以下,我們應(yīng)該將其移除,這在第 162 行中完成:

            if b['rect'].top > WINDOWHEIGHT:baddies.remove(b)

b字典是baddies[:]列表中的當(dāng)前壞蛋數(shù)據(jù)結(jié)構(gòu)。列表中的每個(gè)壞蛋數(shù)據(jù)結(jié)構(gòu)都是一個(gè)帶有'rect'鍵的字典,該鍵存儲(chǔ)一個(gè)Rect對(duì)象。因此,b['rect']是壞蛋的Rect對(duì)象。最后,top屬性是矩形區(qū)域頂部邊緣的 y 坐標(biāo)。請(qǐng)記住,y 坐標(biāo)向下增加。因此,b['rect'].top > WINDOWHEIGHT將檢查壞蛋的頂部邊緣是否在窗口底部以下。如果這個(gè)條件為True,那么第 162 行將從baddies列表中刪除壞蛋數(shù)據(jù)結(jié)構(gòu)。

繪制窗口

在更新所有數(shù)據(jù)結(jié)構(gòu)之后,應(yīng)使用pygame的圖像函數(shù)繪制游戲世界。因?yàn)橛螒蜓h(huán)每秒執(zhí)行多次,當(dāng)壞蛋和玩家在新位置繪制時(shí),它們看起來就像是平穩(wěn)移動(dòng)的。

在繪制任何其他內(nèi)容之前,第 165 行填充整個(gè)屏幕以擦除先前繪制的任何內(nèi)容:

        # Draw the game world on the window.windowSurface.fill(BACKGROUNDCOLOR)

請(qǐng)記住,windowSurface中的Surface對(duì)象很特殊,因?yàn)樗怯?code>pygame.display.set_mode()返回的。因此,在該Surface對(duì)象上繪制的任何內(nèi)容都將在調(diào)用pygame.display.update()后出現(xiàn)在屏幕上。

繪制玩家得分

第 168 和 169 行在窗口的左上角渲染了當(dāng)前得分和最高得分的文本。

        # Draw the score and top score.drawText('Score: %s' % (score), font, windowSurface, 10, 0)drawText('Top Score: %s' % (topScore), font, windowSurface,10, 40)

'Score: %s' % (score) 表達(dá)式使用字符串插值將score變量的值插入字符串中。這個(gè)字符串、存儲(chǔ)在font變量中的Font對(duì)象、用于繪制文本的Surface對(duì)象,以及文本應(yīng)放置的 x 和 y 坐標(biāo)都被傳遞給drawText()方法,該方法將處理對(duì)render()blit()方法的調(diào)用。

對(duì)于最高得分,做同樣的事情。將40作為 y 坐標(biāo)傳遞,而不是0,這樣最高得分的文本就會(huì)出現(xiàn)在當(dāng)前得分的文本下方。

繪制玩家角色和壞蛋

關(guān)于玩家的信息保存在兩個(gè)不同的變量中。playerImage是一個(gè)包含玩家角色圖像的所有彩色像素的Surface對(duì)象。playerRect是一個(gè)存儲(chǔ)玩家角色大小和位置的Rect對(duì)象。

blit()方法在windowSurface上繪制玩家角色的圖像(在playerImage中)在playerRect的位置:

        # Draw the player's rectangle.windowSurface.blit(playerImage, playerRect)

第 175 行的for循環(huán)在windowSurface對(duì)象上繪制每個(gè)壞蛋:

        # Draw each baddie.for b in baddies:windowSurface.blit(b['surface'], b['rect'])

baddies列表中的每個(gè)項(xiàng)目都是一個(gè)字典。字典的'surface''rect'鍵包含了帶有壞蛋圖像的Surface對(duì)象和帶有位置和大小信息的Rect對(duì)象。

現(xiàn)在,所有內(nèi)容都已經(jīng)繪制到windowSurface上,我們需要更新屏幕,以便玩家可以看到其中的內(nèi)容:

        pygame.display.update()

通過調(diào)用update()將這個(gè)Surface對(duì)象繪制到屏幕上。

檢查碰撞

第 181 行檢查玩家是否與任何壞蛋發(fā)生碰撞,調(diào)用playerHasHitBaddie()。如果玩家的角色與baddies列表中的任何一個(gè)壞蛋發(fā)生碰撞,則此函數(shù)將返回True。否則,該函數(shù)返回False。

        # Check if any of the baddies have hit the player.if playerHasHitBaddie(playerRect, baddies):if score > topScore:topScore = score # Set new top score.break

如果玩家的角色撞到了壞蛋,并且當(dāng)前得分高于最高得分,那么第 182 和 183 行將更新最高得分。程序的執(zhí)行會(huì)在第 184 行跳出游戲循環(huán),并移動(dòng)到第 189 行,結(jié)束游戲。

為了防止計(jì)算機(jī)盡可能快地運(yùn)行游戲循環(huán)(這對(duì)玩家來說太快了),調(diào)用mainClock.tick()來暫停游戲很短的時(shí)間:

        mainClock.tick(FPS)

這個(gè)暫停時(shí)間將足夠長(zhǎng),以確保每秒大約進(jìn)行40次(存儲(chǔ)在FPS變量?jī)?nèi)部的值)游戲循環(huán)迭代。

游戲結(jié)束畫面

當(dāng)玩家失敗時(shí),游戲停止播放背景音樂,并播放“游戲結(jié)束”音效:

    # Stop the game and show the "Game Over" screen.pygame.mixer.music.stop()gameOverSound.play()

第 189 行調(diào)用pygame.mixer.music模塊中的stop()函數(shù)來停止背景音樂。第 190 行調(diào)用gameOverSound中存儲(chǔ)的Sound對(duì)象的play()方法。

然后,第 192 行和第 193 行調(diào)用drawText()函數(shù)將“游戲結(jié)束”文本繪制到windowSurface對(duì)象上:

    drawText('GAME OVER', font, windowSurface, (WINDOWWIDTH / 3),(WINDOWHEIGHT / 3))drawText('Press a key to play again.', font, windowSurface,(WINDOWWIDTH / 3) - 80, (WINDOWHEIGHT / 3) + 50)pygame.display.update()waitForPlayerToPressKey()

第 194 行調(diào)用update()來將這個(gè)Surface對(duì)象繪制到屏幕上。在顯示這個(gè)文本后,游戲會(huì)停止,直到玩家按下鍵,調(diào)用waitForPlayerToPressKey()函數(shù)。

玩家按下鍵后,程序執(zhí)行從第 195 行的waitForPlayerToPressKey()調(diào)用返回。根據(jù)玩家按鍵的時(shí)間長(zhǎng)短,可能會(huì)播放“游戲結(jié)束”音效。為了在新游戲開始之前停止這個(gè)音效,第 197 行調(diào)用gameOverSound.stop()

    gameOverSound.stop()

我們的圖形游戲就到這里了!

修改躲避者游戲

你可能會(huì)發(fā)現(xiàn)游戲太容易或太難。幸運(yùn)的是,游戲很容易修改,因?yàn)槲覀兓〞r(shí)間使用常量變量而不是直接輸入值?,F(xiàn)在,我們只需要修改常量變量中設(shè)置的值就可以改變游戲。

例如,如果你想讓游戲總體運(yùn)行速度變慢,可以將第 8 行的FPS變量更改為較小的值,比如20。這將使壞人和玩家角色移動(dòng)得更慢,因?yàn)橛螒蜓h(huán)每秒只執(zhí)行20次,而不是40次。

如果你只想減慢壞人的速度而不是玩家的速度,那么將BADDIEMAXSPEED更改為較小的值,比如4。這將使所有壞人在游戲循環(huán)中的每次迭代之間移動(dòng) 1(BADDIEMINSPEED中的值)到 4 個(gè)像素,而不是 1 到 8 個(gè)像素。

如果你想讓游戲有更少但更大的壞人,而不是許多較小的壞人,那么將ADDNEWBADDIERATE增加到12BADDIEMINSIZE增加到40BADDIEMAXSIZE增加到80。現(xiàn)在,壞人每 12 次游戲循環(huán)添加一次,而不是每 6 次,所以壞人的數(shù)量將減少一半。但為了保持游戲的趣味性,壞人會(huì)更大。

保持基本游戲不變,你可以修改任何常量變量,從而顯著影響游戲的玩法。不斷嘗試新的常量變量值,直到找到最喜歡的值組合。

總結(jié)

與我們的文本游戲不同,躲避者看起來真的像一款現(xiàn)代電腦游戲。它有圖形和音樂,并且使用鼠標(biāo)。雖然pygame提供函數(shù)和數(shù)據(jù)類型作為構(gòu)建塊,但是你作為程序員將它們組合在一起,創(chuàng)造出有趣的互動(dòng)游戲。

你可以做到這一切,因?yàn)槟阒廊绾沃鸩街笇?dǎo)計(jì)算機(jī)做事,一行一行地。通過使用計(jì)算機(jī)的語(yǔ)言,你可以讓它為你進(jìn)行數(shù)字計(jì)算和繪圖。這是一項(xiàng)有用的技能,我希望你會(huì)繼續(xù)學(xué)習(xí)更多關(guān)于 Python 編程的知識(shí)。(還有很多東西要學(xué)!)

現(xiàn)在開始發(fā)揮你的想象力,創(chuàng)造屬于自己的游戲。祝你好運(yùn)!

上一頁(yè):第 20 章 - 使用聲音和圖像

http://www.risenshineclean.com/news/22130.html

相關(guān)文章:

  • 知道域名怎么進(jìn)入網(wǎng)站北京網(wǎng)站建設(shè)公司報(bào)價(jià)
  • 圣輝友聯(lián)劉金鵬做網(wǎng)站鄭州網(wǎng)站關(guān)鍵詞優(yōu)化公司哪家好
  • 供應(yīng)長(zhǎng)沙手機(jī)網(wǎng)站建設(shè)天津關(guān)鍵詞排名推廣
  • wordpress 修改登錄地址seo的定義是什么
  • 企業(yè)網(wǎng)站模板下載需謹(jǐn)慎百度信息流投放在哪些平臺(tái)
  • 上傳自己做的網(wǎng)站后臺(tái)怎么辦常見的網(wǎng)絡(luò)營(yíng)銷方式有哪些
  • 網(wǎng)站重新制作多久google重新收錄網(wǎng)絡(luò)營(yíng)銷策略有哪幾種
  • redis做緩存的網(wǎng)站并發(fā)數(shù)百度推廣關(guān)鍵詞價(jià)格查詢
  • 手機(jī)網(wǎng)站 文件上傳百度熱榜排行
  • 重慶做網(wǎng)站推廣電商網(wǎng)站排名
  • 網(wǎng)站反鏈騰訊廣告
  • 網(wǎng)絡(luò)app制作網(wǎng)站有哪些內(nèi)容app開發(fā)流程
  • 做女朋友的網(wǎng)站qq群引流推廣軟件
  • wordpress 一站多主題國(guó)內(nèi)優(yōu)秀網(wǎng)頁(yè)設(shè)計(jì)賞析
  • 與做機(jī)器人有關(guān)的網(wǎng)站軟件開發(fā)需要學(xué)什么
  • iis 網(wǎng)站目錄權(quán)限網(wǎng)站制作維護(hù)
  • 惠州seo推廣外包北京百度關(guān)鍵詞優(yōu)化
  • icp備案網(wǎng)站更名整站優(yōu)化多少錢
  • 做家具的企業(yè)網(wǎng)站最吸引人的營(yíng)銷廣告詞
  • 免費(fèi)游戲網(wǎng)頁(yè)入口西安網(wǎng)站seo外包
  • 深圳雙語(yǔ)網(wǎng)站制作網(wǎng)站的seo是什么意思
  • 怎么做裝修網(wǎng)站seo優(yōu)化招商
  • 家裝建材網(wǎng)購(gòu)平臺(tái)推薦seo外鏈平臺(tái)
  • 做網(wǎng)站廠家廣告策劃公司
  • 做網(wǎng)站客戶沒有付定金深圳網(wǎng)站優(yōu)化推廣
  • 前端開發(fā)培訓(xùn)機(jī)構(gòu)成都西安官網(wǎng)seo
  • 畢業(yè)設(shè)計(jì)做系統(tǒng)跟做網(wǎng)站哪個(gè)容易打廣告去哪個(gè)平臺(tái)免費(fèi)
  • 香港做批發(fā)的網(wǎng)站有哪些手續(xù)合肥做網(wǎng)站哪家好
  • 音樂播放網(wǎng)站開發(fā)pc端設(shè)計(jì)師網(wǎng)站
  • 論壇門戶網(wǎng)站建設(shè)seo文章外包