本次项目的目标是在树莓派pico及其扩展板上实现贪吃蛇游戏并用扩展板的蜂鸣器播放游戏背景音乐。
所用到的编程软件则是MicroPython。我们将用Python语言实现本次目标。
游戏的思路框图如下:
话不多说,直接开始各部分的介绍。
1.定义按键类keys:为了方便判断是哪个按键以及按键的按下。
class keys(machine.Pin):
def __init__(self, Pin_num, mode=machine.Pin.IN):
super(keys,self).__init__(Pin_num,mode)
self.num=Pin_num
self.last_value=self.value()#该键值改变前的最后值
self.ini_value=self.value()#该键的初始值
def keydown(self):
if self.value()!=self.last_value:
self.last_value=self.value()
if self.value()!=self.ini_value:
return True
return False
keys继承了machine库的Pin类,这样我们能定义管脚以及获取到管脚目前的高低电平,并生成last_value和ini_value属性供我们由keydown函数判断按键是否按下一次。keydown函数通过比较当前该键的电平值是否与初始值不同和是否与上一个状态的值不同来判断按键是否按下一次。后面判断是按键1还是按键2就可以用其num属性来区分。
2.snake类:有蛇身位置数据、方向、目前方向三个属性及move、move1、dead三个方法。
class snake():
def __init__(self):
self.bodies=[(20,0),(10,0),(0,0)] #蛇身数据
self.directions=('right','up','left','down')
self.direction=0
move():需要用到我们定义的keys类,当keys的num属性为7,即当按下按键1时,蛇的前进方向向逆时针改变;当keys的num属性为8,当按下按键2时,蛇的前进方向向顺时针改变。由此通过两个按键控制蛇的前进方向。而且用两个方向的好处还可以避免玩家输入的方向与当前方向完全相反。
def move(self,key):
assert type(key)==keys
if key.num==7 and key.value()==0: #前进方向沿逆时针改变一次
if (self.direction+1)<len(self.directions):
self.direction+=1
else:
self.direction=0
return False
elif key.num==8 and key.value()==0: #前进方向沿顺时针改变一次
if (self.direction-1)>=0:
self.direction-=1
else:
self.direction=len(self.directions)-1
return False
return True
move1():负责蛇身数据随前进方向的更新及判断食物是否被蛇吃到。此方法通过当前方向的不同,判断蛇头在当前方向下下一帧是否会移到食物位置判断蛇是否吃到食物。
def move1(self,food):
direct=self.directions[self.direction]
if direct=='up':
if (self.bodies[0][0],self.bodies[0][1]-10)==food.foodposition:
self.bodies.insert(0,(self.bodies[0][0],self.bodies[0][1]-10))
food.born(self.bodies)
return False
else:
self.bodies.insert(0,(self.bodies[0][0],self.bodies[0][1]-10))
return self.bodies.pop(-1)
elif direct=='right':
if (self.bodies[0][0]+10,self.bodies[0][1])==food.foodposition:
self.bodies.insert(0,(self.bodies[0][0]+10,self.bodies[0][1]))
food.born(self.bodies)
return False
else:
self.bodies.insert(0,(self.bodies[0][0]+10,self.bodies[0][1]))
return self.bodies.pop(-1)
elif direct=='down':
if (self.bodies[0][0],self.bodies[0][1]+10)==food.foodposition:
self.bodies.insert(0,(self.bodies[0][0],self.bodies[0][1]+10))
food.born(self.bodies)
return False
else:
self.bodies.insert(0,(self.bodies[0][0],self.bodies[0][1]+10))
return self.bodies.pop(-1)
elif direct=='left':
if (self.bodies[0][0]-10,self.bodies[0][1])==food.foodposition:
self.bodies.insert(0,(self.bodies[0][0]-10,self.bodies[0][1]))
food.born(self.bodies)
return False
else:
self.bodies.insert(0,(self.bodies[0][0]-10,self.bodies[0][1]))
return self.bodies.pop(-1)
dead():判断蛇是否死亡。蛇的死亡方式有2种:碰到边界和吃到自己。bodycopy是除蛇头外的蛇身数据,用于判断蛇是否吃到自己。
def dead(self,boundary_w,boundary_h):
if self.bodies[0][0]<0 or self.bodies[0][1]<0 or self.bodies[0][0]>=boundary_w or self.bodies[0][1]>=boundary_h:
return True
bodycopy=self.bodies[1::]
if self.bodies[0] in bodycopy:
return True
return False
3.food类:有foodposition一个属性与负责食物刷新的born()方法
class food():
def __init__(self):
x=random.randint(0,23)*10
y=random.randint(0,18)*10
self.foodposition=(x,y)
def born(self,snakebodies):
x=random.randint(0,23)*10
y=random.randint(0,18)*10
if (x,y) not in snakebodies:
self.foodposition=(x,y)
else:
self.foodposition=self.born(snakebodies)
return (x,y)
其中,将y范围设定为最多230是因为为了在屏幕下方显示当前分数,born函数被snake的move1()方法调用,最后一个if是加入关于刷新位置是否在蛇身内的判断,若在其中,则需要再次调用born()函数重新生成位置,此处需要递归。
4.音乐播放函数:将其放在游戏过程中,通过传入当前应该暂停的时间pausetime和音乐类song,计算出在当前暂停时间内应该播放多少个音符并播放,此处每个音符间的播放间隔为40ms。从而达到既控制了游戏帧数又同时播放音乐的效果。
def pause_music(pausetime,song):#用暂停时间放音乐
x=0
pause_times=pausetime/40
while x<pause_times:
x+=1
song.tick()
machine.lightsleep(40)
5.结束动画函数:设计的一段结束动画,在每次游戏主函数返回为False之前调用。
def ending(screen,font_x):#结束动画
screen.fill(st7789.color565(0,0,0))
screen.text(font_x,'Good Bye!',50,100)
for i in range(5000):
screen.pixel(random.randint(0, screen._display_width),
random.randint(0, screen._display_height),
st7789.color565(random.getrandbits(8),random.getrandbits(8),
\random.getrandbits(8)))
6.游戏主体函数:需要屏幕screen和字体font_x两个参数,screen需由st7789库提供的函数初始化,屏幕初始化和字体使用可见本站其它教程,有详细介绍,在此不再赘述。游戏音符由song.txt文件读入,由machine.lightsleep()函数控制游戏帧率。开始部分每隔40ms利用keys.keydown()函数判断按键1和按键2是否按下,若按键1按下打破循环从而进入游戏环节,若按键2按下则加载游戏结束动画,然后直接返回False,退出游戏主体函数。
def main_game(screen,font_x):
cell_size=10
pause_time=300
score=0
screen_w=screen._display_width
screen_h=screen._display_height
white=st7789.color565(255,255,255)
black=st7789.color565(0,0,0)
red=st7789.color565(255,0,0)
screen.fill(black)
apple=food()
mysnake=snake()
beginornot=True
death=False
key1=keys(7,machine.Pin.IN)
key2=keys(8,machine.Pin.IN)
screen.fill(black)
#音乐初始化
with open('song.txt','r') as f:
mysong=music(f.read())
#开始界面
while beginornot:
if key1.keydown():
beginornot=False
elif key2.keydown():
ending(screen,font_x)
return False
screen.text(font_x,'Welcom!',65,50)
screen.text(font_x,'Key1 for begin',10,90)
screen.text(font_x,'Key2 for exit',10,130)
machine.lightsleep(40)
当进入游戏中时,利用上文介绍的pause_music()函数实现在游戏中播放音乐同时控制游戏帧率的效果,用speed_up()函数计算在当前分数下每一帧之间的暂停时间以达到分数越高蛇速度越快的效果(在分数小于30之前速度会随分数增加,在分数达到30以后速度已经够快了不会再继续增加)。同样每隔一段暂停时间判断按键1与按键2是否按下,按下按键1蛇前进方向向逆时针改变一次,按下按键2蛇前进方向向顺时针改变一次。利用snake.move1()函数判断蛇是否吃到食物,吃到则返回吃到的食物坐标给del_pos,这时我们需要在del_pos坐标上将其涂黑以做到使食物消失的效果。每次循环的最后用snake.death()判断蛇是否死亡,控制本阶段的继续或是结束。
screen.fill(black)
screen.hline(0,190,240,white)
screen.text(font_x,'Your score:',0,200)
screen.text(font_x,str(score),190,200)
#游戏中
while not death:
pause_music(speed_up(pause_time,score),mysong)
if key1.keydown():
mysnake.move(key1)
elif key2.keydown():
mysnake.move(key2)
del_pos= mysnake.move1(apple)
screen.fill_rect(apple.foodposition[0],apple.foodposition[1],\
cell_size,cell_size,red)
if del_pos:
screen.fill_rect(del_pos[0],del_pos[1],cell_size,cell_size,black)
else:
score+=1
screen.text(font_x,str(score),190,200)
for x in mysnake.bodies:
screen.fill_rect(x[0],x[1],10,10,white)
death=mysnake.dead(screen_w,screen_h-60)
结束阶段:与开始阶段相似用两个按键控制返回游戏主界面和游戏退出。
#游戏结束界面
mysong.stop()
screen.fill(black)
screen.text(font_x, "GAME OVER", 50, 50)
screen.text(font_x, "Key1 for re", 10, 90)
screen.text(font_x, "Key2 for exit", 10, 130)
while True:
if key1.keydown():
return True
elif key2.keydown():
ending(screen,font_x)
return False
machine.lightsleep(200)
7.游戏循环函数gameloop():利用main_game的返回值控制游戏的重新开始或结束,如果返回为True,则循环继续,游戏重新开始;若返回为False,则打破循环,程序结束。直接调用gameloop()函数即可进行游戏。
def gameloop(screen,font_x):
loop=True
while loop:
loop=main_game(screen,font_x)
machine.lightsleep(500)
本次项目所有所需文件均在附件 snake.rar 中,读者如有需要,可自行下载。
解压缩后将压缩包内所有文件上传到pico中,运行snake.py即可开始游戏。