[作品提交] 【2024 DigiKey 创意大赛】用esp32-s3-lcd-ev-board制作华容道拼图游戏

aramy   2024-10-17 09:42 楼主

这次参与2024 DigiKey“感知万物,乐享生活”大赛,我选择的板子是“esp32-s3-lcd-ev-board”。这块板子非常豪华地配备了一块480*480的触摸屏,这么大的屏幕,能够非常好滴让单片机与人交互。


  1. 作品简介
    ESP32-S3-LCD-EV-Board 是一款基于 ESP32-S3 芯片的屏幕交互开发板,通过搭配不同类型的 LCD 子板,可以驱动 IIC、SPI、8080 以及 RGB 接口的 LCD 显示屏。同时它还搭载双麦克风阵列,支持语音识别和近/远场语音唤醒,具有触摸屏交互和语音交互功能,满足用户对多种不同分辨率以及接口的触摸屏应用产品的开发需求。本项目是使用ESP32-S3-LCD-EV-Board加上480x480 LCD触摸屏,完成了经典游戏“华容道”拼图游戏。
  2. wd_091240qc0rfpdb8fn8ciic.png
    wd_091240x4liike15c5m5cwm.png
  3. 系统框图
    wd_091240e369pmcv9p6fvmmo.png
ESP32-S3-LCD-EV-Board开发板带着一块480X480的触摸屏,很适合与用户以触摸方式进行交互。项目使用了LVGL来进行图形展示和与用户交互。将屏幕分为游戏区和控制区两个部分。
控制区负责控制游戏难度等级,提供了三个按钮。一个按钮为退出按钮,可以退出游戏。另外两个按钮,为调整游戏难度按钮,可以调整游戏难度,一共有16级难度(0~15),调整难度后,都会按当前难度重新初始化游戏界面。
游戏区显示当前各个图块的位置,一共有四种类型的图块,每个图块均可上下左右四个方向移动,用户可以在游戏区通过触摸移动相应的图块进行移动,系统判断当前图块是否符合移动条件,条件符合时就重绘游戏区域,达到移动图块的效果。
当图块符合胜利条件,就胜利,并升级到下一难度等级。
系统开发使用esp-idf,版本选择esp-idf 5.2.1,使用vscode作为开发工具,选择LVGL8.4.0作为UI开发的库。
三、各部分功能说明
  1. 基础框架:使用官方的例程库作为基础框架。https://github.com/espressif/esp-dev-kits
    官方例程库下载下来后,找到esp32-s3-lcd-ev-board下examples里的lvgl_demos项目作为基础项目,在这个基础上叠加自己的功能。
  2. 现在esp-idf使用了组件方式进行编程,组件无法进行修改。这里将lvgl组件移到本地。建立“components”文件夹,将“managed_components”文件夹下的lvgl__lvgl文件夹移动到“components”文件夹下,并改名为lvgl。
    修改main文件夹下的CMakeLists.txt文件。
    set(LV_DEMO_DIR ../components/lvgl/demos)
    file(GLOB_RECURSE LV_DEMOS_SOURCES ${LV_DEMO_DIR}/*.c)

     

  3. 在main文件夹下,删除原有的“ui_printer”和“ui_tuner”文件夹,这两个文件夹,项目中用不到。再创建game文件夹,在game下创建文件夹“huarongdao”,用来存放自己的游戏代码文件。最后还需要修改一下CMakeLists.txt文件。
    wd_091240q66usiuctxcusivu.png
  4. 修改代码。在main.c主函数中,先引入自己的头函数#include "huarongdao/huaorngdao.h" 。在主函数中保留官方例程的lvgl初始化部分,其余部分删除,添加游戏的调用函数。
    void app_main(void)
    {
        bsp_i2c_init();
        lv_disp_t *disp = bsp_display_start();
        ESP_LOGI(TAG, "Display LVGL demo");
        /**
         * To avoid errors caused by multiple tasks simultaneously accessing LVGL,
         * should acquire a lock before operating on LVGL.
         */
        bsp_display_lock(0);
        huarongdao();
        /* Release the lock */
        bsp_display_unlock();
    }

     

  5. wd_091240fz8j70z2s8u52j79.png
  6. 游戏入口函数huarongdao(),在这里开始初始化游戏界面。包括绘制游戏背景图片,绘制按钮,给按钮添加回调事件,游戏负责与用户交互的回调事件为“move_obj_cb”,由这个方法来驱动整个游戏的运作。
wd_091240eu4xx5u1iwixwd61.png
static void move_obj_cb(lv_event_t *e)
{
	static lv_point_t click_point1, click_point2;
	int movex, movey, direction;

	game_obj_type *stage_data = (game_obj_type *)e->user_data;

	if (e->code == LV_EVENT_PRESSED)
	{
		lv_indev_get_point(lv_indev_get_act(), &click_point1);
		return;
	}

	if (e->code == LV_EVENT_RELEASED)
	{
		lv_indev_get_point(lv_indev_get_act(), &click_point2);
		movex = click_point2.x - click_point1.x;
		movey = click_point2.y - click_point1.y;

		if ((movex == 0 && movey == 0) || (movex == movey) || (movex == -movey))
			return;

		if ((movex < 0 && movey < 0 && movex > movey) || (movex > 0 && movey < 0 && movex < -movey))
			direction = up;
		if ((movex > 0 && movey < 0 && movex > -movey) || (movex > 0 && movey > 0 && movex > movey))
			direction = right;
		if ((movex < 0 && movey < 0 && movex < movey) || (movex < 0 && movey > 0 && movex < -movey))
			direction = left;
		if ((movex < 0 && movey > 0 && movex > -movey) || (movex > 0 && movey > 0 && movex < movey))
			direction = down;

		if (direction == up)
		{

			if (stage_data->obj_type == little)
			{
				if (stage_data->y == 0)
					return;

				if (game_map[stage_data->y - 1][stage_data->x] == 0)
				{
					game_map[stage_data->y - 1][stage_data->x] = 1;
					game_map[stage_data->y][stage_data->x] = 0;
					stage_data->y--;
					step_count++;
					lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
					lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
				}
			}

			if (stage_data->obj_type == big)
			{
				if (stage_data->y == 0)
					return;

				if (game_map[stage_data->y - 1][stage_data->x] == 0 && game_map[stage_data->y - 1][(stage_data->x) + 1] == 0)
				{
					game_map[stage_data->y - 1][stage_data->x] = 1;
					game_map[stage_data->y - 1][(stage_data->x) + 1] = 1;
					game_map[stage_data->y + 1][stage_data->x] = 0;
					game_map[stage_data->y + 1][(stage_data->x) + 1] = 0;
					stage_data->y--;
					step_count++;
					lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
					lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
				}
			}

			if (stage_data->obj_type == hor)
			{
				if (stage_data->y == 0)
					return;

				if (game_map[stage_data->y - 1][stage_data->x] == 0 && game_map[stage_data->y - 1][(stage_data->x) + 1] == 0)
				{
					game_map[stage_data->y - 1][stage_data->x] = 1;
					game_map[stage_data->y - 1][(stage_data->x) + 1] = 1;
					game_map[stage_data->y][stage_data->x] = 0;
					game_map[stage_data->y][(stage_data->x) + 1] = 0;
					stage_data->y--;
					step_count++;
					lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
					lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
				}
			}

			if (stage_data->obj_type == ver)
			{
				if (stage_data->y == 0)
					return;

				if (game_map[stage_data->y - 1][stage_data->x] == 0)
				{
					game_map[stage_data->y - 1][stage_data->x] = 1;
					game_map[stage_data->y + 1][stage_data->x] = 0;
					stage_data->y--;
					step_count++;
					lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
					lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
				}
			}
		}

		if (direction == down)
		{

			if (stage_data->obj_type == little)
			{
				if (stage_data->y == 4)
					return;

				if (game_map[stage_data->y + 1][stage_data->x] == 0)
				{
					game_map[stage_data->y + 1][stage_data->x] = 1;
					game_map[stage_data->y][stage_data->x] = 0;
					stage_data->y++;
					step_count++;
					lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
					lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
				}
			}

			if (stage_data->obj_type == big)
			{
				if (stage_data->y == 3)
					return;

				if (game_map[stage_data->y + 2][stage_data->x] == 0 && game_map[stage_data->y + 2][(stage_data->x) + 1] == 0)
				{
					game_map[stage_data->y + 2][stage_data->x] = 1;
					game_map[stage_data->y + 2][(stage_data->x) + 1] = 1;
					game_map[stage_data->y][stage_data->x] = 0;
					game_map[stage_data->y][(stage_data->x) + 1] = 0;
					stage_data->y++;
					step_count++;
					lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
					lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
				}
			}

			if (stage_data->obj_type == hor)
			{
				if (stage_data->y == 4)
					return;

				if (game_map[stage_data->y + 1][stage_data->x] == 0 && game_map[stage_data->y + 1][(stage_data->x) + 1] == 0)
				{
					game_map[stage_data->y + 1][stage_data->x] = 1;
					game_map[stage_data->y + 1][(stage_data->x) + 1] = 1;
					game_map[stage_data->y][stage_data->x] = 0;
					game_map[stage_data->y][(stage_data->x) + 1] = 0;
					stage_data->y++;
					step_count++;
					lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
					lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
				}
			}

			if (stage_data->obj_type == ver)
			{
				if (stage_data->y == 3)
					return;

				if (game_map[stage_data->y + 2][stage_data->x] == 0)
				{
					game_map[stage_data->y + 2][stage_data->x] = 1;
					game_map[stage_data->y][stage_data->x] = 0;
					stage_data->y++;
					step_count++;
					lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
					lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
				}
			}
		}

		if (direction == left)
		{

			if (stage_data->obj_type == little)
			{
				if (stage_data->x == 0)
					return;

				if (game_map[stage_data->y][stage_data->x - 1] == 0)
				{
					game_map[stage_data->y][stage_data->x - 1] = 1;
					game_map[stage_data->y][stage_data->x] = 0;
					stage_data->x--;
					step_count++;
					lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
					lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
				}
			}

			if (stage_data->obj_type == big)
			{
				if (stage_data->x == 0)
					return;

				if (game_map[stage_data->y][stage_data->x - 1] == 0 && game_map[stage_data->y + 1][(stage_data->x) - 1] == 0)
				{
					game_map[stage_data->y][stage_data->x - 1] = 1;
					game_map[stage_data->y + 1][(stage_data->x) - 1] = 1;
					game_map[stage_data->y][stage_data->x + 1] = 0;
					game_map[stage_data->y + 1][(stage_data->x) + 1] = 0;
					stage_data->x--;
					step_count++;
					lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
					lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
				}
			}

			if (stage_data->obj_type == hor)
			{
				if (stage_data->x == 0)
					return;

				if (game_map[stage_data->y][stage_data->x - 1] == 0)
				{
					game_map[stage_data->y][stage_data->x - 1] = 1;
					game_map[stage_data->y][stage_data->x + 1] = 0;
					stage_data->x--;
					step_count++;
					lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
					lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
				}
			}

			if (stage_data->obj_type == ver)
			{
				if (stage_data->x == 0)
					return;

				if (game_map[stage_data->y][stage_data->x - 1] == 0 && game_map[stage_data->y + 1][(stage_data->x) - 1] == 0)
				{
					game_map[stage_data->y][stage_data->x - 1] = 1;
					game_map[stage_data->y + 1][stage_data->x - 1] = 1;
					game_map[stage_data->y][stage_data->x] = 0;
					game_map[stage_data->y + 1][stage_data->x] = 0;
					stage_data->x--;
					step_count++;
					lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
					lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
				}
			}
		}

		if (direction == right)
		{

			if (stage_data->obj_type == little)
			{
				if (stage_data->x == 3)
					return;

				if (game_map[stage_data->y][stage_data->x + 1] == 0)
				{
					game_map[stage_data->y][stage_data->x + 1] = 1;
					game_map[stage_data->y][stage_data->x] = 0;
					stage_data->x++;
					step_count++;
					lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
					lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
				}
			}

			if (stage_data->obj_type == big)
			{
				if (stage_data->x == 2)
					return;

				if (game_map[stage_data->y][stage_data->x + 2] == 0 && game_map[stage_data->y + 1][(stage_data->x) + 2] == 0)
				{
					game_map[stage_data->y][stage_data->x + 2] = 1;
					game_map[stage_data->y + 1][(stage_data->x) + 2] = 1;
					game_map[stage_data->y][stage_data->x] = 0;
					game_map[stage_data->y + 1][(stage_data->x)] = 0;
					stage_data->x++;
					step_count++;
					lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
					lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
				}
			}

			if (stage_data->obj_type == hor)
			{
				if (stage_data->x == 2)
					return;

				if (game_map[stage_data->y][stage_data->x + 2] == 0)
				{
					game_map[stage_data->y][stage_data->x + 2] = 1;
					game_map[stage_data->y][stage_data->x] = 0;
					stage_data->x++;
					step_count++;
					lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
					lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
				}
			}

			if (stage_data->obj_type == ver)
			{
				if (stage_data->x == 3)
					return;

				if (game_map[stage_data->y][stage_data->x + 1] == 0 && game_map[stage_data->y + 1][(stage_data->x) + 1] == 0)
				{
					game_map[stage_data->y][stage_data->x + 1] = 1;
					game_map[stage_data->y + 1][stage_data->x + 1] = 1;
					game_map[stage_data->y][stage_data->x] = 0;
					game_map[stage_data->y + 1][stage_data->x] = 0;
					stage_data->x++;
					step_count++;
					lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
					lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
				}
			}
		}

		if (stage_data->obj_type == big && stage_data->x == 1 && stage_data->y == 3)
		{
			lv_obj_t *clear_lable = lv_label_create(game_window);
			lv_label_set_text(clear_lable, "STAGE CLEAR");
			lv_obj_set_style_text_color(clear_lable, lv_color_hex(0xffffff), 0);
			lv_obj_center(clear_lable);
			if (current_stage < max_stage - 1)
			{
				current_stage++;
			}
			stage_clear();
		}
	}
}

 

 
四、作品源码
DigiKey_contest_2024用esp32-s3-lcd-ev-board制作华容道拼图游戏.doc (3.53 MB)
(下载次数: 2, 2024-10-17 16:27 上传)
五、作品功能演示视频

 
六、项目总结
很伤心,没能完成最初设定的目标!本来想在这个板子上实现机器学习的内容的,实在是能力有限,无法完成。LVGL作为UI实现的工具,功能非常强大,可是总觉着版本有些混乱,不同版本方法差异好大,学习成本有点太高了,但是作为一个连接单片机和人的桥梁还是非常好用的,通过单片机、传感器来感知万物,再用合适的UI展示出来,与人互动。非常喜欢ESP32-S3-LCD-EV-Board开发板,希望能借助这个项目留下这块板子!

本帖最后由 aramy 于 2024-10-17 16:27 编辑

回复评论 (8)

感谢大佬分享

点赞  2024-10-17 11:05

这游戏有点意思,从零开发的呀?

点赞  2024-10-17 16:29
引用: wangerxian 发表于 2024-10-17 16:29 这游戏有点意思,从零开发的呀?

不是,移植开源的项目!

点赞  2024-10-18 12:26
引用: aramy 发表于 2024-10-18 12:26 不是,移植开源的项目!

厉害,厉害,这样看来其他GUI框架也能移植这个游戏了。

点赞  2024-10-18 13:10

华容道规则不麻烦,但算法麻烦,小时候我背过81步解法,现在说是最短48步?

点赞  2024-10-18 23:25

,如果可以,那很多小游戏都可以移植进来玩。。。。。。。。。。。。。。。

点赞  2024-10-19 10:11

牛的

希望做一些大家觉得好用的东西!
点赞  2024-10-19 13:53

厉害,厉害,这样看来其他GUI框架也能移植这个游戏了。

点赞  2024-10-21 16:59
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复