内容介绍
内容介绍
1.项目需求
- 设计或移植一款经典的游戏
- 可以通过LCD屏显示
- 可以通过按键和游戏机姿态控制游戏的动作
2.完成的功能
(1)完成设计(移植)经典游戏《俄罗斯方块》
- 设计平台:Arduino
- 编写语言:C
- 编写环境:Win10
- 硬件平台:M5StickCPlus
(2)完成通过按键和姿态控制《俄罗斯方块》游戏的动作
- 使用函数:M5.BtnB.wasReleased( ); getAhrsData( );
- 硬件平台:Key按键、MPU6886
3.实现思路
- 编写(移植)《俄罗斯方块》游戏的运行逻辑;
- 依托M5提供的LCD库编写LCD屏幕显示界面;
4.实现过程
(1)程序框图
(2)俄罗斯方块逻辑编写
(a)方块生成
这里运用了bool来判断是否要生成新的方块组合。代码如下:
bool GetSquares(Block block, Point pos, int rot, Point* squares) {
bool overlap = false;
for (int i = 0; i < 4; ++i) {
Point p;
p.X = pos.X + block.square[rot][i].X;
p.Y = pos.Y + block.square[rot][i].Y;
overlap |= p.X < 0 || p.X >= Width || p.Y < 0 || p.Y >=
Height || screen[p.X][p.Y] != 0;
squares[i] = p;
}
return !overlap;
}
(b)方块类型信息存储
构造方块结构体,存储方块的结构、颜色等信息,把方块用矩阵来表示,再利用数组来将信息存储。代码如下:
struct Block {Point square[4][4]; int numRotate, color;};
Block blocks[7] = {
{{{{-1,0},{0,0},{1,0},{2,0}},{{0,-1},{0,0},{0,1},{0,2}},
{{0,0},{0,0},{0,0},{0,0}},{{0,0},{0,0},{0,0},{0,0}}},2,1},
{{{{0,-1},{1,-1},{0,0},{1,0}},{{0,0},{0,0},{0,0},{0,0}},
{{0,0},{0,0},{0,0},{0,0}},{{0,0},{0,0},{0,0},{0,0}}},1,2},
{{{{-1,-1},{-1,0},{0,0},{1,0}},{{-1,1},{0,1},{0,0},{0,-1}},
{{-1,0},{0,0},{1,0},{1,1}},{{1,-1},{0,-1},{0,0},{0,1}}},4,3},
{{{{-1,0},{0,0},{0,1},{1,1}},{{0,-1},{0,0},{-1,0},{-1,1}},
{{0,0},{0,0},{0,0},{0,0}},{{0,0},{0,0},{0,0},{0,0}}},2,4},
{{{{-1,0},{0,0},{1,0},{1,-1}},{{-1,-1},{0,-1},{0,0},{0,1}},
{{-1,1},{-1,0},{0,0},{1,0}},{{0,-1},{0,0},{0,1},{1,1}}},4,5},
{{{{-1,1},{0,1},{0,0},{1,0}},{{0,-1},{0,0},{1,0},{1,1}},
{{0,0},{0,0},{0,0},{0,0}},{{0,0},{0,0},{0,0},{0,0}}},2,6},
{{{{-1,0},{0,0},{1,0},{0,-1}},{{0,-1},{0,0},{0,1},{-1,0}},
{{-1,0},{0,0},{1,0},{0,1}},{{0,-1},{0,0},{0,1},{1,0}}},4,7}
};
(c)按键和姿态读取
调用M5官方提供的按键读取函数和IMU的姿态读取函数,对按键的读取和MPU6886姿态的判断单独形成一个函数,后续使用方便。
bool KeyPadLoop(){
M5.IMU.getAhrsData(&pitch, &roll, &yaw);
if(pitch<-5)
{
if(pom==0)
{
pom=1;
ClearKeys();
but_LEFT =true;
return true;
}
}
else
{
pom=0;
}
if(pitch>15)
{
if(pom2==0)
{
pom2=1;
ClearKeys();
but_RIGHT=true;
return true;
}
}else
{
pom2=0;
}
if(M5.BtnB.wasReleased()){
if(pom3==0)
{pom3=1;ClearKeys();but_B =true;return true;}
}else {pom3=0;}
if(M5.BtnA.wasReleased()){
if(pom4==0)
{pom4=1;ClearKeys();but_A =true;return true;}
}else {pom4=0;}
return false;
}
(d)方块移动和旋转
GetNextPosRot()函数结合玩家在硬件操作反馈的结果,来判断方块组合该如何移动。代码如下:
void GetNextPosRot(Point* pnext_pos, int* pnext_rot) {
bool received = KeyPadLoop();
if (but_LEFT) started = true;
if (!started) return;
pnext_pos->X = pos.X;
pnext_pos->Y = pos.Y;
if ((fall_cnt = (fall_cnt + 1) % 10) == 0) pnext_pos->Y += 1;
else if (1)
{
if (but_LEFT)
{ but_LEFT = false; pnext_pos->X -= 1;}
else if (but_RIGHT)
{ but_RIGHT = false; pnext_pos->X += 1;}
else if (but_A)
{ but_A = false;
*pnext_rot = (*pnext_rot + block.numRotate - 1)%block.numRotate;
}
else if(but_B)
{
but_B = false;
*pnext_rot = (*pnext_rot + 1)%block.numRotate;
}
}
}
(e)存储和更新积累的方块
利用for循环,不断扫描screen数组内数值,以判断下落的方块是否与底部接触,接触则更新数组并生成新的方块下落。当无法生成新的方块时,游戏结束。代码如下:
void ReviseScreen(Point next_pos, int next_rot) {
if (!started) return;
Point next_squares[4];
for (int i = 0; i < 4; ++i)
screen[pos.X + block.square[rot][i].X][pos.Y + block.square[rot][i].Y] = 0;
if (GetSquares(block, next_pos, next_rot, next_squares)) {
for (int i = 0; i < 4; ++i){
screen[next_squares[i].X][next_squares[i].Y] = block.color;
}
pos = next_pos;
rot = next_rot;
}
else {
for (int i = 0; i < 4; ++i) screen[pos.X +
block.square[rot][i].X][pos.Y + block.square[rot][i].Y] = block.color;
if (next_pos.Y == pos.Y + 1) {
DeleteLine();
PutStartPos();
if (!GetSquares(block, pos, rot, next_squares)) {
for (int i = 0; i < 4; ++i)
screen[pos.X + block.square[rot][i].X][pos.Y + block.square[rot][i].Y] = block.color;
GameOver();
}
}
}
Draw();
}
(f)自动清除满行方块
从底部开始判断是否有空的一行方块,如果没有空白的就让这一行方块存储上一行方块的信息,以此类推,这样就可以达到消行的效果了。代码如下:
void DeleteLine() {
for (int j = 0; j < Height; ++j) {
bool Delete = true;
for (int i = 0; i < Width; ++i) if (screen[i][j] == 0) Delete = false;
if (Delete)
{
score++;
if(score%5==0)
{
lvl++;
game_speed=game_speed-2;
M5.Lcd.drawString("LEVL:"+String(lvl),88,8,1);
}
M5.Lcd.drawString("SCORE:"+String(score),14,8,1);
for (int k = j; k >= 1; --k)
{
for (int i = 0; i < Width; ++i)
{
screen[i][k] = screen[i][k - 1];
}
}
}}}
(g)LCD屏幕显示
每个方块的数组控制一定大小的像素,调用M5中LCD 的库函数对屏幕进行刷新:
void Draw() { // Draw 120x240 in the center
for (int i = 0; i < Width; ++i) for (int j = 0; j < Height; ++j)
for (int k = 0; k < Length; ++k) for (int l = 0; l < Length; ++l)
backBuffer[j * Length + l][i * Length + k] = BlockImage[screen[i][j]][k][l];
M5.Lcd.drawBitmap(12, 20, 110, 220, (uint8_t*)backBuffer);
}
5.遇到的难题及未来计划
在实现俄罗斯方块的主体功能方面较为顺利,但是在调用LCD的函数显示特定图片方面出现了些许疑问,将图片转换为数组后,并不能顺利的显示成图片,而是许多白点,目前推测是屏幕的刷新方向设置有差错,希望未来能解决这一问题。
M5StickCPlus的外设很多,目前仅使用到了LCD、姿态传感器、按键这些,还有许多功能可以开发,希望以后可以充分利用。
软硬件
元器件
ESP32-PICO-D4
ESP32-PICO-D4 是一款基于 ESP32 的系统级封装 (SiP) 模组,可提供完整的 Wi-Fi 和蓝牙 ® 功能。该模组的外
观尺寸仅为 (7.000±0.100) mm × (7.000±0.100) mm × (0.940±0.100) mm,整体占用的 PCB 面积最小,已集成
1 个 4 MB 串行外围设备接口 (SPI) flash。
MPU6886
MPU-6886是一个6轴运动跟踪设备,它将3轴陀螺仪和3轴加速度计结合在一起。它将一个三轴陀螺仪和一个三轴加速计装在一个 小巧的3 mm x 3 mm x 0.75 mm 24针LGA封装。
BM8563
BM8563是一款低功耗CMOS实时时钟/日历芯片,它提供一个可编程的时钟输出,一个中断输出和一个掉电检测器,所有的地址和数据都通过I2C总线接口串行传递。最大总线速度为400Kbits/s,每次读写数据后,内嵌的字地址寄存器会自动递增。
电路图
附件下载
CplusTetris.ino
团队介绍
2020级学生,对嵌入式感兴趣,但是有点懒q(≧▽≦q)
团队成员
tony
评论
0 / 100
查看更多
猜你喜欢