Merlin's Blog
Just record something
Toggle navigation
Merlin's Blog
Home
Scratch基础教程
About Me
Archives
Tags
pgzero游戏:打字游戏
2023-03-16 10:08:33
46
0
0
merlin
涉及知识点: - 随机生成速度 - 随机生成位置 - 随机生成字母 - 使用集合类型 - 匹配字母按键 - 使用定时器 - 统计游戏积分 - 实现倒计时效果 ##**一、创建一个字母气球** ###**1.创建游戏场景** ``` import pgzrun,random WIDTH = 640 HEIGHT = 480 def draw(): screen.fill((255,255,255)) pgzrun.go() ``` 这次跟之前不同,没使用图块边长,因为气球下落不需要按图块边长移动。 ###**2.创建气球角色** ```python qiqiu = Actor("qiqiu",(WIDTH//2,HEIGHT)) ``` 然后在draw()中绘制气球。 ``` qiqiu.draw() ``` 气球坐标在屏幕底部正中间,但运行后还看不到。 ###**3.让气球升起来** 在update()中,修改气球角色的y属性。 ``` def update(): qiqiu.y += -1 ``` 屏幕的y轴从上到下是递增的,反过来,如果要上升,则要使y的值减少即可。 ###**4.给气球加上字母** 在角色属性中,有一个char属性,它可以保存字符。假设第一个气球要显示字母A,那么先给char属性赋值为A,在初始化位置加入以下语句: ``` qiqiu.char = "A" ``` 如何显示呢?在draw()函数中可以用文本输出函数来实现。 ``` screen.draw.text(qiqiu.char,center=qiqiu.center,color="black") ``` screen.draw.text有3个参数,分别是:输出的内容、位置和颜色。 运行一下看看,气球上是否出现字母A了。 ##**二、添加多个气球** 之前我们只创建了一个角色,实际上我们需要更多的气球。 不但如此,还应该考虑以下问题: - 气球应从不同位置起飞 - 气球速度应不同 - 气球上的字母应各不相同 ###**1.创建多个气球角色** 先定义一个变量和列表。 变量用于控制气球个数,列表用来管理气球角色,例如,在初始化部分加入下面语句: ``` MAX_QQ = 5 qq = [] ``` 之前我们创建了一个气球角色,显然多个气球添加,代码类似,应设计成一个函数,方便添加气球,例如: ```python def add_qq(): qiqiu = Actor("qiqiu",(WIDTH//2,HEIGHT)) qiqiu.char = "A" qq.append(qiqiu) ``` 记得把之前的创建气球角色的代码删除。 显然,气球的上升,也应该设计一个函数管理,例如: ``` def update_qq(): for q in qq: q.y += -1 if q.bottom < 0: qq.remove(q) ``` **思路解释:** 遍历气球角色列表,每个气球往上移动1个像素,如果图块右下角高度(bottom)小于0,说明已经飞出屏幕了,则删除这个气球。 相对的,update()也要修改代码: ``` def update(): if(len(qq)<MAX_QQ): add_qq() update_qq() ``` 记住,update()是Pgzero预留的函数,负责定时刷新。update_qq()是我们自己设计的函数,用于管理气球角色。 当气球个数少于预设时,调用add_qq()增加气球。 创建角色的方式改变了,那么绘制角色也要改变,draw()修改如下: ``` def draw(): screen.fill((255,255,255)) for q in qq: q.draw() screen.draw.text(q.char,center=q.center,color="black") ``` 运行后发现气球都挤在一起了,所以,我们需要为气球设置不同的位置。 ###**2.随机生成气球的坐标** 因为气球从底部上升,所以高度都是HEIGHT,又因为WIDTH范围为(0,WIDTH),但是随机值取这个范围,可能会出现半个气球的情形,所以需要调整区间值。 观察图片像素,宽度为39。因为角色x的值为图片中心位置,所以除以2可得约等于20. 在add_qq()中,修改部分代码: ``` qiqiu = Actor("qiqiu",(random.randint(20,WIDTH-20),HEIGHT)) ``` 运行后发现气球分开了,但是还是会出现重叠或部分重叠的情况,因为随机有一定几率会重复或接近,所以需要思考解决这个问题。 - 当生成一个位置时,跟之前气球的位置进行计算 - 如果跟之前的气球距离过小,则重新生成位置,直到间距符合要求 现在不是一个随机函数就能解决问题,我们也把它设计成函数。 假设间距为50像素 ``` def suiji(): min_dx = 0 #用于保存当前x和之前气球x坐标之间的最小值 while min_dx < 50: #最小值没达到间距50像素 min_dx = WIDTH #这步初始化为了min_dx能立刻被更新 x = random.randint(20,WIDTH-20) #随机生成x坐标 for q in qq: #遍历之前气球的x位置 dx = abs(q.x-x) #求出间距:绝对值 min_dx = min(min_dx,dx) #求最短间距 return x #符合要求后返回x ``` 几个知识点: - abs()是计算两个数的绝对值,即距离 - min()比较两个数谁小 - 因为min_dx要找最小,所以每一轮比较之前的初值不应为0,否则**死循环**,这就是为什么初始化为最大的WIDTH。 不要忘记在add_qq()中调用这个函数。 ``` qiqiu = Actor("qiqiu",(suiji(),HEIGHT)) ``` 运行一下程序,观察气球是否还有重叠现象。 ###**3.随机生成气球的速度** 位置问题解决了,接下来解决速度问题。 利用随机函数生成一个整数,设置相应的范围调速,伪代码如下: ``` 随机生成1~100的整数,保存到变量n if n的值小于等于5: 速度值设为-5 elif n的值大于5且小于等于25: 速度的值设为-4 elif n的值大于25且小于等于75: 速度的值设为-3 elif n的值大于75且小于等于95: 速度的值设为-2 else: 速度的值设为-1 ``` 这样-5和-1的概率为5%,-4的概率为20%,-3的概率为50,-2的概率为20. 同理,设计一个函数用于生成速度: ``` def sudu(): n = random.randint(1,100) if n <=5: sd = -5 elif n<=25: sd = -4 elif n<=75: sd = -3 elif n<=95: sd = -2 else: sd = -1 return sd ``` 角色属性中有一个属性为vy,负数表示向上移动,正数向下移动。相应的还有一个vx,负数向左移动,正数向右移动。 所以,我们可以把vy设置成刚才随机速度生成的函数。 显然在add_qq()中使用刚才设计的函数,add_qq()修改如下: ``` def add_qq(): qiqiu = Actor("qiqiu",(suiji(),HEIGHT)) qiqiu.vy = sudu() qiqiu.char = "A" qq.append(qiqiu) ``` vy是生成了,但之前移动还在用-1,所以在update_qq()中,修改语句: ``` q.y += q.vy ``` 现在运行一下程序,看看是不是速度有明显变化了。 ###**4.随机生成气球的字母** 跟随机位置生成类似,必须知道之前出现了哪些字母,如果字母出现过了,则继续生成新的字母,为了方便判断字母是否出现,我们可以用python中的集合类型,集合类型能保证其元素是唯一的,伪代码思路如下: ``` 定义一个字母集合 遍历之前气球角色,把char属性值加入到集合中 随机生成一个字母 如果字母没有在集合中出现则返回字母,否则循环随机生成新字母,直到集合中没有新生成的字母为止 ``` 为了方便管理,一样设计成函数来实现: ``` def random_char(): chars = set() #集合用set()创造 for q in qq: #遍历气球列表 chars.add(q.char) #集合用add()添加元素 ch = chr(random.randint(65,90)) #字母A的编号为65,Z为90 while ch in chars: #in用于判断是否在集合中,是返回True,反之返回False ch = chr(random.randint(65,90)) return ch #生成的新字母返回 ``` 在add_qq()使用刚才设计的函数 ``` qiqiu.char = random_char() ``` ##**三、实现打字功能** **1.匹配字母的按键** 在制作贪吃蛇的时候,我们接触了on_key_down()函数,它的参数会传入当前按键的信息。 所以,我们获取的按键信息跟气球char属性进行对比,如果相等,则删除气球。 ``` def on_key_down(key): for q in qq: if str(key)=="keys."+q.char: qq.remove(q) break ``` key是枚举类型,需要转换为str才能进行比较。 运行一下看效果。 **2.消除气球 ** 气球消除太快了,游戏体验较差。可以添加如下功能: - 气球文字变色 - 增加音效 颜色变化可以通过角色的typed属性来设置,即add_qq()的时候,把这个属性设置一下。 ``` def add_qq(): qiqiu = Actor("qiqiu",(suiji(),HEIGHT)) qiqiu.vy = sudu() qiqiu.char = random_char() qiqiu.typed = False qq.append(qiqiu) ``` typed属性类型为布尔类型,所以初始化为False。 相应的在draw()中,修改字母输出语句。 ``` def draw(): screen.fill((255,255,255)) for q in qq: q.draw() if q.typed == False: screen.draw.text(q.char,center=q.center,color="black") else: screen.draw.text(q.char,center=q.center,color="red") ``` 如何变色呢?首先在on_key_down()函数内,修改气球的typed属性值。然后,把要删除的气球加到列表中,例如构建一个del_qq列表,再调用clock定时器。 初始化位置加入列表del_qq. ``` del_qq = [] ``` **clock定时器** 每间隔一段时间,自动执行一次相应代码。 延迟操作,可以使用clock对象中的schedule()方法。 例如,设计一个删除气球的函数: ``` def remove_qq(): sounds.eat.play() q = del_qq.pop(0) if q in qq: qq.remove(q) ``` 修改on_key_down()函数如下: ``` def on_key_down(key): for q in qq: if str(key)=="keys."+q.char: q.typed = True del_qq.append(q) clock.schedule(remove_qq,0.3) break` ``` ##**四、完善游戏规则** ###**1.添加游戏积分** 在游戏初始化位置,加入积分变量: ``` score = 0 ``` 然后在on_key_down()函数,添加增加积分的语句,修改如下: ``` def on_key_down(key): global score for q in qq: if str(key)=="keys."+q.char: score += 1 q.typed = True del_qq.append(q) clock.schedule(remove_qq,0.3) break ``` 最后在draw()函数中,输出积分。 ``` screen.draw.text(f"Score:{score}",bottomleft=(10,HEIGHT-10),color="black") ``` bootomleft为左下角。 ###**2.实现游戏倒计时** 游戏倒计时,可以使用Python的time库,它包含了很多处理时间的函数,其中一个名为perf_counter()函数,可以获取当前时间。注:使用前需要导入time库,即: ``` import time ``` 那么,我们记录游戏开始的时间后,再不断用perf_counter()获取当前时间,看是否达到预设的时间。如果是,则游戏结束;反之继续运行。 一开始可以在初始化位置,加入以下两个全局变量,一个用于记录游戏开始时间,一个用于游戏剩余时间。 ``` start_time = time.perf_counter() left_time = 60 ``` 接下来设计计算剩余时间的函数: ``` def count_time(): global left_time play_time = int(time.perf_counter() - start_time) left_time = 60 - play_time ``` 使用int转换是为了省略毫秒部分,只考虑秒。 设计好的函数在update()函数中使用,这样每间隔一段时间就自动执行一次,用于计算剩余时间。 当然还要在draw()函数中输出剩余时间。 ``` screen.draw.text(f"Time:{left_time}",bottomleft=(WIDTH - 80,HEIGHT-10),color="black") ``` 输出位置设定为右下角,注意坐标的计算方式。 ***##判定游戏结束** 我们可以自定义胜利条件,例如积分达到某个分数时获胜,当规定时间内没有达到分数,则游戏失败。 这样需要设定两个全局变量,用于标记游戏胜利还是失败。 在游戏初始化位置,加入以下变量: ``` win = False lost = False ``` 然后设计一个判断游戏结束的函数,在update()函数中执行。 ``` def check_gameover(): global win,lost if score >= 100: win =True sounds.win.play() if left_time <= 0: lost = True sounds.fail.play() ``` 修改后的update()函数如下: ``` def update(): if(len(qq)<MAX_QQ): add_qq() update_qq() count_time() check_gameover() ``` 怎样让游戏结束?我们的一些更新、判断都放在update()中,所以在函数入口处,加上游戏是否结束的判断即可:win或lost其中之一为True,继续修改update()函数。 ``` def update(): if win or lost: return if(len(qq)<MAX_QQ): add_qq() update_qq() count_time() check_gameover() ``` 最后,游戏结束时,在屏幕输出获胜或失败的信息,修改draw()函数如下: ```python def draw(): screen.fill((255,255,255)) for q in qq: q.draw() if q.typed == False: screen.draw.text(q.char,center=q.center,color="black") else: screen.draw.text(q.char,center=q.center,color="red") screen.draw.text(f"Score:{score}",bottomleft=(10,HEIGHT-10),color="black") screen.draw.text(f"Time:{left_time}",bottomleft=(WIDTH - 80,HEIGHT-10),color="black") if win: screen.draw.text("You Win!",center=(WIDTH//2,HEIGHT//2),fontsize=50,color="red") elif lost: screen.draw.text("You Lost!",center=(WIDTH//2,HEIGHT//2),fonszie=50,color="red") ```
Pre: No Post
Next:
pgzero游戏:贪吃蛇
0
likes
46
Weibo
Wechat
Tencent Weibo
QQ Zone
RenRen
Table of content