在本系列的第十一篇有關使用 Python Pygame 模塊進行編程的文章中,顯示玩家得到戰利品或受到傷害時的得分。html
這是仍在進行中的關於使用 Pygame 模塊來在 Python 3 在建立電腦遊戲的第十一部分。先前的文章是:python
若是你已經跟隨這一系列好久,那麼已經學習了使用 Python 建立一個視頻遊戲所需的全部基本語法和模式。然而,它仍然缺乏一個相當重要的組成部分。這一組成部分不只僅對用 Python 編程遊戲重要;無論你探究哪一個計算機分支,你都必需精通:做爲一個程序員,經過閱讀一種語言的或庫的文檔來學習新的技巧。linux
幸運的是,你正在閱讀本文的事實代表你熟悉文檔。爲了使你的平臺類遊戲更加美觀,在這篇文章中,你將在遊戲屏幕上添加得分和生命值顯示。不過,教你如何找到一個庫的功能以及如何使用這些新的功能的這節課程並無多神祕。git
如今,既然你有了能夠被玩家收集的獎勵,那就有充分的理由來記錄分數,以便你的玩家看到他們收集了多少獎勵。你也能夠跟蹤玩家的生命值,以便當他們被敵人擊中時會有相應結果。程序員
你已經有了跟蹤分數和生命值的變量,可是這一切都發生在後臺。這篇文章教你在遊戲期間在遊戲屏幕上以你選擇的一種字體來顯示這些統計數字。github
大多數 Python 模塊都有文檔,即便那些沒有文檔的模塊,也能經過 Python 的幫助功能來進行最小的文檔化。Pygame 的主頁面 連接了它的文檔。不過,Pygame 是一個帶有不少文檔的大模塊,而且它的文檔不像在 Opensource.com 上的文章同樣,以一樣易理解的(和友好的、易解釋的、有用的)敘述風格來撰寫的。它們是技術文檔,而且列出在模塊中可用的每一個類和函數,各自要求的輸入類型等等。若是你不適應參考代碼組件描述,這可能會使人不知所措。編程
在煩惱於庫的文檔前,第一件要作的事,就是來想一想你正在嘗試達到的目標。在這種狀況下,你想在屏幕上顯示玩家的得分和生命值。bash
在你肯定你須要的結果後,想一想它須要什麼的組件。你能夠從變量和函數的方面考慮這一點,或者,若是你尚未天然地想到這一點,你能夠進行通常性思考。你可能意識到須要一些文原本顯示一個分數,你但願 Pygame 在屏幕上繪製這些文本。若是你仔細思考,你可能會意識到它與在屏幕上渲染一個玩家、獎勵或一個平臺並多麼大的不一樣。app
從技術上講,你能夠使用數字圖形,並讓 Pygame 顯示這些數字圖形。它不是達到你目標的最容易的方法,可是若是它是你惟一知道的方法,那麼它是一個有效的方法。不過,若是你參考 Pygame 的文檔,你看到列出的模塊之一是 font
,這是 Pygame 使得在屏幕上來使打印文本像輸入文字同樣容易的方法。框架
font
文檔頁面以 pygame.font.init()
開始,它列出了用於初始化字體模塊的函數。它由 pygame.init()
自動地調用,你已經在代碼中調用了它。再強調一次,從技術上講,你已經到達一個足夠好的點。雖然你尚不知道如何作,你知道你可以使用 pygame.font
函數來在屏幕上打印文本。
然而,若是你閱讀更多一些,你會找到這裏還有一種更好的方法來打印字體。pygame.freetype
模塊在文檔中的描述方式以下:
pygame.freetype
模塊是pygame.fontpygame
模塊的一個替代品,用於加載和渲染字體。它有原函數的全部功能,外加不少新的功能。
在 pygame.freetype
文檔頁面的下方,有一些示例代碼:
import pygame
import pygame.freetype
複製代碼
你的代碼應該已經導入了 Pygame,不過,請修改你的 import
語句以包含 Freetype 模塊:
import pygame
import sys
import os
import pygame.freetype
複製代碼
從 font
模塊的描述中能夠看出,顯然 Pygame 使用一種字體(無論它的你提供的或內置到 Pygame 的默認字體)在屏幕上渲染字體。滾動瀏覽 pygame.freetype
文檔來找到 pygame.freetype.Font
函數:
pygame.freetype.Font
從支持的字體文件中建立一個新的字體實例。
Font(file, size=0, font_index=0, resolution=0, ucs4=False) -> Font
pygame.freetype.Font.name
符合規則的字體名稱。
pygame.freetype.Font.path
字體文件路徑。
pygame.freetype.Font.size
在渲染中使用的默認點大小
複製代碼
這描述瞭如何在 Pygame 中構建一個字體「對象」。把屏幕上的一個簡單對象視爲一些代碼屬性的組合對你來講可能不太天然,可是這與你構建英雄和敵人精靈的方式很是相似。你須要一個字體文件,而不是一個圖像文件。在你有一個字體文件後,你能夠在你的代碼中使用 pygame.freetype.Font
函數來建立一個字體對象,而後使用該對象來在屏幕上渲染文本。
由於並非世界上的每一個人的電腦上都有徹底同樣的字體,所以將你選擇的字體與你的遊戲捆綁在一塊兒是很重要的。要捆綁字體,首先在你的遊戲文件夾中建立一個新的目錄,放在你爲圖像而建立的文件目錄旁邊。稱其爲 fonts
。
即便你的計算機操做系統隨附了幾種字體,可是將這些字體給予其餘人是非法的。這看起來很奇怪,但法律就是這樣運做的。若是想與你的遊戲一塊兒隨附一種字體,你必需找到一種開源或知識共享的字體,以容許你隨遊戲一塊兒提供該字體。
專門提供自由和合法字體的網站包括:
當你找到你喜歡的字體後,下載下來。解壓縮 ZIP 或 TAR 文件,並移動 .ttf
或 .otf
文件到你的項目目錄下的 fonts
文件夾中。
你沒有安裝字體到你的計算機上。你只是放置字體到你遊戲的 fonts
文件夾中,以便 Pygame 可使用它。若是你想,你能夠在你的計算機上安裝該字體,可是沒有必要。重要的是將字體放在你的遊戲目錄中,這樣 Pygame 能夠「描繪」字體到屏幕上。
若是字體文件的名稱複雜且帶有空格或特殊字符,只須要從新命名它便可。文件名稱是徹底任意的,而且對你來講,文件名稱越簡單,越容易將其鍵入你的代碼中。
如今告訴 Pygame 你的字體。從文檔中你知道,當你至少提供了字體文件路徑給 pygame.freetype.Font
時(文檔明確指出全部其他屬性都是可選的),你將在返回中得到一個字體對象:
Font(file, size=0, font_index=0, resolution=0, ucs4=False) -> Font
複製代碼
建立一個稱爲 myfont
的新變量來充當你在遊戲中字體,並放置 Font
函數的結果到這個變量中。這個示例中使用 amazdoom.ttf
字體,可是你可使用任何你想使用的字體。在你的設置部分放置這些代碼:
font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"fonts","amazdoom.ttf")
font_size = tx
myfont = pygame.freetype.Font(font_path, font_size)
複製代碼
如今你已經建立一個字體對象,你須要一個函數來繪製你想繪製到屏幕上的文本。這和你在你的遊戲中繪製背景和平臺是相同的原理。
首先,建立一個函數,並使用 myfont
對象來建立一些文本,設置顏色爲某些 RGB 值。這必須是一個全局函數;它不屬於任何具體的類:
def stats(score,health):
myfont.render_to(world, (4, 4), "Score:"+str(score), WHITE, None, size=64)
myfont.render_to(world, (4, 72), "Health:"+str(health), WHITE, None, size=64)
複製代碼
固然,你此刻已經知道,若是它不在主循環中,你的遊戲將不會發生任何事,因此在文件的底部添加一個對你的 stats
函數的調用:
for e in enemy_list:
e.move()
stats(player.score,player.health) # draw text
pygame.display.flip()
複製代碼
嘗試你的遊戲。
當玩家收集獎勵品時,得分會上升。當玩家被敵人擊中時,生命值降低。成功!
不過,這裏有一個問題。當一個玩家被敵人擊中時,健康度會一路降低,這是不公平的。你剛剛發現一個非致命的錯誤。非致命的錯誤是這些在應用程序中小問題,(一般)不會阻止應用程序啓動或甚至致使中止工做,可是它們要麼沒有意義,要麼會惹惱用戶。這裏是如何解決這個問題的方法。
當前生命值系統的問題是,敵人接觸玩家時,Pygame 時鐘的每一次滴答,健康度都會減小。這意味着一個緩慢移動的敵人可能在一次遭遇中將一個玩家下降健康度至 -200 ,這不公平。固然,你能夠給你的玩家一個 10000 的起始健康度得分,而不用擔憂它;這能夠工做,而且可能沒有人會注意。可是這裏有一個更好的方法。
當前,你的代碼偵查出一個玩家和一個敵人發生碰撞的時候。生命值問題的修復是檢測兩個獨立的事件:何時玩家和敵人碰撞,而且,在它們碰撞後,何時它們中止碰撞。
首先,在你的玩家類中,建立一個變量來表明玩家和敵人碰撞在一塊兒:
self.frame = 0
self.health = 10
self.damage = 0
複製代碼
在你的 Player
類的 update
函數中,移除這塊代碼塊:
for enemy in enemy_hit_list:
self.health -= 1
#print(self.health)
複製代碼
而且在它的位置,只要玩家當前沒有被擊中,檢查碰撞:
if self.damage == 0:
for enemy in enemy_hit_list:
if not self.rect.contains(enemy):
self.damage = self.rect.colliderect(enemy)
複製代碼
你可能會在你刪除的語句塊和你剛剛添加的語句塊之間看到類似之處。它們都在作相同的工做,可是新的代碼更復雜。最重要的是,只有當玩家當前沒有被擊中時,新的代碼才運行。這意味着,當一個玩家和敵人碰撞時,這些代碼運行一次,而不是像之前那樣一直髮生碰撞。
新的代碼使用兩個新的 Pygame 函數。self.rect.contains
函數檢查一個敵人當前是否在玩家的邊界框內,而且當它是 true
時, self.rect.colliderect
設置你的新的 self.damage
變量爲 1,而無論它多少次是 true
。
如今,即便被一個敵人擊中 3 秒,對 Pygame 來講仍然看做一次擊中。
我經過通讀 Pygame 的文檔而發現了這些函數。你沒有必要一次閱讀徹底部的文檔,而且你也沒有必要閱讀每一個函數的每一個單詞。不過,花費時間在你正在使用的新的庫或模塊的文檔上是很重要的;不然,你極有可能在從新發明輪子。不要花費一個下午的時間來嘗試修改拼接一個解決方案到一些東西,而這些東西已經被你正在使用的框架的所解決。閱讀文檔,知悉函數,並從別人的工做中獲益!
最後,添加另外一個代碼語句塊來偵查出何時玩家和敵人再也不接觸。而後直到那時,才從玩家減小一個生命值。
if self.damage == 1:
idx = self.rect.collidelist(enemy_hit_list)
if idx == -1:
self.damage = 0 # set damage back to 0
self.health -= 1 # subtract 1 hp
複製代碼
注意,只有當玩家被擊中時,這個新的代碼纔會被觸發。這意味着,在你的玩家在你的遊戲世界正在探索或收集獎勵時,這個代碼不會運行。它僅當 self.damage
變量被激活時運行。
當代碼運行時,它使用 self.rect.collidelist
來查看玩家是否仍然接觸在你敵人列表中的敵人(當其未偵查到碰撞時,collidelist
返回 -1)。在它沒有接觸敵人時,是該處理 self.damage
的時機:經過設置 self.damage
變量回到 0 來使其無效,並減小一點生命值。
如今嘗試你的遊戲。
如今,你有一個來讓你的玩家知道它們分數和生命值的方法,當你的玩家達到某些里程碑時,你能夠確保某些事件發生。例如,也許這裏有一個特殊的恢復一些生命值的獎勵項目。也許一個到達 0 生命值的玩家不得不從一個關卡的起始位置從新開始。
你能夠在你的代碼中檢查這些事件,而且相應地操縱你的遊戲世界。你已經知道該怎麼作,因此請瀏覽文檔來尋找新的技巧,而且獨立地嘗試這些技巧。
這裏是到目前爲止全部的代碼:
#!/usr/bin/env python3
# draw a world
# add a player and player control
# add player movement
# add enemy and basic collision
# add platform
# add gravity
# add jumping
# add scrolling
# add loot
# add score
# GNU All-Permissive License
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided the copyright
# notice and this notice are preserved. This file is offered as-is,
# without any warranty.
import pygame
import sys
import os
import pygame.freetype
''' Objects '''
class Platform(pygame.sprite.Sprite):
# x location, y location, img width, img height, img file
def __init__(self,xloc,yloc,imgw,imgh,img):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(os.path.join('images',img)).convert()
self.image.convert_alpha()
self.rect = self.image.get_rect()
self.rect.y = yloc
self.rect.x = xloc
class Player(pygame.sprite.Sprite):
''' Spawn a player '''
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.movex = 0
self.movey = 0
self.frame = 0
self.health = 10
self.damage = 0
self.collide_delta = 0
self.jump_delta = 6
self.score = 1
self.images = []
for i in range(1,9):
img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert()
img.convert_alpha()
img.set_colorkey(ALPHA)
self.images.append(img)
self.image = self.images[0]
self.rect = self.image.get_rect()
def jump(self,platform_list):
self.jump_delta = 0
def gravity(self):
self.movey += 3.2 # how fast player falls
if self.rect.y > worldy and self.movey >= 0:
self.movey = 0
self.rect.y = worldy-ty
def control(self,x,y):
''' control player movement '''
self.movex += x
self.movey += y
def update(self):
''' Update sprite position '''
self.rect.x = self.rect.x + self.movex
self.rect.y = self.rect.y + self.movey
# moving left
if self.movex < 0:
self.frame += 1
if self.frame > ani*3:
self.frame = 0
self.image = self.images[self.frame//ani]
# moving right
if self.movex > 0:
self.frame += 1
if self.frame > ani*3:
self.frame = 0
self.image = self.images[(self.frame//ani)+4]
# collisions
enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
if self.damage == 0:
for enemy in enemy_hit_list:
if not self.rect.contains(enemy):
self.damage = self.rect.colliderect(enemy)
if self.damage == 1:
idx = self.rect.collidelist(enemy_hit_list)
if idx == -1:
self.damage = 0 # set damage back to 0
self.health -= 1 # subtract 1 hp
loot_hit_list = pygame.sprite.spritecollide(self, loot_list, False)
for loot in loot_hit_list:
loot_list.remove(loot)
self.score += 1
print(self.score)
plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False)
for p in plat_hit_list:
self.collide_delta = 0 # stop jumping
self.movey = 0
if self.rect.y > p.rect.y:
self.rect.y = p.rect.y+ty
else:
self.rect.y = p.rect.y-ty
ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False)
for g in ground_hit_list:
self.movey = 0
self.rect.y = worldy-ty-ty
self.collide_delta = 0 # stop jumping
if self.rect.y > g.rect.y:
self.health -=1
print(self.health)
if self.collide_delta < 6 and self.jump_delta < 6:
self.jump_delta = 6*2
self.movey -= 33 # how high to jump
self.collide_delta += 6
self.jump_delta += 6
class Enemy(pygame.sprite.Sprite):
''' Spawn an enemy '''
def __init__(self,x,y,img):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(os.path.join('images',img))
self.movey = 0
#self.image.convert_alpha()
#self.image.set_colorkey(ALPHA)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.counter = 0
def move(self):
''' enemy movement '''
distance = 80
speed = 8
self.movey += 3.2
if self.counter >= 0 and self.counter <= distance:
self.rect.x += speed
elif self.counter >= distance and self.counter <= distance*2:
self.rect.x -= speed
else:
self.counter = 0
self.counter += 1
if not self.rect.y >= worldy-ty-ty:
self.rect.y += self.movey
plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False)
for p in plat_hit_list:
self.movey = 0
if self.rect.y > p.rect.y:
self.rect.y = p.rect.y+ty
else:
self.rect.y = p.rect.y-ty
ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False)
for g in ground_hit_list:
self.rect.y = worldy-ty-ty
class Level():
def bad(lvl,eloc):
if lvl == 1:
enemy = Enemy(eloc[0],eloc[1],'yeti.png') # spawn enemy
enemy_list = pygame.sprite.Group() # create enemy group
enemy_list.add(enemy) # add enemy to group
if lvl == 2:
print("Level " + str(lvl) )
return enemy_list
def loot(lvl,tx,ty):
if lvl == 1:
loot_list = pygame.sprite.Group()
loot = Platform(200,ty*7,tx,ty, 'loot_1.png')
loot_list.add(loot)
if lvl == 2:
print(lvl)
return loot_list
def ground(lvl,gloc,tx,ty):
ground_list = pygame.sprite.Group()
i=0
if lvl == 1:
while i < len(gloc):
ground = Platform(gloc[i],worldy-ty,tx,ty,'ground.png')
ground_list.add(ground)
i=i+1
if lvl == 2:
print("Level " + str(lvl) )
return ground_list
def platform(lvl,tx,ty):
plat_list = pygame.sprite.Group()
ploc = []
i=0
if lvl == 1:
ploc.append((20,worldy-ty-128,3))
ploc.append((300,worldy-ty-256,3))
ploc.append((500,worldy-ty-128,4))
while i < len(ploc):
j=0
while j <= ploc[i][2]:
plat = Platform((ploc[i][0]+(j*tx)),ploc[i][1],tx,ty,'ground.png')
plat_list.add(plat)
j=j+1
print('run' + str(i) + str(ploc[i]))
i=i+1
if lvl == 2:
print("Level " + str(lvl) )
return plat_list
def stats(score,health):
myfont.render_to(world, (4, 4), "Score:"+str(score), SNOWGRAY, None, size=64)
myfont.render_to(world, (4, 72), "Health:"+str(health), SNOWGRAY, None, size=64)
''' Setup '''
worldx = 960
worldy = 720
fps = 40 # frame rate
ani = 4 # animation cycles
clock = pygame.time.Clock()
pygame.init()
main = True
BLUE = (25,25,200)
BLACK = (23,23,23 )
WHITE = (254,254,254)
SNOWGRAY = (137,164,166)
ALPHA = (0,255,0)
world = pygame.display.set_mode([worldx,worldy])
backdrop = pygame.image.load(os.path.join('images','stage.png')).convert()
backdropbox = world.get_rect()
player = Player() # spawn player
player.rect.x = 0
player.rect.y = 0
player_list = pygame.sprite.Group()
player_list.add(player)
steps = 10
forwardx = 600
backwardx = 230
eloc = []
eloc = [200,20]
gloc = []
tx = 64 #tile size
ty = 64 #tile size
font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"fonts","amazdoom.ttf")
font_size = tx
myfont = pygame.freetype.Font(font_path, font_size)
i=0
while i <= (worldx/tx)+tx:
gloc.append(i*tx)
i=i+1
enemy_list = Level.bad( 1, eloc )
ground_list = Level.ground( 1,gloc,tx,ty )
plat_list = Level.platform( 1,tx,ty )
loot_list = Level.loot(1,tx,ty)
''' Main loop '''
while main == True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit(); sys.exit()
main = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT or event.key == ord('a'):
print("LEFT")
player.control(-steps,0)
if event.key == pygame.K_RIGHT or event.key == ord('d'):
print("RIGHT")
player.control(steps,0)
if event.key == pygame.K_UP or event.key == ord('w'):
print('jump')
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT or event.key == ord('a'):
player.control(steps,0)
if event.key == pygame.K_RIGHT or event.key == ord('d'):
player.control(-steps,0)
if event.key == pygame.K_UP or event.key == ord('w'):
player.jump(plat_list)
if event.key == ord('q'):
pygame.quit()
sys.exit()
main = False
# scroll the world forward
if player.rect.x >= forwardx:
scroll = player.rect.x - forwardx
player.rect.x = forwardx
for p in plat_list:
p.rect.x -= scroll
for e in enemy_list:
e.rect.x -= scroll
for l in loot_list:
l.rect.x -= scroll
# scroll the world backward
if player.rect.x <= backwardx:
scroll = backwardx - player.rect.x
player.rect.x = backwardx
for p in plat_list:
p.rect.x += scroll
for e in enemy_list:
e.rect.x += scroll
for l in loot_list:
l.rect.x += scroll
world.blit(backdrop, backdropbox)
player.gravity() # check gravity
player.update()
player_list.draw(world) #refresh player position
enemy_list.draw(world) # refresh enemies
ground_list.draw(world) # refresh enemies
plat_list.draw(world) # refresh platforms
loot_list.draw(world) # refresh loot
for e in enemy_list:
e.move()
stats(player.score,player.health) # draw text
pygame.display.flip()
clock.tick(fps)
複製代碼
via: opensource.com/article/20/…
做者:Seth Kenlon 選題:lujun9972 譯者:robsean 校對:wxy