一、项目介绍
本项目使用RP2040实现了一款经典的复古游戏Pong。
该款游戏的规则如图1所示。游戏由在游戏界面中自由移动的方块、玩家一和玩家二各自控制上下移动的板1、板2、上下左右4个内壁等元素组成。在界面中自由移动的方块碰到内壁时会自动反弹,在碰到左边内壁时,如果玩家一控制板1成功接住了方块,则双方分数不增不减;如果玩家1未接住方块,则玩家二得一分。碰到右边内壁时的规则与左侧同理。
图1 Pong游戏规则
本项目中,使用RP2040四向摇杆的x轴动作控制左边接球板的上下移动,按键A控制右边接球板上移,按键B控制右边接球板下移。LCD屏显示游戏界面。游戏的背景音乐播放与暂停用按键“START”控制。
二、设计思路(框图)
整体的设计思路如图2所示:
图2 设计思路
每个函数内的代码流程如下所示:
三、简单的硬件介绍
本项目中用到的硬件资源有:
- 四向摇杆的X轴
- 按键A、按键B
- LED灯D1
- LCD显示屏
- Buzzer蜂鸣器、按键“START”
- 四向摇杆的X轴用于控制左板的上下移动。
(1)四向摇杆的X轴用于控制左板的上下移动
四向摇杆的X轴与核心芯片RP2040的连接原理图如图3所示,X轴与RP2040的GPIO28相连。通过读取GPIO28的值,判断四向摇杆所处的位置,即可控制左板上下移动的速度,进而控制左板的上下移动。速度方向向下,初始速度为0。摇杆向上推,左板速度减1,摇杆向下推,左板速度加1。
图3 四向摇杆X轴与核心芯片RP2040的连接原理图
(2)按键A、按键B用于控制右板的上下移动,按键“START”控制背景音乐播放和暂停
按键A、按键B、按键“START”与核心芯片RP2040的连接原理图如图4所示,按键A、按键B和按键“START”分别与RP2040的GPIO6、GPIO5和GPIO7相连。通过读取GPIO6和GPIO5的值,判断按下的是那个键,即可控制右板上下移动的速度,进而控制右板的上下移动。速度方向向下,初始速度为0。按下A键,右板速度减1,按下B键,右板速度加1。通过读取GPIO7的值,控制背景音乐播放和暂停。
图4 按键A、按键B与核心芯片RP2040的连接原理图
(3)LED灯D1通过闪烁显示游戏正在运行
LED灯D1与核心芯片RP2040的连接原理图如图5所示,LED灯D1与RP2040的GPIO4相连。通过定时改变GPIO4的值,实现LED灯的闪烁。
图5 LED灯D1与核心芯片RP2040的连接原理图
(4)LCD显示屏ST7789显示游戏状态
LCD显示屏ST7789管脚与核心芯片RP2040的连接原理图如图6所示,将LCD_SCK、LCD_SDA、LCD_DC、LCD_RESET4个管脚设置完成后,调用ST7789的库函数,即可完成画线、绘制矩形、显示文字等功能。
图6 LCD显示屏ST7789管脚与核心芯片RP2040的连接原理图
(5)Buzzer蜂鸣器播放背景音乐
Buzzer蜂鸣器与核心芯片RP2040的连接原理图如图7所示,Buzzer蜂鸣器与RP2040的GPIO23相连。通过设定GPIO23的值,播放音乐。
图7 Buzzer蜂鸣器与核心芯片RP2040的连接原理图
四、实现的功能及图片展示
(1)游戏开始前的开屏界面
(2)上下推动摇杆,控制左板的速度增减
(3)按动按键A或B,控制右板速度增或减
按键B控制右板速度向下增加,即速度增加 按键A控制右板速度向上增加,即速度减小
(4)左右板碰到上下壁时反弹
右板碰到下壁反弹
左板碰到下壁反弹
右板碰到上壁反弹
左板碰到上壁反弹
(5)方块碰到内壁时反弹
方块碰到左壁反弹
方块碰到下壁反弹
方块碰到右壁反弹
方块碰到上壁反弹
(6)方块被接住时,上方分数不变
(7)方块未被接住时,对手加一分
五、主要代码片段及说明
(1)硬件初始化
设置LCD显示屏ST7789相关的初始化参数,实例化显示屏为“display”。将显示屏刷黑,显示游戏名称、提示游戏开始并停留1秒。
class hardware():
def init():
spi0=SPI(0,baudrate=4000000, phase=1, polarity=1,
sck=Pin(game_kit.lcd_sck, Pin.OUT),
mosi=Pin(game_kit.lcd_sda, Pin.OUT))
display = st7789.ST7789(spi0, 240, 240,
reset=Pin(game_kit.lcd_rst, Pin.OUT),
dc=Pin(game_kit.lcd_dc, Pin.OUT),
xstart=0, ystart=0, rotation=0)
# 初始界面,提示游戏开始
display.fill(st7789.BLACK)
display.text(font2, "Pong!", 90, 90)
display.text(font2, "Let's go!", 60, 140)
time.sleep(1)
hardware.display = display
(2)游戏相关参数初始化
下面代码涉及到的参数如图8所示:
图8 参数示意图
class pong():
def __init__(self):
# bgm
self.bgm = p_music(p_music.song2, tempo=1, duty=500, pins=[
Pin(game_kit.buzzer, Pin.OUT)])
# 控制音乐暂停和播放的键start
self.key_start = button(game_kit.key_start, self.key_start_callback)
# led
self.led = Pin(game_kit.led_sta, Pin.OUT)
self.scoreChanged = 0
self.width = 240 # 屏幕宽度
self.height = 240 # 屏幕高度
self.CENTER_X = int(self.height/2) # 屏幕中心横向坐标值
self.pad_width = 10 # 板子宽度
self.pad_height = 60 # 板子高度
self.player1 = 0 # 左边玩家的得分
self.player2 = 0 # 右边玩家的得分
self.pad1_pos = [0, self.height/2-self.pad_height/2] # 左板左上角位置
self.pad2_pos = [self.width-self.pad_width, self.height/2-self.pad_height/2] # 右板左上角位置
self.old_pad1_pos = [0, 0] # 用于记录左板位置刷新前左上角坐标
self.old_pad2_pos = [0, 0] # 用于记录右板位置刷新前左上角坐标
self.square_width = 10 # 方块宽度
self.square_height = 10 # 方块高度
self.square_pos = [self.width/2-self.square_width/2, self.height/2-self.square_height/2] # 方块左上角位置
self.old_square_pos = [0, 0] # 用于记录右板位置刷新前左上角坐标
self.pad1_vel = 0 # 左板竖向速度
self.pad2_vel = 0 # 右板竖向速度
self.square_vel = [2, 2] # 方块速度
time.sleep(1)
(3)背景音乐
在上方参数初始化代码中,将按键“START”的回调函数设置为了函数key_start_callback(),该函数的内容如下所示:
def key_start_callback(self, p):
print("key_start pressed")
self.bgm.toggle_pause()
调用的函数toggle_pause()所在类如下所示:
class p_music(music):
song1 = '0 A5 2 26 0.6299212574958801;2 B5 2 26 0.6299212574958801;4 C6 6 26 0.6299212574958801;10 B5 2 26 0.6299212574958801;12 C6 4 26 0.6299212574958801;16 E6 4 26 0.6299212574958801;20 B5 8 26 0.6299212574958801;32 E5 4 26 0.6299212574958801;36 A5 6 26 0.6299212574958801;42 G5 2 26 0.6299212574958801;44 A5 4 26 0.6299212574958801;48 C6 4 26 0.6299212574958801;52 G5 8 26 0.6299212574958801;64 F5 2 26 0.6299212574958801;66 E5 2 26 0.6299212574958801;68 F5 6 26 0.6299212574958801;74 E5 2 26 0.6299212574958801;76 F5 4 26 0.6299212574958801;80 C6 4 26 0.6299212574958801;84 E5 8 26 0.6299212574958801;94 C6 2 26 0.6299212574958801;96 C6 2 26 0.6299212574958801;98 C6 2 26 0.6299212574958801;100 B5 6 26 0.6299212574958801;106 F#5 2 26 0.6299212574958801;108 F#5 4 26 0.6299212574958801;112 B5 4 26 0.6299212574958801;116 B5 8 26 0.6299212574958801;128 A5 2 26 0.6299212574958801;130 B5 2 26 0.6299212574958801;132 C6 6 26 0.6299212574958801;138 B5 2 26 0.6299212574958801;140 C6 4 26 0.6299212574958801;144 E6 4 26 0.6299212574958801;148 B5 8 26 0.6299212574958801;160 E5 2 26 0.6299212574958801;162 E5 2 26 0.6299212574958801;164 A5 6 26 0.6299212574958801;170 G5 2 26 0.6299212574958801;172 A5 4 26 0.6299212574958801;176 C6 4 26 0.6299212574958801;180 G5 8 26 0.6299212574958801;192 E5 4 26 0.6299212574958801;196 F5 4 26 0.6299212574958801;200 C6 2 26 0.6299212574958801;202 B5 2 26 0.6299212574958801;204 B5 4 26 0.6299212574958801;208 C6 4 26 0.6299212574958801;212 D6 4 26 0.6299212574958801;216 E6 2 26 0.6299212574958801;218 C6 2 26 0.6299212574958801;220 C6 8 26 0.6299212574958801;228 C6 2 26 0.6299212574958801;230 B5 2 26 0.6299212574958801;232 A5 4 26 0.6299212574958801;236 B5 4 26 0.6299212574958801;240 G#5 4 26 0.6299212574958801;244 A5 11 26 0.6299212574958801;256 C6 2 26 0.6299212574958801;258 D6 2 26 0.6299212574958801;260 E6 6 26 0.6299212574958801;266 D6 2 26 0.6299212574958801;268 E6 4 26 0.6299212574958801;272 G6 4 26 0.6299212574958801;276 D6 8 26 0.6299212574958801;288 G5 2 26 0.6299212574958801;290 G5 2 26 0.6299212574958801;292 C6 6 26 0.6299212574958801;298 B5 2 26 0.6299212574958801;300 C6 4 26 0.6299212574958801;304 E6 4 26 0.6299212574958801;308 E6 11 26 0.6299212574958801;324 A5 2 26 0.6299212574958801;326 B5 2 26 0.6299212574958801;328 C6 4 26 0.6299212574958801;332 B5 2 26 0.6299212574958801;334 C6 2 26 0.6299212574958801;336 D6 4 26 0.6299212574958801;340 C6 6 26 0.6299212574958801;346 G5 2 26 0.6299212574958801;348 G5 8 26 0.6299212574958801;356 F6 4 26 0.6299212574958801;360 E6 4 26 0.6299212574958801;364 D6 4 26 0.6299212574958801;368 C6 4 26 0.6299212574958801;372 E6 15 26 0.6299212574958801;388 E6 11 26 0.6299212574958801;400 E6 4 26 0.6299212574958801;404 A6 8 26 0.6299212574958801;412 G6 8 26 0.6299212574958801;420 E6 4 26 0.6299212574958801;424 D6 2 26 0.6299212574958801;426 C6 2 26 0.6299212574958801;428 C6 8 26 0.6299212574958801;436 D6 4 26 0.6299212574958801;440 C6 2 26 0.6299212574958801;442 D6 2 26 0.6299212574958801;444 D6 4 26 0.6299212574958801;448 G6 4 26 0.6299212574958801;452 E6 11 26 0.6299212574958801;464 E6 4 26 0.6299212574958801;468 A6 8 26 0.6299212574958801;476 G6 8 26 0.6299212574958801;484 E6 4 26 0.6299212574958801;488 D6 2 26 0.6299212574958801;490 C6 2 26 0.6299212574958801;492 C6 8 26 0.6299212574958801;500 D6 4 26 0.6299212574958801;504 C6 2 26 0.6299212574958801;506 D6 2 26 0.6299212574958801;508 D6 4 26 0.6299212574958801;512 B5 4 26 0.6299212574958801;516 A5 11 26 0.6299212574958801;528 A5 2 26 0.6299212574958801;530 B5 2 26 0.6299212574958801;532 C6 6 26 0.6299212574958801;538 B5 2 26 0.6299212574958801;540 C6 4 26 0.6299212574958801;544 E6 4 26 0.6299212574958801;548 B5 8 26 0.6299212574958801;560 E5 4 26 0.6299212574958801;564 A5 6 26 0.6299212574958801;570 G5 2 26 0.6299212574958801;572 A5 4 26 0.6299212574958801;576 C6 4 26 0.6299212574958801;580 G5 8 26 0.6299212574958801;592 F5 2 26 0.6299212574958801;594 E5 2 26 0.6299212574958801;596 F5 6 26 0.6299212574958801;602 E5 2 26 0.6299212574958801;604 F5 4 26 0.6299212574958801;608 C6 4 26 0.6299212574958801;612 E5 8 26 0.6299212574958801;622 C6 2 26 0.6299212574958801;624 C6 2 26 0.6299212574958801;626 C6 2 26 0.6299212574958801;628 B5 6 26 0.6299212574958801;634 F#5 2 26 0.6299212574958801;636 F#5 4 26 0.6299212574958801;640 B5 4 26 0.6299212574958801;644 B5 8 26 0.6299212574958801;656 A5 2 26 0.6299212574958801;658 B5 2 26 0.6299212574958801;660 C6 6 26 0.6299212574958801;666 B5 2 26 0.6299212574958801;668 C6 4 26 0.6299212574958801;672 E6 4 26 0.6299212574958801;676 B5 8 26 0.6299212574958801;688 E5 2 26 0.6299212574958801;690 E5 2 26 0.6299212574958801;692 A5 6 26 0.6299212574958801;698 G5 2 26 0.6299212574958801;700 A5 4 26 0.6299212574958801;704 C6 4 26 0.6299212574958801;708 G5 8 26 0.6299212574958801;720 E5 4 26 0.6299212574958801;724 F5 4 26 0.6299212574958801;728 C6 2 26 0.6299212574958801;730 B5 2 26 0.6299212574958801;732 B5 4 26 0.6299212574958801;736 C6 4 26 0.6299212574958801;740 D6 4 26 0.6299212574958801;744 E6 2 26 0.6299212574958801;746 C6 2 26 0.6299212574958801;748 C6 8 26 0.6299212574958801;756 C6 2 26 0.6299212574958801;758 B5 2 26 0.6299212574958801;760 A5 4 26 0.6299212574958801;764 B5 4 26 0.6299212574958801;768 G#5 4 26 0.6299212574958801;772 A5 11 26 0.6299212574958801'
song0 = '0 E5 2 14;4 B4 2 14;6 C5 2 14;8 D5 2 14;10 E5 1 14;11 D5 1 14;12 C5 2 14;14 B4 2 14;16 A4 2 14;20 A4 2 14;22 C5 2 14;24 E5 2 14;28 D5 2 14;30 C5 2 14;32 B4 2 14;38 C5 2 14;40 D5 2 14;44 E5 2 14;48 C5 2 14;52 A4 2 14;56 A4 2 14;64 D5 2 14;68 F5 2 14;70 A5 2 14;74 G5 2 14;76 F5 2 14;78 E5 2 14;84 C5 2 14;86 E5 2 14;90 D5 2 14;92 C5 2 14;94 B4 2 14;98 B4 2 14;100 C5 2 14;102 D5 2 14;106 E5 2 14;110 C5 2 14;114 A4 2 14;118 A4 2 14;248 E5 8 14;256 C5 8 14;264 D5 8 14;272 B4 8 14;280 C5 8 14;288 A4 8 14;296 G#4 8 14;304 B4 8 14;313 E5 8 14;321 C5 8 14;329 D5 8 14;337 B4 4 14;341 B4 4 14;345 C5 4 14;349 E5 4 14;353 A5 8 14;361 G#5 8 14;248 C5 8 14;256 A4 8 14;264 B4 8 14;272 G#4 8 14;280 A4 8 14;288 E4 8 14;0 B4 2 14;4 G#4 2 14;6 A4 2 14;8 B4 2 14;12 A4 2 14;14 G#4 2 14;28 B4 2 14;30 A4 2 14;32 G#4 4 14;40 B4 2 14;38 A4 2 14;44 C5 2 14;52 E4 2 14;64 F4 2 14;94 G#4 2 14;100 A4 2 14;102 B4 2 14;106 C5 2 14;110 A4 2 14;114 E4 2 14;114 E4 2 14;118 E4 2 14;98 G#4 2 14;296 E4 8 14;304 G#4 8 14;313 C5 8 14;321 A4 8 14;329 B4 8 14;337 G#4 4 14;341 G#4 4 14;345 A4 4 14;349 C5 4 14;353 E5 8 14;353 E5 8 14;361 E5 8 14;48 A4 2 14;56 E4 2 14;68 D5 2 14;70 F5 2 14;74 E5 2 14;76 D5 2 14;80 E5 2 14;78 C5 2 14;80 C5 2 14;22 A4 2 14;24 C5 2 14;84 A4 2 14;86 C5 2 14;92 A4 2 14;90 B4 2 14;0 E5 2 14;4 B4 2 14;6 C5 2 14;8 D5 2 14;10 E5 1 14;11 D5 1 14;12 C5 2 14;14 B4 2 14;16 A4 2 14;20 A4 2 14;22 C5 2 14;24 E5 2 14;28 D5 2 14;30 C5 2 14;32 B4 2 14;38 C5 2 14;40 D5 2 14;44 E5 2 14;48 C5 2 14;52 A4 2 14;56 A4 2 14;64 D5 2 14;68 F5 2 14;70 A5 2 14;74 G5 2 14;76 F5 2 14;78 E5 2 14;84 C5 2 14;86 E5 2 14;90 D5 2 14;92 C5 2 14;94 B4 2 14;98 B4 2 14;100 C5 2 14;102 D5 2 14;106 E5 2 14;110 C5 2 14;114 A4 2 14;118 A4 2 14;0 B4 2 14;4 G#4 2 14;6 A4 2 14;8 B4 2 14;12 A4 2 14;14 G#4 2 14;28 B4 2 14;30 A4 2 14;32 G#4 4 14;40 B4 2 14;38 A4 2 14;44 C5 2 14;52 E4 2 14;64 F4 2 14;94 G#4 2 14;100 A4 2 14;102 B4 2 14;106 C5 2 14;110 A4 2 14;114 E4 2 14;114 E4 2 14;118 E4 2 14;98 G#4 2 14;48 A4 2 14;56 E4 2 14;68 D5 2 14;70 F5 2 14;74 E5 2 14;76 D5 2 14;80 E5 2 14;78 C5 2 14;80 C5 2 14;22 A4 2 14;24 C5 2 14;84 A4 2 14;86 C5 2 14;92 A4 2 14;90 B4 2 14;124 E5 2 14;128 B4 2 14;130 C5 2 14;132 D5 2 14;134 E5 1 14;135 D5 1 14;136 C5 2 14;138 B4 2 14;140 A4 2 14;144 A4 2 14;146 C5 2 14;148 E5 2 14;152 D5 2 14;154 C5 2 14;156 B4 2 14;162 C5 2 14;164 D5 2 14;168 E5 2 14;172 C5 2 14;176 A4 2 14;180 A4 2 14;188 D5 2 14;192 F5 2 14;194 A5 2 14;198 G5 2 14;200 F5 2 14;202 E5 2 14;208 C5 2 14;210 E5 2 14;214 D5 2 14;216 C5 2 14;218 B4 2 14;222 B4 2 14;224 C5 2 14;226 D5 2 14;230 E5 2 14;234 C5 2 14;238 A4 2 14;242 A4 2 14;124 B4 2 14;128 G#4 2 14;130 A4 2 14;132 B4 2 14;136 A4 2 14;138 G#4 2 14;152 B4 2 14;154 A4 2 14;156 G#4 4 14;164 B4 2 14;162 A4 2 14;168 C5 2 14;176 E4 2 14;188 F4 2 14;218 G#4 2 14;224 A4 2 14;226 B4 2 14;230 C5 2 14;234 A4 2 14;238 E4 2 14;238 E4 2 14;242 E4 2 14;222 G#4 2 14;172 A4 2 14;180 E4 2 14;192 D5 2 14;194 F5 2 14;198 E5 2 14;200 D5 2 14;204 E5 2 14;202 C5 2 14;204 C5 2 14;146 A4 2 14;148 C5 2 14;208 A4 2 14;210 C5 2 14;216 A4 2 14;214 B4 2 14;124 E5 2 14;128 B4 2 14;130 C5 2 14;132 D5 2 14;134 E5 1 14;135 D5 1 14;136 C5 2 14;138 B4 2 14;140 A4 2 14;144 A4 2 14;146 C5 2 14;148 E5 2 14;152 D5 2 14;154 C5 2 14;156 B4 2 14;162 C5 2 14;164 D5 2 14;168 E5 2 14;172 C5 2 14;176 A4 2 14;180 A4 2 14;188 D5 2 14;192 F5 2 14;194 A5 2 14;198 G5 2 14;200 F5 2 14;202 E5 2 14;208 C5 2 14;210 E5 2 14;214 D5 2 14;216 C5 2 14;218 B4 2 14;222 B4 2 14;224 C5 2 14;226 D5 2 14;230 E5 2 14;234 C5 2 14;238 A4 2 14;242 A4 2 14;124 B4 2 14;128 G#4 2 14;130 A4 2 14;132 B4 2 14;136 A4 2 14;138 G#4 2 14;152 B4 2 14;154 A4 2 14;156 G#4 4 14;164 B4 2 14;162 A4 2 14;168 C5 2 14;176 E4 2 14;188 F4 2 14;218 G#4 2 14;224 A4 2 14;226 B4 2 14;230 C5 2 14;234 A4 2 14;238 E4 2 14;238 E4 2 14;242 E4 2 14;222 G#4 2 14;172 A4 2 14;180 E4 2 14;192 D5 2 14;194 F5 2 14;198 E5 2 14;200 D5 2 14;204 E5 2 14;202 C5 2 14;204 C5 2 14;146 A4 2 14;148 C5 2 14;208 A4 2 14;210 C5 2 14;216 A4 2 14;214 B4 2 14;248 C5 2 13;252 C5 2 13;254 E5 2 13;250 E5 2 13;256 C5 2 13;260 C5 2 13;258 A4 2 13;262 A4 2 13;264 D5 2 13;268 D5 2 13;266 B4 2 13;270 B4 2 13;272 G#4 2 13;276 G#4 2 13;274 B4 2 13;278 B4 2 13;280 A4 2 13;284 A4 2 13;282 C5 2 13;286 C5 2 13;347 C5 2 13;351 C5 2 13;313 C5 2 13;317 C5 2 13;319 E5 2 13;315 E5 2 13;321 C5 2 13;325 C5 2 13;323 A4 2 13;327 A4 2 13;329 D5 2 13;333 D5 2 13;331 B4 2 13;335 B4 2 13;337 G#4 2 13;341 G#4 2 13;339 B4 2 13;343 B4 2 13;345 A4 2 13;349 A4 2 13;349 E5 2 13;353 E5 2 13;355 A5 2 13;359 A5 2 13;357 E5 2 13;361 E5 2 13;363 G#5 2 13;367 G#5 2 13;365 E5 2 14;292 A4 2 13;288 A4 2 13;294 E4 2 13;290 E4 2 13;300 G#4 2 13;296 G#4 2 13;302 E4 2 13;298 E4 2 13;308 G#4 2 13;304 G#4 2 13;310 B4 2 13;306 B4 2 13'
song2 = '0 B5 2 23;4 B5 2 23;2 B5 2 23;10 B5 2 23;8 B5 2 23;12 B5 2 23;16 D#6 2 23;18 B5 2 23;20 B5 2 23;22 A5 2 23;24 G5 2 23;26 A5 2 23;28 B5 2 23;32 B5 2 23;36 B5 2 23;34 B5 2 23;42 B5 2 23;40 B5 2 23;44 B5 2 23;58 E5 2 23;60 E5 2 23;48 B5 2 23;50 A5 2 23;52 G5 2 23;54 A5 2 23;56 G5 2 23;64 B5 2 23;68 B5 2 23;66 B5 2 23;74 B5 2 23;72 B5 2 23;76 B5 2 23;80 D#6 2 23;82 B5 2 23;84 B5 2 23;86 A5 2 23;88 G5 2 23;90 A5 2 23;92 B5 2 23;96 B5 2 23;100 B5 2 23;98 B5 2 23;106 B5 2 23;104 B5 2 23;108 B5 2 23;122 E5 2 23;124 E5 2 23;112 B5 2 23;114 A5 2 23;116 G5 2 23;118 A5 2 23;120 G5 2 23;64 B6 2 23;68 B6 2 23;66 B6 2 23;74 B6 2 23;72 B6 2 23;76 B6 2 23;80 D#7 2 23;82 B6 2 23;84 B6 2 23;86 A6 2 23;88 G6 2 23;90 A6 2 23;92 B6 2 23;96 B6 2 23;100 B6 2 23;98 B6 2 23;106 B6 2 23;104 B6 2 23;108 B6 2 23;122 E6 2 23;124 E6 2 23;112 B6 2 23;114 A6 2 23;116 G6 2 23;118 A6 2 23;120 G6 2 23;0 B6 2 23;4 B6 2 23;2 B6 2 23;10 B6 2 23;8 B6 2 23;12 B6 2 23;16 D#7 2 23;18 B6 2 23;20 B6 2 23;22 A6 2 23;24 G6 2 23;26 A6 2 23;28 B6 2 23;32 B6 2 23;36 B6 2 23;34 B6 2 23;42 B6 2 23;40 B6 2 23;44 B6 2 23;58 E6 2 23;60 E6 2 23;48 B6 2 23;50 A6 2 23;52 G6 2 23;54 A6 2 23;56 G6 2 23;0 C#7 4 2;4 C#7 4 2;8 C#7 4 2;12 C#7 4 2;0 C#7 4 2;4 C#7 4 2;8 C#7 4 2;12 C#7 4 2;0 C#7 4 2;4 C#7 4 2;8 C#7 4 2;12 C#7 4 2;32 C#7 4 2;36 C#7 4 2;40 C#7 4 2;44 C#7 4 2;32 C#7 4 2;36 C#7 4 2;40 C#7 4 2;44 C#7 4 2;32 C#7 4 2;36 C#7 4 2;40 C#7 4 2;44 C#7 4 2;64 C#7 4 2;68 C#7 4 2;72 C#7 4 2;76 C#7 4 2;64 C#7 4 2;68 C#7 4 2;72 C#7 4 2;76 C#7 4 2;64 C#7 4 2;68 C#7 4 2;72 C#7 4 2;76 C#7 4 2;96 C#7 4 2;100 C#7 4 2;104 C#7 4 2;108 C#7 4 2;96 C#7 4 2;100 C#7 4 2;104 C#7 4 2;108 C#7 4 2;96 C#7 4 2;100 C#7 4 2;104 C#7 4 2;108 C#7 4 2;8 F#4 4 2;0 F#4 4 2;8 F#4 4 2;0 F#4 4 2;8 F#4 4 2;0 F#4 4 2;8 F#4 4 2;0 F#4 4 2;40 F#4 4 2;32 F#4 4 2;40 F#4 4 2;32 F#4 4 2;40 F#4 4 2;32 F#4 4 2;40 F#4 4 2;32 F#4 4 2;72 F#4 4 2;64 F#4 4 2;72 F#4 4 2;64 F#4 4 2;72 F#4 4 2;64 F#4 4 2;72 F#4 4 2;64 F#4 4 2;104 F#4 4 2;96 F#4 4 2;104 F#4 4 2;96 F#4 4 2;104 F#4 4 2;96 F#4 4 2;104 F#4 4 2;96 F#4 4 2'
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)
def unmute(self):
for pwm in self.pwms:
pwm.duty_u16(self.duty)
def tick(self):
if self.pause:
pass
else:
super().tick()
播放背景音乐的函数tick()被设置为异步进行的任务:
async def process(self):
bgm = asyncio.create_task(self.bgm_process())
self.init_run()
# main loop
while True:
self.dir_select()
self.run()
self.judge()
self.draw()
await self.blink()
async def bgm_process(self):
while True:
await asyncio.sleep_ms(100)
self.bgm.tick()
(4)进入游戏,游戏各模块初始状态
将初始得分设置为0:0;绘制左右边线、中线;左右板垂直居中;方块位于显示屏正中央。
def init_run(self):
# 进入游戏,游戏各模块初始状态
hardware.display.fill(st7789.BLACK)
hardware.display.text(font2, str(self.player1), 80, 10) # 左边得分
hardware.display.text(font2, str(self.player2), 135, 10) # 右边得分
hardware.display.line(self.CENTER_X, 0, self.CENTER_X, self.height, st7789.WHITE) # 中线
hardware.display.line(self.pad_width, 0, self.pad_width, self.height, st7789.WHITE) # 左边线
hardware.display.line(self.width-self.pad_width, 0, self.width-self.pad_width, self.height, st7789.WHITE) # 右边线
hardware.display.fill_rect(int(self.pad1_pos[0]), int(self.pad1_pos[1]), self.pad_width, self.pad_height, st7789.BLUE) # 左板
hardware.display.fill_rect(int(self.pad2_pos[0]+1), int(self.pad2_pos[1]), self.pad_width, self.pad_height, st7789.BLUE) # 右板
hardware.display.fill_rect(int(self.square_pos[0]), int(self.square_pos[1]), self.square_width, self.square_width, st7789.WHITE) # 方块
(5)接收按键、四向摇杆输入的信号,用于设置板1、板2移动的方向和速度
按键按下时,值为0;未按下时,值为1。所以当按键A值为0时,将右板的速度减1;当按键B的值为0时,将右板的速度加1。
四向摇杆上推时,值小于1000;下推时,值大于60000。所以当值小于1000时,将左板的速度减1;当值大于60000时,将左板的速度加1。
def dir_select(self):
KEYA = Pin(game_kit.key_a, Pin.IN, Pin.PULL_UP).value()
KEYB = Pin(game_kit.key_b, Pin.IN, Pin.PULL_UP).value()
xAxis = ADC(Pin(game_kit.joy_x)).read_u16()
step = 1
if KEYA == 0:
self.pad2_vel -= step # 右板速度减1
elif KEYB == 0:
self.pad2_vel += step # 右板速度加1
elif xAxis < 1000:
self.pad1_vel -= step # 左板速度减1
elif xAxis > 60000:
self.pad1_vel += step # 左板速度加1
(6)根据速度改变方块和左右两板的位置,实现方块和板碰壁反弹
由于在后续重新绘制方块和左右两板时,需要将原先存在的元素擦除,所以在更新三者的位置之前,需要先记录原来的位置,用于擦除时使用。根据速度更新位置的原理图9所示:
图9 根据速度更新位置的原理图
另外除了刷新位置信息,还要实现方块和板碰到内外壁时的反弹效果。对于方块,当方块与上下壁碰撞时,方块的纵向速度反向,如图10所示;当方块与左右内壁碰撞时,方块的横向速度反向,如图11所示。对于板,当板与上壁相撞时,将其速度设置为1;当板与下壁相撞时,将其速度设置为-1。
def run(self):
# 刷新方块的位置
self.old_square_pos[0] = self.square_pos[0]
self.old_square_pos[1] = self.square_pos[1]
self.square_pos[0] += self.square_vel[0]
self.square_pos[1] += self.square_vel[1]
# 方块碰撞情况
if self.square_pos[0] <= self.pad_width+1 or self.square_pos[0] >= self.width-self.pad_width-self.square_width-1:
self.square_vel[0] = - self.square_vel[0]
if self.square_pos[1] <= 0 or self.square_pos[1] >= self.height-self.square_height:
self.square_vel[1] = - self.square_vel[1]
# 保持板子在屏幕内
if self.pad1_pos[1] <= 0:
self.pad1_vel = 1
elif self.pad1_pos[1] >= self.height-self.pad_height:
self.pad1_vel = -1
if self.pad2_pos[1] <= 0:
self.pad2_vel = 1
elif self.pad2_pos[1] >= self.height-self.pad_height:
self.pad2_vel = -1
# 记录板子之前的位置用于覆盖原位置
self.old_pad1_pos[0] = self.pad1_pos[0]
self.old_pad1_pos[1] = self.pad1_pos[1]
self.old_pad2_pos[0] = self.pad2_pos[0]
self.old_pad2_pos[1] = self.pad2_pos[1]
# 更新板子的位置
self.pad1_pos[1] += self.pad1_vel
print("pad1_vel:", self.pad1_vel)
print("pad2_vel:", self.pad2_vel)
self.pad2_pos[1] += self.pad2_vel
(7)判断左右板是否接住方块,更新分数
如果方块碰到右壁,且方块不在右板高度范围内,则左侧玩家加1分;如果方块碰到左壁,且方块不在左板高度范围内,右侧玩家加1分。若分数有改变,则将标记分数是否改变的参数scoreChanged设置为“1”,其初始值为“0”。
def judge(self):
if (self.square_pos[0]<=self.pad_width+1) and ((self.square_pos[1] <=(self.pad1_pos[1]-self.square_height)) or (self.square_pos[1] >=(self.pad1_pos[1]+self.pad_height))):
self.player2 += 1
self.scoreChanged = 1
print("player2:", self.player2)
print("scoreChanged:", self.scoreChanged)
elif (self.square_pos[0]>=self.width-self.pad_width-self.square_width-1) and ((self.square_pos[1] <=(self.pad2_pos[1]-self.square_height)) or (self.square_pos[1] >=(self.pad2_pos[1]+self.pad_height))):
self.player1 += 1
self.scoreChanged = 1
print("player1:", self.player1)
print("scoreChanged:", self.scoreChanged)
(8)更新得分,绘制位置信息更改后的方块、左右板
如果标记分数是否改变的参数scoreChanged值为“1”,则将原来的分数用黑色覆盖,重新显示新的得分。
左右板和方块更新的方式类似,都是先将原来的矩形框用黑色填充,再在新的位置上绘制新的左右板和方块。
def draw(self):
# 刷新页面
if(self.scoreChanged == 1):
print("scoreChanged:", self.scoreChanged)
hardware.display.fill_rect(80, 10, 32, 32, st7789.BLACK) # 覆盖原左边得分
hardware.display.fill_rect(135, 10, 32, 32, st7789.BLACK) # 覆盖原右边得分
hardware.display.text(font2, str(self.player1), 80, 10) # 左边得分
hardware.display.text(font2, str(self.player2), 135, 10) # 右边得分
self.scoreChanged = 0
# 绘制板子的位置
hardware.display.fill_rect(int(self.old_pad1_pos[0]), int(self.old_pad1_pos[1]), self.pad_width, self.pad_height, st7789.BLACK) # 覆盖原左板
hardware.display.fill_rect(int(self.old_pad2_pos[0]+1), int(self.old_pad2_pos[1]), self.pad_width, self.pad_height, st7789.BLACK) # 覆盖原右板
hardware.display.fill_rect(int(self.pad1_pos[0]), int(self.pad1_pos[1]), self.pad_width, self.pad_height, st7789.BLUE) # 左板
hardware.display.fill_rect(int(self.pad2_pos[0]+1), int(self.pad2_pos[1]), self.pad_width, self.pad_height, st7789.BLUE) # 右板
# 绘制方块的位置
hardware.display.fill_rect(int(self.old_square_pos[0]), int(self.old_square_pos[1]), self.square_width, self.square_height, st7789.BLACK) # 覆盖原方块
if((int(self.old_square_pos[0]) >= self.CENTER_X-self.square_width) and (int(self.old_square_pos[0]) <= self.CENTER_X+1)):
hardware.display.line(self.CENTER_X, int(self.old_square_pos[1]), self.CENTER_X, int(self.old_square_pos[1])+self.square_height, st7789.WHITE) #补中线
if(((int(self.old_square_pos[0]) >= 80-self.square_width-1) and (int(self.old_square_pos[0]) <= 80+32+1)) and ((int(self.old_square_pos[1] >= 10-self.square_height-1)) and (int(self.old_square_pos[1] <= 10+32+1)))):
hardware.display.text(font2, str(self.player1), 80, 10) # 补左边的分数
if(((int(self.old_square_pos[0]) >= 135-self.square_width-1) and (int(self.old_square_pos[0]) <= 135+32+1)) and ((int(self.old_square_pos[1] >= 10-self.square_height-1)) and (int(self.old_square_pos[1] <= 10+32+1)))):
hardware.display.text(font2, str(self.player2), 135, 10) # 补右边的分数
hardware.display.fill_rect(int(self.square_pos[0]), int(self.square_pos[1]), self.square_width, self.square_height, st7789.WHITE) # 方块
六、遇到的主要难题及解决方法
(1)页面刷新问题
方块不断移动,需要不断刷新才能清楚地显示单个方块。最开始我使用的方法是每次重新绘制方块和左右板前,都把页面整个刷黑,再将中线、左右边线、方块、左右板全部重新绘制一边。这样带来的问题是页面看起来十分卡顿,体验感极差。
解决方法:在改变存储方块和左右板位置的参数square_pos、pad1_pos、pad2_pos之前,将原来的位置用另外的参数old_square_pos、old_pad1_pos、old_pad2_pos保存。在更新页面的时候,根据old_square_pos、old_pad1_pos、old_pad2_pos的值将原来的矩形框用背景色黑色刷黑,再根据更新后的square_pos、pad1_pos、pad2_pos绘制新的方块和左右板。这样不会出现卡顿问题,游戏更加流畅。
(2)擦除方块和左右板原位置的时候中线、分数、左右边线也会被擦除
由于在擦除方块和左右板原位置的时候,是直接将原先的区域用黑色覆盖,当原区域与中线、分数、左右边线覆盖的时候,这些元素也会被擦除。
解决方法:在擦除之后,判断原方块和左右板的区域内是否包含中线、分数、左右边线,如果有,则将这些区域用白线补齐。
(3)方块和左右板的移动速度过快时,屏幕上会有重影
由于程序的运行有时间的消耗,如果方块和板的移动速度过快,擦除原位置的代码会来不及运行,导致在屏幕上留下多个方块,多个板。
解决方法:将方块的速度设置到合理的范围内,在左右板碰到上下壁的时候,将速度降到1,避免速度过快的情况出现。
七、未来的计划或建议
本项目可以在现有的基础上,添加得分时的音效。另外,可以添加通过摇杆左右推实现改变方块速度快慢的效果,增添游戏的趣味性。