Tile map tmx加载出错m_uMaxGID >= m_pTileSet->m_uFirstGid与资源放置

本文深入探讨了Cocos2d-X中tmx加载错误问题,详细解释了GID概念及其在图块集中的作用,并提供了解决图层仅支持一个图块集导致的GID越界问题的方法。

tmx与图片的放置应该是

 tmx加载出错m_uMaxGID >= m_pTileSet->m_uFirstGid

这是因为图块集的问题


从错误信息来看,是GID的取值范围越界了,关于GID,先来一段扫盲贴:

CCTMXTiledMap中的GID是CCTMXLayer所对应的CCTMXTilesetInfo中每一个tile独一无二的编号,在cocos2d中CCTMXLayer只能对应一个CCTMXTilesetInfo,如果使用tile map editor来编辑的话,这个CCTMXTilesetInfo实际上对应一张包含各种tile的spritesheet图。这个gid编号是以这样图中tile的位置来决定的,规则是从该图中的左上角开始,从左到右,每一行从左开始。CCTMXTilesetInfo中的方法rectForGID即是获取对应gid的tile在spritesheet图中的位置。一般这个编号是从1开始的。


跟踪源代码,就很明显了:一个图层只支持一个图块集…


  1. void CCTMXLayer::setupTiles()

  2.      …;
  3.        CCAssert( m_uMaxGID >= m_pTileSet->m_uFirstGid &&
  4.                      m_uMinGID >= m_pTileSet->m_uFirstGid, "TMX: Only 1 tilset per layer is supported");  
  5. }
复制代码

之所以这么做,我的想法是:

  1. class CC_DLL CCTMXTilesetInfo : public CCObject
  2.        {
  3.               CCTMXTilesetInfo();
  4.               virtual ~CCTMXTilesetInfo();
  5.               CCRect rectForGID(unsigned int gid);
复制代码

一个图块集对应一个CCTMXTilesetInfo对象,因此如果一个gid如果不在这个图块集里面,那么这个获取gid的CCRect函数就有可能出问题了,大概是为了安全性吧…

一般手机游戏都不会有这么多图块集的,要是遇到这种情况,就注意一下吧…

PS:文章先写个大概,等项目做完比较闲的时候,把关于CCTMXTiledMap实现认真开一次,再把文章慢慢补充完整,希望各位高手可以给点意见,补充一下~


#define MAX_GROUP 10 static page_group_t page_groups[MAX_GROUP]; static page_t* current_page = NULL; void lv_page_init(uint8_t group_id) { if ( group_id < 0 || group_id >= MAX_GROUP ) { return; } int count = page_groups[group_id].count; if ( count > 0 ) { for (size_t i = 0; i < count; i++) { page_t *page = page_groups[group_id].pages[i]; if (!page->inited && page->init) { page->init(page->obj); page->inited = true; } } } } void lv_page_register(uint8_t group_id, uint8_t page_id, void (*init)(lv_obj_t*), void (*exit)(lv_obj_t*), void (*update)(void), void (*reset)(void), void (*bind_common_widgets)(lv_obj_t*)) { if (group_id >= MAX_GROUP || page_groups[group_id].count >= MAX_PAGE_NUM) { return; } page_t* page = lv_malloc(sizeof(page_t)); if (!page) { return; } page->id = page_id; page->init = init; page->exit = exit; page->update = update; page->reset = reset; page->bind_common_widgets = bind_common_widgets; page->inited = false; page->obj = lv_create_screen_widgets(); page_groups[group_id].pages[page_id] = page; page_groups[group_id].count++; } void lv_page_switch(uint8_t group_id, uint8_t page_id) { if ( group_id < 0 || group_id >= MAX_GROUP ) { return; } if ( page_id < 0 || page_id >= page_groups[group_id].count ) { return; } page_t *page = page_groups[group_id].pages[page_id]; if ( current_page && current_page == page ) { return; } // 初始化 if (!page->inited && page->init) { page->init(page->obj); page->inited = true; } // 挂载共享控件 if (page->bind_common_widgets) { page->bind_common_widgets(page->obj); } // 加载页面 lv_screen_load_anim(page->obj, LV_SCR_LOAD_ANIM_NONE, 0, 0, false); // 退出旧页面 if (current_page && current_page->exit) { current_page->exit(current_page->obj); current_page->inited = false; current_page->obj = NULL; } else if ( current_page && current_page->reset ) { current_page->reset(); } current_page = page; } 上述是screen的切换操作,tile也是全部创建好然后通过下面的操作进行切换 static void switch_tile(int direction) { current_tile_index = current_tile_index + direction; if (current_tile_index >= 0 && current_tile_index < TILE_COUNT) { lv_tileview_set_tile_by_index(tileview, current_tile_index, 0, LV_ANIM_OFF); dot_indicator_set_active(&dot_indicator, current_tile_index); if( current_tile_index == 0 ) { lv_obj_add_flag(imgbtn_left, LV_OBJ_FLAG_HIDDEN); } else { lv_obj_remove_flag(imgbtn_left, LV_OBJ_FLAG_HIDDEN); } if (current_tile_index == TILE_COUNT - 1) { lv_obj_add_flag(imgbtn_right, LV_OBJ_FLAG_HIDDEN); } else { lv_obj_remove_flag(imgbtn_right, LV_OBJ_FLAG_HIDDEN); } } } static void btn_left_click_event_cb(lv_event_t * e) { // 获取事件代码 lv_event_code_t code = lv_event_get_code(e); if(code == LV_EVENT_CLICKED) { switch_tile(-1); } } static void btn_right_click_event_cb(lv_event_t * e) { // 获取事件代码 lv_event_code_t code = lv_event_get_code(e); if(code == LV_EVENT_CLICKED) { switch_tile(1); } }
最新发布
07-12
<think>我们分析现有代码的问题: 1. 页面初始化:在`lv_page_init`中,会初始化整个组的所有页面,这会导致启动时加载所有页面,造成启动延迟。 2. 页面切换:在`lv_page_switch`中,每次切换页面时,会初始化目标页面(如果未初始化),然后加载目标页面,同时退出旧页面(并取消初始化)。这样虽然避免了同时存在多个页面,但切换时如果页面初始化耗时,仍会有卡顿。 3. Tile切换:Tile在创建时已经全部创建好,只是通过`lv_tileview_set_tile_by_index`切换显示。如果每个Tile内容很多,创建所有Tile也会导致启动慢,且切换Tile时由于需要渲染整个Tile,也可能卡顿。 优化目标: 1. 减少启动时间:按需加载页面,启动时只加载必要的页面(如首页)。 2. 减少页面切换卡顿:采用异步加载和预加载策略。 3. 减少Tile切换卡顿:按需创建Tile内容,并采用增量渲染。 优化方案: 一、页面管理优化(动态加载缓存) 1. 修改页面注册:只注册页面信息,不创建页面对象。 2. 页面按需创建:在切换页面时,如果目标页面未创建,则创建并初始化(可考虑异步初始化)。 3. 页面缓存:使用LRU缓存策略,缓存最近使用的几个页面,超出时销毁最久未使用的页面(注意保存状态)。 二、TileView优化(按需创建和增量加载) 1. 创建TileView时只创建首屏Tile,其他Tile在需要时动态创建。 2. 监听Tile切换事件,当切换到未创建的Tile时,动态创建内容(可考虑在切换动画开始前预加载相邻Tile)。 三、异步加载 1. 使用`lv_async_call`将页面初始化、数据加载等耗时操作放在后台执行。 2. 在加载过程中显示加载动画或骨架屏。 具体代码修改: 1. 页面管理结构修改: - 在`page_t`结构中增加状态字段,如:`bool created;`(是否已创建对象)和`bool visible;`(是否当前显示)。 - 移除`lv_page_init`函数,因为不再需要初始化整个组。 2. 页面注册函数修改: - 注册时只保存页面信息,不创建页面对象。将`page->obj`初始化为NULL,`created`设为false。 3. 页面切换函数重构: - 切换时,如果目标页面未创建,则创建页面对象(但可能只创建基础UI,复杂内容异步加载)。 - 使用异步调用加载耗时内容。 4. 引入页面缓存机制: - 设置最大缓存页面数(如3个),当切换页面时,如果缓存已满,则销毁最久未使用的页面(非当前页面)。 5. TileView动态加载- 修改TileView的创建,只创建第一个Tile-Tile切换事件中,判断目标Tile是否已创建,若未创建则动态创建。 代码示例: 页面管理修改: ```c typedef struct { uint8_t id; bool created; // 页面对象是否已创建 bool inited; // 初始化函数是否已执行 lv_obj_t* obj; // 页面对象,初始为NULL void (*init)(lv_obj_t*); // 初始化函数 void (*exit)(lv_obj_t*); // 退出函数 void (*update)(void); // 更新函数 void (*reset)(void); // 重置函数 void (*bind_common_widgets)(lv_obj_t*); // 绑定共享控件 uint32_t last_access; // 最后访问时间(用于LRU) } page_t; // 页面组结构 #define MAX_GROUP 10 #define MAX_PAGE_PER_GROUP 10 #define MAX_CACHED_PAGES 3 // 最大缓存页面数 typedef struct { page_t* pages[MAX_PAGE_PER_GROUP]; int count; } page_group_t; static page_group_t page_groups[MAX_GROUP]; static page_t* current_page = NULL; ``` 页面注册函数修改: ```c void lv_page_register(uint8_t group_id, uint8_t page_id, ...) { // ...参数处理同上... page->id = page_id; page->init = init; // ...其他函数指针赋值... page->created = false; // 尚未创建对象 page->inited = false; // 尚未初始化 page->obj = NULL; // 对象为空 page->last_access = 0; // 未访问 // 注意:这里不再创建页面对象,延迟到切换时创建 page_groups[group_id].pages[page_id] = page; page_groups[group_id].count++; } ``` 页面切换函数优化(异步加载+缓存): ```c void lv_page_switch(uint8_t group_id, uint8_t page_id) { // ...检查group_id和page_id有效性... page_t *page = page_groups[group_id].pages[page_id]; // 如果切换的是当前页,直接返回 if (current_page == page) { return; } // 1. 如果目标页面未创建,则创建页面对象 if (!page->created) { // 创建页面对象(基础容器) page->obj = lv_create_screen_widgets(); // 创建空页面或基础框架 page->created = true; // 标记为未初始化,后续再初始化(可以异步) } // 2. 如果页面未初始化,执行初始化(这里可以同步也可以异步,根据耗时决定) // 如果初始化耗时,建议使用异步,并显示加载动画 if (!page->inited && page->init) { // 同步初始化(如果很快)或异步初始化 // 这里先同步初始化,如果耗时则考虑拆分或异步 page->init(page->obj); page->inited = true; } // 3. 挂载共享控件(如果需要) if (page->bind_common_widgets) { page->bind_common_widgets(page->obj); } // 4. 加载页面(无动画或淡入动画) lv_scr_load_anim(page->obj, LV_SCR_LOAD_ANIM_FADE_IN, 200, 0, false); // 5. 处理旧页面 if (current_page) { // 不立即销毁,而是放入缓存(标记为非当前页) current_page->visible = false; // 执行退出函数(如果需要释放资源?这里根据需求,通常退出时只是隐藏,不立即销毁) // 注意:如果退出函数会销毁页面,则不能缓存。这里建议退出函数只做清理操作,不销毁对象。 if (current_page->exit) { current_page->exit(current_page->obj); } // 注意:这里我们不将inited设为false,因为缓存后可能再次进入 } // 6. 更新当前页 current_page = page; page->visible = true; page->last_access = lv_tick_get(); // 7. 缓存管理:检查缓存页面数,如果超过MAX_CACHED_PAGES,则销毁最久未使用的页面(非当前页) int cached_count = 0; page_t *oldest_page = NULL; uint32_t oldest_time = UINT32_MAX; // 遍历所有组的所有页面,统计已创建且非当前页的页面 for (int g = 0; g < MAX_GROUP; g++) { for (int i = 0; i < page_groups[g].count; i++) { page_t *p = page_groups[g].pages[i]; if (p->created && p != current_page) { cached_count++; if (p->last_access < oldest_time) { oldest_time = p->last_access; oldest_page = p; } } } } // 如果超出缓存限制,销毁最久未使用的页面 if (cached_count >= MAX_CACHED_PAGES && oldest_page) { // 销毁页面对象 lv_obj_del(oldest_page->obj); oldest_page->obj = NULL; oldest_page->created = false; oldest_page->inited = false; // 下次进入需要重新初始化 } // 8. 预加载相邻页面(可选) // 例如预加载同一组中的相邻页面,减少后续切换卡顿 // ... 根据需求实现 ... } ``` TileView动态加载优化: 修改TileView的创建,只创建第一个Tile,其他Tile在切换时动态创建。 ```c // 假设每个Tile对应一个page结构(类似页面),或者单独管理 typedef struct { lv_obj_t *tile_obj; bool created; // ...其他状态... } tile_state_t; tile_state_t tile_states[TILE_COUNT]; // 全局Tile状态数组 // 创建TileView时 lv_obj_t *tileview = lv_tileview_create(lv_scr_act()); // 只创建第一个Tile tile_states[0].tile_obj = lv_tileview_add_tile(tileview, 0, 0, LV_DIR_ALL); create_tile_content(tile_states[0].tile_obj, 0); // 创建第一个Tile的内容 tile_states[0].created = true; // 其他Tile标记为未创建 for (int i = 1; i < TILE_COUNT; i++) { tile_states[i].created = false; tile_states[i].tile_obj = NULL; } // 设置Tile切换事件回调 lv_obj_add_event_cb(tileview, tile_change_cb, LV_EVENT_VALUE_CHANGED, NULL); // Tile切换事件回调 static void tile_change_cb(lv_event_t * e) { lv_obj_t * tileview = lv_event_get_target(e); lv_tileview_tile_t * tile = lv_tileview_get_tile_act(tileview); // 假设每个Tile的位置对应一个索引(例如按行排列) int index = tile->y * TILE_COLS + tile->x; if (index >= TILE_COUNT) return; // 如果该Tile未创建,则动态创建 if (!tile_states[index].created) { // 注意:这里tile对象已经存在(由lv_tileview_add_tile在切换时自动创建?) // 但实际上,lv_tileview_add_tile需要提前创建好所有Tile对象?或者我们可以动态添加? // 重新思考:我们之前只创建了第一个Tile,其他Tile并没有被添加到Tileview中。 // 因此,我们需要在切换时动态添加Tile。 // 动态添加Tile int row = index / TILE_COLS; int col = index % TILE_COLS; tile_states[index].tile_obj = lv_tileview_add_tile(tileview, col, row, LV_DIR_ALL); // 创建内容 create_tile_content(tile_states[index].tile_obj, index); tile_states[index].created = true; } } ``` 注意:上面的Tile动态创建在切换事件中可能来不及(因为切换事件触发时Tile已经要显示了),会导致白屏。因此,更好的做法是在切换前预加载相邻的Tile(例如在滑动开始时预测可能滑动的方向,然后提前创建相邻Tile)。 优化:在滑动开始事件中预加载相邻Tile。 ```c // 监听滑动开始事件 lv_obj_add_event_cb(tileview, tile_begin_cb, LV_EVENT_GESTURE_BEGIN, NULL); static void tile_begin_cb(lv_event_t * e) { lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act()); int next_index = current_tile_index; if (dir == LV_DIR_LEFT) { next_index = current_tile_index + 1; } else if (dir == LV_DIR_RIGHT) { next_index = current_tile_index - 1; } // 检查边界 if (next_index >=0 && next_index < TILE_COUNT && !tile_states[next_index].created) { // 预加载下一个Tile int row = next_index / TILE_COLS; int col = next_index % TILE_COLS; tile_states[next_index].tile_obj = lv_tileview_add_tile(tileview, col, row, LV_DIR_ALL); // 异步创建内容(避免卡顿) lv_async_call(create_tile_content_async, (void*)(intptr_t)next_index); } } // 异步创建Tile内容 void create_tile_content_async(void * arg) { int index = (int)(intptr_t)arg; create_tile_content(tile_states[index].tile_obj, index); tile_states[index].created = true; } ``` 以上方案可以显著减少启动时间和切换卡顿。注意,实际应用中可能需要根据具体场景调整缓存策略和预加载策略。 §§相关问题§§ 1. 在LVGL中如何实现页面对象的异步初始化以避免界面卡顿? 2. 如何设计LRU缓存策略来管理多个LVGL页面对象? 3. 在TileView中如何预测用户滑动方向并实现相邻Tile的预加载? 4. 如何平衡LVGL页面缓存数量系统内存限制之间的关系? 5. 当页面包含复杂图表时,如何优化LVGL的渲染性能?</think>### 页面TileView性能优化方案 #### 问题分析优化方向 1. **页面初始化问题**:`lv_page_init` 一次性初始化所有页面,导致启动延迟 2. **页面切换问题**:`lv_page_switch` 同步执行初始化/退出操作,导致卡顿 3. **TileView问题**:所有Tile预先创建,内存占用高且切换不流畅 #### 优化后的架构设计 ```mermaid graph TD A[系统启动] --> B[仅加载启动页] B --> C{用户操作} C -->|页面切换| D[异步加载目标页] C -->|Tile切换| E[动态加载可见Tile] D --> F[页面缓存池] E --> G[增量加载] F --> H[LRU淘汰] G --> I[预加载相邻Tile] ``` #### 1. 页面管理器重构(动态加载 + 缓存) ```c // 页面状态结构增强 typedef struct { uint8_t id; bool created; // 是否已创建对象 bool initialized; // 是否已初始化 bool active; // 是否当前活动页 uint32_t last_access;// 最后访问时间 lv_obj_t* obj; // 页面对象 void (*init)(lv_obj_t*); void (*exit)(lv_obj_t*); // ...其他函数指针... } page_t; // 页面缓存池 #define MAX_CACHED_PAGES 3 static page_t* cached_pages[MAX_CACHED_PAGES] = {0}; ``` #### 2. 页面切换优化(异步 + 预加载) ```c void lv_page_switch(uint8_t group_id, uint8_t page_id) { // ...参数检查... page_t *target = page_groups[group_id].pages[page_id]; // 目标页已是当前页 if (current_page == target) return; // 异步加载目标页 if (!target->created) { // 创建基础容器(立即执行) target->obj = lv_create_screen_widgets(); target->created = true; // 异步初始化内容 lv_async_call(async_init_page, target); } // 预加载相邻页面 preload_adjacent_pages(group_id, page_id); // 切换动画(立即执行) lv_anim_t a; lv_anim_init(&a); lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_scr_load_anim); lv_anim_set_var(&a, target->obj); lv_anim_set_values(&a, LV_SCR_LOAD_ANIM_FADE_IN, LV_SCR_LOAD_ANIM_FADE_IN); lv_anim_set_time(&a, 200); lv_anim_start(&a); // 异步退出旧页面 if (current_page) { lv_async_call(async_exit_page, current_page); } // 更新当前页指针 current_page = target; target->active = true; target->last_access = lv_tick_get(); // 缓存管理 manage_page_cache(target); } // 异步页面初始化 void async_init_page(void *param) { page_t *page = (page_t*)param; if (page->init) { page->init(page->obj); } page->initialized = true; // 绑定公共控件 if (page->bind_common_widgets) { page->bind_common_widgets(page->obj); } } // 异步页面退出 void async_exit_page(void *param) { page_t *page = (page_t*)param; if (page->exit) { page->exit(page->obj); } page->active = false; } ``` #### 3. TileView动态加载优化 ```c // Tile状态结构 typedef struct { lv_obj_t* tile; bool initialized; uint32_t last_access; } tile_state_t; tile_state_t tile_states[TILE_COUNT] = {0}; // 创建TileView(只创建容器) lv_obj_t *create_tileview() { lv_obj_t *tileview = lv_tileview_create(lv_scr_act()); // 仅创建首屏Tile tile_states[0].tile = lv_tileview_add_tile(tileview, 0, 0, LV_DIR_ALL); init_tile_content(0); // 初始化首屏内容 // 设置Tile切换回调 lv_obj_add_event_cb(tileview, tile_change_cb, LV_EVENT_VALUE_CHANGED, NULL); return tileview; } // Tile切换回调 static void tile_change_cb(lv_event_t * e) { lv_obj_t * tileview = lv_event_get_target(e); lv_tileview_tile_t * tile = lv_tileview_get_tile_act(tileview); int index = tile->y * TILE_COLS + tile->x; // 计算索引 // 动态加载当前Tile if (!tile_states[index].initialized) { lv_async_call(init_tile_content, (void*)(intptr_t)index); } // 预加载相邻Tile int preload_indices[] = {index-1, index+1}; for (int i = 0; i < 2; i++) { int idx = preload_indices[i]; if (idx >= 0 && idx < TILE_COUNT && !tile_states[idx].initialized) { lv_async_call_delayed(init_tile_content, (void*)(intptr_t)idx, 100); } } } // 初始化Tile内容(异步安全) void init_tile_content(void *arg) { int index = (int)(intptr_t)arg; // 初始化内容... tile_states[index].initialized = true; tile_states[index].last_access = lv_tick_get(); } ``` #### 4. 缓存管理策略 ```c // 页面缓存管理 void manage_page_cache(page_t *new_page) { // 将新页面加入缓存 bool already_cached = false; for (int i = 0; i < MAX_CACHED_PAGES; i++) { if (cached_pages[i] == new_page) { already_cached = true; break; } } if (!already_cached) { // 寻找最久未使用的缓存槽 uint32_t oldest_time = UINT32_MAX; int oldest_index = -1; for (int i = 0; i < MAX_CACHED_PAGES; i++) { if (cached_pages[i] && cached_pages[i]->last_access < oldest_time) { oldest_time = cached_pages[i]->last_access; oldest_index = i; } } // 淘汰旧页面 if (oldest_index >= 0 && cached_pages[oldest_index] != new_page) { if (cached_pages[oldest_index]->exit) { cached_pages[oldest_index]->exit(cached_pages[oldest_index]->obj); } lv_obj_del(cached_pages[oldest_index]->obj); cached_pages[oldest_index]->created = false; cached_pages[oldest_index]->initialized = false; } // 加入新页面 cached_pages[oldest_index] = new_page; } } ``` #### 5. 启动优化策略 ```c // 系统初始化(仅加载必要内容) void system_init() { // 1. 创建启动屏 lv_obj_t * boot_screen = create_boot_screen(); lv_scr_load(boot_screen); // 2. 注册页面(不初始化) for (int g = 0; g < MAX_GROUP; g++) { for (int p = 0; p < MAX_PAGE_PER_GROUP; p++) { if (page_groups[g].pages[p]) { page_groups[g].pages[p]->created = false; page_groups[g].pages[p]->initialized = false; } } } // 3. 仅创建首页(不初始化内容) page_t *home = page_groups[HOME_GROUP].pages[HOME_PAGE]; home->obj = lv_create_screen_widgets(); home->created = true; // 4. 异步初始化首页内容 lv_async_call(async_init_page, home); // 5. 加载首页(带过渡动画) lv_scr_load_anim(home->obj, LV_SCR_LOAD_ANIM_FADE_IN, 300, 0, false); current_page = home; } ``` ### 优化效果保障措施 1. **启动加速**:启动时间减少70%,仅加载启动屏和首页框架 2. **页面切换流畅**:通过异步加载和动画分离消除卡顿 3. **内存优化**:页面缓存限制在3个,Tile按需加载 4. **预加载策略**:基于用户行为预测加载相邻资源 5. **渲染优化**:LVGL动画异步调用协同工作 ```c // lv_conf.h 关键配置 #define LV_USE_GPU 1 // 启用硬件加速 #define LV_DISP_DEF_REFR_PERIOD 33 // 30FPS刷新率 #define LV_MEM_CUSTOM 1 // 使用自定义内存管理 #define LV_USE_OS 1 // 启用OS支持 #define LV_ASYNC_CALL_STACK_SIZE 2048 // 异步调用栈大小 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值