前言
上一节我们实现了英雄角色的左右移动及跳跃功能,但是只有一张单一的图片,英雄无法产生更生动的形象。所以本节我们将会在人物运动的过程中为其添加动画效果。
Demo演示
我们先来看下本节的效果:
python使用pygame开发游戏系列之精灵动画的实现。
代码如下:
import json
import pygame
class Player(object):
"""player对象"""
def __init__(self):
self.player_img = "zombie.png"
self.all_frames = self.load_frames_from_sheet()
self.rect = self.all_frames.get("left").get("idle")[0].get_rect()
self.rect.midbottom = (100, screen_height - 135)
self.vel_y = 0
self.current_frame = 0
self.jumped = False
self.direction = 1 # 1:右 -1:左
self.dir_frames = []
self.frames = []
self.state = 'idle'
self.is_attack = False
self.last_updated = 0
self.current_image = self.all_frames.get("left").get("idle")[0]
def handle_state(self):
"""根据状态决定行为"""
if self.direction == -1: # 根据方向选择朝向不同的序列图片
self.dir_frames = self.all_frames.get("left")
else:
self.dir_frames = self.all_frames.get("right")
if self.state == 'idle':
self.frames = self.dir_frames.get("idle")
elif self.state == "run":
self.frames = self.dir_frames.get("run")
elif self.state == "walk":
self.frames = self.dir_frames.get("walk")
elif self.state == "jump":
self.frames = self.dir_frames.get("jump")
elif self.state == "fall":
self.frames = self.dir_frames.get("fall")
elif self.state == "attack":
self.frames = self.dir_frames.get("attack")
def animate(self):
now = pygame.time.get_ticks()
if now - self.last_updated > 100:
self.last_updated = now
self.current_frame = (self.current_frame + 1) % len(self.frames)
self.current_image = self.frames[self.current_frame]
def load_frames_from_sheet(self):
my_sprite_sheet = SpriteSheet(self.player_img)
stand_frames = [my_sprite_sheet.parse_sprite("idle")]
move_frames = [my_sprite_sheet.parse_sprite("walk{}".format(i)) for i in range(0, 8)]
jump_frames = [my_sprite_sheet.parse_sprite("jump")]
fall_frames = [my_sprite_sheet.parse_sprite("fall")]
attack_frames = [my_sprite_sheet.parse_sprite("attack{}".format(i)) for i in range(0, 3)]
run_frames = [my_sprite_sheet.parse_sprite("run{}".format(i)) for i in range(0, 3)]
right_frames = {"idle": stand_frames,
"walk": move_frames,
"jump": jump_frames,
"fall": fall_frames,
"run": run_frames,
"attack": attack_frames}
left_frames = {key: [pygame.transform.flip(frame, True, False) for frame in frames]
for key, frames in right_frames.items()}
all_frames = {"left": left_frames, "right": right_frames}
return all_frames
def update(self):
x_move = 0
y_move = 0
self.state = "idle"
self.is_attack = False
# 获取按键,并进行相应的移动
key = pygame.key.get_pressed()
if key[pygame.K_SPACE] and not self.jumped:
self.vel_y = -15
self.jumped = True
if key[pygame.K_LEFT]:
x_move -= 5
self.direction = -1
self.state = "walk"
elif key[pygame.K_a]:
x_move -= 7
self.direction = -1
self.state = "run"
if key[pygame.K_RIGHT]:
x_move += 5
self.direction = 1
self.state = "walk"
elif key[pygame.K_d]:
x_move += 7
self.direction = 1
self.state = "run"
if key[pygame.K_j] and self.is_attack is False:
self.state = "attack"
self.is_attack = True
if self.vel_y < 0:
self.state = "jump"
if self.jumped and self.vel_y > 0:
self.state = "fall"
# 添加角色重力(跳跃之后自然下落)
self.vel_y += 0.8
if self.vel_y > 10:
self.vel_y = 10
y_move += self.vel_y
self.rect.x += x_move
self.rect.y += y_move
# 控制人物的最低位置
if self.rect.bottom > screen_height - 135:
self.rect.bottom = screen_height - 135
self.jumped = False
self.handle_state()
self.animate()
# 绘制人物
screen.blit(self.current_image, self.rect)
# pygame.draw.rect(screen, (255, 255, 255), self.rect, 2)
class Cloud(object):
"""云层对象"""
def __init__(self, x, y):
self.image = pygame.image.load('cloud.png').convert_alpha()
self.rect = self.image.get_rect()
self.rect.topleft = (x, y)
def update(self):
self.rect.x -= 1 # 云层移动
screen.blit(self.image, self.rect)
if self.rect.x < -1400: # 超出边界后重新在屏幕最右边绘制云层
self.rect.x = 1400
class SpriteSheet(object):
def __init__(self, filename):
self.filename = filename
self.sprite_sheet = pygame.image.load(filename).convert()
self.meta_data = self.filename.replace('png', 'json')
with open(self.meta_data) as f:
self.data = json.load(f)
f.close()
def get_sprite(self, x, y, w, h):
sprite = pygame.Surface((w, h))
sprite.set_colorkey((0, 0, 0))
sprite.blit(self.sprite_sheet, (0, 0), (x, y, w, h))
return sprite
def parse_sprite(self, name):
sprite = self.data['frames'][name]["frame"]
x, y, w, h = int(sprite["x"]), int(sprite["y"]), int(sprite["w"]), int(sprite["h"])
image = self.get_sprite(x, y, w, h)
image = pygame.transform.scale(image, (90, 120))
return image
# --------------------------------加载基本的窗口和时钟----------------------------
pygame.init()
screen_width = 1400
screen_height = 700
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption('player_control')
clock = pygame.time.Clock() # 设置时钟
# -------------------------------- 加载对象 ----------------------------------
bg = pygame.image.load("bg.png").convert()
player = Player()
cloud1 = Cloud(0, 0)
cloud2 = Cloud(1400, 0)
# -------------------------------- 游戏主循环 ----------------------------------
run = True
while run:
clock.tick(60)
screen.blit(bg, (0, 0))
# -------------------------------- 角色更新 ----------------------------------
cloud1.update()
cloud2.update()
player.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
# ------------------------------- 窗口更新并绘制 ------------------------------
pygame.display.update()
pygame.quit()
素材样例(可能存在变形):
背景:
云彩:
英雄图片集合:
英雄图片坐标文件:
{
"frames": {
"idle": {"frame": {"x": "0", "y": "0", "w": "192", "h": "256"}},
"jump": {"frame": {"x": "192", "y": "0", "w": "192", "h": "256"}},
"fall": {"frame": {"x": "384", "y": "0", "w": "192", "h": "256"}},
"duck": {"frame": {"x": "576", "y": "0", "w": "192", "h": "256"}},
"hit": {"frame": {"x": "768", "y": "0", "w": "192", "h": "256"}},
"climb0": {"frame": {"x": "960", "y": "0", "w": "192", "h": "256"}},
"climb1": {"frame": {"x": "1152", "y": "0", "w": "192", "h": "256"}},
"cheer0": {"frame": {"x": "1344", "y": "0", "w": "192", "h": "256"}},
"cheer1": {"frame": {"x": "1536", "y": "0", "w": "192", "h": "256"}},
"back": {"frame": {"x": "0", "y": "256", "w": "192", "h": "256"}},
"slide": {"frame": {"x": "192", "y": "256", "w": "192", "h": "256"}},
"interact": {"frame": {"x": "384", "y": "256", "w": "192", "h": "256"}},
"switch0": {"frame": {"x": "576", "y": "256", "w": "192", "h": "256"}},
"switch1": {"frame": {"x": "768", "y": "256", "w": "192", "h": "256"}},
"kick": {"frame": {"x": "960", "y": "256", "w": "192", "h": "256"}},
"side": {"frame": {"x": "1152", "y": "256", "w": "192", "h": "256"}},
"shove": {"frame": {"x": "1344", "y": "256", "w": "192", "h": "256"}},
"shoveBack": {"frame": {"x": "1536", "y": "256", "w": "192", "h": "256"}},
"talk": {"frame": {"x": "0", "y": "512", "w": "192", "h": "256"}},
"attackKick": {"frame": {"x": "192", "y": "512", "w": "192", "h": "256"}},
"hang": {"frame": {"x": "384", "y": "512", "w": "192", "h": "256"}},
"hold": {"frame": {"x": "576", "y": "512", "w": "192", "h": "256"}},
"show": {"frame": {"x": "768", "y": "512", "w": "192", "h": "256"}},
"behindBack": {"frame": {"x": "960", "y": "512", "w": "192", "h": "256"}},
"run0": {"frame": {"x": "1152", "y": "512", "w": "192", "h": "256"}},
"run1": {"frame": {"x": "1344", "y": "512", "w": "192", "h": "256"}},
"run2": {"frame": {"x": "1536", "y": "512", "w": "192", "h": "256"}},
"attack0": {"frame": {"x": "0", "y": "768", "w": "192", "h": "256"}},
"attack1": {"frame": {"x": "192", "y": "768", "w": "192", "h": "256"}},
"attack2": {"frame": {"x": "384", "y": "768", "w": "192", "h": "256"}},
"think": {"frame": {"x": "576", "y": "768", "w": "192", "h": "256"}},
"down": {"frame": {"x": "768", "y": "768", "w": "192", "h": "256"}},
"drag": {"frame": {"x": "960", "y": "768", "w": "192", "h": "256"}},
"hurt": {"frame": {"x": "1152", "y": "768", "w": "192", "h": "256"}},
"wide": {"frame": {"x": "1344", "y": "768", "w": "192", "h": "256"}},
"rope": {"frame": {"x": "1536", "y": "768", "w": "192", "h": "256"}},
"walk0": {"frame": {"x": "0", "y": "1024", "w": "192", "h": "256"}},
"walk1": {"frame": {"x": "192", "y": "1024", "w": "192", "h": "256"}},
"walk2": {"frame": {"x": "384", "y": "1024", "w": "192", "h": "256"}},
"walk3": {"frame": {"x": "576", "y": "1024", "w": "192", "h": "256"}},
"walk4": {"frame": {"x": "768", "y": "1024", "w": "192", "h": "256"}},
"walk5": {"frame": {"x": "960", "y": "1024", "w": "192", "h": "256"}},
"walk6": {"frame": {"x": "1152", "y": "1024", "w": "192", "h": "256"}},
"walk7": {"frame": {"x": "1344", "y": "1024", "w": "192", "h": "256"}},
"fallDown": {"frame": {"x": "1536", "y": "1024", "w": "192", "h": "256"
}
}
}
}
知识点:
1、动画的实现原理
2、使用相应的坐标文件从图片集合中加载各种动作
ps:如果觉得文章对您有所帮助,欢迎点赞收藏转发,感谢呦!