基于树莓派RP2040的嵌入式系统实现复古游戏移植
基于树莓派RP2040实现对于复古游戏FC版《坦克大战》的移植,在LCD屏上显示并且玩家通过摇杆控制,A键攻击,四向摇杆进行坦克控制,生成地形,消灭敌方坦克就算获胜。
标签
嵌入式系统
FPGA
2022寒假在家练
蓝鲸少年与海
更新2022-03-04
北京理工大学
1009

1 项目介绍

利用RP2040以及所配套的240*240的屏幕进行复古FC游戏《坦克大战》,玩家控制所属黄色坦克,在设计地形中与所创造的敌方绿坦克进行相互攻击,从而消灭全部敌方坦克取得胜利。

2 设计思路

2.1 游戏界面设计与控制

游戏开始界面提示玩家按键“START”进入游戏,循环判定,在游戏界面中胜负判定后进入结算画面,然后提示用户按键“START”返回初始画面,重新开始游戏。主要的界面通过原版FC《坦克大战》中的开始界面和关卡选择界面。

FqUjYzfXev1KYM1Mydv2V-oO96mE 2.2  玩家移动控制

通过检测st7789四向摇杆的状态触发对于玩家坦克的控制,上下左右检测以及是否碰到障碍物和边缘,如符合条件,则通过图像消失和再生成实现对于玩家坦克移动的动画。从网上下载《FC版坦克大战》并进行素材截取后,获取到关于开始界面,地形、玩家形象以及敌人等素材后,在屏幕上得以显示。

Fq-ElUK05DKS4SblZRaGH70aB0nV

2.3 子弹生成与飞行

子弹作为像素块,与玩家控制的坦克类似,这里不过多赘述。

FnuYZblrXeReX5kXJEfsGvJnx_ak

2.4 游戏胜负判定

与原版FC《坦克大战》相似,当消灭敌方全部坦克后,获得胜利。若被子弹打中,则游戏失败。然后对于所发射的子弹等进行每一帧的位置判定,碰到墙壁或碰到敌人触发条件。最后,背景音乐采用定时器,在每一拍产生中断后控制蜂鸣器产生相应音调,在游戏同时产生BGM。

FpfE3skdhksydf7nfb7vQRwdTHp7

 

3 硬件介绍

采用树莓派Pico核心芯片RP2040:双核Arm Cortex M0+内核,可以运行到133MHz,264KB内存;性能强大、高度灵活的可编程IO可用于高速数字接口,片内温度传感器、并支持外部4路模拟信号输入,内部ADC采样率高达500Ksps、12位精度,支持MicroPython、C、C++编程。包括一些配套的设备例如:我所用到的240*240分辨率的彩色IPS LCD,控制器为ST7789四向摇杆 + 2个轻触按键板上外扩2MB Flash,预刷MicroPython的UF2固件,一个三轴姿态传感器MMA7660和一个蜂鸣器。

4 代码介绍

4.1 玩家控制

利用machine中的ADC对于四向摇杆的状态进行提取,从而得到x与y,通过if语句对其进行判断。首先对于障碍物和边缘判定,即是否满足移动的条件,若玩家坦克可以进行移动,首先利用库函数中的将其上一位置图像融入背景即消失,然后在新的坐标生成新的图像,达成移动的样子,同时根据摇杆方向改变其图形,保存方向以做子弹方向。

x=int(user_x.read_u16()*(240-0)/0xffff)
y=int(user_y.read_u16()*(240-0)/0xffff)
        
        #player moving
        
        #wall
if  x < 30 and (player_x <= 15 or (player_y >= 15 and player_y <= 195 and player_x <= 45)):
            player_x = player_x
elif x > 200 and (player_x >= 225 or (player_y >= 15 and player_y <= 195 and player_x >= 195)):
            player_x = player_x
elif y < 30 and (player_y <= 15 or (player_y <= 195 and (player_x <= 45 or player_x >= 195))):
            player_y = player_y
elif y > 200 and (player_y >= 225 or (player_y >= 15 and (player_x <= 45 or player_x >= 195))):
            player_y = player_y
else:
            if x < 30 :
                display.fill_rect(player_x-15,player_y-15,30,30,BLACK)
                player_x = player_x - 5
                display.blit_buffer(tank_left,player_x-15,player_y-15, 30, 30)
                player_direction = 3
            
            elif x > 200 :
                display.fill_rect(player_x-15,player_y-15,30,30,BLACK)
                player_x = player_x + 5
                display.blit_buffer(tank_right,player_x-15,player_y-15, 30, 30)
                player_direction = 4
            
            elif y < 30 :
                display.fill_rect(player_x-15,player_y-15,30,30,BLACK)
                player_y = player_y - 5
                display.blit_buffer(tank_up,player_x-15,player_y-15, 30, 30)
                player_direction = 1
                
            elif y > 200 :
                display.fill_rect(player_x-15,player_y-15,30,30,BLACK)
                player_y = player_y + 5
                display.blit_buffer(tank_down,player_x-15,player_y-15, 30, 30)
                player_direction = 2

4.2 子弹生成与飞行

根据上述保存的坦克方向付于子弹的初始方向,同时循环检测按键A的状态,如果判断A按键按下后则生成子弹(bullet_begin),敌方和玩家坦克均要生成,所以利用for循环不断判定,对于每个子弹都需要判定是否与玩家的坦克坐标重合,如有碰撞则判断游戏结束。子弹飞行与坦克移动动画原理相似,这里不过多阐述

for i in range(0,number):
            #bullet start
            if(bullet_x[i] > player_x-15 and bullet_x[i] < player_x+15 and bullet_y[i] > player_y-15 and bullet_y[i] < player_y+15):
                lose_flag = 1
                break
            if i == 0:
                if buttonValueA == 0 and bullet[i] == 0:
                    bullet_begin[i] = 1
                    bullet_direction[i] = player_direction
                if bullet_begin[i] == 1:
                    bullet[i] = 1
                    bullet_begin[i] = 0
                    if bullet_direction[i] == 1:#up
                        x_begin[i] = player_x
                        y_begin[i] = player_y-15-3
                     
                    elif bullet_direction[i] == 2:#down
                        x_begin[i] = player_x
                        y_begin[i] = player_y+15+3
                
                    elif bullet_direction[i] == 3:#left
                        x_begin[i] = player_x-15-3
                        y_begin[i] = player_y
                
                    elif bullet_direction[i] == 4:#right
                        x_begin[i] = player_x+15+3
                        y_begin[i] = player_y
                        
                    bullet_x[i] = x_begin[i]
                    bullet_y[i] = y_begin[i]
                    display.fill_rect(bullet_x[i]-3,bullet_y[i]-3,6,6,WHITE)      
            else:
                if bullet[i] == 0 and enemy_life[i] == 1:
                    bullet_begin[i] = 1
                if bullet_begin[i] == 1:
                    bullet[i] = 1
                    bullet_begin[i] = 0
                    bullet_x[i] = x_begin[i]
                    bullet_y[i] = y_begin[i]
                    display.fill_rect(bullet_x[i]-3,bullet_y[i]-3,6,6,WHITE)
                    
            #bullet moving
            if bullet[i] == 1:
                display.fill_rect(bullet_x[i]-3,bullet_y[i]-3,6,6,BLACK)
                if bullet_direction[i] == 1:
                        if (bullet_y[i]-6 <= 180 and (bullet_x[i] <= 30 or bullet_x[i] >= 210)) or (bullet_y[i]-6 <= 30 and (bullet_x[i] <= 30 or bullet_x[i] >= 210)):
                            bullet[i] = 0
                        elif bullet_y[i] >= 6:
                            bullet_y[i] = bullet_y[i] - 6
                            display.fill_rect(bullet_x[i]-3,bullet_y[i]-3,6,6,WHITE)
                        else :
                            bullet[i] = 0
                            
                elif bullet_direction[i] == 2:
                        if (bullet_y[i]+6 <= 180 and (bullet_x[i] <= 30 or bullet_x[i] >= 210)) or (bullet_y[i]+6 <= 30 and (bullet_x[i] <= 30 or bullet_x[i] >= 210)):
                            bullet[i] = 0
                        elif bullet_y[i] <= 234:
                            bullet_y[i] = bullet_y[i] + 6
                            display.fill_rect(bullet_x[i]-3,bullet_y[i]-3,6,6,WHITE)
                            
                        else :
                            bullet[i] = 0
                            
                elif bullet_direction[i] == 3:
                        if (bullet_y[i] <= 180 and (bullet_x[i]-6 <= 30 or bullet_x[i]-6 >= 210)) or (bullet_y[i] <= 30 and (bullet_x[i]-6 <= 30 or bullet_x[i]-6 >= 210)):
                            bullet[i] = 0
                        elif bullet_x[i] >= 6 :
                            bullet_x[i] = bullet_x[i] - 6
                            display.fill_rect(bullet_x[i]-3,bullet_y[i]-3,6,6,WHITE)
                        else:
                            bullet[i] = 0
                            
                elif bullet_direction[i] == 4:
                        if (bullet_y[i] <= 180 and (bullet_x[i]+6 <= 30 or bullet_x[i]+6 >= 210)) or (bullet_y[i] <= 30 and (bullet_x[i]+6 <= 30 or bullet_x[i]+6 >= 210)):
                            bullet[i] = 0
                        elif bullet_x[i] <= 234:
                            bullet_x[i] = bullet_x[i] + 6
                            display.fill_rect(bullet_x[i]-3,bullet_y[i]-3,6,6,WHITE)
                        else :
                            bullet[i] = 0

4.3 游戏界面选择与设计

生成初始界面,通过while循环判定“START”按键状态,若“START”按下后进入游戏画面,调用GAME函数,即上述游戏界面以及运行画面,当游戏胜负判定后进入结算界面,提示用户按下“START”返回初始界面,循环判定后返回初始界面。

while True:
    #begin
    display.blit_buffer(begin,0,40,240,82)
    display.text(font2,"START",80,150)
    time.sleep_ms(500)
    image002 = "stage1.bin" 
    f002 = open(image002, 'rb')
    while True:
        buttonValueStart = buttonStart.value()
        buttonValueSelect = buttonSelect.value()
        if buttonValueStart == 0:
            display.fill_rect(0,0,240,240,BLACK)
            break
    #stage1    
    for column in range(1,239):
        buf=f002.read(478)
        display.blit_buffer(buf, 1, column, 240, 1)    
    time.sleep(2)
    display.fill_rect(0,0,240,240,BLACK)
    #game
    flag = Game(display)
    if flag == [True,False]:
        while True:
            buttonValueStart = buttonStart.value()
            if buttonValueStart == 0:
                display.fill_rect(0,0,240,240,BLACK)
                break
        
    elif flag == [False,True] :
        while True:
            buttonValueStart = buttonStart.value()
            if buttonValueStart == 0:
                display.fill_rect(0,0,240,240,BLACK)
                break

5 运行结果

Fv28iozomT63z1JEfZLmSmTDC_CkFlbpKuuwkLYGQfwHZTTwLmvba5AUFgqy2Dz-hBRwM3BvuxRY6cn3NZRfFjcDEK98SoWUHBZ2jwRzPSrSKw0lFpg_FgGvD6VJqvMmJa82ZTSKEqcW

6 遇到难题和解决方法

(1)子弹覆盖墙壁图层导致地形破坏。由于采用st7789库函数,利用图像不断生成形成子弹移动的画面,在障碍物判断可能会受到影响导致子弹不能及时消失,从而进入墙壁导致地形缺失。利用if语句进行判定,当子弹下一时刻进入墙壁后及时收回子弹并保证墙壁不受影响。

(2)背景音乐无法关闭。此游戏中采用定时器进行背景音乐的播放,每次产生中断后对于蜂鸣器进行控制,然而在有时关闭定时器后却蜂鸣器依旧以前一时刻的音调响,解决办法:在关闭定时器后采用buzzer_music中的stop函数关闭蜂鸣器,背景音乐关闭。

7 未来计划

(1)游戏内容较少,可玩性小。目前游戏仅有一关,且较为简单,通关容易,随后会设计更多有意思的关卡并且设计更多元素,尽量还原FC版的《坦克大战》;

(2)游戏控制不可更改,形象不可切换。对于玩家和敌人的形象设定好不可更改,玩家也不能切换自己较为舒适的按键玩法;

(3)代码繁杂冗长。由于通过图层覆盖不断切换画面以及玩家控制移动,所以引用大量IF语句以及循环,使得代码难以辨认且冗长,随后会精进算法并采用更为高效的;

(4)游戏存在于许多bug亟待修改。例如玩家的坦克有时无法移动,子弹卡墙里等等,待随后进行更改,保证游戏能够稳定运行。

附件下载
bin.zip
所用素材bin
code.zip
团队介绍
团队成员
姚博宇
姚博宇,北京理工大学2019级本科生,对FPGA以及单片机应用与开发有着浓厚兴趣,曾多次参加相关竞赛与培训,均有较好的成绩。
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号