开源代码
mx0c/super-mario-python: super mario in python and pygame
一、初步运行
1.终端搭建conda
conda create -n pygame python=3.8 -y
conda activate iot
pip install pygame scipy
补充:介绍两个包+json
pygame
Pygame Front Page — pygame v2.6.0 documentation
scipy
为什么用到scipy:主要是用于实现高斯模糊效果,这在 super-mario-python/classes/GaussianBlur.py
文件,在游戏开发里,高斯模糊常被用于营造特定的视觉效果,像模糊背景以突出前景元素,或者模拟一些特殊场景,比如梦境、雾气等。在这个超级马里奥游戏项目中,高斯模糊或许是用于实现暂停界面的背景模糊效果,增强游戏的视觉表现力。
JSON(JavaScript Object Notation)
是一种轻量级的数据交换格式,它基于 JavaScript 的一个子集,但现在已经成为一种独立于编程语言的数据格式。JSON 以人类可读的文本形式来存储和传输数据,易于人们阅读和编写,同时也易于机器解析和生成。JSON 数据由键值对组成,使用大括号 {}
表示对象,方括号 []
表示数组。
在这个超级马里奥游戏程序中,JSON 文件起到了非常重要的作用,主要用于存储和配置游戏的各种数据。
-
scale
表示该帧图像的缩放因子,这里scale
为 2 意味着图像会被放大到原来的 2 倍。 -
deltaTime
字段指定了动画每帧之间的时间间隔,单位可能是游戏中的时间单位(例如帧或毫秒)。这里设置为 10,表示每 10 个时间单位切换一帧动画。 -
colorKey
字段用于指定图像的透明颜色。在某些图形处理中,可以通过设置颜色键来使特定颜色变为透明。这里colorKey
为null
,表示不使用颜色键,即图像没有特定的透明颜色。
2.配置pycharm中解析解释器
二、代码详细讲解
1.文件结构
super-mario-python ├── compile.py #将超级马里奥游戏的主程序 main.py 以及相关的配置文件、图片、声音等资源打包成一个 Windows 可执行文件 ├── main.py ├── img #存放游戏的图像资源 ├── traits #包含各种角色行为特性的实现 ├── classes #包含游戏的各种类,如动画类、相机类、碰撞检测类等 ├── sprites #游戏精灵(sprites)相关属性的 JSON 文件 ├── entities #定义了游戏中的各种实体,如金币、蘑菇怪、马里奥等。 ├── sfx #存放游戏的音效 └── levels #关卡 ├── Level1-1.json └── Level1-2.json
游戏编程中的精灵(sprite)是什么意思呢?有什么作用呢? - laoshoucun - 博客园
2.main.py
while not menu.start:
menu.update()
mario = Mario(0, 0, level, screen, dashboard, sound)
clock = pygame.time.Clock()
while not mario.restart:
pygame.display.set_caption("Super Mario running with {:d} FPS".format(int(clock.get_fps())))
if mario.pause:
mario.pauseObj.update()
else:
level.drawLevel(mario.camera)
dashboard.update()
mario.update()
pygame.display.update()
clock.tick(max_frame_rate)
return 'restart'
-
while
循环,只要菜单的start
属性为False
,就不断调用menu.update()
方法来更新菜单界面。 -
mario
:创建一个Mario
对象,代表游戏中的马里奥角色,初始位置为 (0, 0),并传入关卡、游戏窗口、仪表盘和音效对象。 -
clock = pygame.time.Clock()
:创建一个pygame
的时钟对象,用于控制游戏的帧率。 -
while not mario.restart
:开始一个主游戏循环,只要马里奥的restart
属性为False
,就不断更新游戏状态。 -
pygame.display.set_caption()
:设置游戏窗口的标题,显示当前游戏的帧率。 -
如果马里奥的
pause
属性为True
,则调用mario.pauseObj.update()
方法来更新暂停界面。 -
否则,调用
level.drawLevel(mario.camera)
方法绘制当前关卡的画面,调用dashboard.update()
方法更新仪表盘信息,调用mario.update()
方法更新马里奥角色的状态。
3.class类文件
1)animation.py
Animation
类在超级马里奥游戏中主要负责管理和更新游戏角色或物体的动画效果。它可以让角色或物体在不同状态下展示出不同的动画帧,使游戏画面更加生动。
-
idleSprite
:角色处于闲置状态时显示的图像。 -
airSprite
:角色在空中时显示的图像。
2)camera.py
模拟游戏中的相机,控制玩家在游戏世界中看到的视野范围。
简单的相机跟随系统,相机可以根据指定实体的位置进行移动。相机的位置在初始化时设置,并且在 move
方法中根据实体的位置进行更新。相机的位置会被转换为实际的像素坐标,以便在游戏中进行渲染。
-
move
方法用于根据关联实体的位置来更新相机的位置。 -
xPosFloat = self.entity.getPosIndexAsFloat().x
:调用entity
对象的getPosIndexAsFloat
方法获取实体在x
轴上的浮点位置。 -
if 10 < xPosFloat < 50:
:判断实体的x
轴位置是否在 10 到 50 之间。如果满足条件,则执行下面的操作。 -
self.pos.x = -xPosFloat + 10
:当实体的x
轴位置在 10 到 50 之间时,更新相机的x
轴位置。这里使用-xPosFloat + 10
来计算新的位置,目的是让相机随着实体的移动而反向移动,保证实体处于屏幕的合适位置。 -
self.x = self.pos.x * 32
和self.y = self.pos.y * 32
:根据更新后的self.pos
重新计算相机在游戏世界中的实际坐标。
class Camera:
def __init__(self, pos, entity):
self.pos = Vec2D(pos.x, pos.y)
self.entity = entity
self.x = self.pos.x * 32
self.y = self.pos.y * 32
def move(self):
xPosFloat = self.entity.getPosIndexAsFloat().x
if 10 < xPosFloat < 50:
self.pos.x = -xPosFloat + 10
self.x = self.pos.x * 32
self.y = self.pos.y * 32
class Mario(EntityBase):#马里奥文件中;EntityBase.py-def getPosIndexAsFloat(self):
def __init__(self, x, y, level, screen, dashboard, sound, gravity=0.8):
super(Mario, self).__init__(x, y, gravity)
3)等级设置 level.py
4)front、dashboard——文字面板
Font
类的主要功能是从精灵表(spritesheet)中加载字体字符的图像,并将每个字符与其对应的图像关联起来,方便在游戏中使用这些字符图像来显示文本。
class Font(Spritesheet):
def __init__(self, filePath, size):
Spritesheet.__init__(self, filename=filePath)
self.chars = " !\"#$%&'()*+,-./0123456789:;<=>? @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
self.charSprites = self.loadFont()
def loadFont(self):
font = {}
row = 0
charAt = 0
for char in self.chars:
if charAt == 16: #if charAt == 16::当列号达到 16 时,将列号重置为 0
charAt = 0
row += 1
font.update(
{
char: self.image_at(
charAt,
row,
2,
colorkey=pygame.color.Color(0, 0, 0),
xTileSize=8,
yTileSize=8
)
}
)
charAt += 1 #列号加 1,处理下一个字符图像
return font
dashboard
继承自 Font
类,用于在游戏界面上绘制和更新仪表盘信息,如玩家得分、金币数量、关卡名称和游戏时间等。
5)碰撞检测
Collider
的类
其主要功能是处理游戏中实体(如角色)与游戏关卡中瓷砖(tile
)之间的碰撞检测,同时也处理实体到达关卡边界的情况。
EntityCollider
类
这个类的主要功能是检查一个实体(self.entity
)与另一个目标实体(target
)是否发生碰撞,并确定碰撞发生的具体位置(顶部碰撞还是其他位置)
def determineSide(self, rect1, rect2):
if (
rect1.collidepoint(rect2.bottomleft)
or rect1.collidepoint(rect2.bottomright)
or rect1.collidepoint(rect2.midbottom)
):
if rect2.collidepoint(
(rect1.midleft[0] / 2, rect1.midleft[1] / 2)
) or rect2.collidepoint((rect1.midright[0] / 2, rect1.midright[1] / 2)):
return CollisionState(True, False)
else:
if self.entity.vel.y > 0:
return CollisionState(True, True)
return CollisionState(True, False)
-
该方法接收两个矩形参数
rect1
和rect2
,用于确定它们之间的碰撞位置。 -
首先检查
rect2
的左下角、右下角或底边中点是否在rect1
内部。如果满足条件,则继续进行下一步判断。 -
接着检查
rect2
是否包含rect1
左中位置或右中位置的一半坐标。如果满足条件,则返回一个CollisionState
对象,其中isColliding
为True
,isTop
为False
。 -
如果不满足上述条件,并且
self.entity
在垂直方向上的速度vel.y
大于 0(表示向下移动),则返回一个CollisionState
对象,其中isColliding
为True
,isTop
为True
,表示是顶部碰撞。 -
如果以上条件都不满足,则返回一个
CollisionState
对象,其中isColliding
为True
,isTop
为False
。
6)input.py
处理游戏中的输入事件,包括键盘输入、鼠标输入,以及处理退出和重启游戏的相关事件。
7)高斯模糊 pause暂停键后使用scipy库的高斯函数进行模糊处理
8)sprite、sprites、spritesheet ——图像的处理
超级玛丽是如何做到占用空间如此之小的呢?
img介绍
从提供的代码和配置文件来看,程序用到了以下6个img
文件夹下的图像文件,它们各自的用途如下:
1. ./img/tiles.png
在Animations.json里用于创建如CoinBox
和coin
等动画精灵的图像。在BackgroundSprites.json里用于创建背景精灵,像sky
、bricks
、ground
等。
2. ./img/characters.gif
角色精灵的精灵表
3. ./img/Items.png
在RedMushroom.json里用于创建红色蘑菇(mushroom
)这个物品的图像,在ItemAnimations.json里用于创建coin-item
的动画图像。
4. ./img/koopas.png
乌龟角色精灵的精灵表。在sprites/Koopa.json中被引用,用于创建koopa-1
、koopa-2
、koopa-hiding
等库巴角色不同状态的图像。
5. ./img/title_screen.png
用于游戏的菜单和暂停界面。在Pause.py里用于创建暂停界面的相关元素,如点(dot
)和灰色点(gray_dot
)。在Menu.py里用于创建菜单界面的横幅(menu_banner
)、点(menu_dot
)等元素。
6. ./img/font.png
显示游戏中的文字,作为字体图像资源。
三者关系
-
Sprite
类封装了单个精灵的基本属性和绘制方法。__init__
方法用于初始化精灵的图像、碰撞属性、动画和背景重绘标志, -
Sprites
类用于管理游戏中所有的精灵。它负责从多个 JSON 文件中加载精灵信息,并将这些精灵存储在一个字典中,方便统一管理和访问。 -
Spritesheet
类,用于处理精灵表。精灵表是一种将多个精灵图像合并到一个大图像文件中的技术,Spritesheet
类负责加载精灵表图像,并从精灵表中提取指定位置和大小的精灵图像。
Sprites
类创建并管理 Sprite
对象
Sprites
类在加载精灵信息时,会根据提取的精灵图像和其他属性创建 Sprite
对象,并将这些对象存储在 spriteCollection
字典中。
Sprites
类依赖 Spritesheet
类
在 Sprites
类的 loadSprites
方法中,会根据 JSON 文件中指定的精灵表路径创建 Spritesheet
对象,然后使用该对象的 image_at
方法从精灵表中提取精灵图像
class Sprite:
def __init__(self, image, colliding, animation=None, redrawBackground=False):
self.image = image
self.colliding = colliding
self.animation = animation
self.redrawBackground = redrawBackground
def drawSprite(self, x, y, screen):
dimensions = (x * 32, y * 32)
if self.animation is None:
screen.blit(self.image, dimensions)
else:
self.animation.update()
screen.blit(self.animation.image, dimensions)
-
colliding
:一个布尔值,用于指示该精灵是否参与碰撞检测。 -
animation
:可选参数,默认为None
,表示精灵的动画对象。如果不为None
,则精灵会有动画效果。 -
dimensions = (x * 32, y * 32)
将网格坐标(x, y)
转换为像素坐标,每个网格单元的大小为 32x32 像素。 -
如果
self.animation
为None
,则直接将self.image
绘制到屏幕的dimensions
位置,使用screen.blit(self.image, dimensions)
。 -
如果
self.animation
不为None
,则先调用self.animation.update()
更新动画状态,然后将动画当前帧的图像self.animation.image
绘制到屏幕的dimensions
位置。class Spritesheet(object): def __init__(self, filename): try: self.sheet = pygame.image.load(filename) self.sheet = pygame.image.load(filename) if not self.sheet.get_alpha(): #self.sheet.get_alpha() 检查图像是否有透明度 self.sheet.set_colorkey((0, 0, 0)) #黑色(RGB 值为 (0, 0, 0))设置为透明色 except pygame.error: print("Unable to load spritesheet image:", filename) raise SystemExit #scalingfactor:表示提取的精灵图像的缩放 //网格坐标或像素坐标,取决于 ignoreTileSize 的值 def image_at(self, x, y, scalingfactor, colorkey=None, ignoreTileSize=False, xTileSize=16, yTileSize=16): if ignoreTileSize: rect = pygame.Rect((x, y, xTileSize, yTileSize)) else: rect = pygame.Rect((x * xTileSize, y * yTileSize, xTileSize, yTileSize)) image = pygame.Surface(rect.size) image.blit(self.sheet, (0, 0), rect) #blit(source,dest=None,special_flags=0)将source参数指定的Surface对象绘制到该对象上。 #dest参数指定绘制的位置。 #dest的值可以是source的左上角坐标,如果传入一个rect对象给dest,那么blit()会使用它的左上角坐标 if colorkey is not None: if colorkey == -1: colorkey = image.get_at((0, 0)) image.set_colorkey(colorkey, pygame.RLEACCEL) return pygame.transform.scale( image, (xTileSize * scalingfactor, yTileSize * scalingfactor) )
class Sprites: def __init__(self): self.spriteCollection = self.loadSprites( [ "./sprites/Mario.json", "./sprites/Goomba.json", "./sprites/Koopa.json", "./sprites/Animations.json", "./sprites/BackgroundSprites.json", "./sprites/ItemAnimations.json", "./sprites/RedMushroom.json" ] ) #字典 def loadSprites(self, urlList):#包含多个 JSON 文件路径的列表 resDict = {}#字典 for url in urlList: with open(url) as jsonData: data = json.load(jsonData) mySpritesheet = Spritesheet(data["spriteSheetURL"]) dic = {} if data["type"] == "background": for sprite in data["sprites"]: try: colorkey = sprite["colorKey"] except KeyError: colorkey = None dic[sprite["name"]] = Sprite( mySpritesheet.image_at( sprite["x"], sprite["y"], sprite["scalefactor"], colorkey, ), sprite["collision"], None, sprite["redrawBg"], ) resDict.update(dic) continue elif data["type"] == "animation": for sprite in data["sprites"]: images = [] for image in sprite["images"]: images.append( mySpritesheet.image_at( image["x"], image["y"], image["scale"], colorkey=sprite["colorKey"], ) ) dic[sprite["name"]] = Sprite( None, None, animation=Animation(images, deltaTime=sprite["deltaTime"]), ) resDict.update(dic) continue elif data["type"] == "character" or data["type"] == "item": for sprite in data["sprites"]: try: colorkey = sprite["colorKey"] except KeyError: colorkey = None try: xSize = sprite['xsize'] ySize = sprite['ysize'] except KeyError: xSize, ySize = data['size'] dic[sprite["name"]] = Sprite( mySpritesheet.image_at( sprite["x"], sprite["y"], sprite["scalefactor"], colorkey, True, xTileSize=xSize, yTileSize=ySize, ), sprite["collision"], ) resDict.update(dic) continue return resDict
try-except Python教学 | 有备无患!详解 Python 异常处理(try-except)_try except函数-优快云博客
4.traits——马里奥的动作
bounceTrait-弹跳 和 jump-跳
jump
:由玩家输入触发,当玩家按下特定按键(如空格键、上方向键等),且实体在地面上(self.entity.onGround
为 True
)时,跳跃行为才会启动。
bounce
:通常是在实体与其他物体碰撞时触发,特别是当实体从上方碰撞到敌人触发
class bounceTrait:
def __init__(self, entity):
self.vel = 5 #初始化弹跳的垂直速度为 5,这个值决定了实体在弹跳时向上移动的速度
self.jump = False
self.entity = entity
def update(self):
if self.jump:
self.entity.vel.y = 0
self.entity.vel.y -= self.vel
self.jump = False
self.entity.inAir = True
def reset(self):
self.entity.inAir = False
-
self.entity.vel.y = 0
:将实体的垂直速度vel.y
重置为 0,这样可以避免之前的垂直速度对弹跳产生影响。 -
self.entity.vel.y -= self.vel
:将实体的垂直速度减去self.vel
的值,因为self.vel
是正值,所以减去它会使实体获得一个向上的速度,从而实现弹跳效果。
go
管理角色的移动速度、动画更新和绘制
动作和键盘交互
class Input:
def __init__(self, entity):
self.mouseX = 0
self.mouseY = 0
self.entity = entity
def checkForInput(self):
events = pygame.event.get()
self.checkForKeyboardInput()
self.checkForMouseInput(events)
self.checkForQuitAndRestartInputEvents(events)
def checkForKeyboardInput(self):
pressedKeys = pygame.key.get_pressed()
# 检测左右移动按键
if pressedKeys[pygame.K_LEFT] or pressedKeys[pygame.K_h] and not pressedKeys[pygame.K_RIGHT]:
self.entity.traits["goTrait"].direction = -1
elif pressedKeys[pygame.K_RIGHT] or pressedKeys[pygame.K_l] and not pressedKeys[pygame.K_LEFT]:
self.entity.traits["goTrait"].direction = 1
else:
self.entity.traits['goTrait'].direction = 0
# 检测跳跃按键
isJumping = pressedKeys[pygame.K_SPACE] or pressedKeys[pygame.K_UP] or pressedKeys[pygame.K_k]
self.entity.traits['jumpTrait'].jump(isJumping)
# 检测加速按键
self.entity.traits['goTrait'].boost = pressedKeys[pygame.K_LSHIFT]
self.entity.traits['goTrait'].direction
class Mario(EntityBase):
def __init__(self, x, y, level, screen, dashboard, sound, gravity=0.8):
# ... 其他代码 ...
self.traits = {
"jumpTrait": JumpTrait(self),
"goTrait": GoTrait(smallAnimation, screen, self.camera, self),
"bounceTrait": bounceTrait(self),
}
# ... 其他代码 ...
class GoTrait:
def __init__(self, animation, screen, camera, ent):
self.animation = animation
self.direction = 0
# ... 其他代码 ...