一、设计目标
仿照经典的fc游戏“公路赛车”,设计一款纵版无尽挑战游戏,具体目标为:
1.玩法与“公路赛车”基本一致,整体游戏画面自上而下滚动,而玩家的赛车限制在屏幕内,制造出赛车行驶的感觉,玩家通过控制自己的赛车躲避其它NPC赛车以尽可能的存活,获得高的分数,这里可以利用开发板上的加速度传感器mma7660。
2.玩家每超越一辆NPC分数都会增加,而且随着游戏的进行速度会越来越快,增加难度。
3.NPC赛车还可设置其他属性,比如奖励分数、生命值等等,NPC不仅仅只会直线行走,还会进行车道变更,变更前会有转向灯提示玩家。所有的NPC都是随机生成的。
4.玩家阵亡或重开要有界面提示,能够显示已得分数。
二、准备工作
1.环境配置
(1)thonny。安装过程具体可参考 https://class.eetree.cn/live_pc/l_60fe7f4fe4b0a27d0e360f74
(2)硬禾学堂2022寒假在家练:基于树莓派RP2040的嵌入式系统学习平台,相关内容可参考https://www.eetree.cn/project/detail/698
2.mpy库
硬禾学堂老师分享的Pimoroni的pico graphics(该库可以做到高速刷新,满足游戏画面的需求,相关uf2文件已上传附件)
3.mma7660加速度传感器
开发板上自带的三轴加速度传感器,可以通过翻转开发板实现交互,控制赛车的移动。
4.赛车图象
本人自己设计了一个赛车的像素画,如下图所示。
车长35,车宽25,其中左右下角的两个黄色部分为转向灯(具体请参考实现过程的画图函数部分),不同赛车的属性以车身的颜色区分。
三、软件流程图
基本包括为一个主循环和碰撞判断,主循环基本分为四个部分:玩家移动,NPC移动,碰撞判断,刷屏,并且每次进行碰撞判断,根据碰撞到的赛车属性进行判断,若碰撞到障碍生命值减少后进入重开或继续界面。
四、实现过程
1.总体框架:
from machine import I2C, Pin,Timer
from board import game_kit
from time import sleep
from mma7660 import MMA7660
from breakout_colourlcd240x240 import BreakoutColourLCD240x240
import uasyncio as asyncio
import random
from button import button
from buzzer_music import music
import gc
class car_process:
async def process(self):
bgm = asyncio.create_task(self.bgm_process())
self.hardware_init()
self.transition_ani_enter()
while True:
while self.score < 100*self.state:
await self.judge()
self.player_run()
self.npc_run()
self.draw()
self.bgm.mute()
await asyncio.sleep_ms(10)
gc.collect() #垃圾回收
#每得100分加速
self.v_forward += 1
self.state +=1
def main():
gc.enable() #开启垃圾回收
s = car_process()
asyncio.run(s.process())
main()
主要参照硬禾学堂提供的范例yasnake.py贪吃蛇小游戏的基本框架,利用uasyncio 库来写并发代码,主循环基本参照上面的软件流程图。
并且每得够100分速度加快,以提高游戏难度。
同时每个循环结束后调用垃圾回收,释放内存。
2.赛车的基本属性
class car:
#汽车属性
def __init__(self,x_location,y_location,v1x,v1y,character1):
self.x = x_location
self.y = y_location
self.vx = v1x
self.vy = v1y
self.character = character1 #角色
self.car_track = 1 #行驶车道
self.car_change = 0 #变更车道
self.blink_flag = 0 #转向灯标志
self.being_hit = False #碰撞标志
其中x,y坐标主要是给画图函数和碰撞函数提供参考,而x和y方向的速度则允许赛车上下左右移动;
character属性则表明屏幕内每一辆车的角色:玩家、障碍还是奖励;
car_track表示赛车当前行驶的车道;
car_change表示赛车将要变换的车道,主要给NPC移动函数提供参考;
blink_flag表示转向灯是否闪烁的标志,主要给画图函数提供参考;
being_hit则是赛车的碰撞标志,表明赛车是否发生了碰撞。
3.游戏基本属性初始化
class car_process:
def __init__(self):
# bgm
self.bgm = p_music(p_music.song, tempo=1, duty=500, pins=[Pin(game_kit.buzzer, Pin.OUT)])
# controller
self.k1 = button(game_kit.key_a, self.k1_callback)
self.k2 = button(game_kit.key_b, self.k2_callback)
#定时器控制转向尾灯闪烁
self.tim = Timer(period=50, mode=Timer.PERIODIC, callback=self.blink)
#总体行驶速度
self.v_forward = 5
#斑马线坐标
self.y = 0
#主要角色
self.car_player = car(100,200,0,0,'player')
self.car_npc1 = car(40,0,0,self.v_forward,'block')
self.car_npc2 = car(160,140,0,self.v_forward,'block')
#变道标志变量
self.x_expected2 =0
self.x_expected1 =0
#生命值
self.life = 1
#转向开光
self.blink_flag = 1
#闪烁标志
self.blink_s = 1
#分数
self.score = 0
#游戏重开开关
self.restart_cw = False
#游戏继续开关
self.keepgoing_cw = False
#速度阶段
self.state = 1
def k1_callback(self, p):
print("k1 pressed")
self.restart_cw = True
self.restart()
def k2_callback(self, p):
print("k2 pressed")
self.keepgoing_cw = True
self.restart()
def blink(self,t):
#闪烁函数
self.blink_s *= -1
初始化游戏的基本属性:音乐、按键、分数、生命值以及赛车属性。
其中玩家赛车属性为“player”,而NPC的属性为"block","life","score"分别对应障碍、加生命值、加分数。
初始化两辆NPC对象循环刷新,间距为140,是为了保证能等距刷新。
总体行驶速度v_forward决定了NPC和斑马线的纵向行进速度。
4.硬件初始化
class car_process:
def hardware_init(self):
#硬件初始化
width = BreakoutColourLCD240x240.WIDTH
height = BreakoutColourLCD240x240.HEIGHT
display_buffer = bytearray(width * height * 2) # 2-bytes per pixel (RGB565)
self.display = BreakoutColourLCD240x240(display_buffer)
self.display.set_backlight(1.0)
i2c1 = I2C(1, scl=Pin(game_kit.accelerometer_scl), sda=Pin(game_kit.accelerometer_sda))
self.acc = MMA7660(i2c1)
self.acc.on(True)
初始化屏幕和加速度传感器。
5.玩家移动
def player_run(self):
#玩家移动函数
self.d = bytearray(3)
self.acc.getSample(self.d)
self.rl = twos_compliment(self.d[1], 6)
self.fb = twos_compliment(self.d[0], 6)
self.car_player.vx = self.rl
self.car_player.vy = -self.fb
self.car_player.x += self.car_player.vx
self.car_player.y += self.car_player.vy
if self.car_player.x >165:
self.car_player.x = 165
if self.car_player.x <30:
self.car_player.x = 30
if self.car_player.y >204:
self.car_player.y = 204
if self.car_player.y <0:
self.car_player.y = 0
由于加速度传感器在水平时x和y分量为0,而x轴或y轴发生倾斜时可根据正负号判断倾斜方向,据此特点我们可以直接利用加速度传感器采集到的数据作为速度,那么移动后的坐标则表示为当前坐标加上速度,在处理时要注意y轴方向采集的结果要取相反,这样就能实现前倾前进,后倾后退,左倾左移,右倾右移。之后加上边界条件限制玩家的移动区域即可实现要求。
6.NPC的生成
def npc1_produce(self):
#npc1生成函数
self.car_npc1.being_hit = False
#10%的几率生成绿色车,20%的几率生成橙色车,70%的几率生成蓝色车
value = random.randint(1,10)
if value == 1 :
self.car_npc1.character = 'life'
elif (value == 2) or (value == 3):
self.car_npc1.character = 'score'
else:
self.car_npc1.character = 'block'
#确定车道和生成欲变道车道,实现随机转向
self.car_npc1.car_track = random.randint(1,3)
self.car_npc1.car_change = random.randint(1,3)
if self.car_npc1.car_track == 1:
self.car_npc1.x = 40
if self.car_npc1.car_track == 2:
self.car_npc1.x = 100
if self.car_npc1.car_track == 3:
self.car_npc1.x = 160
if (self.car_npc1.car_change-self.car_npc1.car_track)>0:
self.car_npc1.vx = 5
if (self.car_npc1.car_change-self.car_npc1.car_track)<0:
self.car_npc1.vx = -5
if (self.car_npc1.car_change-self.car_npc1.car_track)==0:
self.car_npc1.vx = 0
def npc2_produce(self):
#npc2生成函数
self.car_npc2.being_hit = False
value = random.randint(1,10)
#10%的几率生成绿色车,20%的几率生成橙色车,70%的几率生成蓝色车
if value == 1 :
self.car_npc2.character = 'life'
elif (value == 2) or (value == 3):
self.car_npc2.character = 'score'
else:
self.car_npc2.character = 'block'
#确定车道和生成欲变道车道,实现随机转向
self.car_npc2.car_track = random.randint(1,3)
self.car_npc2.car_change = random.randint(1,3)
if self.car_npc2.car_track == 1:
self.car_npc2.x = 40
if self.car_npc2.car_track == 2:
self.car_npc2.x = 100
if self.car_npc2.car_track == 3:
self.car_npc2.x = 160
if (self.car_npc2.car_change-self.car_npc2.car_track)>0:
self.car_npc2.vx = 5
if (self.car_npc2.car_change-self.car_npc2.car_track)<0:
self.car_npc2.vx = -5
if (self.car_npc2.car_change-self.car_npc2.car_track)==0:
self.car_npc2.vx = 0
针对两个NPC对象,每次走到屏幕底端后就重新生成,利用随机数实现几率生成不同NPC属性,同时随机生成行驶车道和欲变更车道,固定车道轨迹,分别对应:车道1:40,车道2:100,车道3:160(如下图所示),利用行驶车道和欲变更车道做差的结果判断NPC的横向移动方向,可以保证所有的NPC变道后依然保持在三条车道内。同时重置碰撞标志being_hit(具体请参考碰撞判定函数部分)
7.NPC的移动
def npc_run(self):
#npc移动函数
self.car_npc1.vy = self.v_forward
self.car_npc2.vy = self.v_forward
self.car_npc1.y += self.car_npc1.vy
self.car_npc2.y += self.car_npc2.vy
#车道1:40 车道2:100 车道3:160
if self.car_npc1.car_change == 1:
self.x_expected1 = 40
if self.car_npc1.car_change == 2:
self.x_expected1 = 100
if self.car_npc1.car_change == 3:
self.x_expected1 = 160
if self.car_npc1.x == self.x_expected1:
self.car_npc1.vx = 0
if self.car_npc2.car_change == 1:
self.x_expected2 = 40
if self.car_npc2.car_change == 2:
self.x_expected2 = 100
if self.car_npc2.car_change == 3:
self.x_expected2 = 160
if self.car_npc2.x == self.x_expected2:
self.car_npc2.vx = 0
#打转向灯,变道
if self.car_npc1.y <80 :
self.car_npc1.blink_flag = 1
else:
self.car_npc1.blink_flag = 0
self.car_npc1.x += self.car_npc1.vx
if self.car_npc2.y <80 :
self.car_npc2.blink_flag = 1
else:
self.car_npc2.blink_flag = 0
self.car_npc2.x += self.car_npc2.vx
#重新生成npc
if self.car_npc1.y >239:
self.car_npc1.y = -40
self.score += 10
self.npc1_produce()
if self.car_npc2.y >239:
self.car_npc2.y = -40
self.score += 10
self.npc2_produce()
每个NPC在到达底部(y=239)后会重新在顶部(y=-40)生成,到达底部后增加分数,视为超越了一辆车。
生成后在y>80时变更车道,变更前闪烁转向灯,变更时停止闪烁,通过blink_flag标志控制,这里用到的变量x_expected1和x_expected2主要时用来界定NPC是否已经行驶到欲变更的车道,若行驶到了固定轨道:40,100,160时则停止横向变道。
8.碰撞判断函数
如上图所示,我设置的赛车图形是以25*35的矩形的左上角为基准点的,所以其实要判断是否发生碰撞也很简单:先判断玩家的横向两边界是否在NPC的横向两边界内,然后再判断玩家的纵向两边界是否在NPC的纵向两边界即可。由此我们可以对编写对赛车碰撞的判断函数。
async def judge(self):
#碰撞判断函数
self.restart_cw = False
self.keepgoing_cw = False
if ((self.car_player.x > self.car_npc1.x) and (self.car_player.x<self.car_npc1.x+25)) or ((self.car_player.x+25 > self.car_npc1.x) and (self.car_player.x+25<self.car_npc1.x+25)):
if ((self.car_player.y > self.car_npc1.y) and (self.car_player.y<self.car_npc1.y+35)) or ((self.car_player.y+35 > self.car_npc1.y) and (self.car_player.y+35<self.car_npc1.y+35)):
if self.car_npc1.being_hit == False:
if self.car_npc1.character == 'block':
#障碍车生命值-1
self.life -= 1
await self.crach()
self.car_npc1.being_hit = True
if self.car_npc1.character == 'score':
#分数车分数加10
self.score += 10
self.car_npc1.being_hit = True
self.bgm.unmute()
if self.car_npc1.character == 'life':
#生命车生命值加1
self.life += 1
self.car_npc1.being_hit = True
self.bgm.unmute()
if((self.car_player.x > self.car_npc2.x) and (self.car_player.x<self.car_npc2.x+25)) or ((self.car_player.x+25 > self.car_npc2.x) and (self.car_player.x+25<self.car_npc2.x+25)):
if((self.car_player.y > self.car_npc2.y) and (self.car_player.y<self.car_npc2.y+35)) or ((self.car_player.y+35 > self.car_npc2.y) and (self.car_player.y+35<self.car_npc2.y+35)):
if self.car_npc2.being_hit == False:
if self.car_npc2.character == 'block':
self.life -= 1
await self.crach()
self.car_npc2.being_hit = True
if self.car_npc2.character == 'score':
self.score += 10
self.car_npc2.being_hit = True
self.bgm.unmute()
if self.car_npc2.character == 'life':
self.life += 1
self.car_npc2.being_hit = True
self.bgm.unmute()
async def crach(self):
#碰撞处理函数
self.bgm.unmute()
await asyncio.sleep_ms(1000)
self.bgm.mute()
if self.life == 0:
self.gg_ani()
else:
self.restart_ani()
self.transition_ani_enter()
赛车发生碰撞的判定:将所有的赛车视为25*35的矩形,首先判断玩家的赛车矩形的两个x的边界(player.x、player.x+25)是否在NPC的x边界(NPC.x、NPC.x+25)内,若在其中的话则继续判断玩家的赛车矩形的两个y的边界(player.y、player.y+35)是否在NPC的x边界(NPC.y、NPC.y+25)内。满足以上两个条件可判定为两辆车重叠了,但是为了保证NPC的等距刷新,我设置了一个碰撞标志being_hit。当第一次发生重叠时,就将这个碰撞标志设为True,之后不对NPC的运动做任何改变,只是利用这个标志在接下来的画图函数中进行判断,不再画已碰撞的NPC(具体可参照画图函数部分),直到NPC再次生成(具体可参照NPC生成部分)时重置标志为False,这样做不仅能保证碰撞后画图不会重叠,也能NPC保证等距刷新和游戏的正常运行(增加分数,奖励车被碰撞后立即消失且有一小段音效提示,只会判定为一次碰撞而不会反复判定为碰撞)。
若是碰撞了障碍车(block)则会进入crash函数进行处理,播放一段长音乐(具体请参照游戏音效部分),碰撞后若生命值依然有剩余,则进入并进入阵亡画面restart_ani()否则则进入游戏结束画面gg_ani。
class car_process:
def __init__(self):
# controller
self.k1 = button(game_kit.key_a, self.k1_callback)
self.k2 = button(game_kit.key_b, self.k2_callback)
def k1_callback(self, p):
print("k1 pressed")
self.restart_cw = True
self.restart()
def k2_callback(self, p):
print("k2 pressed")
self.keepgoing_cw = True
self.restart()
def restart(self):
self.car_npc2.vy = self.v_forward
self.car_npc1.vy = self.v_forward
def restart_ani(self):
#重新开始界面(游戏继续)
s = str(self.score)
l = str(self.life)
self.display.set_pen(40, 40, 40)
self.display.clear()
self.display.update()
self.display.set_pen(40, 40, 40)
self.display.clear()
self.display.set_pen(255,255,255)
self.display.text("YOUR SCORE:",0,0,200,3)
self.display.text(s,170,0,200,3)
self.display.text("YOUR HP:",0,30,200,3)
self.display.text(l,170,30,200,3)
self.display.text("Keep GOING?", 40,70, 200, 4)
self.display.text("press B to continue", 40,150, 200, 3)
self.display.text("press A to give up", 40,190, 200, 3)
self.display.update()
gc.collect()
while self.keepgoing_cw == False:
pass
if self.restart_cw == True:
self.gg_ani()
self.keepgoing_cw = True
def gg_ani(self):
#游戏结束界面(游戏结束)
s = str(self.score)
self.display.set_pen(40, 40, 40)
self.display.clear()
self.display.update()
self.display.set_pen(40, 40, 40)
self.display.clear()
self.display.set_pen(255,255,255)
self.display.text("YOUR SCORE:",0,0,200,3)
self.display.text(s,170,0,200,3)
self.display.text("GAME OVER!!", 40, 70, 200, 4)
self.display.text("press A to restart", 40,150, 200, 3)
self.display.update()
gc.collect()
self.restart_cw = False
while self.restart_cw == False:
pass
self.score = 0
self.v_forward = 5
self.life = 1
self.state = 1
在阵亡界面restart_ani中,会显示出分数和剩余的生命值数,而游戏结束界面gg_ani则只会显示出分数。
在gg_ani中,程序将卡在一个空循环中,这个时候只有按下按键A改变restart_cw的值才能跳出循环,重新进入到入场动画transition_ani_enter(),游戏数据清0(分数、生命值)。
而在restart_ani中,程序将卡在一个循环中不断判断,这个时候若按B键则会跳出这个空循环(B键的中断服务函数改变了keepgoing_cw的值),然后重新进入到入场动画transition_ani_enter(),游戏之前的数据都会保持(分数、生命值);若这时按下A键则视为放弃挑战,界面变为游戏结束画面gg_ani,同时也会改变keepgoing_cw的值以保证程序之后能跳出restart_ani的循环,当程序进入到gg_ani的空循环中,之后的操作就与进入gg_ani时一致,按A键跳出gg_ani的空循环和restart_ani的循环,游戏重新开始。
def transition_ani_enter(self):
#游戏开始动画
self.car_player.y = 274
self.car_player.vy = -5
self.car_player.x = 100
self.car_npc1.y = -35
self.car_npc2.y = -175
self.npc1_produce()
self.npc2_produce()
while self.car_player.y > 150 :
self.car_player.y += self.car_player.vy
self.draw()
sleep(0.02)
每次游戏重新开始时,都会先确定玩家和NPC的纵向位置,以保证等距刷新NPC,然后随机生成NPC属性。为了避免游戏一上来就出现相当快的速度,所以设置这个小的过场动画,当玩家的赛车缓慢出现到画面中时,才正式开始游戏的运行。
9.画图函数
def draw(self):
#主画图函数
s = str(self.score)
l = str(self.life)
v= str(self.v_forward*10-int(self.car_player.vy))
self.display.set_pen(40, 40, 40)
self.display.clear()
#马路
self.display.set_pen(190,190,190)
self.display.rectangle(31, 0, 160,240)
#记分栏
self.display.set_pen(173,216,230)
self.display.rectangle(190, 0,50,40)
self.display.set_pen(0,139,69)
self.display.text("sco", 190, 0, 40, 2)
self.display.text(s, 190, 20, 40, 2)
#速度栏
self.display.set_pen(0, 0, 255)
self.display.rectangle(190, 40,50,40)
self.display.set_pen(0, 255, 0)
self.display.text(v, 190, 40, 40, 2)
self.display.text("km/h", 190, 60, 40, 2)
#生命数栏
self.display.set_pen(54, 100, 139)
self.display.rectangle(190, 80,50,40)
self.display.set_pen(255, 0, 0)
self.display.text("HP", 190, 80, 40, 2)
self.display.text(l, 190, 100, 40, 2)
#画斑马线
self.y += self.v_forward
self.cross_line(self.y)
if self.y>239:
self.y=0
# draw player
self.draw_car(self.car_player.x,self.car_player.y,self.car_player.character,0,0)
# draw npc
if self.car_npc1.being_hit == False:
self.draw_car(self.car_npc1.x,self.car_npc1.y,self.car_npc1.character,self.car_npc1.blink_flag,self.car_npc1.vx)
if self.car_npc2.being_hit == False:
self.draw_car(self.car_npc2.x,self.car_npc2.y,self.car_npc2.character,self.car_npc2.blink_flag,self.car_npc2.vx)
self.display.update()
总的画图函数,基本元素包括马路、斑马线、得分栏、速度栏、生命值栏以及赛车,赛道宽度为30到190。
当NPC的碰撞标志being_hit为True时,则不画该NPC(具体请参考碰撞函数判断部分),就能实现碰撞后消失的效果。
def cross_line(self,y1):
#斑马线运动动画
for i in range(3):
if y1 >239:
y1 = y1-239
self.display.set_pen(255,255,255)
if y1+50 > 239:
self.display.rectangle(80, y1, 5,239-y1)
self.display.rectangle(135, y1, 5,239-y1)
self.display.rectangle(80, 0, 5,y1+50-239)
self.display.rectangle(135, 0, 5,y1+50-239)
else:
self.display.set_pen(255,255,255)
self.display.rectangle(80, y1, 5,50)
self.display.rectangle(135, y1, 5,50)
y1 = y1+80
画斑马线函数,斑马线横向位置定为80和135,宽5,相邻两根斑马线纵向位置为30,与NPC的刷新类似,运行到底部则回到顶部重新刷新,超出屏幕部分在上方显示,运动速度与NPC的纵向移动速度一致。
def blink(self,t):
#闪烁函数
self.blink_s *= -1
def draw_car(self,x,y,ch,b_flag,v):
#车身
if ch == 'player':
self.display.set_pen(255,0,0)
if ch == 'block':
self.display.set_pen(0,0,255)
if ch == 'score':
self.display.set_pen(255,165,0)
if ch == 'life':
self.display.set_pen(0,255,0)
self.display.rectangle(x+4, y, 17,3)
self.display.rectangle(x+1, y+3,23,7)
self.display.rectangle(x+1, y+21,23,10)
self.display.rectangle(x+3, y+3,19,26)
#车轮
self.display.set_pen(0,0,0)
self.display.rectangle(x, y+4, 3,5)
self.display.rectangle(x+22, y+4,3,5)
self.display.rectangle(x, y+23,3,5)
self.display.rectangle(x+22, y+23,3,5)
#车窗
self.display.rectangle(x+5, y+23, 15,2)
self.display.rectangle(x+6, y+20,13,3)
self.display.rectangle(x+7, y+18,11,2)
self.display.rectangle(x+8, y+16,9,2)
self.display.rectangle(x+9, y+15,7,1)
self.display.rectangle(x+10, y+14,5,1)
#尾翼
self.display.rectangle(x+6, y+32,13,2)
self.display.rectangle(x+7, y+30,2,2)
self.display.rectangle(x+16, y+30,2,2)
#前灯
self.display.set_pen(255,255,0)
self.display.rectangle(x+2, y+1,3,2)
self.display.rectangle(x+20, y+1,3,2)
#转向尾灯
if b_flag == 1:
self.display.set_pen(255,255,0)
if v>0:
if self.blink_s >0:
self.display.rectangle(x+19, y+29,6,6)
if v<0:
if self.blink_s >0:
self.display.rectangle(x, y+29,6,6)
画赛车函数,按照自制的像素画绘制赛车,不同属性的赛车以不同颜色区分(玩家:红色,障碍:蓝色,加分:橙色,加生命:绿色)。
当转向灯变量blink_flag为1时,开启闪烁,在定时器中周期改变状态变量blink_s,根据横向速度vx来决定左转向灯还是右转向灯闪烁。
10.游戏音效
class p_music(music):
song = "0 E4 1 15;2 E4 1 15;1 F4 1 15;3 F4 1 15;4 F#4 2 15;6 G#4 2 15;9 G#4 1 15;8 A#4 1 15;10 F#4 1 15;11 F4 1 15;12 D#4 1 15;13 C#4 1 15;14 D#4 1 15;15 D#4 1 15;16 D#4 1 15;19 C#4 1 15;21 D#4 1 15;23 C#4 1 15;24 E4 1 15;26 E4 1 15;25 F4 1 15;27 F4 1 15;28 F#4 2 15;8 C5 1 13;6 B4 1 13;7 B4 1 13;5 A4 1 13;4 A4 1 13;3 G4 1 13;2 F#4 1 13;1 G4 1 13;0 F#4 1 13;9 A#4 1 13;10 G#4 1 13;11 G4 1 13;12 F4 1 13;17 D4 1 13;18 C#4 1 13;21 C#4 1 13;24 F#4 1 13;25 G4 1 13;26 F#4 1 13;27 G4 1 13;28 G#4 1 13;29 G#4 1 13;30 A#4 1 13;14 F4 1 13;15 F4 1 13;16 F4 1 13;19 D#4 1 13;21 F4 1 13;23 D#4 1 13"
#音效
def __init__(self, songString='0 D4 8 0', looping=True, tempo=3, duty=2512, pin=None, pins=[Pin(0)]):
super().__init__(songString, looping, tempo, duty, pin, pins)
self.pause = True
def toggle_pause(self):
self.pause = not self.pause
if self.pause:
print("mute\n")
self.mute()
else:
print("unmute\n")
self.unmute()
def mute(self):
for pwm in self.pwms:
pwm.duty_u16(0)
self.pause = True
def unmute(self):
for pwm in self.pwms:
pwm.duty_u16(self.duty)
self.pause = False
def tick(self):
if self.pause:
pass
else:
super().tick()
这里主要参照了yasnake.py的音乐处理,但我的需求是能够在触碰奖励车或发生碰撞时有实时的音效,所以我修改了一下其中的mute()和unmute()方法,使其能改变状态变量pause。
async def bgm_process(self):
while True:
await asyncio.sleep_ms(100)
self.bgm.tick()
在音乐进程中我缩短了间隔时间为100ms。 在碰撞判断函数中(具体请参照碰撞判断函数部分),若碰撞了奖励车,则会调用unmute()方法,在大循环中发声10ms(await asyncio.sleep_ms(10)),然后调用mute()关闭,形成短促的得分音效;若碰撞了障碍车,先调用unmute()方法,然后程序按照car_process()>judge()>crash()>bgm_process()的顺序移交控制权,发声1000ms(await asyncio.sleep_ms(1000)),然后调用unmute()关闭,形成长音效。
四、后记
整个游戏开发下来还是画了不少的时间,利用Pimoroni的pico graphics来刷新虽然可以做到流畅运行,但需要及时调用垃圾回收函数以释放内存,其中的缘由我尚未弄明白。另外这个游戏我没有设置背景音乐,因为我觉得直接用硬禾学堂的蜂鸣器驱动例程做出来的声音效果欠佳,而且声音过大,有点影响使用体验,而本人也没有什么音乐造诣,也不会自己编曲,所以音乐这一块没做好,也希望能有高人能给予指导。最后,这也是本人第一次利用micropython来进行嵌入式开发,可以明显感受到其运行效率不如C或C++,但是它简易好上手,不失为一种极其便利的工具。