本项目是基于树莓派RP2040游戏机套件完成的经典小游戏推箱子的移植。推箱子小游戏的逻辑简单,整体的实现比较简单,整体实现逻辑为:绘制地图→根据地图确定放置箱子的初始地点,目的地,人的初始位置→利用四向摇杆控制人移动→判定两个箱子都推到目的地显示“Success”,因为这个游戏的特殊性,还需要在无路可走的时候设置B键回到初始位置。
本次提供的套件由树莓派RP2040芯片控制,其功能有:采用树莓派Pico核心芯片RP2040:
1、双核Arm Cortex M0+内核,可以运行到133MHz
2、264KB内存
3、性能强大、高度灵活的可编程IO可用于高速数字接口
4、片内温度传感器、并支持外部4路模拟信号输入,内部ADC采样率高达500Ksps、12位精度
5、支持MicroPython、C、C++编程
套件功能包括:
1、240*240分辨率的彩色IPS LCD,SPI接口,控制器为ST7789
2、四向摇杆 + 2个轻触按键 + 一个三轴姿态传感器MMA7660用做输入控制
3、板上外扩2MB Flash,预刷MicroPython的UF2固件
4、一个红外接收管 + 一个红外发射管
5、一个三轴姿态传感器MMA7660
6、一个蜂鸣器
7、双排16Pin连接器,有SPI、I2C以及2路模拟信号输入
8、可以使用MicroPython、C、C++编程
9、USB Type C连接器用于供电、程序下载
本次游戏移植得以实现的软件流程简要的可以概括为:
游戏实现
本次移植借鉴了网上的经典地图,实现了地图的复刻,完成了游戏的LCD屏幕显示、四向摇杆控制移动,按键控制游戏初始化以及开始游戏和重置游戏、蜂鸣器播放背景音乐的功能。具体如下:
开始界面
初始化地图
摇杆控制移动
推到指定位置,显示success、游戏结束
动态展示详见展示视频。
主要代码片段
1、调用声明
import uos
import machine
from machine import Pin, ADC, Timer
from time import sleep_us
import st7789 as st7789
from fonts import vga2_8x8 as font1
from fonts import vga1_16x32 as font2
from fonts import my_16x16 as font3
import random
import framebuf
import uasyncio as asyncio
from buzzer_music import music
2、PIN脚开放(四向摇杆、按键start与B)与音乐文件导入
xAxis = ADC(Pin(28))
yAxis = ADC(Pin(29))
buttonB = Pin(5,Pin.IN, Pin.PULL_UP)
buttonStart = Pin(7,Pin.IN, Pin.PULL_UP)
led = Pin(4, Pin.OUT)
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'
3、LCD屏幕初始化
class hardware():
def init():
st7789_res = 0
st7789_dc = 1
spi_sck=machine.Pin(2)
spi_tx=machine.Pin(3)
spi = machine.SPI(0,baudrate=4000000, phase=1, polarity=1, sck=spi_sck, mosi=spi_tx)
tft = st7789.ST7789(
spi,
240,
240,
reset=machine.Pin(st7789_res, machine.Pin.OUT),
dc=machine.Pin(st7789_dc, machine.Pin.OUT),
rotation=0)
tft.fill(color_cfg.background)
hardware.tft = tft
4、绘制标题界面与游戏成功界面
def title(self): #绘制标题界面
hardware.tft.text(font2, 'Pushing Square', 10, 80, st7789.RED, color_cfg.background)
hardware.tft.text(font1, 'Press start to continue!', 30, 130, st7789.GREEN, color_cfg.background)
def win(self):#游戏判定成功,显示字幕
hardware.tft.text(font2, 'Success!', 2*16, 6*32, st7789.YELLOW, color_cfg.background)
5、进行绘图以及相关颜色设置
class color_cfg: #颜色设定
wall = st7789.WHITE
box = st7789.MAGENTA
blue = st7789.BLUE
man = st7789.RED
background = st7789.BLACK
def setcolor(c): #设置作图颜色
global pen_color
pen_color = c
def outtextxy(x, y, c, color): #作图函数
global pen_color
hardware.tft.text(font3, c, x*25, y*25, pen_color, color)
6、游戏地图数据以及地图绘制
#地图数据
m = [
0,0,0,0,0,0,0,-1,
0,1,3,0,1,1,0,-1,
0,1,1,1,1,1,0,-1,
0,0,1,0,0,1,0,0,
-1,0,1,0,0,1,1,0,
-1,0,3,1,1,1,1,0,
-1,0,1,1,0,1,1,0,
-1,0,0,0,0,0,0,0,]
class game():
ma = []
d = 0
x = 1
y = 1
mySong = music(song, pins=[Pin(23)])#bgm,song文件在上面给出
def draw(self): #绘制地图
for i in range(8):
for n in range(8):
if self.ma[n*8+i] == 0:
setcolor(color_cfg.wall)
outtextxy(i+1, n+1, '0',color_cfg.wall)
if self.ma[n*8+i] == 1:
setcolor(color_cfg.background)
outtextxy(i+1, n+1, '0',color_cfg.background)
if self.ma[n*8+i] == 2:
setcolor(color_cfg.box)
outtextxy(i+1, n+1, '*',color_cfg.blue)
if self.ma[n*8+i] == 3:
setcolor(color_cfg.man)
outtextxy(i+1, n+1, 'o',color_cfg.background)
if self.ma[n*8+i] == 4:
setcolor(color_cfg.background)
outtextxy(i+1, n+1, 'a',color_cfg.man)
7、采集四向摇杆的移动数据以及设置B键初始化地图
def dir_select(self):
xValue = xAxis.read_u16()
yValue = yAxis.read_u16()
buttonValueB = buttonB.value()
if xValue <1000:
self.d = 1
elif xValue >40000:
self.d = 3
if yValue <1000:
self.d = 4
elif yValue >40000:
self.d = 2
if buttonValueB == 0:#按B键直接初始化地图,游戏重新开始
self.init_run()
8、根据四向摇杆的移动数据调整画面显示
def run(self):
if self.d == 1: #向上
if self.ma[(self.y-1)*8 + self.x] == 0:
return
elif self.ma[(self.y-1)*8 + self.x] == 2:
if self.ma[(self.y-2)*8 + self.x] == 0 or self.ma[(self.y-2)*8 + self.x] == 2:
return
else:
self.ma[(self.y-2)*8 + self.x] = 2
self.ma[(self.y-1)*8 + self.x] = 4
self.ma[self.y*8 + self.x] = m[self.y*8 + self.x]
self.y = self.y - 1
else:
self.ma[(self.y-1)*8 + self.x] = 4
self.ma[self.y*8 + self.x] = m[self.y*8 + self.x]
self.y = self.y - 1
elif self.d == 3: #向下
if self.ma[(self.y+1)*8 + self.x] == 0:
return
elif self.ma[(self.y+1)*8 + self.x] == 2:
if self.ma[(self.y+2)*8 + self.x] == 0 or self.ma[(self.y+2)*8 + self.x] == 2:
return
else:
self.ma[(self.y+2)*8 + self.x] = 2
self.ma[(self.y+1)*8 + self.x] = 4
self.ma[self.y*8 + self.x] = m[self.y*8 + self.x]
self.y = self.y + 1
else:
self.ma[(self.y+1)*8 + self.x] = 4
self.ma[self.y*8 + self.x] = m[self.y*8 + self.x]
self.y = self.y + 1
elif self.d == 4: #向左
if self.ma[self.y*8 + self.x-1] == 0:
return
elif self.ma[self.y*8 + self.x-1] == 2:
if self.ma[self.y*8 + self.x-2] == 0 or self.ma[self.y*8 + self.x-2] == 2:
return
else:
self.ma[self.y*8 + self.x-2] = 2
self.ma[self.y*8 + self.x-1] = 4
self.ma[self.y*8 + self.x] = m[self.y*8 + self.x]
self.x = self.x - 1
else:
self.ma[self.y*8 + self.x-1] = 4
self.ma[self.y*8 + self.x] = m[self.y*8 + self.x]
self.x = self.x - 1
elif self.d == 2: #向右
if self.ma[self.y*8 + self.x+1] == 0:
return
elif self.ma[self.y*8 + self.x+1] == 2:
if self.ma[self.y*8 + self.x+2] == 0 or self.ma[self.y*8 + self.x+2] == 2:
return
else:
self.ma[self.y*8 + self.x+2] = 2
self.ma[self.y*8 + self.x+1] = 4
self.ma[self.y*8 + self.x] = m[self.y*8 + self.x]
self.x = self.x + 1
else:
self.ma[self.y*8 + self.x+1] = 4
self.ma[self.y*8 + self.x] = m[self.y*8 + self.x]
self.x = self.x + 1
9、游戏结束判定
def judge(self):
if self.ma[1*8+2] == 2 and self.ma[5*8+2] == 2:
self.win()
10、LED闪烁和BGM播放
async def blink(self):
led.value(1)
await asyncio.sleep_ms(50)
led.value(0)
await asyncio.sleep_ms(50)
async def bgm_process(self):
while True:
self.mySong.tick()
await asyncio.sleep(0.04)
11、游戏主题进程
async def process(self):
bgm = asyncio.create_task(self.bgm_process()) #播放bgm
self.title() #标题界面
while True:
buttonValueStart = buttonStart.value()
if buttonValueStart == 0: #检测按下start键时进入游戏
hardware.init()
self.init_run()
while True:
self.d = 0
self.dir_select() #采集四向摇杆移动方向
self.run() #根据移动修改地图数据
self.draw() #对地图进行更新
self.judge() #判断游戏是否结束
await self.blink() #led灯闪烁
12、主函数调用上面的函数,完成游戏的实现
def main():
hardware.init()
s = game()
asyncio.run(s.process())
main()
主要问题与项目总结
这次移植因为对板卡套件不够熟悉,对这个游戏的底层逻辑不熟悉产生过很多问题,其中之一就是地图的实现问题,最开始地图只占整个屏幕的四分之一,后来通过调参让地图遍布屏幕,但是地图就断开了,没办法做到兼顾;另外就是蜂鸣器播放背景音乐,由于老师上课给的网址一直打不开,所以我就用了例子里面的音乐;但是在游戏里它的节奏变慢,我尝试改动,要么是没有声音了,要么是断断续续,最后保留了节奏缓慢的版本。
通过这次游戏移植,我意识到自己对嵌入式的理解还是没有那么深刻,完整的进行一个项目和单纯实现某个函数的功能还有很大的不同。在项目中如何对各个函数进行协调,使其能够在利用最少资源的情况下完成功能,这是很大一门学问。在未来的日子里我将继续依托这个套件去对嵌入式进行更深一步的学习和了解,勤加练习,争取可以精进自己的能力和加深对嵌入式系统的理解。