基于树莓派RP2040制作经典游戏Pong
用树莓派RP2040移植经典游戏Pong,可以选择游戏难度和背景音乐的开关,游戏中也会有难度的升级
标签
游戏移植
2022寒假在家练
树莓派RP2040
经典游戏Pong
程序abe
更新2022-03-01
江苏科技大学
884

1项目要求

(1)设计或移植一款经典的游戏(硬禾学堂上已经实现的贪吃蛇等游戏除外),通过LCD屏显示,通过按键和四向摇杆控制游戏的动作

(2)在游戏中要通过蜂鸣器播放背景音乐

2完成的功能及达到的性能

2.1开机动画显示

用一个for循环函数实现动态的加载界面

Fr9wyjDyiyf87sZBnzzcAhIUHgBx

2.2菜单界面

MODE:显示的游戏难度有Easy、Normal和Hard三种模式可由按键来切换

MUSIC:可选择开启和关闭游戏背景音乐用按键切换

右下角的Start是选择开始游戏

FmM_4BngGGyupAEusHVXb4z8wlnb

2.3游戏中场景

游戏还原是经典游戏Pong

通过左右摇杆控制底部白板的位置来使小球不掉落

每接住一次得分加一

当得分为10的倍数时难度升级

Fn9y4gprEkQAsP3J4NNuhkRqxOOS

2.4游戏结束页面

Score:游戏中得分

Menu:回到主菜单

Start:重新开始下一局游戏

FrnsNU1tWx_tZ9YDg1CPDHbdqoIY

3 实现思路

调用例程给的LCD驱动代码驱动LCD屏

编写各个界面的屏幕显示

用状态标志位判断显示哪个界面

用按键切换各个状态

调用例程中的蜂鸣器音乐代码作为游戏的背景音乐

 

4 实现过程

4.1程序流程图

FrkMhJsGNVDroh4dMs4uhjjhsS1j

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.去移植其他更加复杂的经典游戏如超级玛丽合金弹头等

 

附件下载
PONG.rar
解压后可得到所有.py文件
团队介绍
江苏科技大学
团队成员
程序abe
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号