為你的 Python 平臺類遊戲新增跳躍功能
在本期使用 Python Pygame 模組編寫影片遊戲中,學會如何使用跳躍來對抗重力。
在本系列的 前一篇文章 中,你已經模擬了重力。但現在,你需要賦予你的角色跳躍的能力來對抗重力。
跳躍是對重力作用的暫時延緩。在這一小段時間裡,你是向上跳,而不是被重力拉著向下落。但你一旦到達了跳躍的最高點,重力就會重新發揮作用,將你拉回地面。
在程式碼中,這種變化被表示為變數。首先,你需要為玩家精靈建立一個變數,使得 Python 能夠跟蹤該精靈是否正在跳躍中。一旦玩家精靈開始跳躍,他就會再次受到重力的作用,並被拉回最近的物體。
設定跳躍狀態變數
你需要為你的 Player
類新增兩個新變數:
- 一個是為了跟蹤你的角色是否正在跳躍中,可透過你的玩家精靈是否站在堅實的地面來確定
- 一個是為了將玩家帶回地面
將如下兩個變數新增到你的 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 看看會發生什麼。
跳躍中的碰撞
如果你是跳到一個蹦床上,那你的跳躍一定非常優美。但是如果你是跳向一面牆會發生什麼呢?(千萬不要去嘗試!)不管你的起跳多麼令人印象深刻,當你撞到比你更大更硬的物體時,你都會立馬停下。(LCTT 譯註:原理參考動量守恆定律)
為了在你的影片遊戲中模擬這一點,你需要在你的玩家精靈與地面等東西發生碰撞時,將 self.collide_delta
變數設為 0。如果你的 self.collide_delta
不是 0 而是其它的什麼值,那麼你的玩家就會發生跳躍,並且當你的玩家與牆或者地面發生碰撞時無法跳躍。
在你的 Player
類的 update
方法中,將地面碰撞相關程式碼塊修改為如下所示:
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_delta
和 self.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
語句),給玩家多一種選擇。
現在來嘗試你的遊戲吧!在下一篇文章中,你將讓你的遊戲捲動起來。
以下是目前為止的所有程式碼:
#!/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 期。往期文章為:
- 透過構建一個簡單的擲骰子游戲去學習怎麼用 Python 程式設計
- 使用 Python 和 Pygame 模組構建一個遊戲框架
- 如何在你的 Python 遊戲中新增一個玩家
- 用 Pygame 使你的遊戲角色移動起來
- 如何向你的 Python 遊戲中新增一個敵人
- 在 Pygame 遊戲中放置平臺
- 在你的 Python 遊戲中模擬引力
via: https://opensource.com/article/19/12/jumping-python-platformer-game
作者:Seth Kenlon 選題:lujun9972 譯者:cycoe 校對:wxy
本文由 LCTT 原創編譯,Linux中國 榮譽推出
相關文章
- 跳躍遊戲遊戲
- [Leetcode]44.跳躍遊戲Ⅰ&&45.跳躍遊戲ⅡLeetCode遊戲
- 貪心——55. 跳躍遊戲 && 45.跳躍遊戲II遊戲
- 0055-跳躍遊戲遊戲
- 專訪《逐光之旅》製作人:為何要開發一款平臺跳躍遊戲遊戲
- 跳躍遊戲精細化遊戲
- 平臺遊戲中走與跳的實現遊戲
- leetcode 45. 跳躍遊戲 IILeetCode遊戲
- 55-jump Game 跳躍遊戲GAM遊戲
- 雷亞新遊《MO : Astray 細胞迷途》:解謎、戰鬥雜糅的畫素平臺跳躍遊戲AST遊戲
- 試著跳一下?講講遊戲中的“跳躍”遊戲
- LeetCode 45跳躍遊戲&46全排列LeetCode遊戲
- 動漫風3D平臺跳躍遊戲RKGK將於5月23日登陸Steam3D遊戲
- 「KTSC 2024 R2」跳躍遊戲 題解遊戲
- LeetCode 55. 跳躍遊戲 ( 回溯 dp 貪心LeetCode遊戲
- 【LeetCode】55. 跳躍遊戲 (動態規劃)LeetCode遊戲動態規劃
- 平臺跳躍虐心闖關多人合作遊戲《魔導戰爭》今日正式在好遊快爆APP上線遊戲APP
- Aurora Engine 遊戲引擎入門 13(新增平臺的輸入)遊戲引擎
- 平臺跳躍試驗小品《Himno》免費上架中
- 奔跑、跳躍、攀爬,遊戲基礎動作的趣味設計遊戲
- leetcode:跳躍遊戲II(java貪心演算法)LeetCode遊戲Java演算法
- 位元組跳動打遊戲,抖音平臺當先鋒?遊戲
- 新增計分到你的 Python 遊戲Python遊戲
- 如何為平臺遊戲設計關卡?遊戲設計
- 《崩潰製造》挑戰你的平臺遊戲天分遊戲
- 椅子捏六天,跳躍做一年 為何開發遊戲這麼難?開發遊戲
- 位元組跳動遊戲業務新動作,上線休閒遊戲分發平臺遊戲
- 手遊折扣平臺 遊戲打折扣的平臺推薦遊戲
- 《Dreams》不僅是遊戲,更是你施展創意的平臺遊戲
- Python複習筆記跳躍版Python筆記
- 完美世界平臺跳躍新作《逐光之旅》將登陸PC和NS
- PC全平臺預約開啟!《以閃亮之名》成為首個霸佔主流遊戲平臺的女性遊戲遊戲
- 皇家遊戲平臺 16606959990遊戲
- 為你的論壇系統新增一個『提及』功能
- 遊戲行業Youtube? Epic為遊戲創作平臺注資1500萬遊戲行業
- "跳躍"在遊戲中的運用,多樣化關卡和戰鬥技能,唯一的信仰之躍遊戲
- 從馬里奧到只狼,“跳躍”何以貫穿遊戲史?遊戲
- 圈圈跳躍