效果演示
鼠标放置在工具栏格子上图像放大
按下数字键盘或鼠标左键点击切换对应物品
角色攻击动作和使用工具实现思路
首先需要一个角色动作字典,初始化的时候导入所有的动作
角色攻击是不同于玩家移动,移动的时候只要玩家离开按键就不需要继续播放帧动画,但是角色攻击的时候玩家离开按键还需要继续播放帧动画,这里一个实现思路是角色攻击的时候启动一个定时器,定时器激活的时候禁止玩家移动,等待定时器超时才停止播放攻击动作或使用工具动作并恢复玩家移动
部分代码演示
导入玩家动作帧
def import_player_actions(self):
"""导入玩家的动作帧"""
self.player_actions = {'down': [], 'down_sword': [], 'down_axe': [], 'down_hoe': [], 'down_idle': [],
'down_pickaxe': [], 'down_water': [],
'right': [], 'right_sword': [], 'right_axe': [], 'right_hoe': [], 'right_idle': [],
'right_pickaxe': [], 'right_water': [],
'up': [], 'up_sword': [], 'up_axe': [], 'up_hoe': [], 'up_idle': [],
'up_pickaxe': [], 'up_water': [],
'death': []}
for player_action in self.player_actions.keys():
full_path = '../images/characters/' + player_action
self.player_actions[player_action] = import_image(full_path)
定义定时器
# 角色使用工具
self.timer = {'attack': Timer(350), # 角色攻击时间为350ms
'use tool': Timer(400) # 使用工具时间间隔为400ms
}
# 角色正在使用的工具和武器
self.weapons_list = ['sword']
self.tools_list = ['axe', 'hoe', 'pickaxe', 'water']
self.using = None
键盘输入
def input(self):
"""获取键盘输入"""
keys = pygame.key.get_pressed()
# 角色不攻击才能执行相关操作
if not self.timer['attack'].active and not self.timer['use tool'].active:
# 没有键盘输入将玩家置于空闲
for direction in ['up', 'down', 'right']:
if direction in self.player_action:
self.player_action = direction + '_idle'
# 上下移动
if keys[pygame.K_UP] or keys[pygame.K_w]:
self.direction.y = -1
self.player_action = 'up'
elif keys[pygame.K_DOWN] or keys[pygame.K_s]:
self.direction.y = 1
self.player_action = 'down'
else:
self.direction.y = 0
# 左右移动
if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
self.direction.x = 1
self.player_action = 'right'
elif keys[pygame.K_LEFT] or keys[pygame.K_a]:
self.direction.x = -1
else:
self.direction.x = 0
# 攻击
if keys[pygame.K_SPACE] and self.using is not None:
if self.using in self.weapons_list:
self.timer['attack'].activate()
elif self.using in self.tools_list:
self.timer['use tool'].activate()
self.direction = pygame.math.Vector2((0, 0)) # 将向量重新至0
self.player_action_index = 0 # 攻击动从第一帧开始播放
for direction in ['up', 'down', 'right']:
if direction in self.player_action:
self.player_action = direction + '_' + self.using
break
播放帧动画
def animate(self, dt):
"""根据玩家行为动作切换动作帧"""
self.player_action_index += 4 * dt # 根据时间增量不断累加
# 如果超过玩家动作列表范围,重新置于第一帧
if self.player_action_index >= len(self.player_actions[self.player_action]):
self.player_action_index = 0
self.image = self.player_actions[self.player_action][int(self.player_action_index)]
self.rect = self.image.get_rect(center=self.rect.center)
# 将图像绘制在屏幕上
self.display_surface.blit(self.image, self.rect)
工具栏实现
pygame实现工具栏所有功能还是太繁琐,下一步看看能不能实现鼠标点击拖动物品到不同的格子里面,现在还只是一个雏形
实现思路
工具栏里面的每一个框就是一个矩形,只需要在特定位置绘制矩形就可以绘制所有的框了,框的位置确定了,之后就是填充图像,图像大小需要变换,然后就是把图像放在框中央,上代码吧
类封装
class Image:
"""生成框中的图像"""
def __init__(self, image, target_rect):
# 获取游戏界面
self.display_surface = pygame.display.get_surface()
# 初始化显示图像
self.target_rect = target_rect # 目标位置
self.init_image = image
self.init_width, self.init_height = self.init_image.get_size()
self.k = (self.target_rect.height + self.target_rect.width) / 2 / (self.init_width + self.init_height)
self.convert_image = pygame.transform.scale(self.init_image,
(self.k * self.init_width, self.k * self.init_height))
self.magnify_image = pygame.transform.scale(self.init_image,
(1.4 * self.k * self.init_width,
1.4 * self.k * self.init_height))
# 需要绘制的图像和位置
self.image, self.rect = None, None
def show_image(self, collision):
"""显示图像"""
self.image = self.magnify_image if collision else self.convert_image
self.rect = self.image.get_rect(center=self.target_rect.center)
self.display_surface.blit(self.image, self.rect)
class Frame(pygame.sprite.Sprite):
"""绘制一个物品框"""
def __init__(self, group, color, size, image=None, rect=None):
super().__init__(group)
# 获取游戏界面
self.display_surface = pygame.display.get_surface()
# 框中图像
self.image = image
self.frame_image = None
# 框设置
self.rect = rect # 位置
self.color = color # 颜色
self.size = size # 框的粗细
# 初始鼠标状态
self.mouse_pos, self.collision = None, None
def update_frame_image(self):
"""更新框中的图片"""
del self.frame_image
self.frame_image = Image(self.image, self.rect)
def draw_rect(self):
"""画框"""
pygame.draw.rect(self.display_surface, self.color, self.rect, self.size)
def get_mouse_status(self):
"""获取鼠标状态"""
self.mouse_pos = pygame.mouse.get_pos()
self.collision = self.rect.collidepoint(self.mouse_pos)
def fill_image(self):
"""填充图像"""
self.frame_image.show_image(self.collision)
def update(self):
if self.rect is not None:
self.draw_rect()
self.get_mouse_status()
if self.image is not None:
self.update_frame_image()
self.fill_image()
绘制工具栏
# 初始化物品框区域
self.width, self.height = 40, 40 # 物品框的高宽
self.frame_numbers = 10 # 物品框数量
self.frame_area = pygame.Rect(int((SCREEN_WIDTH / 2) - (self.frame_numbers * self.width / 2)),
self.display_surface_rect.bottom - self.height,
self.frame_numbers * self.width,
self.height) # 物品框区域
# 生成物品框
for number in range(0, self.frame_numbers):
rect = pygame.Rect(self.frame_area.left + number * self.width, self.frame_area.top, self.width, self.height)
Frame(group=self.frame_collision, color='blue', size=3, rect=rect)
完整代码
目录结构
main.py(主循环)
import pygame, sys
from level import Level
from settings import *
class Game:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption('测试') # 设置标题
self.clock = pygame.time.Clock() # 创建时钟对象
self.level = Level()
def run(self):
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
dt = self.clock.tick(FPS) / 1000 # 计算自上次调用到现在经过的秒数
self.level.run(dt)
pygame.display.flip()
if __name__ == '__main__':
game = Game()
game.run()
player.py(角色)
import pygame
from settings import *
from support import import_image, Timer
class Player(pygame.sprite.Sprite):
def __init__(self, gruop):
super().__init__(gruop)
# 获取显示界面
self.display_surface = pygame.display.get_surface()
# 角色速度
self.speed = 200
# 角色移动方向
self.direction = pygame.math.Vector2()
# 角色动作帧
self.import_player_actions()
self.player_action = 'down_idle' # 角色初始的状态
self.player_action_index = 0 # 角色动作列表索引
self.image = self.player_actions[self.player_action][self.player_action_index] # 显示的图像
self.rect = self.image.get_rect()
self.rect.center = (SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2) # 起始将角色置于屏幕中心
# 角色使用工具
self.timer = {'attack': Timer(350), # 角色攻击时间为350ms
'use tool': Timer(400) # 使用工具时间间隔为400ms
}
# 角色正在使用的工具和武器
self.weapons_list = ['sword']
self.tools_list = ['axe', 'hoe', 'pickaxe', 'water']
self.using = None
def import_player_actions(self):
"""导入玩家的动作帧"""
self.player_actions = {'down': [], 'down_sword': [], 'down_axe': [], 'down_hoe': [], 'down_idle': [],
'down_pickaxe': [], 'down_water': [],
'right': [], 'right_sword': [], 'right_axe': [], 'right_hoe': [], 'right_idle': [],
'right_pickaxe': [], 'right_water': [],
'up': [], 'up_sword': [], 'up_axe': [], 'up_hoe': [], 'up_idle': [],
'up_pickaxe': [], 'up_water': [],
'death': []}
for player_action in self.player_actions.keys():
full_path = '../images/characters/' + player_action
self.player_actions[player_action] = import_image(full_path)
def input(self):
"""获取键盘输入"""
keys = pygame.key.get_pressed()
# 角色不攻击才能执行相关操作
if not self.timer['attack'].active and not self.timer['use tool'].active:
# 没有键盘输入将玩家置于空闲
for direction in ['up', 'down', 'right']:
if direction in self.player_action:
self.player_action = direction + '_idle'
# 上下移动
if keys[pygame.K_UP] or keys[pygame.K_w]:
self.direction.y = -1
self.player_action = 'up'
elif keys[pygame.K_DOWN] or keys[pygame.K_s]:
self.direction.y = 1
self.player_action = 'down'
else:
self.direction.y = 0
# 左右移动
if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
self.direction.x = 1
self.player_action = 'right'
elif keys[pygame.K_LEFT] or keys[pygame.K_a]:
self.direction.x = -1
else:
self.direction.x = 0
# 攻击
if keys[pygame.K_SPACE] and self.using is not None:
if self.using in self.weapons_list:
self.timer['attack'].activate()
elif self.using in self.tools_list:
self.timer['use tool'].activate()
self.direction = pygame.math.Vector2((0, 0)) # 将向量重新至0
self.player_action_index = 0 # 攻击动从第一帧开始播放
for direction in ['up', 'down', 'right']:
if direction in self.player_action:
self.player_action = direction + '_' + self.using
break
def move(self, dt):
"""玩家移动"""
# 如果方向向量长度大于0才执行相关动作
if self.direction.magnitude() > 0:
self.direction = self.direction.normalize() # 归一化
self.rect.x += self.direction.x * self.speed * dt
self.rect.y += self.direction.y * self.speed * dt
def animate(self, dt):
"""根据玩家行为动作切换动作帧"""
self.player_action_index += 4 * dt # 根据时间增量不断累加
# 如果超过玩家动作列表范围,重新置于第一帧
if self.player_action_index >= len(self.player_actions[self.player_action]):
self.player_action_index = 0
self.image = self.player_actions[self.player_action][int(self.player_action_index)]
self.rect = self.image.get_rect(center=self.rect.center)
# 将图像绘制在屏幕上
self.display_surface.blit(self.image, self.rect)
def update_all_timer(self):
"""更新所有定时器"""
for timer in self.timer.values():
timer.start()
def update(self, dt):
"""更新玩角色相关行为"""
self.update_all_timer()
self.input() # 获取键盘输入
self.move(dt) # 角色移动
self.animate(dt) # 播放动画帧
settings.py(设置文件)
# 屏幕宽度、屏幕高度
SCREEN_WIDTH, SCREEN_HEIGHT = 1200, 800
# 帧率
FPS = 90
support.py(支持文件,导入图像和自定义定时器)
# 屏幕宽度、屏幕高度
SCREEN_WIDTH, SCREEN_HEIGHT = 1200, 800
# 帧率
FPS = 90
level.py(游戏进度文件)
import pygame
from player import Player
from display_tools import DisplayTools
class Level:
def __init__(self):
# 获取显示界面
self.display_surface = pygame.display.get_surface()
self.display_surface_rect = self.display_surface.get_rect()
# 精灵族
self.all_sprites = pygame.sprite.Group() # 存放屏幕上的所有元素
# 初始化角色
self.player = Player(self.all_sprites)
self.display_tools = DisplayTools(self.player)
def run(self, dt):
self.display_surface.fill('gray') # 填充白色
self.display_tools.update()
self.all_sprites.update(dt)
display_tools.py(工具栏文件)
import pygame
from settings import *
class Image:
"""生成框中的图像"""
def __init__(self, image, target_rect):
# 获取游戏界面
self.display_surface = pygame.display.get_surface()
# 初始化显示图像
self.target_rect = target_rect # 目标位置
self.init_image = image
self.init_width, self.init_height = self.init_image.get_size()
self.k = (self.target_rect.height + self.target_rect.width) / 2 / (self.init_width + self.init_height)
self.convert_image = pygame.transform.scale(self.init_image,
(self.k * self.init_width, self.k * self.init_height))
self.magnify_image = pygame.transform.scale(self.init_image,
(1.4 * self.k * self.init_width,
1.4 * self.k * self.init_height))
# 需要绘制的图像和位置
self.image, self.rect = None, None
def show_image(self, collision):
"""显示图像"""
self.image = self.magnify_image if collision else self.convert_image
self.rect = self.image.get_rect(center=self.target_rect.center)
self.display_surface.blit(self.image, self.rect)
class Frame(pygame.sprite.Sprite):
"""绘制一个物品框"""
def __init__(self, group, color, size, image=None, rect=None):
super().__init__(group)
# 获取游戏界面
self.display_surface = pygame.display.get_surface()
# 框中图像
self.image = image
self.frame_image = None
# 框设置
self.rect = rect # 位置
self.color = color # 颜色
self.size = size # 框的粗细
# 初始鼠标状态
self.mouse_pos, self.collision = None, None
def update_frame_image(self):
"""更新框中的图片"""
del self.frame_image
self.frame_image = Image(self.image, self.rect)
def draw_rect(self):
"""画框"""
pygame.draw.rect(self.display_surface, self.color, self.rect, self.size)
def get_mouse_status(self):
"""获取鼠标状态"""
self.mouse_pos = pygame.mouse.get_pos()
self.collision = self.rect.collidepoint(self.mouse_pos)
def fill_image(self):
"""填充图像"""
self.frame_image.show_image(self.collision)
def update(self):
if self.rect is not None:
self.draw_rect()
self.get_mouse_status()
if self.image is not None:
self.update_frame_image()
self.fill_image()
class DisplayTools:
"""在页面底部绘制全部物品框"""
def __init__(self, player):
# 角色
self.player = player
# 获取屏幕对象
self.display_surface = pygame.display.get_surface()
self.display_surface_rect = self.display_surface.get_rect()
# 初始化物品框区域
self.width, self.height = 40, 40 # 物品框的高宽
self.frame_numbers = 10 # 物品框数量
self.frame_area = pygame.Rect(int((SCREEN_WIDTH / 2) - (self.frame_numbers * self.width / 2)),
self.display_surface_rect.bottom - self.height,
self.frame_numbers * self.width,
self.height) # 物品框区域
# 玩家物品
self.player_items = self.player.weapons_list + self.player.tools_list
# 精灵族
self.frame_collision = pygame.sprite.Group() # 绘制每一个框的时候都加入碰撞组里面
self.selected_frame_collosion = pygame.sprite.Group()
# 导入工具图片
self.items_corresponding_image = {}
self.player_have_item = [None for _ in range(0, self.frame_numbers)] # 每个格子存放的物品
self.setup()
# 选择框
self.selected_frame = Frame(self.selected_frame_collosion, 'black', 5)
self.selected_frame_index = None
def setup(self):
"""导入物品对应图片,初始化物品框物品"""
for item in self.player_items:
path = f'../images/tools/{item}.png'
self.items_corresponding_image[item] = pygame.image.load(path).convert_alpha()
# 生成物品框
for number in range(0, self.frame_numbers):
rect = pygame.Rect(self.frame_area.left + number * self.width, self.frame_area.top, self.width, self.height)
Frame(group=self.frame_collision, color='blue', size=3, rect=rect)
def update_have_item(self):
"""更新物品"""
self.last_player_have_item = self.player_have_item.copy() # 存储上一次角色拥有物品
for index, item in enumerate(self.player_items):
if index < self.frame_numbers:
if item in (self.player.tools_list + self.player.weapons_list):
self.player_have_item[index] = item
def update_frame(self):
"""更新相应框中的图像"""
# 上次到这次角色物品框改变了才执行相关操作
if self.last_player_have_item != self.player_have_item:
for number, item in enumerate(self.player_have_item):
if item is not None:
self.frame_collision.sprites()[number].image = self.items_corresponding_image[item]
def update_selected_frame(self):
"""更新选择框"""
# 更新框的位置
if self.selected_frame_index is not None:
rect = pygame.Rect(self.frame_area.left + self.selected_frame_index * self.width,
self.frame_area.top, self.width + 2, self.height)
self.selected_frame.rect = rect
def input(self):
"""键盘和鼠标输入"""
# 获取键盘输入
keys = pygame.key.get_pressed()
for number in range(1, 10):
if keys[getattr(pygame, f'K_{number}')]:
self.selected_frame_index = number - 1
# 获取鼠标输入
mouse = pygame.mouse.get_pos()
mouse_left_clicked = pygame.mouse.get_pressed()[0]
for number, sprite in enumerate(self.frame_collision.sprites()):
if sprite.rect is not None:
if sprite.rect.collidepoint(mouse) and mouse_left_clicked:
self.selected_frame_index = number
# 更新玩家使用的工具
if self.selected_frame_index is not None:
self.player.using = self.player_have_item[self.selected_frame_index]
def update(self):
"""更新"""
self.input()
self.update_have_item()
self.update_frame()
self.update_selected_frame()
# 更新所有的框
for sprite in self.frame_collision.sprites():
sprite.update()
# 更新选择框
for sprite in self.selected_frame_collosion.sprites():
sprite.update()
游戏原始帧动画资源
角色移动攻击帧动画
角色使用工具帧动画
游戏工具
加工后的游戏图形资源
资源已经和文章绑定,可在文章顶部查看