项目展示        

贪吃蛇使用stm32f103rct6,2.8寸TFT LCD屏幕,代码已上传至仓库,有意思的小制作。

GitCode - 全球开发者的开源社区,开源代码托管平台GitCode是面向全球开发者的开源社区,包括原创博客,开源代码托管,代码协作,项目管理等。与开发者社区互动,提升您的研发效率和质量。 https://gitcode.com/foamz/stm32-touchscreen-greedy-snake

stm32触屏贪吃蛇

 

 


目录

一、图片显示

二、汉字显示

三、贪吃蛇

1.蛇的移动

2.食物生成

3.碰撞检测

4.触屏检测

5.定时器中断

6.死亡动画


 

 一、图片显示

        图片和汉字的数组都放在了array_chinese.c文件,然后在array_chinese.h使用extern声明出去,方便其他文件内引用。

使用image2Lcd软件,选用16位真彩色,对选取的图像取模。左侧自由设定生成图片的大小。

获得图像的16进制数组。

配合show_picture函数,遍历图片数组内的值,用描点方式在屏幕上打印图片。

void show_picture(uint16_t x, uint16_t y, uint16_t column, uint16_t row, uint16_t *pic)
{
	uint16_t m,h;
	uint16_t *data=(uint16_t*)pic;
	for(h=0+y;h<row+y;h++)//60
	{
		for(m=0+x;m<column+x;m++)//180
		{
				lcd_draw_point(m,h,*data++);
		}
	}
	
}

        实际调用如下。0,0是图片左上顶点,在屏幕的x,y坐标。印象里图片生成选的224*224,实际烧程序观察224和205显示更好。这个看自己实际情况调整。 gImage_1是图片数组。

  show_picture(0,0,224,205,(uint16_t *)gImage_1);     

 

二、汉字显示

        图片和汉字的数组都放在了array_chinese.c文件,然后在array_chinese.h使用extern声明出去,方便其他文件内引用。

        因为使用汉字较少,没有选择字库方式。使用汉字取模软件ATK-XFONT,获取汉字的模值数组,配合函数读取数组值显示汉字,跟显示图片差不多。生成设置如下图,然后字大小在软件可以自己调整。

 点击生成字模后,下方对话框出现的数组,就是这个字的数据,复制到文件中储存。

显示汉字的函数

void Show_Graph(uint16_t x,uint16_t y,uint8_t *num,uint8_t size,uint8_t mode)
{
	uint8_t temp;
	uint16_t t,t1;
	uint16_t y0 = y;
	uint16_t x0 = x;
	uint16_t csize=(size/8+((size%8)?1:0))*(size);
	for(t = 0;t < csize;t++)
	{
		temp = num[t];
		for(t1 = 0;t1 < 8;t1++)
		{
			if(temp&0x80)lcd_draw_point(x,y,g_point_color);
			else if(mode==0)lcd_draw_point(x,y,g_back_color); 
			temp<<=1;
			y++;
			if((y-y0) == size)
			{
				y=y0;
				x++;
				break;
			}
			if((x-x0) == size)
				{
					x = x0;
					break;
				}
		}
	}
}

        实际调用如下,50和250是汉字左上顶点,在屏幕的坐标位置,lin是汉字数组名,自己起的。16是字宽,最后的参数0是选了颜色,对函数进行修改去掉也可以。

Show_Graph(50,250,(uint8_t*)lin,16,0);

 

三、贪吃蛇

        细节太多,不太容易说,主要讲几个功能实现上的重点。

        主要有两个结构体变量来储存信息,在gtim.h。其他基本都在gtim.c文件中

//贪吃蛇信息
typedef struct {
    int x[100];  
    int y[100];
    int length;  
    int direction;
} Snake;

//食物信息
typedef struct {
    int x;
    int y;
} Food;

1.蛇的移动

        蛇每次移动,其实都是删除屏幕上显示的蛇,然后重新绘制移动后的蛇。其实移动蛇就是通过改变蛇结构体内的运动方向,然后更新位置来实现的。

//绘制蛇
void draw_snake(Snake *snake) {
    for (int i = 0; i < snake->length; i++) {
        lcd_fill(snake->x[i], snake->y[i], snake->x[i] + SNAKE_SIZE, snake->y[i] + SNAKE_SIZE, RED);
    }
}

// 清除蛇
void clear_snake(Snake *snake) {
    for (int i = 0; i < snake->length; i++) {
        lcd_fill(snake->x[i], snake->y[i], snake->x[i] + SNAKE_SIZE, snake->y[i] + SNAKE_SIZE, WHITE);
    }
}

// 移动蛇
void move_snake(Snake *snake) {
    // 先清除当前的蛇
    clear_snake(snake);
    
    // 将蛇向前移动
    for (int i = snake->length - 1; i > 0; i--) {
        snake->x[i] = snake->x[i - 1];
        snake->y[i] = snake->y[i - 1];
    }

    // 检测蛇的运动方向
    if (snake->direction == UP) {
        snake->y[0] -= SNAKE_SIZE;
    } else if (snake->direction == DOWN) {
        snake->y[0] += SNAKE_SIZE;
    } else if (snake->direction == LEFT) {
        snake->x[0] -= SNAKE_SIZE;
    } else if (snake->direction == RIGHT) {
        snake->x[0] += SNAKE_SIZE;
    }

    // 绘制
    draw_snake(snake);
}

 

2.食物生成

        随机生成的食物,不能生成在蛇身上。通过一个 while 循环反复生成食物位置,直到食物位置不与蛇身重叠为止。

void init_food(Food *food, Snake *snake) {
    int valid_position = 0;  // 是否刷新到蛇身上
    while (!valid_position) {
        food->x = rand() % (GAME_SCREEN_WIDTH / SNAKE_SIZE) * SNAKE_SIZE;
        food->y = rand() % (GAME_SCREEN_HEIGHT / SNAKE_SIZE) * SNAKE_SIZE;
        valid_position = 1;  // 假设不在蛇身上
        for (int i = 0; i < snake->length; i++) {
            if (food->x == snake->x[i] && food->y == snake->y[i]) {
                valid_position = 0;  // 位置无效,重新生成
                break;
            }
        }
    }
    draw_food(food);  // 绘制食物
}

     

    3.碰撞检测

            首先设定不能碰到墙壁。然后不能碰到自己的身体。然后吃到食物,其实也是检测蛇是否跟食物发生碰撞。但由此又出现新的问题,蛇必须禁止后退移动。比如说正在向右运动,按键让他向左,需要让向左无效化,不然会直接原地去世。这个限制放到触屏检测里。

    // 碰撞事件
    int check_collision(Snake *snake) {
        if (snake->x[0] < 0 || snake->x[0] >= GAME_SCREEN_WIDTH || snake->y[0] < 0 || snake->y[0] >= GAME_SCREEN_HEIGHT) {
            return 1;  // 撞墙
        }
        for (int i = 1; i < snake->length; i++) {
            if (snake->x[0] == snake->x[i] && snake->y[0] == snake->y[i]) {
                return 1;  // 撞到自己
            }
        }
        return 0; 
    }
    // 吃到食物时
    int check_food_collision(Snake *snake, Food *food) {
        if (snake->x[0] == food->x && snake->y[0] == food->y) {
            return 1;  
        }
        return 0; 
    }
    

     

    4.触屏检测

            检测电阻屏上按键,是否按下。划定触点检测区域,然后用if判断去除反方向移动的指令。加入200ms延迟是为了防止蛇向右,还没有移动就按出两个指令 下 和 左 ,还是会变成直接调头的这种情况。而且蛇最快速度就是200ms/步,设定200ms延迟不会影响游戏操作。

    // 检测电阻屏按键是否按下
    void process_touch_input(Snake *snake) {
        uint16_t x, y;
    
        tp_dev.scan(0); 
        if (tp_dev.sta & TP_PRES_DOWN) 
    	{  // 获取触点坐标
            x = tp_dev.x[0];
            y = tp_dev.y[0];
    	    //检测触点是否在按键区域
    	    if (x >= UP_X1 && x <= UP_X2 && y >= UP_Y1 && y <= UP_Y2) 
            {
    		    if (snake->direction != DOWN)  // 不能反方向移动会直接与自己相撞死亡
                    snake->direction = UP; 
    				delay_ms(200); //防止按的过快,一下按两个方向回头撞死
    		}
            else if (x >= DOWN_X1 && x <= DOWN_X2 && y >= DOWN_Y1 && y <= DOWN_Y2)
             {
    			if (snake->direction != UP)  
                    snake->direction = DOWN;
    				delay_ms(200);
    		 } 
            else if (x >= LEFT_X1 && x <= LEFT_X2 && y >= LEFT_Y1 && y <= LEFT_Y2)
            {
    			if (snake->direction != RIGHT) 
                    snake->direction = LEFT;
    				delay_ms(200);							
    		}
            else if (x >= RIGHT_X1 && x <= RIGHT_X2 && y >= RIGHT_Y1 && y <= RIGHT_Y2) 
            {
    			if (snake->direction !=LEFT) 
                    snake->direction = RIGHT; 
    				delay_ms(200);	
    		}
        }
    }
    

     

    5.定时器中断

            在中断里移动蛇,进行碰撞检测。如果吃到食物,就改变定时器Arr的装填值,来提升行进速率。吃得越多,速度越快。然后设定一个最小值Arr_min,防止移动过快使得后期无法操作。

    void GTIM_TIMX_INT_IRQHandler(void)
    {
        /* 以下代码没有使用定时器HAL库共用处理函数来处理,而是直接通过判断中断标志位的方式 */
        if(__HAL_TIM_GET_FLAG(&g_timx_handle, TIM_FLAG_UPDATE) != RESET)
        {
    				move_snake(&snake);  //移动蛇
    
            if (check_food_collision(&snake, &food)) {  // 看有没有吃到食物
                grow_snake(&snake);
                init_food(&food,&snake); 
    						game.Score++;
    						lcd_show_num(55,187,game.Score,2,16,BLUE);
    						uint16_t new_arr = __HAL_TIM_GET_AUTORELOAD(&g_timx_handle) * 95 / 100; // 让 ARR的装填值减少5%,提升5%的行进速度
                if (new_arr < ARR_MIN) {
                    new_arr = ARR_MIN;  //设定最低值,防止速度过快。
                }
    						game.V=(new_arr+1)/10;
    						lcd_show_num(110,167,game.V,3,16,BLUE);
    					  draw_food(&food);  // 绘制食物
                __HAL_TIM_SET_AUTORELOAD(&g_timx_handle, new_arr); // 更新定时器内 ARR 
            }
    
            if (check_collision(&snake)) { 
                game_over(&snake); 
            }
    
            __HAL_TIM_CLEAR_IT(&g_timx_handle, TIM_IT_UPDATE);  /* 清除定时器溢出中断标志位 */
        }
    }

    6.死亡动画

            为了更有趣些,做了死亡动画,在检测到碰撞死亡后,将蛇身体变色,然后整个屏幕由外到内填充矩形。最后加入GAME OVER! 字样,并且关闭定时器。

    //绘制死亡动画
    void death_animation(Snake* snake) {
    
        int x_start = 0, y_start = 0;        
        int x_end = 240, y_end = 160;
    		uint16_t colors[] = {BLUE, GREEN, CYAN, BROWN, YELLOW, BLACK}; // 蛇可以变化的颜色
        int color_count = sizeof(colors) / sizeof(colors[0]); // 计算颜色数量
    
        // 让蛇的身体变化颜色
        for (int c = 0; c < color_count; c++) 
    		{
    			for (int i = 0; i < snake->length; i++) 
    			{
            lcd_fill(snake->x[i], snake->y[i], snake->x[i] + SNAKE_SIZE, snake->y[i] + SNAKE_SIZE, colors[c]);
    				delay_ms(2); // 每次颜色变化延迟一段时间
    			}
            delay_ms(100); // 每次颜色变化延迟一段时间
        }
    		// 由外到内矩形填充动画
        while (x_start <= x_end && y_start <= y_end) {
            lcd_draw_line(x_start, y_start, x_end, y_start, BLACK);
            y_start++;
    
            lcd_draw_line(x_end, y_start, x_end, y_end, BLACK);
            x_end--;
    
            if (y_start <= y_end) {
                lcd_draw_line(x_end, y_end, x_start, y_end, BLACK);
                y_end--;
            }
    
            if (x_start <= x_end) {
                lcd_draw_line(x_start, y_end, x_start, y_start - 1, BLACK);
                x_start++;
            }
    
            delay_ms(50);  
        }
    }
    // 死亡事件
    void game_over(Snake* snake) {
        //lcd_clear(WHITE);
    	
    		death_animation(snake);
        // "GAME OVER!"
    		g_back_color=BLACK;//改变game over底色适配黑色
        lcd_show_string(40, 75, 64, 32, 32, "GAME", RED);
    		lcd_show_string(120, 75, 80, 32, 32, "OVER!", RED);
    		g_back_color=WHITE;
    		
    		//关闭定时器中断,停止更新蛇。
    		HAL_NVIC_DisableIRQ(GTIM_TIMX_INT_IRQn);
        delay_ms(2000);
    }
    

     

     

    Logo

    智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

    更多推荐