Tile Map保存恢复功能中texture_rect指针读取后变为0x0的问题
Tile Map保存恢复功能中texture_rect指针读取后变为0x0的问题
哥们儿,你遇到的这个问题核心原因其实是不该直接把指针的内存地址存到文件里。指针的值本质上是当前进程内的临时内存位置,每次程序启动或运行时,内存布局都会变化——上次运行时0x600000f181b0这个地址指向你的texture_rect,但下次运行时这个地址可能已经无效,甚至被分配给了其他数据。而且fscanf处理%p格式时,很容易因为文件里的地址格式和当前进程的内存模型不匹配,导致读取失败,最后就变成了0x0空指针。
下面给你两个实用的解决方案,按推荐程度排序:
方案一:保存Tile索引(最推荐)
既然你的texture_rect指向的是tileset图里的固定区域,完全可以给每个tile分配一个唯一索引(比如0、1、2...),保存时存这个索引,读取时根据索引直接指向预先定义好的tileset rect数组。这种方式既省空间,又彻底避开了指针的坑。
步骤1:预先定义tileset的rect数组
先把所有tile的源rect存在一个全局数组里,比如:
#define NUM_TILES 16 // 根据你的tileset实际数量调整 SDL_FRect tileset_rects[NUM_TILES] = { {0.0f, 0.0f, 32.0f, 32.0f}, // 索引0:第一个tile {32.0f, 0.0f, 32.0f, 32.0f}, // 索引1:第二个tile // ... 剩下的tile rect依次添加 };
步骤2:修改保存函数
保存时,找到当前cell的texture_rect对应的索引,把索引存到文件里(空tile存-1):
static void save_tile_map(Cell cells[GRID_WIDTH][GRID_HEIGHT]) { FILE* f = fopen("tile_map.txt", "w"); if (f == NULL) { printf("Error! Could not open the file\n"); exit(-1); } for (int x = 0; x < GRID_WIDTH; ++x) { for (int y = 0; y < GRID_HEIGHT; ++y) { int tile_idx = -1; // 如果tile是激活的,查找对应的索引 if (cells[x][y].is_active && cells[x][y].texture_rect != NULL) { for (int i = 0; i < NUM_TILES; ++i) { if (cells[x][y].texture_rect == &tileset_rects[i]) { tile_idx = i; break; } } } // 用逗号分隔数据,格式简单不易出错 fprintf(f, "%d,%.2f,%.2f,%.2f,%.2f,%d\n", cells[x][y].is_active, cells[x][y].rect.x, cells[x][y].rect.y, cells[x][y].rect.w, cells[x][y].rect.h, tile_idx); } } fclose(f); }
步骤3:修改读取函数
读取时根据索引直接指向tileset数组里的对应rect:
static void read_tile_map(Cell saved_tiles[GRID_WIDTH][GRID_HEIGHT]) { FILE* f = fopen("tile_map.txt", "r"); if (f == NULL) { printf("Error! Could not open the file\n"); exit(-1); } for (int x = 0; x < GRID_WIDTH; ++x) { for (int y = 0; y < GRID_HEIGHT; ++y) { int is_active, tile_idx; float rx, ry, rw, rh; // 读取逗号分隔的数据 fscanf(f, "%d,%f,%f,%f,%f,%d", &is_active, &rx, &ry, &rw, &rh, &tile_idx); saved_tiles[x][y].is_active = is_active; saved_tiles[x][y].rect = (SDL_FRect){rx, ry, rw, rh}; // 根据索引设置texture_rect if (tile_idx != -1 && tile_idx < NUM_TILES) { saved_tiles[x][y].texture_rect = &tileset_rects[tile_idx]; } else { saved_tiles[x][y].texture_rect = NULL; } } } fclose(f); }
方案二:保存texture_rect的实际数据(适合动态tile场景)
如果你的tileset是动态生成的,没法预先定义索引,那可以直接保存texture_rect的x、y、w、h四个值,读取时再重新分配内存(或复用内存)。
修改保存函数
把texture_rect的四个值存进去(空tile用0作为标记):
static void save_tile_map(Cell cells[GRID_WIDTH][GRID_HEIGHT]) { FILE* f = fopen("tile_map.txt", "w"); if (f == NULL) { printf("Error! Could not open the file\n"); exit(-1); } for (int x = 0; x < GRID_WIDTH; ++x) { for (int y = 0; y < GRID_HEIGHT; ++y) { float tx = 0.0f, ty = 0.0f, tw = 0.0f, th = 0.0f; if (cells[x][y].texture_rect != NULL) { tx = cells[x][y].texture_rect->x; ty = cells[x][y].texture_rect->y; tw = cells[x][y].texture_rect->w; th = cells[x][y].texture_rect->h; } fprintf(f, "%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f\n", cells[x][y].is_active, cells[x][y].rect.x, cells[x][y].rect.y, cells[x][y].rect.w, cells[x][y].rect.h, tx, ty, tw, th); } } fclose(f); }
修改读取函数
读取时重新分配内存并赋值:
static void read_tile_map(Cell saved_tiles[GRID_WIDTH][GRID_HEIGHT]) { FILE* f = fopen("tile_map.txt", "r"); if (f == NULL) { printf("Error! Could not open the file\n"); exit(-1); } for (int x = 0; x < GRID_WIDTH; ++x) { for (int y = 0; y < GRID_HEIGHT; ++y) { int is_active; float rx, ry, rw, rh, tx, ty, tw, th; fscanf(f, "%d,%f,%f,%f,%f,%f,%f,%f,%f", &is_active, &rx, &ry, &rw, &rh, &tx, &ty, &tw, &th); saved_tiles[x][y].is_active = is_active; saved_tiles[x][y].rect = (SDL_FRect){rx, ry, rw, rh}; if (is_active) { // 动态分配内存,记得后续要free避免内存泄漏 saved_tiles[x][y].texture_rect = malloc(sizeof(SDL_FRect)); saved_tiles[x][y].texture_rect->x = tx; saved_tiles[x][y].texture_rect->y = ty; saved_tiles[x][y].texture_rect->w = tw; saved_tiles[x][y].texture_rect->h = th; } else { saved_tiles[x][y].texture_rect = NULL; } } } fclose(f); }
几个重要提醒
- 绝对不要保存指针地址到文件!指针是进程私有内存的地址,跨运行实例完全无效,这是你当前问题的根本原因。
- 如果用方案二的动态分配,一定要记得在程序退出或不再使用saved_tiles时,遍历数组free掉每个非NULL的texture_rect,避免内存泄漏。
- 用逗号分隔数据比你原来的
is_active: %d格式更可靠,fscanf处理带字符串前缀的格式很容易因为空格、换行等细节出错。
内容来源于stack exchange
相关产品推荐
相关产品推荐

