超级玛丽的 python 实现
在经过三四天的摸索,参考了Github上的一个大神的代码的前提下,也算是初步搭建起了自己的超级玛丽,下面就给大家分享一些自己踩的坑。
这里是Github上大神的代码,对超级玛丽的第一关进行了很好的还原。
推荐一下Github上一个pygame的游戏仓库
推荐一本《python和pygame游戏开发指南》,想要深入研究的朋友可以去翻阅一下
关于 pygame 模块可以查看官方文档,这里还推荐一下CSDN上的中译版,毕竟官网的配色比较靓眼。或者看我前一篇博客的大致整理。这里移步个人博客
在开始之前你需要:
- 掌握 python 的基本语法
- 熟悉 pygame 模块的基本使用
由于pygame游戏的基本入门在之前一篇博客中有见过这里就不再赘述
1. 画面和角色的导入
创建屏幕、从图片中导入Mario
# 屏幕创建和初始化参数
self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
self.rect = self.screen.get_rect()
pygame.display.set_caption(TITLE)
# 加载关卡图片
self.background = load_image('level.png')
self.back_rect = self.background.get_rect()
# 这里载入图片需要乘上特定的系数来适配屏幕的尺寸
self.background = pygame.transform.scale(self.background,
(int(self.back_rect.width * BACKGROUND_SIZE),
int(self.back_rect.height * BACKGROUND_SIZE))).convert()
# 导入Mario
self.sheet = load_image('mario.png')
# 这里由于Mario会有奔跑和跳跃的速度,所以需要导入一整张图片再裁剪使用。
self.load_from_sheet()
# 初始化角色的一些基本常量
self.rect = self.image.get_rect()
self.pos = vec(WIDTH * 0.5, GROUND_HEIGHT - 70)
self.vel = vec(0, 0)
self.acc = vec(0, 0)
2. 角色的落地、跳跃和移动
在这之前要解决一下Mario如何才能站在我们定义的地面上
self.acc = vec(0, GRAVITY)
if GROUND_HEIGHT < self.mario.pos.y:
# 如果Mario低于我们定义的地面,就之间将他的所有速度加速度都置零,之间放在我们的地面上
# 如果速度和加速度不值零,可能会出现Mario卡在地面上抖动的情况,由于y值的不断变化
self.mario.acc.y = 0
self.mario.vel.y = 0
self.mario.pos.y = self.ground_collide.rect.top
self.mario.landing = True
正如之前那一篇文章所说,角色的移动如果只是单纯的实现以像素为单位向左向右移动,无疑会很影响玩家的游戏体验正如以下[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-18TNEWIP-1588172494406)(https://i.loli.net/2020/04/29/tcpYBHFKq8ISP54.gif)]
可以明显感觉到两个方向的运动的不同体验,下面是两个方向的代码作为比对
keys = pygame.key.get_pressed()
if keys[pygame.K_RIGHT]:
# 向右
self.pos.x += 5 # ------------------------简单的改变位置
elif keys[pygame.K_LEFT]:
# 向左
if self.vel.x < 0:
# 这里很细节的加入了一个转向的速度控制
self.acc.x = -TURNAROUND
if self.vel.x >= 0: # ------------------------改变加速度来改变运动
self.acc.x = -ACC
# 这里加入了一个最大速度限制
if abs(self.vel.x) < MAX_SPEED:
self.vel.x += self.acc.x
elif keys[pygame.K_LEFT]:
self.vel.x = -MAX_SPEED
elif keys[pygame.K_RIGHT]:
self.vel.x = MAX_SPEED
# 这里对加速度和速度进行计算得出位移并在下一帧时改变Mario的位置
self.acc.x += self.vel.x * FRICTION
# 同时还要引用一个 摩擦力 的概念,随着速度的增大而增大
self.vel += self.acc
self.pos += self.vel + 0.5 * self.acc
self.rect.midbottom = self.pos
对于角色的跳跃,一定要对其状态加以限制,让其必须在 “落地” 的状态下才能开始跳跃,不然就会产生下面的情况
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JfPXR0yT-1588172494408)(https://i.loli.net/2020/04/29/kLSh19zaAJ5KptX.gif)]
为了避免这种情况,我们引入了一个self.landing
状态,只有但其在为true
的时候才能响应跳跃事件
if keys[pygame.K_SPACE]:
if self.landing:
# 这里跳跃的参数,只是给Mario一个向上的速度,类似于物理中的上抛运动
self.vel.y = -JUMP
这里以上的所有大写常量参数都是定义在单独的配置文件中,方便修改。其参数的大小可以自行调节找出最合适的一组
对于这些运动的参数大家可以自己去调试调试,尝试一下不同的操作体验,也可以去文末的代码自寻
3. 角色的动作图片的切换
在提供的素材中是张表如图,我们需要自行裁剪下我们所需要的图片
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mtM5LLFY-1588172494410)(https://i.loli.net/2020/04/29/CRtDbnMAKFuGarN.png)]
这里可以在工具类中定义一个加载图片的方法
def load_image(filename):
src = os.path.dirname(os.path.abspath(__file__))
path = os.path.join(src, 'resources', 'graphics', filename)
return pygame.image.load(path)
从sheet中裁切图片
# 裁切方法
def get_image(self, x, y, width, height):
image = pg.Surface([width, height])
rect = image.get_rect()
image.blit(self.sheet, (0, 0), (x, y, width, height))
image.set_colorkey(BLACK)
image = pg.transform.scale(image,
(int(rect.width * MARIO_SIZE),
int(rect.height * MARIO_SIZE)))
return image
# 裁切并加入容器
def load_from_sheet(self):
self.right_frames = []
self.left_frames = []
self.right_frames.append(
self.get_image(178, 32, 12, 16)) # 站立
self.right_frames.append(
self.get_image(80, 32, 15, 16)) # 跑 1
self.right_frames.append(
self.get_image(96, 32, 16, 16)) # 跑 2
self.right_frames.append(
self.get_image(112, 32, 16, 16)) # 跑 3
self.right_frames.append(
self.get_image(144, 32, 16, 16)) # 跳
# 将向右的图片水平翻转就是向左的图片了
for frame in self.right_frames:
new_image = pg.transform.flip(frame, True, False)
self.left_frames.append(new_image)
# 最后全部加入容器方便我们之间通过下标
self.frames = self.right_frames + self.left_frames
图片的切换,一开始我是采用一帧换一张图片的方向,下面是代码
# 定义一个方法来通过运动方向更换图片
def walk(self, facing):
if facing == 'right':
if self.image_index > 3:
self.image_index = 0
if facing == 'left':
if self.image_index > 8:
self.image_index = 5
if self.image_index < 5:
self.image_index = 5
self.image_index += 1
但后来在运行的时候我发现了一个让人懵逼的效果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-joJBm3k7-1588172494412)(https://i.loli.net/2020/04/29/3bdTG8tfQKvqLPE.gif)]
后来发现这是由于我的图片是每帧一换,快的让人反应不过来,产生了这种让人苦笑不得的效果。
后来在参考大神的源码的时候,发现了一种控制图片切换速度的方法。不得不说大神还是很细节的,引入的一些常量系数和时间戳,通过Mario的移动速度来控制图片切换,让其更加自然平滑。下面是代码
# 改进后的代码
def walk(self, facing):
if self.image_index == 0:
self.image_index += 1
# 加入一个时间戳
self.walking_timer = pg.time.get_ticks()
else:
# 比较时间变化和当前的Mario的速度
if (pg.time.get_ticks() - self.walking_timer >
self.calculate_animation_speed()):
self.image_index += 1
self.walking_timer = pg.time.get_ticks()