1项目要求
(1)设计或移植一款经典的游戏(硬禾学堂上已经实现的贪吃蛇等游戏除外),通过LCD屏显示,通过按键和四向摇杆控制游戏的动作
(2)在游戏中要通过蜂鸣器播放背景音乐
2完成的功能及达到的性能
2.1开机动画显示
用一个for循环函数实现动态的加载界面
2.2菜单界面
MODE:显示的游戏难度有Easy、Normal和Hard三种模式可由按键来切换
MUSIC:可选择开启和关闭游戏背景音乐用按键切换
右下角的Start是选择开始游戏
2.3游戏中场景
游戏还原是经典游戏Pong
通过左右摇杆控制底部白板的位置来使小球不掉落
每接住一次得分加一
当得分为10的倍数时难度升级
2.4游戏结束页面
Score:游戏中得分
Menu:回到主菜单
Start:重新开始下一局游戏
3 实现思路
调用例程给的LCD驱动代码驱动LCD屏
编写各个界面的屏幕显示
用状态标志位判断显示哪个界面
用按键切换各个状态
调用例程中的蜂鸣器音乐代码作为游戏的背景音乐
4 实现过程
4.1程序流程图
4.2主菜单显示逻辑函数
通过按键切换标志位赋值,再通过标志位决定显示内容,保证按键按下一次只切换一次需要切换的内容,减少屏幕闪烁
def menu(self):
if k3.value()==0:
time.sleep_ms(20)
if k3.value()==0:
display.fill(st7789.BLACK)
self.begin()
self.statue=0
return
if k1.value()==0:
time.sleep_ms(20)
if k1.value()==0:
self.umode=1
if(self.key_mode==0):
self.key_mode=1
else:
self.key_mode=0
if self.key_mode==0:
if self.umode==1:
self.umode=0
display.text(font2, "MUSIC:",30,100)
display.text(font2, "MODE:",30,30,st7789.RED)
self.ukey=1
if self.music_mode==0:
display.text(font2, "Open",130,100)
else:
display.text(font2, "Close",130,100)
if k2.value()==0:
time.sleep_ms(20)
if k2.value()==0:
self.ukey=1
self.game_mode=self.game_mode+1
if self.game_mode==3:
self.game_mode=0
if self.game_mode==0:
if self.ukey==1:
self.ukey=0
display.text(font2, "Hard",130,30,st7789.BLACK)
display.text(font2, "Easy",130,30,st7789.RED)
if self.game_mode==1:
if self.ukey==1:
self.ukey=0
display.text(font2, "Easy",130,30,st7789.BLACK)
display.text(font2, "Normal",130,30,st7789.RED)
if self.game_mode==2:
if self.ukey==1:
self.ukey=0
display.text(font2, "Normal",130,30,st7789.BLACK)
display.text(font2, "Hard",130,30,st7789.RED)
else:
if self.umode==1:
self.umode=0
self.umus=1
display.text(font2, "MODE:",30,30)
display.text(font2, "MUSIC:",30,100,st7789.RED)
if self.game_mode==0:
display.text(font2, "Easy",130,30)
else:
if self.game_mode==1:
display.text(font2, "Normal",130,30)
else:
display.text(font2, "Hard",130,30)
if k2.value()==0:
time.sleep_ms(20)
if k2.value()==0:
self.umus=1
if self.music_mode==0:
self.music_mode=1
else:
self.music_mode=0
if self.music_mode==0:
if self.umus==1:
self.umus=0
display.text(font2, "Close",130,100,st7789.BLACK)
display.text(font2, "Open",130,100,st7789.RED)
else:
if self.umus==1:
self.umus=0
display.text(font2, "Open",130,100,st7789.BLACK)
display.text(font2, "Close",130,100,st7789.RED)
display.text(font2,"Start",160,190)
4.3白板移动
通过ADC读取摇杆的值,从而来控制白板的左右移动
白板移动遵循移动一步只在前端画一条横线,尾部画黑线就不用不停清屏画整个白板导致屏幕不停闪烁影响游戏显示
白板长度和移动最大的距离由全局变量决定,在后期就可以通过改变全局变量来改变白板长度,从而改变游戏难度
def board_move(self):
adc = control.read_u16()
duty = int(adc * (3000-0)/0xffff)+100
if duty>2000:
display.fill_rect(self.board_width+self.board_position,230,2,self.board_height,st7789.WHITE)
display.fill_rect(self.board_position,230,2,self.board_height,st7789.BLACK)
self.board_position=self.board_position+2
if self.board_position>=239-self.board_width:
self.board_position=239-self.board_width
if duty<1000:
display.fill_rect(self.board_width+self.board_position,230,2,self.board_height,st7789.BLACK)
display.fill_rect(self.board_position,230,2,self.board_height,st7789.WHITE)
self.board_position=self.board_position-2
if self.board_position<=0:
self.board_position=0
4.4小球移动
小球移动也遵循移动一步只改变小部分的颜色变化,而不是重画整个小球
小球左右碰壁和上碰壁就按反射角45度弹走,而碰到底部就直接游戏结束
def ball_move(self):
if self.direction==0:
display.fill_rect(self.ball_x+1,20+self.ball_y,20,1,st7789.WHITE)
display.fill_rect(20+self.ball_x,self.ball_y+1,1,19,st7789.WHITE)
display.fill_rect(self.ball_x,self.ball_y,20,1,st7789.BLACK)
display.fill_rect(self.ball_x,self.ball_y+1,1,19,st7789.BLACK)
self.ball_x=self.ball_x+1
self.ball_y=self.ball_y+1
if self.ball_y>=219:
self.statue=1
display.fill(st7789.BLACK)
return
if self.ball_x>=219:
self.ball_x=219
self.direction=2
return
if self.direction==1:
display.fill_rect(self.ball_x-1,self.ball_y-1,20,1,st7789.WHITE)
display.fill_rect(self.ball_x-1,self.ball_y,1,19,st7789.WHITE)
display.fill_rect(self.ball_x+19,self.ball_y,1,20,st7789.BLACK)
display.fill_rect(self.ball_x,self.ball_y+19,19,1,st7789.BLACK)
self.ball_x=self.ball_x-1
self.ball_y=self.ball_y-1
if self.ball_y<=0:
self.ball_y=0
self.direction=2
return
if self.ball_x<=0:
self.ball_x=0
self.direction=3
return
if self.direction==2:
display.fill_rect(self.ball_x-1,self.ball_y+1,1,20,st7789.WHITE)
display.fill_rect(self.ball_x,self.ball_y+20,19,1,st7789.WHITE)
display.fill_rect(self.ball_x,self.ball_y,19,1,st7789.BLACK)
display.fill_rect(self.ball_x+19,self.ball_y,1,20,st7789.BLACK)
self.ball_x=self.ball_x-1
self.ball_y=self.ball_y+1
if self.ball_y>=219:
self.statue=1
display.fill(st7789.BLACK)
return
if self.ball_x<=0:
self.ball_x=0
self.direction=0
return
if self.direction==3:
display.fill_rect(self.ball_x+1,self.ball_y-1,20,1,st7789.WHITE)
display.fill_rect(self.ball_x+20,self.ball_y,1,19,st7789.WHITE)
display.fill_rect(self.ball_x,self.ball_y,1,20,st7789.BLACK)
display.fill_rect(self.ball_x+1,self.ball_y+19,19,1,st7789.BLACK)
self.ball_x=self.ball_x+1
self.ball_y=self.ball_y-1
if self.ball_y<=0:
self.ball_y=0
self.direction=0
return
if self.ball_x>=219:
self.ball_x=219
self.direction=1
return
4.5球与板碰撞判断
球与板碰撞限制在一定顶范围内算做碰撞,球碰板厚的反弹方向由随机函数决定,从而增加游戏的变化性
def collision(self):
if self.ball_y>=199+self.board_height:
i=self.board_position-20
j=self.board_position+self.board_width
if i<=0:
i=0
if self.ball_x>=i:
if self.ball_x<=j:
n=random.randint(0,9)
#print(n)
if self.direction==0:
if n<=4:
self.direction=1
else:
self.direction=3
if self.direction==2:
if n<=4:
self.direction=3
else:
self.direction=1
self.score=self.score+1
print(self.score)
if self.score%10==0:
self.game_mode=self.game_mode+1
if self.game_mode==1:
display.fill_rect(self.board_position+41,230,20,10,st7789.BLACK)
else:
if self.game_mode==2:
display.fill_rect(self.board_position+21,230,20,10,st7789.BLACK)
if self.game_mode>=2:
self.game_mode=2
4.6主函数
各个界面标志位的判断从而决定当前模式和显示界面
背景音乐使用例程中蜂鸣器音乐的驱动代码
import uos
import mypong
from buzzer_music import music
from machine import Pin
from time import sleep_ms
from machine import Pin,PWM
pwm = PWM(Pin(23))
pong=mypong.Pong()
song = '0 A#4 1 1;2 F5 1 1;4 D#5 1 1;8 D5 1 1;11 D5 1 1;6 A#4 1 1;14 D#5 1 1;18 A#4 1 1;20 D#5 1 1;22 A#4 1 1;24 D5 1 1;27 D5 1 1;30 D#5 1 1;32 A#4 1 1;34 F5 1 1;36 D#5 1 1;38 A#4 1 1;40 D5 1 1;43 D5 1 1;46 D#5 1 1;50 A#4 1 1;52 D#5 1 1;54 G5 1 1;56 F5 1 1;59 D#5 1 1;62 F5 1 1;64 A#4 1 1;66 F5 1 1;68 D#5 1 1;70 A#4 1 1;72 D5 1 1;75 D5 1 1;78 D#5 1 1;82 A#4 1 1;84 D#5 1 1;86 A#4 1 1;88 D5 1 1;91 D5 1 1;94 D#5 1 1;96 A#4 1 1;100 D#5 1 1;102 A#4 1 1;104 D5 1 1;107 D5 1 1;110 D#5 1 1;114 A#4 1 1;116 D#5 1 1;118 G5 1 1;120 F5 1 1;123 D#5 1 1;126 F5 1 1;98 F5 1 1'
mySong = music(song, pins=[Pin(23)])
i=0
pong.start_page()
pong.menu_begin()
while True:
pong.mode()
if pong.music_mode==0 and pong.statue==0:
i=i+1
if i%pong.times==0:
mySong.tick()
else:
pwm.duty_u16(0)
if pong.statue==0:
pong.run()
else:
if pong.statue==1:
pong.stop()
else:
pong.menu()
print(pong.game_mode)
sleep_ms(pong.time)
5 遇到的主要难题
5.1LCD屏幕显示
LCD清一次屏闪烁明显影响美观,所以不能一直通过清屏重新显示来改变显示的内容,所以就得采取读取一次按键变化值把原来显示的字体变为黑色再重新显示想要的文字内容,造成了逻辑异常复杂,而且标志位特别多,耽误大量时间来理清逻辑
5.2板子和球的移动
我首先是想采取每次移动变化就重新画整个球和板子,但是实际发现重新画整个图耗费大量时间,导致图像显示不连贯,所以就采用每次移动只画一条白线和一条黑线从而让整个画面没有任何闪烁,达到游戏的流畅和美观的效果
由于LCD屏幕的限制,所以球体的移动一个方向需要特定的代码,代码量巨大,所以整个游戏小球的运动方向是固定的四个方向,导致可玩性比较低,在后期我会继续编写小球其他方向的运动的代码,增加可玩性
5.3板子和小球的碰撞判断
板子与小球的反弹方向是固定的导致白板只需要固定到一个地方就可以一直接到球,不具可玩性,所以改进之后小球的反馈方向由随机函数决定
5.4蜂鸣器音乐
无源蜂鸣器的特点是:
1、 无源内部不带震荡源,所以如果用直流信号无法令其鸣叫。必须用2K~5K的方波(建议使用PWM)去驱动它
2、 声音频率可控,可以做出“多来米发索拉西”的效果。
3、 可以使呈现的发音效果更丰富,当然控制方式也因此变得比有源蜂鸣器更复杂一点。
让其发送出想要的音乐旋律难度很大,也需要了解一些乐理知识,需要找到一首歌曲的简谱,写出对应音符的数组和对应节拍的数组。
6 未来的计划建议
该项目已经成功实现了复古游戏的功能,并达到了预期指标。然而通过增加游戏玩法,还有许多可以提升与扩展的地方:
1.目前游戏中只用到了摇杆的左右通道值,玩法单一,之后可以把摇杆的上下和两个按键加进来从而提高游戏可玩性
2.游戏界面比较简陋,之后美化游戏界面,增加彩色元素,可以用图片代替直接用LCD画点
3.游戏背景音乐不能随游戏的变化而变化,之后编写其他音乐声音的数组,提高游戏音乐的复杂性
4.去移植其他更加复杂的经典游戏如超级玛丽合金弹头等