zoukankan      html  css  js  c++  java
  • Python 超级玛丽代码实现:人物行走和碰撞检测

    功能介绍

    人物行走
    人物的行走速度这边分成水平方向(X轴)和竖直方向(Y轴),水平方向的速度要考虑加速度和摩擦力,竖直方向的速度要考虑重力加速度。
    • 水平方向:设定X轴向右走的速度为大于0,向左走的速度为小于0
    • 竖直方向:设定Y轴向下的速度为大于0,向上的速度为小于0
    游戏中的人物有下面几个主要的状态:
    • 站立不动:水平方向速度为0,且竖直方向站在某个物体上。
    • 向左或向右走:水平方向速度的绝对值大于0,且竖直方向站在某个物体上。
    • 向上跳:竖直方向方向速度小于0,且上方没有碰到某个物体,同时需要玩家按住jump键。
    • 向下降落:竖直方向方向速度大于0或者玩家没有按住jump键,且下方没有碰到某个物体。
    向上跳和向下降落的状态判断可能一开始比较难理解,可以看后面的具体实现,目的是如果玩家长按jump键时,可以让人物跳的更高。
    上面的判断是否站在某个物体上,或者是否碰到某个物体,就需要用到物体之间的碰撞检测。

    碰撞检测

    对于游戏中出现的每一样东西,比如砖块,箱子,水管,地面,还有人物都可以看成是一个独立的物体,所以每个物体类都继承了pygame的精灵类pg.sprite.Sprite,可以使用精灵类提供的碰撞检测函数来判断。
     
    设置sourceconstants.py 中的变量DEBUG值为True,可以看到图1的游戏截图,比如最简单的地面,可以看成是一个长方形的物体。
    • 下方红色的长方形物体就是地面(ground)
    • 右边的几个红色小方块是阶梯(step)
    • 左边空中的像墙一样的是砖块(brick)
    • 带问号的是箱子(box)
    因为人物是站在地面上,且水平速度为0,所以当前的人物状态就是站立不动。
     

     

    游戏代码

     游戏实现代码的github链接 超级玛丽

     这边是csdn的下载链接 超级玛丽  

    代码介绍 

    人物行走代码 

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

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

    代码已打包,学习python可加交流群:887934385 分享视频教程

     1 学习python可加交流群:887934385 分享视频教程
     2  def handle_state(self, keys, fire_group):
     3         if self.state == c.STAND:
     4             self.standing(keys, fire_group)
     5         elif self.state == c.WALK:
     6             self.walking(keys, fire_group)
     7         elif self.state == c.JUMP:
     8             self.jumping(keys, fire_group)
     9         elif self.state == c.FALL:
    10             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中值的含义如下:

    1 keybinding = {
    2     'action':pg.K_s,
    3     'jump':pg.K_a,
    4     'left':pg.K_LEFT,
    5     'right':pg.K_RIGHT,
    6     'down':pg.K_DOWN
    7 }
    • 先根据当前是否有按下 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。
     1   def walking(self, keys, fire_group):        
     2         if keys[tools.keybinding['action']]:
     3             self.max_x_vel = self.max_run_vel
     4             self.x_accel = self.run_accel
     5         else:
     6             self.max_x_vel = self.max_walk_vel
     7             self.x_accel = self.walk_accel
     8         
     9         if keys[tools.keybinding['jump']]:
    10             if self.allow_jump:
    11                 self.state = c.JUMP
    12                 if abs(self.x_vel) > 4:
    13                     self.y_vel = self.jump_vel - .5
    14                 else:
    15                     self.y_vel = self.jump_vel
    16                 
    17         if keys[tools.keybinding['left']]:
    18             self.facing_right = False
    19             if self.x_vel > 0:
    20                 self.frame_index = 5
    21                 self.x_accel = c.SMALL_TURNAROUND
    22          
    23             self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel, True)
    24         elif keys[tools.keybinding['right']]:
    25             self.facing_right = True
    26             if self.x_vel < 0:
    27                 self.frame_index = 5
    28                 self.x_accel = c.SMALL_TURNAROUND
    29             
    30             self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel)
    31         else:
    32             if self.facing_right:
    33                 if self.x_vel > 0:
    34                     self.x_vel -= self.x_accel
    35                 else:
    36                     self.x_vel = 0
    37                     self.state = c.STAND
    38             else:
    39                 if self.x_vel < 0:
    40                     self.x_vel += self.x_accel
    41                 else:
    42                     self.x_vel = 0
    43                     self.state = c.STAND
    44    
    45    def cal_vel(self, vel, max_vel, accel, isNegative=False):
    46         """ max_vel and accel must > 0 """
    47         if isNegative:
    48             new_vel = vel * -1
    49         else:
    50             new_vel = vel
    51         if (new_vel + accel) < max_vel:
    52             new_vel += accel
    53         else:
    54             new_vel = max_vel
    55         if isNegative:
    56             return new_vel * -1
    57         else:
    58             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
     1 JUMP_GRAVITY = .31
     2 GRAVITY = 1.01
     3 
     4     def jumping(self, keys, fire_group):
     5         """ y_vel value: positive is down, negative is up """  
     6         self.allow_jump = False
     7         self.frame_index = 4
     8         self.gravity = c.JUMP_GRAVITY
     9         self.y_vel += self.gravity
    10         
    11         if self.y_vel >= 0 and self.y_vel < self.max_y_vel:
    12             self.gravity = c.GRAVITY
    13             self.state = c.FALL
    14 
    15         if keys[tools.keybinding['right']]:
    16             self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel)
    17         elif keys[tools.keybinding['left']]:
    18             self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel, True)
    19         
    20         if not keys[tools.keybinding['jump']]:
    21             self.gravity = c.GRAVITY
    22             self.state = c.FALL

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

    碰撞检测代码

        人物的碰撞检测代码在 sourcestateslevel.py 中的入口是

    update_player_position函数 ,可以看到这边分成水平方向和竖直方向:

    • 根据人物的水平方向速度x_vel 更新人物的X轴位置,同时人物的X轴位置不能超出游戏地图的X轴范围,然后调用check_player_x_collisions函数进行水平方向的碰撞检测。
    • 根据人物的水平方向速度x_vel 更新人物的X轴位置,同时人物的X轴位置不能超出游戏地图的X轴范围,然后调用check_player_x_collisions函数进行水平方向的碰撞检测。
    • 根据人物的竖直方向速度y_vel 更新人物的Y轴位置,然后调用check_player_y_collisions函数进行竖直方向的碰撞检测
     1   def update_player_position(self):
     2         self.player.rect.x += round(self.player.x_vel)
     3         if self.player.rect.x < self.start_x:
     4             self.player.rect.x = self.start_x
     5         elif self.player.rect.right > self.end_x:
     6             self.player.rect.right = self.end_x
     7         self.check_player_x_collisions()
     8         
     9         if not self.player.dead:
    10             self.player.rect.y += round(self.player.y_vel)
    11             self.check_player_y_collisions()

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

    1 pygame.sprite.Group
    2   A container class to hold and manage multiple Sprite objects.
    3   Group(*sprites) -> Group

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

    1 pygame.sprite.spritecollideany()
    2   Simple test if a sprite intersects anything in a group.
    3   spritecollideany(sprite, group, collided = None) -> Sprite Collision with the returned sprite.
    4   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轴位置。

     1 def check_player_x_collisions(self):
     2         ground_step_pipe = pg.sprite.spritecollideany(self.player, self.ground_step_pipe_group)
     3         brick = pg.sprite.spritecollideany(self.player, self.brick_group)
     4         box = pg.sprite.spritecollideany(self.player, self.box_group)
     5         ...
     6         if box:
     7             self.adjust_player_for_x_collisions(box)
     8         elif brick:
     9             self.adjust_player_for_x_collisions(brick)
    10         elif ground_step_pipe:
    11             if (ground_step_pipe.name == c.MAP_PIPE and
    12                 ground_step_pipe.type == c.PIPE_TYPE_HORIZONTAL):
    13                 return
    14             self.adjust_player_for_x_collisions(ground_step_pipe)
    15         elif powerup:
    16             ...
    17         elif enemy:
    18             ...
    19         elif coin:
    20             ...

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

    1  def adjust_player_for_x_collisions(self, collider):
    2         if collider.name == c.MAP_SLIDER:
    3             return
    4 
    5         if self.player.rect.x < collider.rect.x:
    6             self.player.rect.right = collider.rect.left
    7         else:
    8             self.player.rect.left = collider.rect.right
    9         self.player.x_vel = 0

    学习python可加交流群:887934385 分享视频教程

    check_player_y_collisions 函数也是对不同group分别进行碰撞检测,Y轴方向这3类group如果检测到有碰撞时,会调用adjust_player_for_y_collisions 函数,来调整人物的Y轴位置。 最后调用check_is_falling函数判断人物是否要设成 向下降落 的状态。 

     1   def walking(self, keys, fire_group):        
     2         if keys[tools.keybinding['action']]:
     3             self.max_x_vel = self.max_run_vel
     4             self.x_accel = self.run_accel
     5         else:
     6             self.max_x_vel = self.max_walk_vel
     7             self.x_accel = self.walk_accel
     8         
     9         if keys[tools.keybinding['jump']]:
    10             if self.allow_jump:
    11                 self.state = c.JUMP
    12                 if abs(self.x_vel) > 4:
    13                     self.y_vel = self.jump_vel - .5
    14                 else:
    15                     self.y_vel = self.jump_vel
    16                 
    17         if keys[tools.keybinding['left']]:
    18             self.facing_right = False
    19             if self.x_vel > 0:
    20                 self.frame_index = 5
    21                 self.x_accel = c.SMALL_TURNAROUND
    22          
    23             self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel, True)
    24         elif keys[tools.keybinding['right']]:
    25             self.facing_right = True
    26             if self.x_vel < 0:
    27                 self.frame_index = 5
    28                 self.x_accel = c.SMALL_TURNAROUND
    29             
    30             self.x_vel = self.cal_vel(self.x_vel, self.max_x_vel, self.x_accel)
    31         else:
    32             if self.facing_right:
    33                 if self.x_vel > 0:
    34                     self.x_vel -= self.x_accel
    35                 else:
    36                     self.x_vel = 0
    37                     self.state = c.STAND
    38             else:
    39                 if self.x_vel < 0:
    40                     self.x_vel += self.x_accel
    41                 else:
    42                     self.x_vel = 0
    43                     self.state = c.STAND
    44    
    45    def cal_vel(self, vel, max_vel, accel, isNegative=False):
    46         """ max_vel and accel must > 0 """
    47         if isNegative:
    48             new_vel = vel * -1
    49         else:
    50             new_vel = vel
    51         if (new_vel + accel) < max_vel:
    52             new_vel += accel
    53         else:
    54             new_vel = max_vel
    55         if isNegative:
    56             return new_vel * -1
    57         else:
    58             return new_vel
  • 相关阅读:
    Delphi的 Format格式化函数
    Delphi的Trim函数
    Delphi容器类之---Tlist,TStringlist,THashedStringlist的效率比较
    Delphi容器类之---TOrderedList、TStack、TQueue、TObjectStack、TObjectQueue
    Delphi容器类之---TList、TObjectList、TComponentList、TClassList
    Delphi的分配及释放---New/Dispose, GetMem/FreeMem及其它函数的区别与相同
    Delphi容器类之---TList、TStringList、TObjectList,以及一个例程的代码分析
    Linux C编程学习6---字符串处理、数据转换
    Linux C编程学习之开发工具3---多文件项目管理、Makefile、一个通用的Makefile
    Linux C编程学习之开发工具2---GDB调试器
  • 原文地址:https://www.cnblogs.com/pypypy/p/12161507.html
Copyright © 2011-2022 走看看