【2024 DigiKey 创意大赛】用esp32-s3-lcd-ev-board制作华容道拼图游戏
本帖最后由 aramy 于 2024-10-17 16:27 编辑<p>这次参与2024 DigiKey“感知万物,乐享生活”大赛,我选择的板子是“esp32-s3-lcd-ev-board”。这块板子非常豪华地配备了一块480*480的触摸屏,这么大的屏幕,能够非常好滴让单片机与人交互。</p>
<hr />
<ol>
<li><strong>作品简介</strong><br />
ESP32-S3-LCD-EV-Board 是一款基于 ESP32-S3 芯片的屏幕交互开发板,通过搭配不同类型的 LCD 子板,可以驱动 IIC、SPI、8080 以及 RGB 接口的 LCD 显示屏。同时它还搭载双麦克风阵列,支持语音识别和近/远场语音唤醒,具有触摸屏交互和语音交互功能,满足用户对多种不同分辨率以及接口的触摸屏应用产品的开发需求。本项目是使用ESP32-S3-LCD-EV-Board加上480x480 LCD触摸屏,完成了经典游戏“华容道”拼图游戏。</li>
<li><br />
</li>
<li><strong>系统框图</strong><br />
</li>
</ol>
<div>ESP32-S3-LCD-EV-Board开发板带着一块480X480的触摸屏,很适合与用户以触摸方式进行交互。项目使用了LVGL来进行图形展示和与用户交互。将屏幕分为游戏区和控制区两个部分。</div>
<div>控制区负责控制游戏难度等级,提供了三个按钮。一个按钮为退出按钮,可以退出游戏。另外两个按钮,为调整游戏难度按钮,可以调整游戏难度,一共有16级难度(0~15),调整难度后,都会按当前难度重新初始化游戏界面。</div>
<div>游戏区显示当前各个图块的位置,一共有四种类型的图块,每个图块均可上下左右四个方向移动,用户可以在游戏区通过触摸移动相应的图块进行移动,系统判断当前图块是否符合移动条件,条件符合时就重绘游戏区域,达到移动图块的效果。</div>
<div>当图块符合胜利条件,就胜利,并升级到下一难度等级。</div>
<div>系统开发使用esp-idf,版本选择esp-idf 5.2.1,使用vscode作为开发工具,选择LVGL8.4.0作为UI开发的库。</div>
<div><strong>三、各部分功能说明</strong></div>
<ol>
<li>基础框架:使用官方的例程库作为基础框架。https://github.com/espressif/esp-dev-kits<br />
官方例程库下载下来后,找到esp32-s3-lcd-ev-board下examples里的lvgl_demos项目作为基础项目,在这个基础上叠加自己的功能。</li>
<li>现在esp-idf使用了组件方式进行编程,组件无法进行修改。这里将lvgl组件移到本地。建立“components”文件夹,将“managed_components”文件夹下的lvgl__lvgl文件夹移动到“components”文件夹下,并改名为lvgl。<br />
修改main文件夹下的CMakeLists.txt文件。
<pre>
<code>set(LV_DEMO_DIR ../components/lvgl/demos)
file(GLOB_RECURSE LV_DEMOS_SOURCES ${LV_DEMO_DIR}/*.c)</code></pre>
<p> </p>
</li>
<li>在main文件夹下,删除原有的“ui_printer”和“ui_tuner”文件夹,这两个文件夹,项目中用不到。再创建game文件夹,在game下创建文件夹“huarongdao”,用来存放自己的游戏代码文件。最后还需要修改一下CMakeLists.txt文件。<br />
</li>
<li>修改代码。在main.c主函数中,先引入自己的头函数#include "huarongdao/huaorngdao.h" 。在主函数中保留官方例程的lvgl初始化部分,其余部分删除,添加游戏的调用函数。
<pre>
<code>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();
}</code></pre>
<p> </p>
</li>
<li></li>
<li>游戏入口函数huarongdao(),在这里开始初始化游戏界面。包括绘制游戏背景图片,绘制按钮,给按钮添加回调事件,游戏负责与用户交互的回调事件为“move_obj_cb”,由这个方法来驱动整个游戏的运作。</li>
</ol>
<div style="text-align: left;">
<pre>
<code class="language-cpp">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 == 0)
{
game_map = 1;
game_map = 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 == 0 && game_map[(stage_data->x) + 1] == 0)
{
game_map = 1;
game_map[(stage_data->x) + 1] = 1;
game_map = 0;
game_map[(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 == 0 && game_map[(stage_data->x) + 1] == 0)
{
game_map = 1;
game_map[(stage_data->x) + 1] = 1;
game_map = 0;
game_map[(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 == 0)
{
game_map = 1;
game_map = 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 == 0)
{
game_map = 1;
game_map = 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 == 0 && game_map[(stage_data->x) + 1] == 0)
{
game_map = 1;
game_map[(stage_data->x) + 1] = 1;
game_map = 0;
game_map[(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 == 0 && game_map[(stage_data->x) + 1] == 0)
{
game_map = 1;
game_map[(stage_data->x) + 1] = 1;
game_map = 0;
game_map[(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 == 0)
{
game_map = 1;
game_map = 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 == 0)
{
game_map = 1;
game_map = 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 == 0 && game_map[(stage_data->x) - 1] == 0)
{
game_map = 1;
game_map[(stage_data->x) - 1] = 1;
game_map = 0;
game_map[(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 == 0)
{
game_map = 1;
game_map = 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 == 0 && game_map[(stage_data->x) - 1] == 0)
{
game_map = 1;
game_map = 1;
game_map = 0;
game_map = 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 == 0)
{
game_map = 1;
game_map = 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 == 0 && game_map[(stage_data->x) + 2] == 0)
{
game_map = 1;
game_map[(stage_data->x) + 2] = 1;
game_map = 0;
game_map[(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 == 0)
{
game_map = 1;
game_map = 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 == 0 && game_map[(stage_data->x) + 1] == 0)
{
game_map = 1;
game_map = 1;
game_map = 0;
game_map = 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();
}
}
}</code></pre>
<p> </p>
</div>
<div style="text-align: center;"> </div>
<div><strong>四、作品源码</strong></div>
<div><a href="https://download.eeworld.com.cn/detail/aramy/634585" target="_blank">https://download.eeworld.com.cn/detail/aramy/634585</a></div>
<div></div>
<div><strong>五、</strong><strong>作品功能演示视频</strong></div>
<div><strong><iframe allowfullscreen="true" border="0" frameborder="no" framespacing="0" height="450px" scrolling="no" src="//player.bilibili.com/player.html?isOutside=true&aid=113316974298010&bvid=BV1FjmKYVEh2&cid=26317816344&p=1" width="700px"></iframe></strong><br />
</div>
<div><strong>六、项目总结</strong></div>
<div>很伤心,没能完成最初设定的目标!本来想在这个板子上实现机器学习的内容的,实在是能力有限,无法完成。LVGL作为UI实现的工具,功能非常强大,可是总觉着版本有些混乱,不同版本方法差异好大,学习成本有点太高了,但是作为一个连接单片机和人的桥梁还是非常好用的,通过单片机、传感器来感知万物,再用合适的UI展示出来,与人互动。非常喜欢ESP32-S3-LCD-EV-Board开发板,希望能借助这个项目留下这块板子!</div>
<p><!--importdoc--></p>
<p>感谢大佬分享</p>
<p>这游戏有点意思,从零开发的呀?</p>
wangerxian 发表于 2024-10-17 16:29
这游戏有点意思,从零开发的呀?
<p>不是,移植开源的项目!</p>
aramy 发表于 2024-10-18 12:26
不是,移植开源的项目!
<p>厉害,厉害,这样看来其他GUI框架也能移植这个游戏了。</p>
<p>华容道规则不麻烦,但算法麻烦,小时候我背过81步解法,现在说是最短48步?</p>
<p><img height="48" src="https://bbs.eeworld.com.cn/static/editor/plugins/hkemoji/sticker/facebook/loveliness.gif" width="48" />,如果可以,那很多小游戏都可以移植进来玩。。。。。。。。。。。。。。。</p>
<p>牛的</p>
<p>厉害,厉害,这样看来其他GUI框架也能移植这个游戏了。</p>
页:
[1]