由于硬件设备的限制,在pico的FPGA拓展版上移植一款较为复杂的游戏不太可能。于是我选择了这款在Chrome浏览器中自带的Dino小游戏进行操作。
先说一下自带库的安装,我一开始使用的是pycharm软件,但发现找不到串口。后来改用了更加适合新手的thonny,但运行程序时找不到自定义的库,网上搜索无果后经过自己反复琢磨才想到要将库文件传入pico中,成功解决问题。
Dino是Chrome浏览器断网出现的小游戏。游戏的内容非常简单易懂,玩家使用上下按钮操控一只在一望无际平原上奔跑的小恐龙,使其跳跃或者低头躲避路上的障碍物(仙人掌)与空中的障碍物(鸟)。当小恐龙撞到仙人掌或者鸟的时候,则弹出GAMEOVER的提示代表游戏结束。
整个游戏大体上可以分为三个部分:计分与控制部分,碰撞判定部分与绘制部分。
首先是绘制部分。
由于LCD屏并不会自动擦除我们画过的内容,所以我们要在画出图形再写个程序把它擦掉来达到使物体移动起来的效果(即用黑色再画一遍)。这里用到的LCD库是漂移菌写的st7789库。但由于环境与硬件限制导致在绘制较多内容后移动效果并不是非常理想(主要还是micropython的原因,如果用C环境会更加流畅),看起来更像是在播放PPT动画。
代码示例如下:
#绘制障碍物
def draw_stone(xpos,ypos,width,typeof):
display.rect(xpos,ypos,width,width,color=st7789.color565(255,0,0))
#擦除障碍物
def erase_stone(xpos,ypos,width,typeof):
display.rect(xpos,ypos,width,width,color=st7789.color565(0,0,0))
而在绘制过程中,先调用draw_stone函数,再调用erase_stone函数进行擦除即可。
在解决了绘制问题后,如何让画面动起来呢?我使用了随着时间变化而线性变化的参数i,i代表横坐标。当i位于屏幕边缘的时候,将i重置为初始值,从而达到循环往复的动态效果。而障碍物的x坐标都随着i发生变化,小恐龙的横坐标保持不动,即可达到小恐龙奔跑的效果。参数speed代表了当前的移动速度,随着分数的增加而增加来提升难度。
while True:
i=i-speed; #屏幕移动
if(i<2):
i=234
其次是碰撞判定部分
当小恐龙撞到障碍物时判定为游戏结束,同时分数清零。小恐龙可以近似为一个长方体,这样问题就转化为两个长方体的碰撞判定,即小恐龙的中心位于障碍物周围的一定范围内就判定为碰撞。
部分代码如下:
#碰撞判定与游戏结束
def die(x,y,nowstone):
dieflag=0
for a in range(3):
if(stonexpos[a]>-1):
#当小恐龙的中心位于障碍物周围的一定范围内时即可判定为碰撞,弹出游戏结束提示窗口
if((x>=stonexpos[a]-12)and(x<=stonexpos[a]+stonewidth[a]+12)and((y-7>=stoneypos[a]-7)and(y-7<=stoneypos[a]+stonewidth[a]+7))):
dieflag=1
return dieflag
最后是关于计分与控制部分。
随着i移动,score分数+1。而通过读取按键K1,K2的输入达到控制跳跃与Restart的效果。
#首先对按键进行初始化
key1=machine.Pin(8,machine.Pin.IN,machine.Pin.PULL_UP) #K2按键
key2=machine.Pin(7,machine.Pin.IN,machine.Pin.PULL_UP) #K1按键
#当不处于跳跃阶段时,如果按下K1按钮,小恐龙开始跳跃
if((flag==0)and(down==0)):
if(key2.value()==0):
flag=15;
小恐龙的跳跃分为两个阶段,上升阶段与下降阶段。上升阶段为flag,而下降阶段为down。
if(flag>0): #上升阶段
flag=flag-2
ypos=ypos-20
if(flag==-1): #上升到最高点开始下降
down=down+1
flag=0
if((down>0)and(down<9)): #下降阶段
ypos=ypos+20
down=down+1
if(down==9): #下降到最低点,同时可以再次跳跃
down=0
这次项目是对pico的一次简单探索,运用到的知识也较为浅显易懂,适合我这种初学者进行尝试,只需要理解游戏原理即可。遗憾的是由于硬件与环境限制不能制作出一个较为精细的游戏,障碍物只能用长方形替代(尝试过仙人掌,但是运行后明显卡顿,为了流畅度不得不用简单的长方形替代)
最后附上完整代码如下:
import uos
import machine
import st7789 as st7789
import vga2_8x8 as font1
import vga1_16x32 as font2
import random
import time
import utime
# 定义默认SPI(0)引脚
sck = 2
mosi = 3
rst = 0
dc = 1
key=machine.Pin(6,machine.Pin.IN) #旋转编码器按键
keyleft=machine.Pin(4,machine.Pin.IN) #旋转编码器左旋
keyright=machine.Pin(5,machine.Pin.IN) #旋转编码器右旋
key1=machine.Pin(8,machine.Pin.IN,machine.Pin.PULL_UP) #上按键
key2=machine.Pin(7,machine.Pin.IN,machine.Pin.PULL_UP) #下按键
buzzle=machine.PWM(machine.Pin(28)) #蜂鸣器
# 定义屏幕信息
width = 240
height = 240
CENTER_Y = int(width/2)
CENTER_X = int(height/2)
# 实例化一个display对象
spi0 = machine.SPI(0, baudrate=40000000, polarity=1, phase=0, sck=machine.Pin(sck), mosi=machine.Pin(mosi))
display = st7789.ST7789(spi0, width, width, reset=machine.Pin(rst, machine.Pin.OUT),dc=machine.Pin(dc, machine.Pin.OUT),xstart=0, ystart=0, rotation=0)
#绘制边框
display.hline(2,2,236,st7789.color565(0,0,255))
display.vline(2,2,236,st7789.color565(0,0,255))
display.vline(238,2,236,st7789.color565(0,0,255))
display.hline(2,238,236,st7789.color565(0,0,255))
#绘制一朵云
def draw_cloud(xpos,ypos,col=st7789.color565(255, 255, 255)):
display.hline(xpos-5,ypos,10,col)
display.pixel(xpos-5,ypos-1,col)
display.pixel(xpos+4,ypos-1,col)
display.pixel(xpos-4,ypos-1,col)
display.pixel(xpos-3,ypos-2,col)
display.pixel(xpos-2,ypos-3,col)
display.pixel(xpos-1,ypos-3,col)
display.pixel(xpos,ypos-3,col)
display.pixel(xpos+1,ypos-2,col)
display.pixel(xpos+2,ypos-1,col)
display.pixel(xpos+3,ypos-2,col)
display.pixel(xpos+4,ypos-1,col)
#擦除这朵云
def erase(xpos,ypos,col=st7789.color565(0, 0, 0)):
display.hline(xpos-5,ypos,10,col)
display.pixel(xpos-5,ypos-1,col)
display.pixel(xpos+4,ypos-1,col)
display.pixel(xpos-4,ypos-1,col)
display.pixel(xpos-3,ypos-2,col)
display.pixel(xpos-2,ypos-3,col)
display.pixel(xpos-1,ypos-3,col)
display.pixel(xpos,ypos-3,col)
display.pixel(xpos+1,ypos-2,col)
display.pixel(xpos+2,ypos-1,col)
display.pixel(xpos+3,ypos-2,col)
display.pixel(xpos+4,ypos-1,col)
#绘制地面
def draw_floor(col=st7789.color565(255,30,78)):
display.hline(5,230,232,col)
display.hline(5,224,232,col)
display.hline(5,218,232,col)
def draw_floor_line(xpos,col=st7789.color565(255,30,78)):
for i in range(13):
r1=xpos+i*20
r2=r1+8
r3=r2+8
while(r1>236):
r1=r1-236
while(r2>236):
r2=r2-236
while(r3>236):
r3=r3-236
display.vline(r1,230,6,col)
display.vline(r2,224,6,col)
display.vline(r3,218,6,col)
def erase_floor_line(xpos,col=st7789.color565(0,0,0)):
for i in range(13):
r1=xpos+i*20
r2=r1+8
r3=r2+8
while(r1>236):
r1=r1-236
while(r2>236):
r2=r2-236
while(r3>236):
r3=r3-236
display.vline(r1,230,6,col)
display.vline(r2,224,6,col)
display.vline(r3,218,6,col)
#绘制小恐龙
def draw_dinasaur(xpos,ypos,col=st7789.color565(80,255,80)):
display.vline(xpos-12,ypos-6,3,col)
display.vline(xpos-10,ypos-6,5,col)
display.vline(xpos-8,ypos-5,8,col)
display.vline(xpos-6,ypos-4,9,col)
display.vline(xpos-4,ypos-3,18,col)
display.vline(xpos-2,ypos-2,10,col)
display.vline(xpos,ypos-3,12,col)
display.vline(xpos+2,ypos-5,14,col)
display.vline(xpos+4,ypos-13,28,col)
display.vline(xpos+6,ypos-14,19,col)
display.vline(xpos+8,ypos-14,8,col)
display.vline(xpos+10,ypos-14,8,col)
display.vline(xpos+12,ypos-13,7,col)
display.pixel(xpos+6,ypos-12,st7789.color565(0,0,0))
display.pixel(xpos+7,ypos-12,st7789.color565(0,0,0))
display.hline(xpos+7,ypos-1,4,col)
display.vline(xpos-11,ypos-6,3,col)
display.vline(xpos-9,ypos-6,5,col)
display.vline(xpos-7,ypos-5,8,col)
display.vline(xpos-5,ypos-4,9,col)
display.vline(xpos-3,ypos-3,18,col)
display.vline(xpos-1,ypos-2,10,col)
display.vline(xpos+1,ypos-5,14,col)
display.vline(xpos+3,ypos-13,28,col)
display.vline(xpos+5,ypos-14,19,col)
display.vline(xpos+7,ypos-14,8,col)
display.vline(xpos+9,ypos-14,8,col)
display.vline(xpos+11,ypos-13,7,col)
def erase_dinasaur(xpos,ypos,col=st7789.color565(0,0,0)):
display.vline(xpos-12,ypos-6,3,col)
display.vline(xpos-10,ypos-6,5,col)
display.vline(xpos-8,ypos-5,8,col)
display.vline(xpos-6,ypos-4,9,col)
display.vline(xpos-4,ypos-3,18,col)
display.vline(xpos-2,ypos-2,10,col)
display.vline(xpos,ypos-3,12,col)
display.vline(xpos+2,ypos-5,14,col)
display.vline(xpos+4,ypos-13,28,col)
display.vline(xpos+6,ypos-14,19,col)
display.vline(xpos+8,ypos-14,8,col)
display.vline(xpos+10,ypos-14,8,col)
display.vline(xpos+12,ypos-13,7,col)
display.pixel(xpos+6,ypos-12,st7789.color565(0,0,0))
display.pixel(xpos+7,ypos-12,st7789.color565(0,0,0))
display.hline(xpos+7,ypos-1,4,col)
display.vline(xpos-11,ypos-6,3,col)
display.vline(xpos-9,ypos-6,5,col)
display.vline(xpos-7,ypos-5,8,col)
display.vline(xpos-5,ypos-4,9,col)
display.vline(xpos-3,ypos-3,18,col)
display.vline(xpos-1,ypos-2,10,col)
display.vline(xpos+1,ypos-5,14,col)
display.vline(xpos+3,ypos-13,28,col)
display.vline(xpos+5,ypos-14,19,col)
display.vline(xpos+7,ypos-14,8,col)
display.vline(xpos+9,ypos-14,8,col)
display.vline(xpos+11,ypos-13,7,col)
#绘制障碍物
def draw_stone(xpos,ypos,width,typeof):
display.rect(xpos,ypos,width,width,color=st7789.color565(255,0,0))
def erase_stone(xpos,ypos,width,typeof):
display.rect(xpos,ypos,width,width,color=st7789.color565(0,0,0))
#碰撞判定与游戏结束
def die(x,y,nowstone):
dieflag=0
display.text(font1," ",80,120,color=st7789.color565(0,0,0))
for a in range(3):
if(stonexpos[a]>-1):
#当小恐龙的中心位于障碍物周围的一定范围内时即可判定为碰撞,弹出游戏结束提示窗口
if((x>=stonexpos[a]-12)and(x<=stonexpos[a]+stonewidth[a]+12)and((y-7>=stoneypos[a]-7)and(y-7<=stoneypos[a]+stonewidth[a]+7))):
display.text(font1,"Score: ",150,25,st7789.color565(0,0,0))
while(key1.value()==1):
display.rect(34,106,172,42,color=st7789.color565(255,255,255))
display.text(font1,"YOU DIED",88,116,color=st7789.color565(255,0,0))
display.text(font1,"PRESS K2 TO RESTART",44,132,color=st7789.color565(255,0,0))
dieflag=1
display.rect(34,106,172,42,color=st7789.color565(0,0,0))
display.text(font1,"YOU DIED",88,116,color=st7789.color565(0,0,0))
display.text(font1,"PRESS K2 TO RESTART",44,132,color=st7789.color565(0,0,0))
return dieflag
i=234
ypos=204
flag=0
down=0
score=0
speed=8
flag2=0
barrier=0;
stonexpos=[-1,-1,-1]
stoneypos=[-1,-1,-1]
stonewidth=[-1,-1,-1]
temp=0
a=0
stoneout=0
nowstone=0
while True:
display.hline(2,2,236,st7789.color565(0,0,255)) #绘制边界的上边框和左边框
display.vline(2,2,236,st7789.color565(0,0,255))
draw_cloud(i,20) #绘制云
draw_floor_line(i)
draw_dinasaur(50,ypos) #绘制小恐龙
display.text(font1,"Score: "+str(score),150,25) #在右上角打印分数
erase(i,20)
erase_floor_line(i)
draw_floor()
i=i-speed; #屏幕移动
stoneout=stoneout+speed
for a in range(3):
#绘制石头,如果stonexpos数组中的数值大于2(屏幕边界),即代表该障碍物存在,画出该障碍物
#否则擦除该障碍物,当前障碍物数量-1
if(stonexpos[a]>2):
erase_stone(stonexpos[a],stoneypos[a],stonewidth[a],barrier)
stonexpos[a]=stonexpos[a]-speed
else:
erase_stone(stonexpos[a],stoneypos[a],stonewidth[a],barrier)
stonexpos[a]=-1
stoneypos[a]=-1
stonewidth[a]=-1
if(temp>0):
temp=temp-1
score=score+1
if(i<2):
i=234
if((flag==0)and(down==0)):
if(key2.value()==0):
flag=15
buzzle.freq(3000)
buzzle.duty_u16(2047)
erase_dinasaur(50,ypos)
else:
erase_dinasaur(50,ypos)
#跳跃过程
if(flag>0):
flag=flag-2
ypos=ypos-20
if(flag==-1):
down=down+1
flag=0
if((down>0)and(down<9)):
ypos=ypos+20
down=down+1
if(down==9):
down=0
#随机生成障碍物
if(stoneout>speed*15):
stoneout=0
barrier=random.randint(1,3)
if(barrier==1):
stonewidth[nowstone]=34
elif(barrier==2):
stonewidth[nowstone]=30
elif(barrier==3):
stonewidth[nowstone]=39;
stonexpos[nowstone]=234-stonewidth[nowstone]
stoneypos[nowstone]=218-stonewidth[nowstone]
if(nowstone==2):
nowstone=0
else:
nowstone=nowstone+1
temp=temp+1
for a in range(3):
if(stonexpos[a]>-1):
draw_stone(stonexpos[a],stoneypos[a],stonewidth[a],barrier)
#加速
if((score>100)and(speed<14)):
speed=14
flag2=5
if((score>200)and(speed<19)and(speed>=14)):
speed=20
flag2=5
if(flag2>0):
flag2=flag2-1
display.text(font1,"Speed up!!!",80,120,color=st7789.color565(255,255,0))
display.text(font1,"Speed up!!!",80,120,color=st7789.color565(255,0,0))
else:
display.text(font1," ",80,120,color=st7789.color565(0,0,0))
#碰撞判定
if(die(50,ypos,temp)==1):
for a in range(3):
if(stonexpos[a]>2):
erase_stone(stonexpos[a],stoneypos[a],stonewidth[a],barrier)
stonexpos[a]=stonexpos[a]-speed
else:
erase_stone(stonexpos[a],stoneypos[a],stonewidth[a],barrier)
stonexpos[a]=-1
stoneypos[a]=-1
stonewidth[a]=-1
if(temp>0):
temp=temp-1
i=234
ypos=204
flag=0
down=0
score=0
speed=8
flag2=0
barrier=0;
stonexpos=[-1,-1,-1]
stoneypos=[-1,-1,-1]
stonewidth=[-1,-1,-1]
temp=0
a=0
nowstone=0
buzzle.freq(5000)
buzzle.duty_u16(2047)