如何用 Python 实现超级玛丽的人物行走和碰撞检测?
off999 2024-09-27 13:51 50 浏览 0 评论
作者 | 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_vel
self.x_accel = self.run_accel
else:
self.max_x_vel = self.max_walk_vel
self.x_accel = self.walk_accel
if keys[tools.keybinding['jump']]:
if self.allow_jump:
self.state = c.JUMP
if abs(self.x_vel) > 4:
self.y_vel = self.jump_vel - .5
else:
self.y_vel = self.jump_vel
if keys[tools.keybinding['left']]:
self.facing_right = False
if self.x_vel > 0:
self.frame_index = 5
self.x_accel = c.SMALL_TURNAROUND
self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel, True)
elif keys[tools.keybinding['right']]:
self.facing_right = True
if self.x_vel < 0:
self.frame_index = 5
self.x_accel = c.SMALL_TURNAROUND
self.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_accel
else:
self.x_vel = 0
self.state = c.STAND
else:
if self.x_vel < 0:
self.x_vel += self.x_accel
else:
self.x_vel = 0
self.state = c.STAND
def cal_vel(self, vel, max_vel, accel, isNegative=False):
""" max_vel and accel must > 0 """
if isNegative:
new_vel = vel * -1
else:
new_vel = vel
if (new_vel + accel) < max_vel:
new_vel += accel
else:
new_vel = max_vel
if isNegative:
return new_vel * -1
else:
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.01
defjumping(self, keys, fire_group):
""" y_vel value: positive is down, negative is up """
self.allow_jump = False
self.frame_index = 4
self.gravity = c.JUMP_GRAVITY
self.y_vel += self.gravity
if self.y_vel >= 0 and self.y_vel < self.max_y_vel:
self.gravity = c.GRAVITY
self.state = c.FALL
if 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.GRAVITY
self.state = c.FALLstanding函数和 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_x
elif self.player.rect.right > self.end_x:
self.player.rect.right = self.end_x
self.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.Group
A 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 and
ground_step_pipe.type == c.PIPE_TYPE_HORIZONTAL):
return
self.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:
return
if self.player.rect.x < collider.rect.x:
self.player.rect.right = collider.rect.left
else:
self.player.rect.left = collider.rect.right
self.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 box
if 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, False
if 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 and
sprite.type == c.PIPE_TYPE_HORIZONTAL):
return
self.player.y_vel = 7
self.player.rect.top = sprite.rect.bottom
self.player.state = c.FALL
else:
self.player.y_vel = 0
self.player.rect.bottom = sprite.rect.top
if self.player.state == c.FLAGPOLE:
self.player.state = c.WALK_AUTO
elif self.player.state == c.END_OF_LEVEL_FALL:
self.player.state = c.WALK_AUTO
else:
self.player.state = c.WALKcheck_is_falling函数 判断人物下方是否有物体,有个小技巧,就是先将人物的Y轴位置向下移动1,然后判断和上面三类group是否有碰撞:
如果没有碰撞,表示人物下方没有物体,这时候如果人物状态不是 c.JUMP 和一些特殊状态,就设置人物状态为 c.FALL。
如果有碰撞,则不用管。
最后将人物的Y轴位置恢复(向上移动1)。
def check_is_falling(self, sprite):
sprite.rect.y += 1
check_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 or
sprite.state == c.END_OF_LEVEL_FALL):
sprite.state = c.END_OF_LEVEL_FALL
elif (sprite.state != c.JUMP and
sprite.state != c.FLAGPOLE and
not self.in_frozen_state):
sprite.state = c.FALL
sprite.rect.y -= 1版权声明:本文为CSDN博主「marble_xu」的原创文章,原文https://blog.csdn.net/marble_xu/article/details/100022385。
相关推荐
- 安全教育登录入口平台(安全教育登录入口平台官网)
-
122交通安全教育怎么登录:122交通网的注册方法是首先登录网址http://www.122.cn/,接着打开网页后,点击右上角的“个人登录”;其次进入邮箱注册,然后进入到注册页面,输入相关信息即可完...
- 大鱼吃小鱼经典版(大鱼吃小鱼经典版(经典版)官方版)
-
大鱼吃小鱼小鱼吃虾是于谦跟郭麒麟的《我的棒儿呢?》郭德纲说于思洋郭麒麟作诗的相声,最后郭麒麟做了一首,师傅躺在师母身上大鱼吃小鱼小鱼吃虾虾吃水水落石出师傅压师娘师娘压床床压地地动山摇。...
-
- 哪个软件可以免费pdf转ppt(免费的pdf转ppt软件哪个好)
-
要想将ppt免费转换为pdf的话,我们建议大家可以下一个那个wps,如果你是会员的话,可以注册为会员,这样的话,在wps里面的话,就可以免费将ppt呢转换为pdfpdf之后呢,我们就可以直接使用,不需要去直接不需要去另外保存,为什么格式转...
-
2026-02-04 09:03 off999
- 电信宽带测速官网入口(电信宽带测速官网入口app)
-
这个网站看看http://www.swok.cn/pcindex.jsp1.登录中国电信网上营业厅,宽带光纤,贴心服务,宽带测速2.下载第三方软件,如360等。进行在线测速进行宽带测速时,尽...
- 植物大战僵尸95版手机下载(植物大战僵尸95 版下载)
-
1可以在应用商店或者游戏平台上下载植物大战僵尸95版手机游戏。2下载教程:打开应用商店或者游戏平台,搜索“植物大战僵尸95版”,找到游戏后点击下载按钮,等待下载完成即可安装并开始游戏。3注意:确...
- 免费下载ppt成品的网站(ppt成品免费下载的网站有哪些)
-
1、Chuangkit(chuangkit.com)直达地址:chuangkit.com2、Woodo幻灯片(woodo.cn)直达链接:woodo.cn3、OfficePlus(officeplu...
- 2025世界杯赛程表(2025世界杯在哪个国家)
-
2022年卡塔尔世界杯赛程公布,全部比赛在卡塔尔境内8座球场举行,2022年,决赛阶段球队全部确定。揭幕战于当地时间11月20日19时进行,由东道主卡塔尔对阵厄瓜多尔,决赛于当地时间12月18日...
- 下载搜狐视频电视剧(搜狐电视剧下载安装)
-
搜狐视频APP下载好的视频想要导出到手机相册里方法如下1、打开手机搜狐视频软件,进入搜狐视频后我们点击右上角的“查找”,找到自已喜欢的视频。2、在“浏览器页面搜索”窗口中,输入要下载的视频的名称,然后...
- 永久免费听歌网站(丫丫音乐网)
-
可以到《我爱音乐网》《好听音乐网》《一听音乐网》《YYMP3音乐网》还可以到《九天音乐网》永久免费听歌软件有酷狗音乐和天猫精灵,以前要跳舞经常要下载舞曲,我从QQ上找不到舞曲下载就从酷狗音乐上找,大多...
- 音乐格式转换mp3软件(音乐格式转换器免费版)
-
有两种方法:方法一在手机上操作:1、进入手机中的文件管理。2、在其中选择“音乐”,将显示出手机中的全部音乐。3、点击“全选”,选中所有音乐文件。4、点击屏幕右下方的省略号图标,在弹出菜单中选择“...
- 电子书txt下载(免费的最全的小说阅读器)
-
1.Z-library里面收录了近千万本电子书籍,需求量大。2.苦瓜书盘没有广告,不需要账号注册,使用起来非常简单,直接搜索预览下载即可。3.鸠摩搜书整体风格简洁清晰,书籍资源丰富。4.亚马逊图书书籍...
- 最好免费观看高清电影(播放免费的最好看的电影)
-
在目前的网上选择中,IMDb(互联网电影数据库)被认为是最全的电影网站之一。这个网站提供了各种类型的电影和电视节目的海量信息,包括剧情介绍、演员表、评价、评论等。其还提供了有关电影制作背后的详细信息,...
- 孤单枪手2简体中文版(孤单枪手2简体中文版官方下载)
-
要将《孤胆枪手2》游戏的征兵秘籍切换为中文,您可以按照以下步骤进行操作:首先,打开游戏设置选项,通常可以在游戏主菜单或游戏内部找到。然后,寻找语言选项或界面选项,点击进入。在语言选项中,选择中文作为游...
欢迎 你 发表评论:
- 一周热门
- 最近发表
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python进度条 (67)
- python吧 (67)
- python的for循环 (65)
- python格式化字符串 (61)
- python静态方法 (57)
- python列表切片 (59)
- python面向对象编程 (60)
- python 代码加密 (65)
- python串口编程 (77)
- python封装 (57)
- python写入txt (66)
- python读取文件夹下所有文件 (59)
- python操作mysql数据库 (66)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python多态 (60)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)
