如何用 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 def jumping(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.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_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.WALK
check_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
如何用 Python 实现超级玛丽的人物行走和碰撞检测?的更多相关文章
- 如何用python抓取js生成的数据 - SegmentFault
如何用python抓取js生成的数据 - SegmentFault 如何用python抓取js生成的数据 1赞 踩 收藏 想写一个爬虫,但是需要抓去的的数据是js生成的,在源代码里看不到,要怎么才能抓 ...
- 如何用python下载一张图片
如何用python下载一张图片 这里要用到的主要工具是requests这个工具,需要先安装这个库才能使用,该库衍生自urllib这个库,但是要比它更好用.多数人在做爬虫的时候选择它,是个不错的选择. ...
- [置顶]
如何用PYTHON代码写出音乐
如何用PYTHON代码写出音乐 什么是MIDI 博主本人虽然五音不全,而且唱歌还很难听,但是还是非常喜欢听歌的.我一直在做这样的尝试,就是通过人工智能算法实现机器自动的作词和编曲(在这里预告下,通过深 ...
- 以下三种下载方式有什么不同?如何用python模拟下载器下载?
问题始于一个链接https://i1.pixiv.net/img-zip-...这个链接在浏览器打开,会直接下载一个不完整的zip文件 但是,使用下载器下载却是完整文件 而当我尝试使用python下载 ...
- 小姐姐带你一起学:如何用Python实现7种机器学习算法(附代码)
小姐姐带你一起学:如何用Python实现7种机器学习算法(附代码) Python 被称为是最接近 AI 的语言.最近一位名叫Anna-Lena Popkes的小姐姐在GitHub上分享了自己如何使用P ...
- 如何用python“优雅的”调用有道翻译?
前言 其实在以前就盯上有道翻译了的,但是由于时间问题一直没有研究(我的骚操作还在后面,记得关注),本文主要讲解如何用python调用有道翻译,讲解这个爬虫与有道翻译的js“斗争”的过程! 当然,本文仅 ...
- 如何用Python实现do...while语句
我在编程的时候可能会遇到如下代码: a = 0 while a != 0: a = input() print a 我所设想的运行过程是这样的: 很显然我是想先运行后判断的模式,即 do...whil ...
- 如何用Python统计《论语》中每个字的出现次数?10行代码搞定--用计算机学国学
编者按: 上学时听过山师王志民先生一场讲座,说每个人不论干什么,都应该学习国学(原谅我学了计算机专业)!王先生讲得很是吸引我这个工科男,可能比我的后来的那些同学听课还要认真些,当然一方面是兴趣.一方面 ...
- 如何用Python从海量文本抽取主题?
摘自https://www.jianshu.com/p/fdde9fc03f94 你在工作.学习中是否曾因信息过载叫苦不迭?有一种方法能够替你读海量文章,并将不同的主题和对应的关键词抽取出来,让你谈笑 ...
随机推荐
- AE 打开Shp文件
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...
- HTML /和./的区别 - Web开发
"/"访问根目录 例1 https://www.cnblogs.com/test 里有 <a href="/Edsuns"></a> 则 ...
- R data formats
R data formats: Rdata Rda Rds 1.概念 rds是R语言中利用二进制保存的源文件,加载readr包以后,使用write_rds(x,file='x.rds')保存文件,re ...
- day22 用户添加相关命令 特殊权限为 防止root修改文件
2) 用户相关的命令 useradd -u -g -G -M -s -c usermod -u -g -G -s -c userdel -r groupadd groupmod groupdel ch ...
- centos docker 防火墙设置(多个ip之间互相访问)
[Solution] Update firewall policy 1) Disabled docker rules of iptables --- docker will create ...
- mysql 中LIKE 与FIND_IN_SET 与关联表left join 速度效率比较
有一张表Table有IDStr字段,如下只显示二个字段还有很多其他字段 方式一 字段逗号分割,直接用UserIDStr字段,里面存多个ID用逗号分割 UUID UserIDStr 1111 1,2,3 ...
- Android 开发 SurfaceView 总结
Android中一种常见的自定义画UI接口类:SurfaceView.可以在异步线程中,完成相关数据更新. 首先介绍几个基本的定义,在其他知识中也会设计如下名词: 1.Paint 画笔,所有的图像.图 ...
- 一次列表页伪静态的实现;结合nginx rewrite
nginx伪静态: rewrite ^/(.*)-htm-(.*)$ /$1.php?$2; 将 list-html-t-3-p-4.html 转到list.php?t-3-p-4 t-3-p-4 用 ...
- MySql -- unique唯一约束
3.UNIQUE 约束 约束唯一标识数据库表中的每条记录. 创建一张测试表 CREATE TABLE `test`.`info`( `id` ) UNSIGNED NOT NULL AUTO_INCR ...
- Request功能
1.获取请求消息数据 获取请求行数据 获取请求头数据 获取请求体数据 请求空行没必要获取 1.获取请求行数据 GET /虚拟目录 /servlet路径 ?请求参数 HTTP/1.1 GET/day1 ...