在本期使用 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_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
方法。
注意此处,与 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 期。往期文章为:
via: opensource.com/article/19/…
做者:Seth Kenlon 选题:lujun9972 译者:cycoe 校对:wxy