0%

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

前言

在第6节内容中,实现了游戏结束逻辑与玩家图片化,但跳跳兔只是一张简单的图片,显得比较呆板,本节会为跳跳兔添加上相应的动画效果,并将平台替换成相应的图片。

添加动画

跳跳兔在站立时,希望有上下蹲的动画,在走动时,希望有左右走动的动画,在跳跃时,希望有跳跃动画。

动画的本质就是不同图片间的切换,在pygame中要实现动画,只需要在不同帧使用不同的图片则可。

在Player的__init__()方法中定义多个变量用于记录不同的状态,代码如下

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

class Player(pg.sprite.Sprite):
def __init__(self, game):
pg.sprite.Sprite.__init__(self)
self.game = game
# 不同的状态
self.walking = False
self.jumping = False
# 当前帧(用于判断当前要执行哪个动画)
self.current_frame = 0
self.last_update = 0
self.load_images() # 加载图片
self.image = self.standing_frames[0]
self.rect = self.image.get_rect()
self.rect.center = (WIDTH / 2, HEIGHT / 2)
self.pos = vec(WIDTH / 2, HEIGHT / 2)
self.vel = vec(0, 0) # 速度
self.acc = vec(0, 0) # 加速度

在__init__()方法中,定义了self.walking与self.jumping状态,用于表示玩家对象是在行走状态还是跳跃状态,接着定义了self.current_frame用于表示当前帧,定义了self.last_update用于记录上一次的时间点,随后,编调用了load_images()方法来载入图片,该方法代码如下

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

def load_images(self):
# 站立状态
self.standing_frames = [self.game.spritesheet.get_image(614, 1063, 120, 191),
self.game.spritesheet.get_image(690, 406, 120, 201)]
for frame in self.standing_frames:
frame.set_colorkey(BLACK) # 将图像矩阵中除图像外周围的元素都设置为透明的

# 走动状态
self.walk_frames_r = [self.game.spritesheet.get_image(678, 860, 120, 201),
self.game.spritesheet.get_image(692, 1458, 120, 207)]
self.walk_frames_l = []
for frame in self.walk_frames_r:
frame.set_colorkey(BLACK)
# 水平翻转
self.walk_frames_l.append(pg.transform.flip(frame, True, False))

# 跳跃状态
self.jump_frame = self.game.spritesheet.get_image(382, 763, 150, 181)
self.jump_frame.set_colorkey(BLACK)

在load_images()方法中,为不同的状态载入了不同的图片,其中,走动状态的图片还做了水平翻转处理,这是因为原始的大图中,走动的图片只有一个方向的,而走动可以往左走也可以往右走,所以需要将图片水平翻转一下。

调用pygame.transform.flip(Surface, xbool, ybool)用于翻转,xbool => True 为水平翻转,ybool => True 为垂直翻转。

图片准备好后,动画效果的基本素材就准备好了,在Player类的update()方法中调用动画方法。

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
41
# sprites.py/Player

def update(self):
# 动画
self.animate()
...

def animate(self):
# 获得当前过了多少毫秒
now = pg.time.get_ticks()

if self.vel.x != 0: # 判断速度在x轴方向是否为0,从而判断玩家对象是否移动
self.walking = True
else:
self.walking = False
# 走动状态下的动画
if self.walking:
# 当前时间 - 上次时间 大于 180,即间隔时间大于180时
if now - self.last_update > 180:
self.last_update = now
# 当前帧 加一 与 walk_frames_l 长度取余,从而得到当前要做哪个东西
self.current_frame = (self.current_frame + 1) % len(self.walk_frames_l)
bottom = self.rect.bottom
# 向左走还是向右走
if self.vel.x > 0:
# 当前帧要做的动作
self.image = self.walk_frames_r[self.current_frame]
else:
self.image = self.walk_frames_l[self.current_frame]
self.rect = self.image.get_rect()
self.rect.bottom = bottom

# 站立状态下的动画
if not self.jumping and not self.walking:
if now - self.last_update > 350:
self.last_update = now
self.current_frame = (self.current_frame + 1) % len(self.standing_frames)
bottom = self.rect.bottom
self.image = self.standing_frames[self.current_frame]
self.rect = self.image.get_rect()
self.rect.bottom = bottom

在pygame 中的时间是以毫秒(千分之一秒)表示的,通过 pygame.time.get_ticks 函数可以获得 pygame.init 后经过的时间的毫秒数。

随后的逻辑通过注释可以比较简单的理解。判断当前时间与上一层记录时间的间隔,如果满足条件,则只需图片的切换逻辑,注意,时间都是毫秒级的。

切换图片的核心逻辑就是当前帧与图片列表长度取余,获得下标,通过下标去取列表中的图片。

走动时效果如下:

有个细节需要注意,在判断玩家对象是否是走动状态时,利用了速度变量的x轴是否为0来判断

1
2
3
4
if self.vel.x != 0: # 判断速度在x轴方向是否为0,从而判断玩家对象是否移动
self.walking = True
else:
self.walking = False

但self.vel.x通常不会为0,所以需要处理一下,修改一下update()方法中的逻辑,代码如下

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
def update(self):
# 动画
self.animate()
self.acc = vec(0, PLAYER_GRAV)
keys = pg.key.get_pressed()
if keys[pg.K_LEFT]:
self.acc.x = -PLAYER_ACC
if keys[pg.K_RIGHT]:
self.acc.x = PLAYER_ACC

# 获得加速度
self.acc.x += self.vel.x * PLAYER_FRICTION
# 速度与加速度
self.vel += self.acc
# 如果速度小于0.1,则速度为0(比如这样设置,不然速度永远无法0)
if abs(self.vel.x) < 0.1:
self.vel.x = 0
self.pos += self.vel + 0.5 * self.acc
# wrap around the sides of the screen
if self.pos.x > WIDTH:
self.pos.x = 0
if self.pos.x < 0:
self.pos.x = WIDTH

self.rect.midbottom = self.pos

如果self.vel.x的绝对值小于0.1,则让self.vel.x为0。

平台图片化

跳跳兔要跳跃到相应的平台上,现在平依旧是方块,这里以相同的方式将平台替换成相应的图片。

在Platform的__init__()中,实现载入图片的逻辑,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
class Platform(pg.sprite.Sprite):
def __init__(self, game, x, y):
pg.sprite.Sprite.__init__(self)
self.game = game
# 载入图片
images = [self.game.spritesheet.get_image(0, 288, 380, 94),
self.game.spritesheet.get_image(213, 1662, 201, 100)]
# 随机选择一种
self.image = random.choice(images)
self.image.set_colorkey(BLACK)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y

载入完图片,随机选择一个图片作为样式,需要注意,我们修改了__init__()的参数,此时该方法只需要获得(x,y)坐标以及game实例则可。

因为__init__()被修改了,所以实例化逻辑也要修改一下。

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
#setting.py
# 起始平台
PLATFORM_LIST = [(0, HEIGHT - 60),
(WIDTH / 2 - 50, HEIGHT * 3 / 4 - 50),
(125, HEIGHT - 350),
(350, 200),
(175, 100)]

# main.py/Game

def new(self):
self.score = 0
self.all_sprites = pg.sprite.Group()
self.platforms = 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.run()

def update(self):
# ...
# 判断平台数,产生新的平台
while len(self.platforms) < 6:
width = random.randrange(50, 100)
# 平台虽然是随机生成的,但会生成在某一个范围内
p = Platform(self, random.randrange(0, WIDTH - width),
random.randrange(-75, -30))
self.platforms.add(p)
self.all_sprites.add(p)

最终效果如下

在本节中,我们实现了玩家对象的动画效果以及平台的图片化。

因为考虑到篇幅,文中没有给出完整的代码,但为了方便大家理解,我将相应的代码上传到了github

https://github.com/ayuLiao/jumprabbit

如果文章对你有帮助或你觉得有点意思,点击「在看」支持作者一波。