对于马里奥的站立和跳跃状态来说,它们都只包含一张图片,因此在显示时只需要简单地替换之前的小方块即可。在Mario类中,我们需要增加三个变量以实现状态和方向的管理:
l 变量state:表示当前状态,不同状态对应不同的图片。
l 变量image:表示当前状态使用的图片
l 变量direction:表示当前的运动方向,向右运动使用脸朝右边的图片,向左运动反之。
首先,常量类增加表示方向的常量,如下所示:
06\02\game_globals\Constants.py
|
# 方向 DIRECTION_LEFT = -1 # 方向向左 DIRECTION_RIGHT = 1 # 方向向右 |
向右是X坐标增加,所以变量设置为1,向左是-1,在计算坐标时,只需要将步伐乘以方向即可,非常方便。修改Mario类,代码如下所示:
06\02\mario\Mario.py
|
import pygame from pygame import Rect from utils.utils import * from game_globals.Globals import Globals
class Mario(object):
# 构造方法,记录初始坐标 def __init__(self, x, y, screen): self.screen = screen self.rect = Rect(x, y, MAP_BLOCK_SIZE, MAP_BLOCK_SIZE) self.walkStepDefault = 2 # 走路步伐,默认值 self.jumpStepDefault = 10 # 跳跃步伐,默认值 self.dropStepDefault = 4 # 掉落步伐,默认值 self.walkStep = 2 # 走路步伐,实际值 self.jumpStep = 10 # 跳跃步伐,实际值 self.dropStep = 4 # 掉落步伐,实际值 self.state = "stand" # 站立状态 self.direction = DIRECTION_RIGHT # 运动方向,右是1,左 self.image = Globals.marioImageCache.marioStand # 站立图片
# 更新状态及图片 def updateState(self, newState, newDirection): self.state = newState if newDirection is not None: self.direction = newDirection
# 根据状态及方向,变更对应的图片 if self.state == "stand": # 站立 if self.direction == DIRECTION_RIGHT: self.image = Globals.marioImageCache.marioStand else: self.image = Globals.marioImageCache.marioLeftStand elif self.state == "walk": # 走路 if self.direction == DIRECTION_RIGHT: self.image = Globals.marioImageCache.marioWalk1 else: self.image = Globals.marioImageCache.marioLeftWalk1 elif self.state == "jump": # 跳跃 if self.direction == DIRECTION_RIGHT: self.image = Globals.marioImageCache.marioJump else: self.image = Globals.marioImageCache.marioLeftJump
# 更新 def update(self, keys):
# 步伐,先恢复默认值 self.walkStep = self.walkStepDefault self.jumpStep = self.jumpStepDefault self.dropStep = self.dropStepDefault
# 是否滚动地图 isScrollMap = False
# 按键状态 if keys[pygame.K_LEFT]: print("按了左方向键")
# 更新状态 self.updateState("walk", DIRECTION_LEFT)
# 窗口边界判断 if self.rect.x - self.walkStep <= 0: self.walkStep = self.rect.x
# 下一步预判断 nextStepRect = self.rect.move(-1 * self.walkStep, 0) pipeIndex = screenToMap(nextStepRect).collidelist(Globals.game.objectArray) # 是否碰撞 if pipeIndex != -1: self.walkStep = self.rect.x - mapToScreen(Globals.game.objectArray[pipeIndex]).right self.rect = self.rect.move(-1 * self.walkStep, 0) if keys[pygame.K_RIGHT]: print("按了右方向键")
# 更新状态 self.updateState("walk", DIRECTION_RIGHT)
# 超过屏幕中线才滚动地图,地图末尾右半屏也不滚动 if self.rect.x >= SCREEN_WIDTH / 2 and screenToMap( self.rect).x < Globals.game.mapViewFromMax + SCREEN_WIDTH / 2:
# 如果地图滚动后,有碰撞发生,则不能滚动 # 地图向左滚动,相当于马里奥向右移动。 # 这里不能修改mapViewFrom的值 nextStepRect = self.rect.move(self.walkStep, 0) pipeIndex = screenToMap(nextStepRect).collidelist(Globals.game.objectArray) if pipeIndex == -1: isScrollMap = True else: # 窗口边界判断 if self.rect.right + self.walkStep >= SCREEN_WIDTH: self.walkStep = SCREEN_WIDTH - self.rect.right
# 下一步预判断 nextStepRect = self.rect.move(self.walkStep, 0) pipeIndex = screenToMap(nextStepRect).collidelist(Globals.game.objectArray) if pipeIndex != -1: self.walkStep = mapToScreen(Globals.game.objectArray[pipeIndex]).x - self.rect.right self.rect = self.rect.move(self.walkStep, 0) if keys[pygame.K_g]: print("按了跳跃键")
# 更新状态 self.updateState("jump", None)
# 窗口边界判断 if self.rect.y - self.jumpStep <= 0: self.jumpStep = self.rect.y
# 下一步预判断 nextStepRect = self.rect.move(0, -1 * self.jumpStep) pipeIndex = screenToMap(nextStepRect).collidelist(Globals.game.objectArray) if pipeIndex != -1: self.jumpStep = (mapToScreen(Globals.game.objectArray[pipeIndex]).bottom - self.rect.y) * -1 self.rect = self.rect.move(0, -1 * self.jumpStep)
# 没有走路,没有跳跃,则变成站立 if not (keys[pygame.K_LEFT] or keys[pygame.K_RIGHT] or keys[pygame.K_g]): self.updateState("stand", None)
# ----重力影响---- # 下一步预判断 nextStepRect = self.rect.move(0, self.dropStep) pipeIndex = screenToMap(nextStepRect).collidelist(Globals.game.objectArray) if pipeIndex != -1: self.dropStep = mapToScreen(Globals.game.objectArray[pipeIndex]).y - self.rect.bottom
# 更新状态 if self.state == "walk": pass else: self.updateState("stand", None) self.rect = self.rect.move(0, self.dropStep)
# 显示马里奥 self.screen.blit(self.image, self.rect)
# 滚动地图 if isScrollMap: Globals.game.scrollMap(self.walkStep) |
代码中新增了一个updateState()方法,用于统一管理状态和图片,前面写的图片缓存派上了用场。当按下方向键或跳跃键时,根据按键更新对应状态。如果没有走路或跳跃,则更新为站立状态。当发生掉落碰撞时,也更新为站立状态,但要排除当前正在走路的情况。因为马里奥在走路时,Y轴方向一直与地面碰撞,如果不排除此情况,将导致马里奥无法移动。
原作中松开方向键后,马里奥会继续走一段路程才会停下来,松开跳跃键后,马里奥也会继续跳跃一段距离。由于我们的程序尚未处理惯性问题,因此表现会有些差异。这没有关系,细节问题我们可以在后续进行优化。
运行程序,马里奥站立状态的画面如图6‑3所示。

图6‑3马里奥站立状态
马里奥跳跃的画面如图6‑4所示。

图6‑4马里奥跳跃状态
马里奥走路的画面如图6‑5所示。

图6‑5马里奥走路状态
由于文件越来越多,所以重新整理了一下目录结构,修改后的结构如图6‑6所示。

图6‑6修改后目录结构
工具类被移动至utils目录下,图片存放在resources/images目录中,全局文件如图片缓存等存放在game_globals下,原计划取名为 global,但由于 global 是 Python 的保留关键字,因此不能使用。
每个Python文件夹下都添加了一个__init__.py文件,用于表示该文件夹是一个包,之后可以按包的方式导入并使用,导入的格式如下:
|
from mario.Mario import Mario from game_globals.TilesImageCache import TilesImageCache from utils.utils import * |