添加計分到你的 Python 遊戲

在本系列的第十一篇有關使用 Python Pygame 模塊進行編程的文章中,顯示玩家得到戰利品或受到傷害時的得分。html

這是仍在進行中的關於使用 Pygame 模塊來在 Python 3 在建立電腦遊戲的第十一部分。先前的文章是:python

若是你已經跟隨這一系列好久,那麼已經學習了使用 Python 建立一個視頻遊戲所需的全部基本語法和模式。然而,它仍然缺乏一個相當重要的組成部分。這一組成部分不只僅對用 Python 編程遊戲重要;無論你探究哪一個計算機分支,你都必需精通:做爲一個程序員,經過閱讀一種語言的或庫的文檔來學習新的技巧。linux

幸運的是,你正在閱讀本文的事實代表你熟悉文檔。爲了使你的平臺類遊戲更加美觀,在這篇文章中,你將在遊戲屏幕上添加得分和生命值顯示。不過,教你如何找到一個庫的功能以及如何使用這些新的功能的這節課程並無多神祕。git

在 Pygame 中顯示得分

如今,既然你有了能夠被玩家收集的獎勵,那就有充分的理由來記錄分數,以便你的玩家看到他們收集了多少獎勵。你也能夠跟蹤玩家的生命值,以便當他們被敵人擊中時會有相應結果。程序員

你已經有了跟蹤分數和生命值的變量,可是這一切都發生在後臺。這篇文章教你在遊戲期間在遊戲屏幕上以你選擇的一種字體來顯示這些統計數字。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
複製代碼

在 Pygame 中使用字體

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)
複製代碼

在 Pygame 中顯示文本

如今你已經建立一個字體對象,你須要一個函數來繪製你想繪製到屏幕上的文本。這和你在你的遊戲中繪製背景和平臺是相同的原理。

首先,建立一個函數,並使用 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()
複製代碼

嘗試你的遊戲。

當玩家收集獎勵品時,得分會上升。當玩家被敵人擊中時,生命值降低。成功!

Keeping score in Pygame
Keeping score in Pygame

不過,這裏有一個問題。當一個玩家被敵人擊中時,健康度會一路降低,這是不公平的。你剛剛發現一個非致命的錯誤。非致命的錯誤是這些在應用程序中小問題,(一般)不會阻止應用程序啓動或甚至致使中止工做,可是它們要麼沒有意義,要麼會惹惱用戶。這裏是如何解決這個問題的方法。

修復生命值計數

當前生命值系統的問題是,敵人接觸玩家時,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

本文由 LCTT 原創編譯,Linux中國 榮譽推出

相關文章
相關標籤/搜索