如何用Python实现超级玛丽的人物行走和碰撞检测?
作者 | 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
相关文章:

WMI技术介绍和应用——查询正在运行的进程信息
在《WMI技术介绍和应用——使用VC编写一个半同步查询WMI服务的类》一文中,我们介绍到了一个半同步查询WMI类的框架。本文将是该技术的一个应用,介绍如何使用WMI技术查询正在运行的进程信息。(转载请指明出于breaksoftware的csdn博客ÿ…

20个经典要诀学好英语
出处:我学网互助论坛第一要诀:收听英语气象报告 有些教学录音带为配合初学者的学习,故意放慢语速,这对英语听力的训练是不够的。如果听语速正常的英语,初学者又会感到力不从心。英语气象报告的速度虽快,…

ArduinoYun教程之通过网络为Arduino Yun编程
ArduinoYun教程之通过网络为Arduino Yun编程 Arduino Yun的软件部分 通过第一章的介绍后读者就明白了Arduino Yun除了是一个类似其他Arduino的单片机之外,它的另一大组成部分就是运行着一个特殊Linux发行版的Atheros AR9331芯片。所以,本章将会介绍Ather…

WMI技术介绍和应用——查询正在运行的线程信息
本文使用了《WMI技术介绍和应用——使用VC编写一个半同步查询WMI服务的类》中代码做为基础。 一般来说,如果试图枚举系统中的线程。需要先枚举系统中的进程,然后再枚举每个进程中的线程。而WMI给我们提供了一种比较简便的枚举线程信息的方法。࿰…
开源生态也难逃“卡脖子”危机?中国AI开发者的警醒和突围
开源不是一个新名词,也不是一个新行动。软件时代,开源推动了全球范围的创新技术成果落地,从而促进全球信息技术发生了全局性、持续性的重大变革,这使它甚至成为一条非常关键且成功的技术路线。随着AI时代的来临,阿里、…

Linux下应用软件的安装
对于刚刚接触Linux的朋友来说,安装一些应用软件是一件头疼的事,因为在Linux下安装应用软件和Windows下截然不同的,下面介绍一下Linux下安装应用软件来解决刚刚接触Linux而不会安装软件朋友的困惑.Linux下软件包有两种比较常见的形式,一种是以 RPM、deb包为代表的智能…

Hibernate 的 session.load()使用方法
2019独角兽企业重金招聘Python工程师标准>>> protected Person getOne(int id){ Session session HibernateSessionFactory.getSession(); Person person new Person(); try{ session.load(person, id); }catch(Exception e){ e.printStackTrace(); }final…

WMI技术介绍和应用——查询桌面信息
本文使用了《WMI技术介绍和应用——使用VC编写一个半同步查询WMI服务的类》中代码做为基础。 在Windows操作系统中,存在很多Windows Station。而每一个Windows Station又存在一个或者多个Windows Desktop。我们一般所说的桌面只是这些众多桌面中的一个。以下内容将介…
联泰集群发布水晶系列工作站,用于深度学习场景
北京时间2019年12月26日,联泰集群在北京正式发布了水晶系列工作站产品 W722、W7224和W5232。 联泰集群硬件产品技术中心总监刘振锋、软件产品技术中心总监孙建军、硬件产品技术中心工程师肖学文分别从应用方向、水晶工作站一体化软件平台及水晶系列产品硬件方面对本…

航空黑客私人YY
坐飞机从从云南飞回老家广州~~~当然绝对支持深圳航空啦!嘎嘎!为啥?当然是服务好 MM太PL了!呵呵!而且在看杂志的同时还看到了一则新闻 内容如下 近日,深圳航空公司与瑞士OnAir公司在香港签约,合作推动在飞机上实现万米高空的自由通信。2008年奥运会前&am…

Redis安装整理(window平台) +php扩展redis
window平台Redis安装 redis windows安装文件下载地址:http://code.google.com/p/servicestack/wiki/RedisWindowsDownload#Download_32bit_Cygwin_builds_for_Windows我选择的redis为最新版的安装文件,见下图: Redis安装文件解压后,有以下…

WMI技术介绍和应用——查询环境变量
本文使用了《WMI技术介绍和应用——使用VC编写一个半同步查询WMI服务的类》中代码做为基础。 我们可以通过系统属性查看当前系统和当前用户的环境变量。(转载请指明出于breaksoftware的csdn博客) 如何使用WMI枚举所有环境变量的信息? CSynQue…
想学新的编程语言?考虑下Go吧
作者 | Lewis Fairweather译者 | 弯月,责编 | Elle来源 | CSDN(ID:CSDNnews)【导读】快速的运行时、高效的并发、简单易学的语法,这些都是Go语言最吸引人的特性。以下为译文:Go语言的入门门槛之低令我感到惊…

oracle学习总结三(bulk collect用法)
通过bulk collect减少loop处理的开销发表人:logzgh | 发表时间: 2006年五月19日, 10:56采用bulk collect可以将查询结果一次性地加载到collections中。而不是通过cursor一条一条地处理。可以在select into,fetch into,returning into语句使用bulk collect。注意在使用bulk coll…

MQTT的学习研究(五) MQTT moquette 的 Blocking API 发布消息服务端使用
参看官方文档: http://publib.boulder.ibm.com/infocenter/wmqv7/v7r0/index.jsp?topic/com.ibm.mq.amqtat.doc/tt00000_.htm * Java 为 MQ Telemetry Transport 创建异步发布程序 *在此任务中,您将遵循教程来修改第一个发布程序。通过修改,…

WMI技术介绍和应用——查询驱动信息
本文使用了《WMI技术介绍和应用——使用VC编写一个半同步查询WMI服务的类》中代码做为基础。 如何使用WMI查询所有驱动信息?(转载请指明出于breaksoftware的csdn博客) CSynQueryData recvnotify(L"root\\CIMV2",L"SELECT * FR…

NumericUpDown
NumericUpDown控件主要功能是为一个TextBox控件添加上下按钮,当单击按钮时实现数字的加减,同时也可以提供静态数据,实现这些数据的上下选择。 属性列表: TargetControlID:该控件的目标作用控件。 Width&…
提高建模效率,改变手工作坊式生产,AutoML的技术研究与应用进展如何了?
整理 | 王银出品 | AI科技大本营(ID:rgznai100)【导读】12 月 5-7 日,由中国计算机学会主办,CCF 大数据专家委员会承办,CSDN、中科天玑协办的中国大数据技术大会(BDTC 2019)在北京长城饭店隆重举…

.net使用memcached
Windows中memached安装 -------------服务器端配置 1>开始>运行:CMD(确定) 2>cd C:\memcached(回车) 3>memcached -d install(回车 这步执行安装) 4>memcached -d start(回车 这步执行启动memcache服务器,默认分配64M内存&…
22张精炼图笔记,深度学习专项学习必备
作者 | DL&CV_study9编辑 | Elle来源 | CSDN 博客本文为人工智能学习笔记记录。【深度学习基础篇】一、深度学习基本概念监督学习:所有输入数据都有确定的对应输出数据,在各种网络架构中,输入数据和输出数据的节点层都位于网络的两端&…

WMI技术介绍和应用——查询文件夹信息
本文使用了《WMI技术介绍和应用——使用VC编写一个半同步查询WMI服务的类》中代码做为基础。本节只是列出了WQL语句,具体使用参看前面的例子。 本节主要介绍Win32_Directory类。通过该类我们将可以获得部分常用的文件夹信息。在该类中,有属性Name&#x…

CSLA .NET概述
CSLA是Component-based, Scalable, Logical Architecture的简写,CSLA .NET是Rockford Lhotka基于.Net设计的一套N-tier分布式框架。 CSLA .NET包含如下功能: l n-Level undo capability 译:n层撤销功能 l Tracking broke…

简短的几句js实现css压缩和反压缩功能
写在前面 最近一直在整理css,但因为现在Visual Studio 2013太智能了,它每每在我按ctrlED进行格式化代码的时候,就会将css进行层次格式化(如下图所示),而这个格式让我老大实在无法忍受,我老大认为…
迁移学习前沿研究亟需新鲜血液,深度学习理论不能掉链子
作者 | Frederico Guth,Tefilo Emidio de Campos编译 | 夕颜出品 | AI科技大本营(ID:rgznai100)【导读】人类可以从很少的样本中学习,显示出了人类卓越的泛化能力,而这一点学习算法仍远做不到。当前,最成功的模型需要大量标记好的…

WMI技术介绍和应用——查询磁盘分区和逻辑磁盘信息
本文使用了《WMI技术介绍和应用——使用VC编写一个半同步查询WMI服务的类》中代码做为基础。本节只是列出了WQL语句,具体使用参看前面的例子。(转载请指明出于breaksoftware的csdn博客) 本节主要介绍Win32_DiskPartition和Win32_LogicalDisk两…

用 jQuery 的 AJAX 功能发现的一个错误/注意点:HTTP Error 411
今天程序中有个地方需要 Ajax 方式 POST 数据,发现在 IE 6.0 下正常,而 FireFox 2.0.0.9 下则出错。通过 FireBug 抓取 ajax 回发后得到的页面信息如下:HTTP Error 411 - Length required 经过 google 搜索发现,这个 HTTP 状态码对…

jquery 取消 radio checked 属性,重新选中的问题解决
<input type"radio" name"test"/> <input type"radio" name"test" id "input2"/> (说明:使用的jquery 版本是 1.10.2。) 使用 jquery 的removeAttr(),清除掉 radio 的checked属性后。使用 attr(…

使用×××版软件中常见的一些错误代码
1、错误代码(691):由于域上的用户名或密码无效而拒绝访问。如果是使用的易游提供的服务器,请在帐务系统确认使用的帐号是否状态正常,刚设置好的帐号需要等5分钟才能使用。如果是外部服务器请直接找服务器提供商。2、错…
程序员在地铁写代码遭疯狂吐槽!网友:装什么装
01作为了解程序员这个行业的人来说程序员的工作真的很累加班已经成为他们的标签有的时候网站出事或者需求比较紧急的时候可能路边也是他们的工作场地所以这个时候对于程序员们的工作来说也是不分场合的02之前看到网上有人拍到程序员在地铁上写代码的照片并将之发到网络上 图片一…

如何定制一款12306抢票浏览器——构架
快春节了,火车票一票难求。虽然黄牛市场冷淡了,但是互联网“娱乐界”却越来越闹腾了。先是猎豹等浏览器推出抢票专版(插件),然后是铁道部约谈金山,之后流传工信部叫停抢票插件,之后再是工信部出…