爲你的 Python 平臺類遊戲添加跳躍功能

在本期使用 Python Pygame 模塊編寫視頻遊戲中,學會如何使用跳躍來對抗重力。html

在本系列的 前一篇文章 中,你已經模擬了重力。但如今,你須要賦予你的角色跳躍的能力來對抗重力。python

跳躍是對重力做用的暫時延緩。在這一小段時間裏,你是向跳,而不是被重力拉着向下落。但你一旦到達了跳躍的最高點,重力就會從新發揮做用,將你拉回地面。linux

在代碼中,這種變化被表示爲變量。首先,你須要爲玩家精靈創建一個變量,使得 Python 可以跟蹤該精靈是否正在跳躍中。一旦玩家精靈開始跳躍,他就會再次受到重力的做用,並被拉回最近的物體。git

設置跳躍狀態變量

你須要爲你的 Player 類添加兩個新變量:github

  • 一個是爲了跟蹤你的角色是否正在跳躍中,可經過你的玩家精靈是否站在堅實的地面來肯定
  • 一個是爲了將玩家帶回地面

將以下兩個變量添加到你的 Player 類中。在下方的代碼中,註釋前的部分用於提示上下文,所以只須要添加最後兩行:編程

                self.movex = 0
                self.movey = 0
                self.frame = 0
                self.health = 10
                # 此處是重力相關變量
                self.collide_delta = 0
                self.jump_delta = 6
複製代碼

第一個變量 collide_delta 被設爲 0 是由於在正常狀態下,玩家精靈沒有處在跳躍中的狀態。另外一個變量 jump_delta 被設爲 6,是爲了防止精靈在第一次進入遊戲世界時就發生反彈(實際上就是跳躍)。當你完成了本篇文章的示例,嘗試把該變量設爲 0 看看會發生什麼。bash

跳躍中的碰撞

若是你是跳到一個蹦牀上,那你的跳躍必定很是優美。可是若是你是跳向一面牆會發生什麼呢?(千萬不要去嘗試!)無論你的起跳多麼使人印象深入,當你撞到比你更大更硬的物體時,你都會立馬停下。(LCTT 譯註:原理參考動量守恆定律)app

爲了在你的視頻遊戲中模擬這一點,你須要在你的玩家精靈與地面等東西發生碰撞時,將 self.collide_delta 變量設爲 0。若是你的 self.collide_delta 不是 0 而是其它的什麼值,那麼你的玩家就會發生跳躍,而且當你的玩家與牆或者地面發生碰撞時沒法跳躍。框架

在你的 Player 類的 update 方法中,將地面碰撞相關代碼塊修改成以下所示:ide

        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 # 中止跳躍
            if self.rect.y > g.rect.y:
                self.health -=1
                print(self.health)
複製代碼

這段代碼塊檢查了地面精靈和玩家精靈之間發生的碰撞。當發生碰撞時,它會將玩家 Y 方向的座標值設置爲遊戲窗口的高度減去一個瓷磚的高度再減去另外一個瓷磚的高度。以此保證了玩家精靈是站在地面,而不是嵌在地面裏。同時它也將 self.collide_delta 設爲 0,使得程序可以知道玩家未處在跳躍中。除此以外,它將 self.movey 設爲 0,使得程序可以知道玩家當前未受到重力的牽引做用(這是遊戲物理引擎的奇怪之處,一旦玩家落地,也就沒有必要繼續將玩家拉向地面)。

此處 if 語句用來檢測玩家是否已經落到地面之,若是是,那就扣除一點生命值做爲懲罰。此處假定了你但願當你的玩家落到地圖以外時失去生命值。這個設定不是必需的,它只是平臺類遊戲的一種慣例。更有可能的是,你但願這個事件可以觸發另外一些事件,或者說是一種可以讓你的現實世界玩家沉迷於讓精靈掉到屏幕以外的東西。一種簡單的恢復方式是在玩家精靈掉落到地圖以外時,將 self.rect.y 從新設置爲 0,這樣它就會在地圖上方從新生成,並落到堅實的地面上。

撞向地面

模擬的重力使你玩家的 Y 座標不斷增大(LCTT 譯註:此處原文中爲 0,但在 Pygame 中越靠下方 Y 座標應越大)。要實現跳躍,完成以下代碼使你的玩家精靈離開地面,飛向空中。

在你的 Player 類的 update 方法中,添加以下代碼來暫時延緩重力的做用:

        if self.collide_delta < 6 and self.jump_delta < 6:
            self.jump_delta = 6*2
            self.movey -= 33  # 跳躍的高度
            self.collide_delta += 6
            self.jump_delta    += 6
複製代碼

根據此代碼所示,跳躍使玩家精靈向空中移動了 33 個像素。此處是 33 是由於在 Pygame 中,越小的數表明距離屏幕頂端越近。

不過此事件視條件而定,只有當 self.collide_delta 小於 6(缺省值定義在你 Player 類的 init 方法中)而且 self.jump_delta 也於 6 的時候纔會發生。此條件可以保證直到玩家碰到一個平臺,才能觸發另外一次跳躍。換言之,它可以阻止空中二段跳。

在某些特殊條件下,你可能不想阻止空中二段跳,或者說你容許玩家進行空中二段跳。舉個栗子,若是玩家得到了某個戰利品,那麼在他被敵人攻擊到以前,都可以擁有空中二段跳的能力。

當你完成本篇文章中的示例,嘗試將 self.collide_deltaself.jump_delta 設置爲 0,從而得到百分之百的概率觸發空中二段跳。

在平臺上着陸

目前你已經定義了在玩家精靈摔落地面時的抵抗重力條件,但此時你的遊戲代碼仍保持平臺與地面置於不一樣的列表中(就像本文中作的不少其餘選擇同樣,這個設定並非必需的,你能夠嘗試將地面做爲另外一種平臺)。爲了容許玩家精靈站在平臺之上,你必須像檢測地面碰撞同樣,檢測玩家精靈與平臺精靈之間的碰撞。將以下代碼放於你的 update 方法中:

        plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False)
        for p in plat_hit_list:
            self.collide_delta = 0 # 跳躍結束
            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
複製代碼

此處 if 語句代碼塊的第一個子句阻止玩家精靈從平臺正下方跳到平臺上。若是它檢測到玩家精靈的座標比平臺更大(在 Pygame 中,座標更大意味着在屏幕的更下方),那麼將玩家精靈新的 Y 座標設置爲當前平臺的 Y 座標加上一個瓷磚的高度。實際效果就是保證玩家精靈距離平臺一個瓷磚的高度,防止其從下方穿過平臺。

else 子句作了相反的事情。當程序運行到此處時,若是玩家精靈的 Y 座標比平臺的更大,意味着玩家精靈是從空中落下(不管是因爲玩家剛剛今後處生成,或者是玩家執行了跳躍)。在這種狀況下,玩家精靈的 Y 座標被設爲平臺的 Y 座標減去一個瓷磚的高度(切記,在 Pygame 中更小的 Y 座標表明在屏幕上的更高處)。這樣就能保證玩家在平臺,除非他從平臺上跳下來或者走下來。

你也能夠嘗試其餘的方式來處理玩家與平臺之間的互動。舉個栗子,也許玩家精靈被設定爲處在平臺的「前面」,他可以無障礙地跳躍穿過平臺並站在上面。或者你能夠設計一種平臺會減緩而又不徹底阻止玩家的跳躍過程。甚至你能夠經過將不一樣平臺分到不一樣列表中來混合搭配使用。

觸發一次跳躍

目前爲此,你的代碼已經模擬了全部必需的跳躍條件,但仍缺乏一個跳躍觸發器。你的玩家精靈的 self.jump_delta 初始值被設置爲 6,只有當它比 6 小的時候纔會觸發更新跳躍的代碼。

爲跳躍變量設置一個新的設置方法,在你的 Player 類中建立一個 jump 方法,並將 self.jump_delta 設爲小於 6 的值。經過使玩家精靈向空中移動 33 個像素,來暫時減緩重力的做用。

    def jump(self,platform_list):
        self.jump_delta = 0
複製代碼

無論你相信與否,這就是 jump 方法的所有。剩餘的部分在 update 方法中,你已經在前面實現了相關代碼。

要使你遊戲中的跳躍功能生效,還有最後一件事情要作。若是你想不起來是什麼,運行遊戲並觀察跳躍是如何生效的。

問題就在於你的主循環中沒有調用 jump 方法。先前你已經爲該方法建立了一個按鍵佔位符,如今,跳躍鍵所作的就是將 jump 打印到終端。

調用 jump 方法

在你的主循環中,將方向鍵的效果從打印一條調試語句,改成調用 jump 方法。

注意此處,與 update 方法相似,jump 方法也須要檢測碰撞,所以你須要告訴它使用哪一個 plat_list

            if event.key == pygame.K_UP or event.key == ord('w'):
                player.jump(plat_list)
複製代碼

若是你傾向於使用空格鍵做爲跳躍鍵,使用 pygame.K_SPACE 替代 pygame.K_UP 做爲按鍵。另外一種選擇,你能夠同時使用兩種方式(使用單獨的 if 語句),給玩家多一種選擇。

如今來嘗試你的遊戲吧!在下一篇文章中,你將讓你的遊戲捲動起來。

Pygame 平臺類遊戲
Pygame platformer

如下是目前爲止的全部代碼:

#!/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

# 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

''' Objects '''

class Platform(pygame.sprite.Sprite):
    # x 座標,y 座標,圖像寬度,圖像高度,圖像文件
    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):
    '''     生成一個玩家     '''
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.movex = 0
        self.movey = 0
        self.frame = 0
        self.health = 10
        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):
        '''         控制玩家移動         '''
        self.movex += x
        self.movey += y
       
    def update(self):
        '''         更新精靈位置         '''
       
        self.rect.x = self.rect.x + self.movex
        self.rect.y = self.rect.y + self.movey

        # 向左移動
        if self.movex < 0:
            self.frame += 1
            if self.frame > ani*3:
                self.frame = 0
            self.image = self.images[self.frame//ani]

        # 向右移動
        if self.movex > 0:
            self.frame += 1
            if self.frame > ani*3:
                self.frame = 0
            self.image = self.images[(self.frame//ani)+4]

        # 碰撞
        enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
        for enemy in enemy_hit_list:
            self.health -= 1
            #print(self.health)

        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):
    '''     生成一個敵人     '''
    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):
        '''         敵人移動         '''
        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') # 生成敵人
            enemy_list = pygame.sprite.Group() # 建立敵人組
            enemy_list.add(enemy)              # 將敵人添加到敵人組
           
        if lvl == 2:
            print("Level " + str(lvl) )

        return enemy_list

    def loot(lvl,lloc):
        print(lvl)

    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((0,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

''' Setup '''
worldx = 960
worldy = 720

fps = 40 # 幀率
ani = 4  # 動畫循環
clock = pygame.time.Clock()
pygame.init()
main = True

BLUE  = (25,25,200)
BLACK = (23,23,23 )
WHITE = (254,254,254)
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() # 生成玩家
player.rect.x = 0
player.rect.y = 0
player_list = pygame.sprite.Group()
player_list.add(player)
steps = 10 # how fast to move
jump = -24

eloc = []
eloc = [200,20]
gloc = []
#gloc = [0,630,64,630,128,630,192,630,256,630,320,630,384,630]
tx = 64 # 瓷磚尺寸
ty = 64 # 瓷磚尺寸

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 )

''' 主循環 '''
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

#    world.fill(BLACK)
    world.blit(backdrop, backdropbox)
    player.gravity() # 檢查重力
    player.update()
    player_list.draw(world) # 刷新玩家位置
    enemy_list.draw(world)  # 刷新敵人
    ground_list.draw(world)  # 刷新地面
    plat_list.draw(world)   # 刷新平臺
    for e in enemy_list:
        e.move()
    pygame.display.flip()
    clock.tick(fps)
複製代碼

本期是使用 Pygame 模塊在 Python 3 中建立視頻遊戲連載系列的第 7 期。往期文章爲:


via: opensource.com/article/19/…

做者:Seth Kenlon 選題:lujun9972 譯者:cycoe 校對:wxy

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

相關文章
相關標籤/搜索