一.前言
上一节完成了玩家移动动画的制作,这次要完成实用工具动作的绘制。效果就是按下空格,玩家使用工具。
二.思路
self.selected_tool = 'water'
接着,我们想要达成按下空格,玩家实施浇水动作,这个动作持续0.35秒(也可以是其他时间,但0.35秒的视觉效果是不错的)。
import pygame
class Timer:
def __init__(self,duration,func = None):
self.duration = duration#计时器的持续时间
self.func = func#计时结束后要执行的函数
self.start_time = 0#开始时间,初始化为0
self.active = False#bool,表示是否正在计时
def activate(self):
self.active = True
# pygame.time.get_ticks()与pygame.time.Clock().get_ticks()不同
# 这里返回的是 从程序开始 到现在的时间,单位是ms
self.start_time = pygame.time.get_ticks()
def deactivate(self):
self.active = False
self.start_time = 0
def update(self):
current_time = pygame.time.get_ticks()
if current_time - self.start_time >=self.duration:
if self.func and self.start_time !=0:
self.func()
self.deactivate()
传入的参数中,duration是计时器需要记的时间,单位是ms,比如传入的duration是350,那么经过350ms后,计时器结束。func是计时器结束后会执行的函数,缺省值为None。
大体思想就是每一帧都通过pygame.time.get_ticks()来获取现在的时间,并与计时器开始计时的时间进行相减,得到的就是经过的时间,如果经过的时间大于了我们需要记的时间,就结束计时器,并且执行func函数
接下来回到player.py中,我们从timer.py中导入Timer类,并且创建一个关于工具使用的计时器。
由于以后可能其他的动作也会用到计时器,所以这里把计时器写到一个元组里,用这个元组存储以后可能会创建的所有计时器
self.timers = {
'tool use':Timer(350,self.use_tool)
}
于是,self.timers['tool use']就是一个计时时间为350ms,计时结束后会执行self.use_tool的计时器
至于工具具体使用的效果,我们以后再写,因此先把这个函数pass掉
def use_tool(self):
pass
由于计时器的uptade函数是要每一帧都要执行的,因此也要在player.py的update函数中调用它
def update_timers(self):
for timer in self.timers.values():
timer.update()
在player.py的update函数中调用self.update_timers函数:
def update(self,dt):
self.input()
self.get_status()
self.update_timers()
self.move(dt)
self.animate(dt)
接下来,就是在我们按键检测的部分,添加如下的功能:如果检测到空格按下,那么就启动self.timers['tool use']这个计时器,并且,在这个计时器启动的期间(玩家正在使用工具的期间),是不能移动或者再次使用工具的
因此,我们需要在已经使用工具的时候(self.timers['tool use'].active == True)的时候,停止对按键的检测
def input(self):
keys = pygame.key.get_pressed()
#已经在使用工具的时候,停止对按键的检测
if not self.timers['tool use'].active:
if keys[pygame.K_UP]:
self.direction.y = -1
self.status = 'up'
elif keys[pygame.K_DOWN]:
self.direction.y = 1
self.status = 'down'
else:
self.direction.y = 0#不要忘记加上这一句,不然按下键盘后再松开也不会停
if keys[pygame.K_RIGHT]:
self.direction.x = 1
self.status = 'right'
elif keys[pygame.K_LEFT]:
self.direction.x = -1
self.status = 'left'
else:
self.direction.x = 0
if keys[pygame.K_SPACE]:
#启动计时器
self.timers['tool use'].activate()
#实用工具的时候是不能移动的
self.direction = pygame.math.Vector2()
#要从第一张图片开始绘制
self.frame_index = 0
其中,至于为什么self.fream_index 要重新设为0,不仅是因为在执行一个动作的时候要从第一张图片开始绘制,还因为使用工具的动作基本上都是两张图片,但是移动的动作都是四张图片,如果在使用工具之前正在移动,刚好播放到第四张图片(fream_index = 3),然后变成实用工具的状态,此时不把它清零,就会播放使用工具的第四张图片,使用工具是没有第四张图片的,就会报错,程序崩溃
如果self.timers['tool use']这个计时器是正在计时的(表示正在使用工具),就把self.status变成方向 + "_" +工具名
def get_status(self):
# idle
if self.direction.magnitude() == 0:
self.status = self.status.split('_')[0] + '_idle'
if self.timers['tool use'].active:
self.status = self.status.split('_')[0] + '_' + self.selected_tool
要注意,上面这两个if的顺序不能颠倒,因为使用工具的时候玩家是不动的,此时self.direction.magnitude()为0,如果颠倒顺序,就会先执行self.status = self.status.split('_')[0] + '_' + self.selected_tool,再执行 self.status = self.status.split('_')[0] + '_idle',就会出现一帧不到的实用工具动作,随后都是待机动作
三.完整代码
import pygame
from settings import *
from support import *
from timer import Timer
class Player(pygame.sprite.Sprite):
def __init__(self,pos,group):
#这个参数可以传一个精灵组,这样就会自动把该精灵加入到该精灵组中
#也可以为空,这样需要在外面手动调用精灵组的add函数来将这个精灵加入到精灵组中
super().__init__(group)
self.import_assets()
self.status = 'down_idle'
self.frame_index = 0
#这里的变量名一定要叫image,因为这是它父类Sprite规定的
self.image = self.animations[self.status][self.frame_index]
#这个get_rect()也是父类中设置的方法
#返回值是有很多,大概有x,y,centerx,centery,center,width,height这几类
#大概就是image的x坐标,y坐标,中心的x坐标,中心的y坐标,中心点的坐标,宽度,高度等
#参数可以不填,那么位置就默认是(0,0),也可以填一个列表,比如(100,100),那么初始的位置就是(100,100)
#也可以是center = 一个坐标,这表示设置该图像的中心在这个坐标上
#同样的这里的变量名也一定要叫rect,这是父类规定的
self.rect = self.image.get_rect(center = pos)
#创建一个二维的向量,参数不填默认是(0,0)
self.direction = pygame.math.Vector2()#速度的方向
self.pos = pygame.math.Vector2(self.rect.center)#位置
self.speed = 200#速度
self.timers = {
'tool use':Timer(350,self.use_tool)
}
self.selected_tool = 'water'
def use_tool(self):
pass
def import_assets(self):
self.animations = {'up': [], 'down': [], 'left': [], 'right': [],
'right_idle': [], 'left_idle': [], 'up_idle': [], 'down_idle': [],
'right_hoe': [], 'left_hoe': [], 'up_hoe': [], 'down_hoe': [],
'right_axe': [], 'left_axe': [], 'up_axe': [], 'down_axe': [],
'right_water': [], 'left_water': [], 'up_water': [], 'down_water': []}
for animation in self.animations.keys():
full_path = '../graphics/character/' + animation
self.animations[animation] = import_folder(full_path)
def animate(self, dt):
# 4 是比较合适的数字
# 数字 决定做动作的快慢
# 做一个动作需要 1/4秒
# fream_index += dt 的话,要经过1s,才能变成下一个整数,做下一个动作
# fream_index += 4*dt,那么增加的速度就快了四倍,经过1/4秒就能做下一个动作了
self.frame_index += 4 * dt
# print(self.frame_index)
if self.frame_index >= len(self.animations[self.status]):
self.frame_index = 0
self.image = self.animations[self.status][int(self.frame_index)]
def input(self):
keys = pygame.key.get_pressed()
#已经在使用工具的时候,停止对按键的检测
if not self.timers['tool use'].active:
if keys[pygame.K_UP]:
self.direction.y = -1
self.status = 'up'
elif keys[pygame.K_DOWN]:
self.direction.y = 1
self.status = 'down'
else:
self.direction.y = 0#不要忘记加上这一句,不然按下键盘后再松开也不会停
if keys[pygame.K_RIGHT]:
self.direction.x = 1
self.status = 'right'
elif keys[pygame.K_LEFT]:
self.direction.x = -1
self.status = 'left'
else:
self.direction.x = 0
if keys[pygame.K_SPACE]:
#启动计时器
self.timers['tool use'].activate()
#实用工具的时候是不能移动的
self.direction = pygame.math.Vector2()
#要从第一张图片开始绘制
self.frame_index = 0
def get_status(self):
# idle
if self.direction.magnitude() == 0:
# self.status += '_idle'
# 上面这种方法不可取因为每一帧都会在字符串后面加上一个_idel
# 所以status会变成 xxx_idle_idle_idle
# 实际上当出现两个_idle的时候就已经报错了
# 下面这种方法
# split('_')是把一个字符串 以 '_' 为分节符分开
# 他返回的是一个列表
# 比如 a_b_c_d
# 返回的就是[a,b,c,d]
# 所以下面的【0】获取的就是_之前的内容
self.status = self.status.split('_')[0] + '_idle'
if self.timers['tool use'].active:
self.status = self.status.split('_')[0] + '_' + self.selected_tool
def update_timers(self):
for timer in self.timers.values():
timer.update()
def move(self,dt):
#向量归一化,比如一个n维向量为(x1,x2,x3...,xn)
#那么向量归一化的操作就是等比例缩放x1到xn的大小让 根号下(x1的平方+x2的平方+...+xn的平方) 等于1
#归一化的目的是如果同时按右键和上,那么direction就会变成(1,1),他的速度向量就是一个大小为根2,方向右上的向量了
#magnitude()返回的是一个float类型的数据,他的大小为根号下(x1的平方+x2的平方+...+xn的平方)
if self.direction.magnitude() > 0:#这表示如果有按键按下,如果向量里面全是0,会使归一化中的数学计算出现错误
self.direction = self.direction.normalize()
#位移 = 方向 * 速度 * 变化的时间()
self.pos.x += self.direction.x * self.speed * dt
self.rect.centerx = self.pos.x
self.pos.y += self.direction.y * self.speed * dt
self.rect.centery = self.pos.y
def update(self,dt):
self.input()
self.get_status()
self.update_timers()
self.move(dt)
self.animate(dt)
import pygame
class Timer:
def __init__(self,duration,func = None):
self.duration = duration#计时器的持续时间
self.func = func#计时结束后要执行的函数
self.start_time = 0#开始时间,初始化为0
self.active = False#bool,表示是否正在计时
def activate(self):
self.active = True
# pygame.time.get_ticks()与pygame.time.Clock().get_ticks()不同
# 这里返回的是 从程序开始 到现在的时间,单位是ms
self.start_time = pygame.time.get_ticks()
def deactivate(self):
self.active = False
self.start_time = 0
def update(self):
current_time = pygame.time.get_ticks()
if current_time - self.start_time >=self.duration:
if self.func and self.start_time !=0:
self.func()
self.deactivate()