0%

利用pygame开发一款游戏:「跳跳兔」(完结篇)

前言

通过前面九篇文章的编写,「跳跳兔」游戏基本已经被编写出来了,本节在此基础上进一步完善它,并添加上云彩背景。

添加云彩背景

添加云彩背景的大致步骤如下。

  • 1.编写云彩类
  • 2.载入云彩图片
  • 3.随机生成云彩
  • 4.云彩同步移动

一步步来编写,首先是创建云彩类,代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# sprites.py

class Cloud(pg.sprite.Sprite):
def __init__(self, game):
self._layer = CLOUD_LAYER
self.groups = game.all_sprites, game.clouds
pg.sprite.Sprite.__init__(self, self.groups)
self.game = game
self.image = random.choice(self.game.cloud_images)
self.image.set_colorkey(BLACK)
self.rect = self.image.get_rect()
# 随机出现位置
scale = random.randrange(50, 101) / 100
self.image = pg.transform.scale(self.image, (int(self.rect.width * scale),
int(self.rect.height * scale)))
self.rect.x = random.randrange(WIDTH - self.rect.width)
self.rect.y = random.randrange(-500, -50)

def update(self):
# 云朵大于2倍高度,就被消除
if self.rect.top > HEIGHT * 2:
self.kill()

代码内容与此前内容非常类似,不再详细分析。

但你仔细观察,你会发现,Cloud类的__init__()方法中创建了 self._layer,并通过如下形式将其加入到相应的groups中。

1
2
self.groups = game.all_sprites, game.clouds
pg.sprite.Sprite.__init__(self, self.groups)

这是一个优化点,后文再讨论。

构建了Cloud类后,接着要做的就是载入图片、随机生成以及同步移动了,轻车熟路。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# main.py/Game

def load_data(self): # 加载数据
# ... 省略无关代码
# 加载云彩图片
self.cloud_images = []
for i in range(1, 4):
self.cloud_images.append(pg.image.load(os.path.join(img_dir, 'cloud{}.png'.format(i))).convert())

def new(self):
self.score = 0
# ... 省略无关代码
# 创建云彩
for i in range(8):
c = Cloud(self)
c.rect.y += 500
self.run()

def update(self):
# 玩家到达游戏框 1/4 处时(注意,游戏框,头部为0,底部为游戏框长度,到到游戏框的1/4处,表示已经到达了顶部一部分了)
if self.player.rect.top <= HEIGHT / 4:
# 玩家位置移动(往下移动)
self.player.pos.y += max(abs(self.player.vel.y), 2)
# 随机生成新云朵
if random.randrange(100) < 10:
Cloud(self)
# 云彩同步移动
for cloud in self.clouds:
cloud.rect.y += max(abs(self.player.vel.y / 2), 2)
# 敌人位置同步往下移动
for mob in self.mobs:
mob.rect.y += max(abs(self.player.vel.y), 2)
# 平台在游戏框外时,将其注销,避免资源浪费
for plat in self.platforms:
# 平台移动位置(往下移动,移动的距离与玩家相同,这样玩家才能依旧站立在原本的平台上)
plat.rect.y += max(abs(self.player.vel.y), 2)
if plat.rect.top >= HEIGHT:
plat.kill()
# 分数增加 - 平台销毁,分数相加
self.score += 10

woo~,搞定,如果有疑惑,可以拉下github代码对照着看。

优化

云彩类添加完了,接着来进行一些优化。

首先,对碰撞检测的优化,如果你仔细观察,你会发现,玩家对象与敌人对象的本体还没有接触到,就触发了碰撞检测,游戏就结束了,造成这种现象的原因是,玩家也好、敌人也好,游戏中的任何元素都是一个对应大小的长方形,碰撞检测默认形式就是对这两个方块进行检测,此时两个元素本身可能没有接触,但对应的方块接触到了,所以触发了碰撞检测。

为了避免这种情况,可以利用pygame的蒙版机制mask,为Player、Mob都创建相应的蒙版,具体做法如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# sprites.py

class Player(pg.sprite.Sprite):
def animate(self):
# ... 省略无关代码
self.mask = pg.mask.from_surface(self.image) # 创建蒙版

class Mob(pg.sprite.Sprite):

def update(self):
# ... 省略无关代码
self.rect = self.image.get_rect()
self.mask = pg.mask.from_surface(self.image) # 创建蒙版
self.rect.center = center
# ... 省略无关代码

检测时,加上pygame.sprite.collide_mask回调则可

1
2
3
4
5
6
def update(self):
# ... 省略无关代码
# 碰撞检测 - 如果碰撞到了敌人,游戏结束
mob_hits = pg.sprite.spritecollide(self.player, self.mobs, False, pg.sprite.collide_mask)
if mob_hits:
self.playing = False

此外还有个需要优化的问题,就是元素图层关系,增加云彩对象后,图层关系的问题显得明显,云彩作为背景却会遮挡玩家对象、敌人对象、平台对象等,这是不合理的,不同元素应该在不同图层,从而合理的显示出来。

首先定义好不同元素要出现的图层。

1
2
3
4
5
6
7
8
# settings.py

# 不同元素在不同层
PLAYER_LAYER = 2 # 玩家
PLATFORM_LAYER = 1 # 平台
POW_LAYER = 1 # 道具
MOB_LAYER = 2 # 敌人
CLOUD_LAYER = 0 # 云

然后为不同的元素对象都添加上设置图层的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#Sprite.py

class Player(pg.sprite.Sprite):
def __init__(self, game):
self._layer = PLAYER_LAYER # 对应的图层
self.groups = game.all_sprites # 所在的组
pg.sprite.Sprite.__init__(self, self.groups) # 添加、实例化
# ... 省略无关代码

class Platform(pg.sprite.Sprite):
def __init__(self, game, x, y):
self._layer = PLATFORM_LAYER # 对应的图层
self.groups = game.all_sprites, game.platforms # 所在的组
pg.sprite.Sprite.__init__(self, self.groups) # 添加、实例化
# ... 省略无关代码

class Pow(pg.sprite.Sprite):
def __init__(self, game, plat):
self._layer = POW_LAYER
self.groups = game.all_sprites, game.powerups
pg.sprite.Sprite.__init__(self, self.groups)
# ... 省略无关代码

class Mob(pg.sprite.Sprite):
def __init__(self, game):
self._layer = MOB_LAYER
self.groups = game.all_sprites, game.mobs
pg.sprite.Sprite.__init__(self, self.groups)
# ... 省略无关代码

class Cloud(pg.sprite.Sprite):
def __init__(self, game):
self._layer = CLOUD_LAYER
self.groups = game.all_sprites, game.clouds
pg.sprite.Sprite.__init__(self, self.groups)
# ... 省略无关代码

优化后,再修改一个Game类的new()方法,使用pygame.sprite.LayeredUpdates()来实例化all_sprites对象。

LayeredUpdates(分层更新)是一个精灵组,它可以处理图层并顺序绘制元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# main.py

class Game:
def new(self):
self.score = 0
# self.all_sprites = pg.sprite.Group()
# 层次添加,避免元素重叠显示(如背景云遮挡住平台与玩家)
self.all_sprites = pg.sprite.LayeredUpdates()
self.platforms = pg.sprite.Group()
self.powerups = pg.sprite.Group() # 急速飞跃道具
self.mobs = pg.sprite.Group() # 敌人对象
self.clouds = pg.sprite.Group() # 云彩对象
self.player = Player(self)
self.all_sprites.add(self.player)
for plat in PLATFORM_LIST:
p = Platform(self, *plat)
# self.all_sprites.add(p)
# self.platforms.add(p)
self.mob_timer = 0
# 游戏的背景音乐
pg.mixer.music.load(os.path.join(self.snd_dir, 'Happy Tune.ogg'))
# 创建云彩
for i in range(8):
c = Cloud(self)
c.rect.y += 500
self.run()

最终效果如下。

「跳跳兔」至此开发完啦,Pygame系列的文章也暂时告一段落啦。

「跳跳兔」代码github:https://github.com/ayuLiao/jumprabbit

Pygame还有很多有趣的功能在「跳跳兔」游戏中并没有体现出来。

正如本系列第一篇文章所说,这些文章只是学习笔记,此外还有下面两个游戏的学习笔记,一个是打飞机、一个是RPG打僵尸游戏。

如果大家感兴趣,记得告诉我,我才有动力继续分享,后面按个人计划应该会以漫画形式分享算法、计算机基础方面的东西,希望喜欢。

最后,再次声明一下,学习内容来自:http://kidscancode.org/,游戏并不是自主原创的。