6.2    站立与跳跃

对于马里奥的站立和跳跃状态来说,它们都只包含一张图片,因此在显示时只需要简单地替换之前的小方块即可。在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所示。

63马里奥站立状态

         马里奥跳跃的画面如6‑4所示。

64马里奥跳跃状态

         马里奥走路的画面如6‑5所示。

65马里奥走路状态

由于文件越来越多,所以重新整理了一下目录结构,修改后的结构如6‑6所示。

66修改后目录结构

         工具类被移动至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 *