文章目录
- 一、简介
- 二、应用介绍
- 三、应用开发
-
- 1、应用文件夹创建
- 2、文件目录结构说明
- 3、配置图片资源
-
- [3.1 图标放置](#3.1 图标放置)
- [3.2 应用内游戏资源](#3.2 应用内游戏资源)
- [3.3 图片资源说明](#3.3 图片资源说明)
- 4、编写代码文件
- 5、编译下载
-
- [5.1 编译](#5.1 编译)
- [5.2 下载](#5.2 下载)
- [5.3 显示](#5.3 显示)
一、简介
学习思澈科技solution开发外置C应用
这里以开发一个井字棋游戏为例。
参考文档
二、应用介绍
solution 应用分为两种 1、内置应用 2、外置应用
1、内置应用:
\open_source_solution_v2.4.1\solution\examples\xxx\application2、外置应用:
\open_source_solution_v2.4.1\solution\examples\_dynamic_app其中C应用在:
\open_source_solution_v2.4.1\solution\examples\_dynamic_app\c\app下
三、应用开发
优先参考
创建一个新APP文档
1、应用文件夹创建
直接复制粘贴
sport文件夹,并重命名为game

此时在butterfli下点击刷新,可以看到game出现在C app中,选中预置即可将其编译到工程中

2、文件目录结构说明
目录下包含
resource和src两个目录resource:资源目录,存放APP使用到的各种资源
src:源代码目录,存放应用C源代码
readme.ini 编译依赖配置文件 & SConscript 编译连接脚本

其中
resource下包含font_bitmapimageslangthumbnailsfont_bitmap:bitmap字体
images:图片资源文件,包括gif等
lang:多语言表
thumbnails:APP图标,图片名称需为tn.png

3、配置图片资源
3.1 图标放置
图标名称需为tn.png 当需要不同风格的图标时依次命名为 tn.png\tn2.png\tn3.png ,图标风格可以是方形,圆形等。在应用切换列表显示或者宫格显示时会切换不同风格的图标显示。


3.2 应用内游戏资源
这里井字棋游戏的显示可以完全使用C代码画图实现,这样可以不在这里加任何其他图片资源。也可以使用LVGL图片控件显示img图片资源。
将需要的图片资源替换掉原有的图片资源,其中empty用于清空棋盘显示。





3.3 图片资源说明
可以看到,图片资源目录下 如
image下还有很多个目录。这些都有各自的用途。
图片资源说明文档 :包括如何在solution使用获取图片文件
4、编写代码文件
保留 sport_main_gui.c文件,修改名称如:game_main.c ,并删除其他资源文件,修改其中的代码


c
#define DYN_APP /* 声明此为外置APP,资源通过外置方式获取 */
#include <rtthread.h>
#include <lvgl.h>
#include "global.h"
#include "lvsf_multobj.h"
#include "lvsf_multswipe.h"
#include "lvsf_scrollbar.h"
#include "lvsf_bg_db.h"
/* 定义模块名称(必须与动态应用APP_ID相同,模拟器调试使用)且需要在 #include "app_module.h" 之前 */
#define _MODULE_NAME_ "game"
#include "app_module.h"
typedef struct
{
lv_obj_t *bg_cont;
}game_main_t;
static void on_start(void)
{
/*从框架获取申请的内存,必须要注册的时候填入需要的内存大小*/
p_lane_play = APP_GET_PAGE_MEM_PTR;
}
static void on_resume(void)
{
}
static void on_pause(void)
{
}
static void on_stop(void)
{
}
/* 注册应用 */
/* app_get_strid:为应用名称设置
NULL:第二个参数为 缩略图设置,外置动态应用中时此参数无用,可以填NULL
"game": 应用名称,必须与对应的应用ID 相同
size:应用全局内存大小,该内存由框架申请释放,页面可以直接使用
*/
APPLICATION_REGISTER(app_get_strid(key_sport, "Game"), NULL, "game", sizeof(game_main_t));
4.1 主体函数说明
前置代码文件编辑设置完成后,后面就是应用主体的编写
应用主体分为4个函数
on_start\on_resume\on_pause\on_stop
on_start
页面初始化(仅调用一次)
on_resume
页面激活(每次显示时调用)
on_pause
页面暂停(被切换至后台时调用)
on_stop
页面销毁(退出时调用)
4.2 on_start 初始化
初始化 获取框架申请的内存,全局指针所对应的结构体大小一定要和申请的大小一致
APP_GET_PAGE_MEM_PTR是框架提供的接口,用于获取预先申请的内存指针game :
static game_main_t *game = NULL;
c
static void on_start(void)
{
game = (game_main_t *)APP_GET_PAGE_MEM_PTR;
RT_ASSERT(game);
jingziqi_app_gui_init(); //自定义页面初始化函数
}
4.3 on_resume 激活配置
c
static void on_resume(void)
{
/*
井字棋游戏中只有简单的触摸事件,激活启动时也不需额外的配置或刷新定时器,因此这个函数为NULL。
*/
}
4.4 on_pause 暂停
c
static void on_pause(void)
{
/*
同on_resume函数。
*/
}
4.5 on_stop
c
static void on_stop(void)
{
/* 将全局指针变量置空,防止其他地方使用时非空判断出现异常,该指针指向的内存会在框架执行完stop消息后释放 */
game = NULL;
}
4.6 初始化时完成游戏代码配置
接下来就是在初始化时就完成井字棋游戏的代码编写。
【完整C代码】

c
/*********************
* INCLUDES
*********************/
#define DYN_APP
#include <rtthread.h>
#include <lvgl.h>
#include "global.h"
#include "lvsf_multobj.h"
#include "lvsf_multswipe.h"
#include "lvsf_scrollbar.h"
#include "lvsf_bg_db.h"
#define _MODULE_NAME_ "game"
#include "app_module.h"
typedef enum {
PLAYER_X_TURN, // X玩家回合
PLAYER_O_TURN, // O玩家回合
PLAYER_X_WIN, // X玩家获胜
PLAYER_O_WIN, // O玩家获胜
GAME_DRAW // 平局
} GameState;
// 棋盘状态
typedef enum {
EMPTY,
PLAYER_X,
PLAYER_O
} CellState;
typedef struct
{
CellState board[3][3]; // 3x3棋盘
GameState current_state; // 当前游戏状态
lv_obj_t *cell_btns[3][3]; // 按钮对象数组
lv_obj_t *status_label; // 状态显示标签
lv_obj_t *restart_btn; // 重新开始按钮
lv_obj_t *game_board; // 游戏棋盘对象
lv_obj_t *bg_cont;
}game_main_t;
static game_main_t *game = NULL;
/**
* 检查是否有获胜者
* @return 获胜者(PLAYER_X, PLAYER_O)或EMPTY
*/
static CellState check_winner(void) {
// 检查行
for (int i = 0; i < 3; i++) {
if (game->board[i][0] != EMPTY &&
game->board[i][0] == game->board[i][1] &&
game->board[i][1] == game->board[i][2]) {
return game->board[i][0];
}
}
// 检查列
for (int j = 0; j < 3; j++) {
if (game->board[0][j] != EMPTY &&
game->board[0][j] == game->board[1][j] &&
game->board[1][j] == game->board[2][j]) {
return game->board[0][j];
}
}
// 检查对角线
if (game->board[0][0] != EMPTY &&
game->board[0][0] == game->board[1][1] &&
game->board[1][1] == game->board[2][2]) {
return game->board[0][0];
}
if (game->board[0][2] != EMPTY &&
game->board[0][2] == game->board[1][1] &&
game->board[1][1] == game->board[2][0]) {
return game->board[0][2];
}
return EMPTY;
}
/**
* 检查是否平局
* @return true如果平局
*/
static bool check_draw(void) {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (game->board[i][j] == EMPTY) {
return false;
}
}
}
return true;
}
/**
* 更新按钮显示
*/
static void update_cell_display(int row, int col) {
lv_obj_t *btn = game->cell_btns[row][col];
lv_obj_t *img = lv_obj_get_child(btn, 0);
switch (game->board[row][col]) {
case EMPTY:
lv_img_set_src(img, APP_GET_IMG(empty));
break;
case PLAYER_X:
lv_img_set_src(img, APP_GET_IMG(cha));
break;
case PLAYER_O:
lv_img_set_src(img, APP_GET_IMG(gou));
break;
}
}
// /**
// * 处理单元格点击
// */
static void cell_click_cb(lv_event_t *e) {
//如果游戏已经结束,不处理点击
if (game->current_state == PLAYER_X_WIN ||
game->current_state == PLAYER_O_WIN ||
game->current_state == GAME_DRAW) {
return;
}
// 获取点击的按钮
lv_obj_t *btn = lv_event_get_target(e);
// 找到按钮对应的行列
int row = -1, col = -1;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (game->cell_btns[i][j] == btn) {
row = i;
col = j;
break;
}
}
if (row != -1) break;
}
// 如果单元格不为空,不处理
if (game->board[row][col] != EMPTY) {
return;
}
// 根据当前玩家设置单元格
if (game->current_state == PLAYER_X_TURN) {
game->board[row][col] = PLAYER_X;
update_cell_display(row, col);
//检查游戏状态
CellState winner = check_winner();
if (winner == PLAYER_X) {
game->current_state = PLAYER_X_WIN;
lv_label_set_text(game->status_label, "玩家 X 获胜!");
} else if (check_draw()) {
game->current_state = GAME_DRAW;
lv_label_set_text(game->status_label, "平局!");
} else {
game->current_state = PLAYER_O_TURN;
lv_label_set_text(game->status_label, "玩家 O 回合");
}
}
else if (game->current_state == PLAYER_O_TURN) {
game->board[row][col] = PLAYER_O;
update_cell_display(row, col);
//检查游戏状态
CellState winner = check_winner();
if (winner == PLAYER_O) {
game->current_state = PLAYER_O_WIN;
lv_label_set_text(game->status_label, "玩家 O 获胜!");
} else if (check_draw()) {
game->current_state = GAME_DRAW;
lv_label_set_text(game->status_label, "平局!");
} else {
game->current_state = PLAYER_X_TURN;
lv_label_set_text(game->status_label, "玩家 X 回合");
}
}
}
void game_init(void) {
// 清空棋盘
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
game->board[i][j] = EMPTY;
}
}
// 设置初始状态为X玩家回合
game->current_state = PLAYER_X_TURN;
// 更新状态显示
lv_label_set_text(game->status_label, "玩家 X 回合");
}
/**
* 重新开始游戏回调
*/
static void restart_click_cb(lv_event_t *e) {
(void)e; // 未使用参数
game_init();
// 清空所有按钮显示
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
update_cell_display(i, j);
}
}
}
static void jingziqi_app_gui_init()
{
lv_obj_t *parent = lv_scr_act();
lv_obj_t *bg_cont = lv_obj_create(parent);
lv_obj_set_size(bg_cont, 390, 390);
lv_obj_set_pos(bg_cont, 0, 0);
lv_obj_set_style_bg_color(bg_cont, LV_COLOR_BLACK, LV_PART_MAIN);
/* 棋盘 */
lv_obj_t *game_board = lv_img_create(bg_cont);
lv_img_set_src(game_board, APP_GET_IMG(qipan));
lv_obj_set_size(game_board, 390, 390);
lv_obj_set_pos(game_board, 0, 0);
/* 状态标签 */
lv_obj_t *label = lv_label_create(parent);
lv_label_set_text(label, "玩家 X 回合"); // 初始状态
lv_obj_set_style_text_color(label, lv_color_white(), 0);
lv_obj_set_pos(label, 50, 400);
game->game_board = game_board;
game->status_label = label;
// 创建3x3棋盘按钮
for (int i = 0; i < 3; i++) {
// 创建行容器
lv_obj_t *row_cont = lv_obj_create(bg_cont);
//lv_obj_remove_style_all(row_cont);
lv_obj_set_size(row_cont, 300, 100);
lv_obj_set_style_bg_opa(row_cont, 0, 0);
lv_obj_set_pos(row_cont, 45, 45 + i * 100);
for (int j = 0; j < 3; j++) {
// 创建单元格按钮
lv_obj_t *btn = lv_btn_create(row_cont);
lv_obj_set_size(btn, 100, 100);
lv_obj_set_style_bg_opa(btn, 0, 0);
lv_obj_set_pos(btn, j * 100,0);
// 创建按钮标签
lv_obj_t *img = lv_img_create(btn);
lv_img_set_src(img, NULL);
lv_obj_center(img);
// 存储按钮引用
game->cell_btns[i][j] = btn;
// 添加点击事件
lv_obj_add_event_cb(btn, cell_click_cb, LV_EVENT_CLICKED, NULL);
}
}
/* 重新开始按钮 */
lv_obj_t *restart_btn = lv_btn_create(parent);
lv_obj_set_size(restart_btn, 100, 40);
lv_obj_set_pos(restart_btn, 240, 400);
lv_obj_t *restart_label = lv_label_create(restart_btn);
lv_label_set_text(restart_label, "重新开始");
lv_obj_center(restart_label);
lv_obj_add_event_cb(restart_btn, restart_click_cb, LV_EVENT_CLICKED, NULL);
game->restart_btn = restart_btn;
game_init();
lv_obj_update_layout(parent);
}
static void on_start(void)
{
game = (game_main_t *)APP_GET_PAGE_MEM_PTR;
RT_ASSERT(game);
jingziqi_app_gui_init();
}
static void on_resume(void)
{
}
static void on_pause(void)
{
}
static void on_stop(void)
{
}
APPLICATION_REGISTER(app_get_strid(key_sport, "Game"), NULL, "game", sizeof(game_main_t));
5、编译下载
5.1 编译
C应用中勾选对应应用为预置

如果你和我一样只修改过C应用部分代码,且之前有关编译,可以选择部分编译,否则需要进行全编译


5.2 下载
选中对应的COM口并下载

5.3 显示



