stm32f103触屏贪吃蛇项目(附代码) 食物提速+死亡动画
使用stm32f103rct6,2.8寸TFT LCD屏幕,代码已上传至仓库,有意思的小制作。GitCode - 全球开发者的开源社区,开源代码托管平台GitCode是面向全球开发者的开源社区,包括原创博客,开源代码托管,代码协作,项目管理等。与开发者社区互动,提升您的研发效率和质量。stm32触屏贪吃蛇。
项目展示
贪吃蛇使用stm32f103rct6,2.8寸TFT LCD屏幕,代码已上传至仓库,有意思的小制作。
stm32触屏贪吃蛇




目录
一、图片显示
图片和汉字的数组都放在了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);
}
更多推荐



所有评论(0)