作者 | marble_xu

编辑 | 郭芮

出品 | CSDN博客

在《如何用 Python 实现超级玛丽的界面和状态机?》这篇文章中我们讲解如何用代码实现界面和状态机,本文详解人物行走和碰撞检测的实现。

功能介绍

人物行走

人物的行走速度这边分成水平方向(X轴)和竖直方向(Y轴),水平方向的速度要考虑加速度和摩擦力,竖直方向的速度要考虑重力加速度。

  • 水平方向:设定X轴向右走的速度为大于0,向左走的速度为小于0;

  • 竖直方向:设定Y轴向下的速度为大于0,向上的速度为小于0。

游戏中的人物有下面几个主要的状态:

  • 站立不动:水平方向速度为0,且竖直方向站在某个物体上。

  • 向左或向右走:水平方向速度的绝对值大于0,且竖直方向站在某个物体上。

  • 向上跳:竖直方向方向速度小于0,且上方没有碰到某个物体,同时需要玩家按住jump键。

  • 向下降落:竖直方向方向速度大于0或者玩家没有按住jump键,且下方没有碰到某个物体。

向上跳和向下降落的状态判断可能一开始比较难理解,可以看后面的具体实现,目的是如果玩家长按jump键时,可以让人物跳的更高。

上面的判断是否站在某个物体上,或者是否碰到某个物体,就需要用到物体之间的碰撞检测。

碰撞检测

对于游戏中出现的每一样东西,比如砖块,箱子,水管,地面,还有人物都可以看成是一个独立的物体,所以每个物体类都继承了pygame的精灵类pg.sprite.Sprite,可以使用精灵类提供的碰撞检测函数来判断。

设置source\constants.py 中的变量DEBUG值为True,可以看到图1的游戏截图,比如最简单的地面,可以看成是一个长方形的物体。

  • 下方红色的长方形物体就是地面(ground);

  • 右边的几个红色小方块是阶梯(step);

  • 左边空中的像墙一样的是砖块(brick);

  • 带问号的是箱子(box)。

因为人物是站在地面上,且水平速度为0,所以当前的人物状态就是站立不动。

图1

游戏代码

游戏实现代码的github链接:

https://github.com/marblexu/PythonSuperMario.git

这边是csdn的下载链接:

https://download.csdn.net/download/marble_xu/11391533

代码介绍

人物行走代码

有一个单独的人物类,在source\components\player.py 中,其中有个handle_state 函数,根据人物当前的状态执行不同的函数。

为了简洁下面所有函数中将不相关的代码都省略掉了。

    def handle_state(self, keys, fire_group):if self.state == c.STAND:self.standing(keys, fire_group)elif self.state == c.WALK:self.walking(keys, fire_group)elif self.state == c.JUMP:self.jumping(keys, fire_group)elif self.state == c.FALL:self.falling(keys, fire_group)

人物的状态就是上面说的4个状态:

  • 站立不动:c.STAND

  • 向左或向右走:c.WALK

  • 向上跳:c.JUMP

  • 向下降落:c.FALL

人物类关于行走速度的成员变量先了解下:

水平方向相关的:

  • x_accel:水平方向的加速度,值大于0,不区别方向。

  • max_x_vel:水平方向的最大速度,值大于0,不区别方向。

  • x_vel:水平方向的速度,值大于0表示向右走,值小于0表示向左走。

  • 初始值:max_run_vel和max_walk_vel 表示最大速度,run_accel和walk_accel表示加速度。

  • facing_right:值为True表示当前是向右走,值为False表示当前是向左走,这个是用来设置人物的图像。

竖直方向相关的:

  • gravity:重力加速度,值大于0,表示方向向下。

  • jump_vel:起跳时竖直方向的初始速度,值小于0,表示方向向上。

  • y_vel:竖直方向的速度。

看下最复杂的 walking 函数,keys数组是当前按下的键盘输入,tools.keybinding中值的含义如下:

keybinding = {'action':pg.K_s,'jump':pg.K_a,'left':pg.K_LEFT,'right':pg.K_RIGHT,'down':pg.K_DOWN
}
  • 先根据当前是否有按下 keybinding[‘action’] 键来设置不同的最大水平方向速度和水平方向加速度。

  • 如果有按下 keybinding[‘jump’] 键,则设置人物状态为c.JUMP,初始化竖直方向的速度。

  • 如果有按下keybinding[‘left’]键,表示要向左走,如果 x_vel 大于0,表示之前是向右走的,所以设置一个转身的加速度为SMALL_TURNAROUND,然后调用cal_vel 函数根据之前的速度和加速度,计算出当前的速度。

  • 如果有按下keybinding[‘right’]键,表示要向右走,和上面类似。

  • 如果没有按下keybinding[‘left’]键和keybinding[‘right’]键,就像有摩擦力的存在,则水平方向的速度会慢慢变成0,如果 x_vel 值为0,则设置人物状态为c.STAND。

    def walking(self, keys, fire_group):        if keys[tools.keybinding['action']]:self.max_x_vel = self.max_run_velself.x_accel = self.run_accelelse:self.max_x_vel = self.max_walk_velself.x_accel = self.walk_accelif keys[tools.keybinding['jump']]:if self.allow_jump:self.state = c.JUMPif abs(self.x_vel) > 4:self.y_vel = self.jump_vel - .5else:self.y_vel = self.jump_velif keys[tools.keybinding['left']]:self.facing_right = Falseif self.x_vel > 0:self.frame_index = 5self.x_accel = c.SMALL_TURNAROUNDself.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel, True)elif keys[tools.keybinding['right']]:self.facing_right = Trueif self.x_vel < 0:self.frame_index = 5self.x_accel = c.SMALL_TURNAROUNDself.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel)else:if self.facing_right:if self.x_vel > 0:self.x_vel -= self.x_accelelse:self.x_vel = 0self.state = c.STANDelse:if self.x_vel < 0:self.x_vel += self.x_accelelse:self.x_vel = 0self.state = c.STANDdef cal_vel(self, vel, max_vel, accel, isNegative=False):""" max_vel and accel must > 0 """if isNegative:new_vel = vel * -1else:new_vel = velif (new_vel + accel) < max_vel:new_vel += accelelse:new_vel = max_velif isNegative:return new_vel * -1else:return new_vel

再看下jumping 函数:

  • 开始gravity 设为 c.JUMP_GRAVITY,可以看到JUMP_GRAVITY 比GRAVITY值小很多,如果玩家长按jump键时,可以让人物跳的更高。

  • 如果竖直方向速度y_vel 大于0,表示方向向下,则设置人物状态为c.FALL。

  • 如果按下 keybinding[‘left’]键或 keybinding[‘right’]键,则计算水平方向的速度。

  • 如果没有按 keybinding[‘jump’]键,则设置人物状态为c.FALL。

JUMP_GRAVITY = .31
GRAVITY = 1.01def jumping(self, keys, fire_group):""" y_vel value: positive is down, negative is up """  self.allow_jump = Falseself.frame_index = 4self.gravity = c.JUMP_GRAVITYself.y_vel += self.gravityif self.y_vel >= 0 and self.y_vel < self.max_y_vel:self.gravity = c.GRAVITYself.state = c.FALLif keys[tools.keybinding['right']]:self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel)elif keys[tools.keybinding['left']]:self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel, True)if not keys[tools.keybinding['jump']]:self.gravity = c.GRAVITYself.state = c.FALL

standing函数和 falling 函数比较简单,就省略了。

碰撞检测代码

人物的碰撞检测代码在 source\states\level.py 中的入口是update_player_position函数 ,可以看到这边分成水平方向和竖直方向:

  • 根据人物的水平方向速度x_vel 更新人物的X轴位置,同时人物的X轴位置不能超出游戏地图的X轴范围,然后调用check_player_x_collisions函数进行水平方向的碰撞检测。

  • 根据人物的竖直方向速度y_vel 更新人物的Y轴位置,然后调用check_player_y_collisions函数进行竖直方向的碰撞检测。

    def update_player_position(self):self.player.rect.x += round(self.player.x_vel)if self.player.rect.x < self.start_x:self.player.rect.x = self.start_xelif self.player.rect.right > self.end_x:self.player.rect.right = self.end_xself.check_player_x_collisions()if not self.player.dead:self.player.rect.y += round(self.player.y_vel)self.check_player_y_collisions()

具体实现时将同一类物体放在一个pygame.sprite.Group类中:

pygame.sprite.GroupA container class to hold and manage multiple Sprite objects.Group(*sprites) -> Group

这样每次调用pg.sprite.spritecollideany 函数就能判断人物和这一类物体是否有碰撞。

pygame.sprite.spritecollideany()Simple test if a sprite intersects anything in a group.spritecollideany(sprite, group, collided = None) -> Sprite Collision with the returned sprite.spritecollideany(sprite, group, collided = None) -> None No collision

不同物体的group如下,另外敌人,金币和蘑菇等物体的碰撞检测先忽略。

  • ground_step_pipe_group:地面,阶梯和水管的group。

  • brick_group:砖块的group, 如果是金币砖块,从下面碰撞会获取金币。

  • box_group:箱子的group,从下面碰撞箱子可以出现金币,蘑菇,花等的奖励。

因为不同种类group撞击时,后续产生的结果会有区别,所有需要对每一类group分别进行碰撞检测。

X轴方向上面3类group如果检测到有碰撞时,会调用adjust_player_for_x_collisions 函数,来调整人物的X轴位置。

    def check_player_x_collisions(self):ground_step_pipe = pg.sprite.spritecollideany(self.player, self.ground_step_pipe_group)brick = pg.sprite.spritecollideany(self.player, self.brick_group)box = pg.sprite.spritecollideany(self.player, self.box_group)...if box:self.adjust_player_for_x_collisions(box)elif brick:self.adjust_player_for_x_collisions(brick)elif ground_step_pipe:if (ground_step_pipe.name == c.MAP_PIPE andground_step_pipe.type == c.PIPE_TYPE_HORIZONTAL):returnself.adjust_player_for_x_collisions(ground_step_pipe)elif powerup:...elif enemy:...elif coin:...

adjust_player_for_x_collisions 函数先根据人物和碰撞物体的X轴相对位置,判断人物在碰撞物体的左边还是右边,来调整人物的X轴位置,然后设置人物水平方向的速度为0。

    def adjust_player_for_x_collisions(self, collider):if collider.name == c.MAP_SLIDER:returnif self.player.rect.x < collider.rect.x:self.player.rect.right = collider.rect.leftelse:self.player.rect.left = collider.rect.rightself.player.x_vel = 0

check_player_y_collisions 函数也是对不同group分别进行碰撞检测,Y轴方向这3类group如果检测到有碰撞时,会调用adjust_player_for_y_collisions 函数,来调整人物的Y轴位置。

最后调用check_is_falling函数判断人物是否要设成向下降落的状态。

    def check_player_y_collisions(self):ground_step_pipe = pg.sprite.spritecollideany(self.player, self.ground_step_pipe_group)# decrease runtime delay: when player is on the ground, don't check brick and boxif self.player.rect.bottom < c.GROUND_HEIGHT:brick = pg.sprite.spritecollideany(self.player, self.brick_group)box = pg.sprite.spritecollideany(self.player, self.box_group)brick, box = self.prevent_collision_conflict(brick, box)else:brick, box = False, Falseif box:self.adjust_player_for_y_collisions(box)elif brick:self.adjust_player_for_y_collisions(brick)elif ground_step_pipe:self.adjust_player_for_y_collisions(ground_step_pipe)elif enemy:...elif shell:...self.check_is_falling(self.player)

adjust_player_for_y_collisions 函数先根据人物和碰撞物体的Y轴相对位置,判断人物在碰撞物体的下边还是上边,来调整人物的Y轴位置:

  • 如果人物在碰撞物体的下边,则有一个反弹的效果,设置人物的竖直方向速度为7,调整人物的Y轴位置,设置人物状态为c.FALL。如果碰撞物体为砖块或箱子,还要进行后续处理。

  • 如果人物在碰撞物体的上边,设置人物的竖直方向速度为0,调整人物的Y轴位置,一般情况下设置人物状态为c.WALK。

    def adjust_player_for_y_collisions(self, sprite):if self.player.rect.top > sprite.rect.top:if sprite.name == c.MAP_BRICK:...elif sprite.name == c.MAP_BOX:...elif (sprite.name == c.MAP_PIPE andsprite.type == c.PIPE_TYPE_HORIZONTAL):returnself.player.y_vel = 7self.player.rect.top = sprite.rect.bottomself.player.state = c.FALLelse:self.player.y_vel = 0self.player.rect.bottom = sprite.rect.topif self.player.state == c.FLAGPOLE:self.player.state = c.WALK_AUTOelif self.player.state == c.END_OF_LEVEL_FALL:self.player.state = c.WALK_AUTOelse:self.player.state = c.WALK

check_is_falling函数 判断人物下方是否有物体,有个小技巧,就是先将人物的Y轴位置向下移动1,然后判断和上面三类group是否有碰撞:

  • 如果没有碰撞,表示人物下方没有物体,这时候如果人物状态不是 c.JUMP 和一些特殊状态,就设置人物状态为 c.FALL。

  • 如果有碰撞,则不用管。

最后将人物的Y轴位置恢复(向上移动1)。

    def check_is_falling(self, sprite):sprite.rect.y += 1check_group = pg.sprite.Group(self.ground_step_pipe_group,self.brick_group, self.box_group)if pg.sprite.spritecollideany(sprite, check_group) is None:if (sprite.state == c.WALK_AUTO orsprite.state == c.END_OF_LEVEL_FALL):sprite.state = c.END_OF_LEVEL_FALLelif (sprite.state != c.JUMP and sprite.state != c.FLAGPOLE andnot self.in_frozen_state()):sprite.state = c.FALLsprite.rect.y -= 1

版权声明:

本文为CSDN博主「marble_xu」的原创文章,原文链接:

https://blog.csdn.net/marble_xu/article/details/100022385

推荐阅读

  • 滴滴叶杰平:年运送乘客百亿次,AI如何“服务”出行领域?| BDTC 2019

  • 人工智能尴尬的2019:需要钱却没钱可烧了

  • 不要让 Chrome 成为下一个 IE

  • 通向人工智能产业落地化的道路在哪?

  • OPPO 物联网开放之路

  • 把自己朝九晚五的工作自动化了,有错吗?

  • 迎风而来|刮向央视的这朵云是什么来头?

  • 量子通信,到底是什么工作原理?

  • 这三名男子靠开加密矿池获得7.22 亿美元,却不兑现收益拿去奢侈挥霍……

  • 你点的每个“在看”,我都认真当成了AI

如何用Python实现超级玛丽的人物行走和碰撞检测?相关推荐

  1. 如何用 Python 实现超级玛丽的人物行走和碰撞检测?

    功能介绍 人物行走 人物的行走速度这边分成水平方向(X轴)和竖直方向(Y轴),水平方向的速度要考虑加速度和摩擦力,竖直方向的速度要考虑重力加速度. 水平方向:设定X轴向右走的速度为大于0,向左走的速度 ...

  2. 如何用Python实现超级玛丽的界面和状态机?

    作者 | marble_xu 编辑 | 郭芮 来源 | CSDN博客 小时候的经典游戏,代码参考了github上的项目Mario-Level-1(https://github.com/justinme ...

  3. 如何用 Python 实现超级玛丽的界面和状态机?

    作者 | marble_xu 责编 | 郭芮 出品 | CSDN博客 小时候的经典游戏,代码参考了github上的项目Mario-Level-1(https://github.com/justinme ...

  4. 如何用python读取文本中指定行的内容

    如何用python读取文本中指定行的内容 搜索资料 我来答 分享 新浪微博 QQ空间 浏览 5284 次 查看全文 http://www.taodudu.cc/news/show-64036.ht ...

  5. python判断素数的函数_如何用python求素数

    如何用python求100以内的素数? 质数(primenumber)又称素数,有无限个.质数定义为在大于1的自然数中,除了1和它本身以外不再有其他因数的数称为质数,如:2.3.5.7.11.13.1 ...

  6. 怎么用python画个电脑_python语言还是java如何用python画爱心

    用python绘制爱心的基本步骤如下: 002pc.com对<python语言还是java如何用python画爱心>总结来说,为我们学习Python很实用. 首先先下载安装好python程 ...

  7. python怎么读_如何用Python读写文件

    前面我们已经介绍了很多Python相关的基础知识,大家是不是对Python已经有了进一步认识了呢?作为人工智能时代的热门编程语言,开始接触并学习Python的孩子越来越多,家长们都不想让自己的孩子落于 ...

  8. 如何用Python批量提取PDF文本内容?

    本文为你展示,如何用Python把许多PDF文件的文本内容批量提取出来,并且整理存储到数据框中,以便于后续的数据分析. 问题 最近,读者们在后台的留言,愈发五花八门了. 写了几篇关于自然语言处理的文章 ...

  9. 如何用Python和BERT做中文文本二元分类?| 程序员硬核评测

    点击上方↑↑↑蓝字关注我们~ 「2019 Python开发者日」全日程揭晓,请扫码咨询 ↑↑↑ 作者 | 王树义 来源 | 王树芝兰(ID:nkwangshuyi) 兴奋 去年, Google 的 B ...

最新文章

  1. Linux常用命令的简单实用
  2. 免费教材丨第47期:业界大牛中文教学视频《深度学习:进阶》第21-24讲
  3. 【Linux基础】查看硬件信息-内存和硬盘
  4. android鼠标dpi,对Android 中 px、DPI、dp(dip)、density的理解
  5. python的数组属性_[Python]numpy use array属性,pythonnumpy,使用,数组
  6. 【ABAP增强】基于BADI的增强
  7. Linux下如何删除长时间不使用的旧文件?
  8. Android 系统(177)---Android消息机制分析:Handler、Looper、MessageQueue源码分析
  9. vue学习-声明周期和实例
  10. brew mysql要多久_mac brew 安装 mysql
  11. 修改棋牌服务器,关于棋牌服务器的一些程序搭建和数据应用步骤
  12. 二进制原码一位乘法运算
  13. 人行261号文关于电子账户的解读
  14. 1395786-30-7,DBCO Maleimide,DBCO-Mal
  15. [Android]SIM字段EF_SUME(6F54) 可用作STK app name
  16. 将Excel数据转换为XML
  17. 使用数据库进行用户身份认证
  18. php打印99乘法表加粗,PHP基础循环语句之打印99乘法表
  19. Python数据转换-有符号短整型转换无符号短整型
  20. 明明装了驱动,当前打印机不可用,怎么办?

热门文章

  1. Codeforces 862B - Mahmoud and Ehab and the bipartiteness
  2. 正则表达式grep、egrep--already
  3. linux中登录类型及配置文件
  4. Spring MVC配置
  5. 外观模式 - 设计模式学习
  6. 作为程序员应有10项权利
  7. 下一代安全威胁的内幕故事
  8. 在winform中从外部拖动节点到树形结构(treeview和listview相互拖动)(一)
  9. MySQL常见面试题及答案汇总1000道(春招+秋招+社招)
  10. 动态检测内存错误利器ASan