zhengrenzhe's blog   About

msp430单片机下的一个迷宫游戏

最近一周在完成学校的专业课程设计项目,就是学习使用msp430单片机,在从没用过单片机,c语言已经忘了大半的情况下,3天做书上的实验,1天完成创新性实验,还真是不易啊啊啊。

创新性实验是指在完成书上的情况下,自己创造一个要跟书上介绍的完全不同的实验,而且还要求一定难度,像什么跑马灯那肯定是不行的。我的创新性实验是一个简单的迷宫游戏,用十字按键控制黄点的移动方向,要走出整个迷宫,碰到边界会gameover。先上单片机及游戏截图。

使用的单片机型号为 MSP430F6638 EVM,这是清华与ti联合设计的一款单片机,一看就比市面上的那些高端多了,据老师说一块得1k多呢。 游戏功能如下: 使用右下角的十字方向键控制黄点的移动,碰到墙壁(绿线)会显示 gameover,3s后会重绘整个界面,走出上面的缺口后显示 you win,3s后会重绘整个界面,逻辑比较简单,下面讲一下开发流程。

代码的重点部分有以下几个:点移动的视觉效果,碰撞检测,方向键的控制。 首先需要绘制出界面的边框,绘制很简单,使用自带的 etft_AreaSet 函数即可,将画框写成了一个函数,方便之后重绘调用。

void drawBorder(){
  // left, bottom, right borde;
  etft_AreaSet(0,0,0,239,2016);
  etft_AreaSet(0,239,319,239,2016);
  etft_AreaSet(319,0,319,239,2016);
  // top border;
  etft_AreaSet(0,0,140,0,2016);
  etft_AreaSet(180,0,319,0,2016);
}

接着画点,这里需要注意,这块屏幕能提供的功能只有三个:在屏幕上画字符串,给屏幕上的特定区域着色,在屏幕上显示图片。而点的移动包括以下几个步骤:1.绘制新点 2.移除之前的点。因为屏幕只有绘制的功能,所以只能变通一下,把之前的点绘制为黑色就行了。而如何记录之前的点的位置呢,我的解决办法是建立一个长度为1000数组,数组的初始元素为点默认的起始位置,每次按键点击后先将数组中的最后一个元素所记录的点的位置的颜色绘制为黑色,接着将点移动响应的距离后将新的坐标保存进数组,这样就实现了点随着按键按下而移动的效果。下面为点移动的核心代码。

// define point position array
int pointPosition[1000][2];

void clearPoint(){
  int lastcalled = called -1;
  int x = pointPosition[lastcalled][0];
  int y = pointPosition[lastcalled][1];
  etft_AreaSet(x, y, x+10, y+10, 0);
}

// draw point by offset
int drawpointByoffset(int x_offset, int y_offset){
  clearPoint();
  int x = pointPosition[called -1][0] + x_offset;
  int y = pointPosition[called -1][1] + y_offset;
  etft_AreaSet(x, y, x + 10, y+10, 65504);
  pointPosition[called][0] = x;
  pointPosition[called][1] = y;
  called = called + 1;
}

上面的 drawpointByoffset 在每次按键按下后被调用,参数为 x,y 坐标的变化值,对于上下左右四个方向来说就是下面四种情况

called初始值为0,每次按键按下后+1,方便操作数组,现在每次按键后的重绘点功能已经完成了,接着就要实现按键控制。 按键控制使用比较简单,只要研究了书上的第一个实验代码就行了,下面是按键功能实现的核心代码:

while(1){
  cur_btn = P4IN & 0x1F;
  temp = (cur_btn ^ last_btn) & last_btn;
  last_btn = cur_btn;
  if(lock){
    break;
  }
  int i;
  for(i=0;i<5;++i){
    if(temp & (1 << i)){
      bee();
      switch(i){
        case 0 : drawpointByoffset(-10, 0);break;
        case 1 : drawpointByoffset(0, 10);break;
        case 3 : drawpointByoffset(0, -10);break;
        case 4 : drawpointByoffset(10, 0);break;
      }
      *LED_GPIO[i]->PxOUT ^= LED_PORT[i];
    }
  }
  __delay_cycles(3276);
}

可以看到,for循环中的i对应着四个方向键:0 => 左,1 => 下,3 => 上,4 => 右。循环中的bee函数用来控制蜂鸣器,按一下键蜂鸣器也会短暂的响一下。到此按键控制及点的移动算是搞完了,接着就要画框中的墙壁以及碰撞检测了,在单片机所提供的现有函数下,这可是个苦力活。画墙壁只要给函数提供坐标就好了,我设定了20条线,使用 etft_AreaSet 挨个绘制就好了。对于这个游戏来说一定要有碰撞检测的,不然游戏也就失去了意义。 在现有条件下,只能单纯的通过点及墙壁的坐标来判断两者时候重叠,所以得像下面这样挨个定义点与20条线的碰撞范围:

if(x<=180 && 180<=(x+10) && 180<y+10 && y+10<=240){
  bee2();
  drawGameover();
}

bee2 也是控制蜂鸣器的函数,只是蜂鸣时间长了些,drawGameover 用来绘制前面图中的那个gameover框,相应的还有 drawWin 函数。 碰撞判断的代码是写在 drawpointByoffset 函数中的,每次按键移动后判断是否碰撞。

核心功能的原理差不多就这样了,整个游戏实现起来比较简单,但还有些遗憾,本来还想在上面的液晶屏上显示当前的步数的,但因为液晶屏与tft有端口复用,所以就不能实现了。

← 富文本编辑器中代码输入区域的处理  ES6声明总结 →