1 项目介绍
利用RP2040以及所配套的240*240的屏幕进行复古FC游戏《坦克大战》,玩家控制所属黄色坦克,在设计地形中与所创造的敌方绿坦克进行相互攻击,从而消灭全部敌方坦克取得胜利。
2 设计思路
2.1 游戏界面设计与控制
游戏开始界面提示玩家按键“START”进入游戏,循环判定,在游戏界面中胜负判定后进入结算画面,然后提示用户按键“START”返回初始画面,重新开始游戏。主要的界面通过原版FC《坦克大战》中的开始界面和关卡选择界面。
2.2 玩家移动控制
通过检测st7789四向摇杆的状态触发对于玩家坦克的控制,上下左右检测以及是否碰到障碍物和边缘,如符合条件,则通过图像消失和再生成实现对于玩家坦克移动的动画。从网上下载《FC版坦克大战》并进行素材截取后,获取到关于开始界面,地形、玩家形象以及敌人等素材后,在屏幕上得以显示。
2.3 子弹生成与飞行
子弹作为像素块,与玩家控制的坦克类似,这里不过多赘述。
2.4 游戏胜负判定
与原版FC《坦克大战》相似,当消灭敌方全部坦克后,获得胜利。若被子弹打中,则游戏失败。然后对于所发射的子弹等进行每一帧的位置判定,碰到墙壁或碰到敌人触发条件。最后,背景音乐采用定时器,在每一拍产生中断后控制蜂鸣器产生相应音调,在游戏同时产生BGM。
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 运行结果
6 遇到难题和解决方法
(1)子弹覆盖墙壁图层导致地形破坏。由于采用st7789库函数,利用图像不断生成形成子弹移动的画面,在障碍物判断可能会受到影响导致子弹不能及时消失,从而进入墙壁导致地形缺失。利用if语句进行判定,当子弹下一时刻进入墙壁后及时收回子弹并保证墙壁不受影响。
(2)背景音乐无法关闭。此游戏中采用定时器进行背景音乐的播放,每次产生中断后对于蜂鸣器进行控制,然而在有时关闭定时器后却蜂鸣器依旧以前一时刻的音调响,解决办法:在关闭定时器后采用buzzer_music中的stop函数关闭蜂鸣器,背景音乐关闭。
7 未来计划
(1)游戏内容较少,可玩性小。目前游戏仅有一关,且较为简单,通关容易,随后会设计更多有意思的关卡并且设计更多元素,尽量还原FC版的《坦克大战》;
(2)游戏控制不可更改,形象不可切换。对于玩家和敌人的形象设定好不可更改,玩家也不能切换自己较为舒适的按键玩法;
(3)代码繁杂冗长。由于通过图层覆盖不断切换画面以及玩家控制移动,所以引用大量IF语句以及循环,使得代码难以辨认且冗长,随后会精进算法并采用更为高效的;
(4)游戏存在于许多bug亟待修改。例如玩家的坦克有时无法移动,子弹卡墙里等等,待随后进行更改,保证游戏能够稳定运行。