Python 实现平台类游戏添加跳跃功能


Posted in Python onMarch 27, 2020

 在本期使用 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_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 语句),给玩家多一种选择。

现在来尝试你的游戏吧!在下一篇文章中,你将让你的游戏卷动起来。

Python 实现平台类游戏添加跳跃功能

以下是目前为止的所有代码:

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

总结

到此这篇关于Python 实现平台类游戏添加跳跃功能的文章就介绍到这了,更多相关python 平台类游戏 跳跃内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
python将多个文本文件合并为一个文本的代码(便于搜索)
Mar 13 Python
python基础教程之元组操作使用详解
Mar 25 Python
python应用程序在windows下不出现cmd窗口的办法
May 29 Python
python继承和抽象类的实现方法
Jan 14 Python
python reduce 函数使用详解
Dec 05 Python
Python实现自定义顺序、排列写入数据到Excel的方法
Apr 23 Python
对python3中, print横向输出的方法详解
Jan 28 Python
pytorch构建多模型实例
Jan 15 Python
new_zeros() pytorch版本的转换方式
Feb 18 Python
使用K.function()调试keras操作
Jun 17 Python
Python requests及aiohttp速度对比代码实例
Jul 16 Python
linux mint中搜狗输入法导致pycharm卡死的问题
Oct 28 Python
django配置app中的静态文件步骤
Mar 27 #Python
使用卷积神经网络(CNN)做人脸识别的示例代码
Mar 27 #Python
django实现HttpResponse返回json数据为中文
Mar 27 #Python
python对XML文件的操作实现代码
Mar 27 #Python
Python Socketserver实现FTP文件上传下载代码实例
Mar 27 #Python
使用python从三个角度解决josephus问题的方法
Mar 27 #Python
解决django接口无法通过ip进行访问的问题
Mar 27 #Python
You might like
PHP daddslashes 使用方法介绍
2012/10/26 PHP
PHP实现将科学计数法转换为原始数字字符串的方法
2014/12/16 PHP
使用PHP访问RabbitMQ消息队列的方法示例
2018/06/06 PHP
php7 新增功能实例总结
2020/05/25 PHP
基础的prototype.js常用函数及其用法
2007/03/10 Javascript
JavaScript 处理Iframe自适应高度(同或不同域名下)
2013/03/29 Javascript
jQuery 网易相册鼠标移动显示隐藏效果实现代码
2013/03/31 Javascript
JavaScript的setAttribute兼容性问题解决方法
2013/11/11 Javascript
常见的jQuery选择器汇总
2014/11/24 Javascript
jquery.ajax之beforeSend方法使用介绍
2014/12/08 Javascript
百度搜索框智能提示案例jsonp
2016/11/28 Javascript
js前端日历控件(悬浮、拖拽、自由变形)
2017/03/02 Javascript
jquery PrintArea 实现票据的套打功能(代码)
2017/03/17 Javascript
详解用vue.js和laravel实现微信支付
2017/06/23 Javascript
浅谈关于.vue文件中style的scoped属性
2017/08/19 Javascript
jQuery UI实现动画效果代码分享
2018/08/19 jQuery
Vue状态模式实现窗口停靠功能(灵动、自由, 管理后台Admin界面)
2020/03/06 Javascript
[48:24]完美世界DOTA2联赛循环赛LBZS vs Forest 第一场 10月30日
2020/10/31 DOTA
python基础教程之基本数据类型和变量声明介绍
2014/08/29 Python
初步介绍Python中的pydoc模块和distutils模块
2015/04/13 Python
python快速建立超简单的web服务器的实现方法
2018/02/17 Python
python实现支付宝当面付(扫码支付)功能
2018/05/30 Python
Python实现查找二叉搜索树第k大的节点功能示例
2019/01/24 Python
Python range、enumerate和zip函数用法详解
2019/09/11 Python
Python列表删除元素del、pop()和remove()的区别小结
2019/09/11 Python
python 错误处理 assert详解
2020/04/20 Python
TensorFlow使用Graph的基本操作的实现
2020/04/22 Python
DataFrame.groupby()所见的各种用法详解
2020/06/14 Python
详解HTML5中ol标签的用法
2015/09/08 HTML / CSS
俄罗斯便宜的在线服装商店:GroupPrice
2020/04/10 全球购物
Prototype如何实现页面局部定时刷新
2013/08/06 面试题
物业公司的岗位任命书
2014/06/06 职场文书
庆祝国庆节演讲稿2014
2014/09/19 职场文书
离职报告格式
2014/11/04 职场文书
青涩记忆观后感
2015/06/18 职场文书
详解MongoDB的条件查询和排序
2021/06/23 MongoDB