C语言贪吃蛇游戏添加TTF字体失败求助:问题排查与解决
SDL2贪吃蛇字体加载失败解决方案
问题概述
基于SDL2开发的C语言贪吃蛇游戏,尝试加载程序同目录下的字体文件用于显示分数和游戏结束文本,但加载失败。已确认字体路径正确、更换字体仍无效。
问题根源分析
从提供的代码中可定位三个核心问题:
- 未初始化SDL_ttf库:仅初始化了SDL视频模块,未调用
TTF_Init()启动字体渲染库,这是SDL_ttf加载字体的必要前提。 - 局部变量覆盖全局变量:
init()函数中重新声明了局部的TTF_Font* font,导致全局font指针始终为NULL,后续渲染文本时使用无效指针。 - 未清理字体资源:程序关闭时未调用
TTF_CloseFont()释放字体资源,存在内存泄漏风险。
修复步骤
- 初始化SDL_ttf库:在
init()函数中,SDL初始化完成后添加TTF初始化逻辑,并检查初始化结果。 - 修复变量覆盖:删除
init()中TTF_Font* font的局部声明,直接给全局font变量赋值。 - 验证字体路径(可选):若仍加载失败,可打印当前工作目录(用
getcwd())确认程序运行路径,或使用字体文件绝对路径测试。 - 添加资源清理:在
close()函数中调用TTF_CloseFont(font)释放字体资源。
修改后的完整代码
#include <stdio.h> #include <stdbool.h> #include <unistd.h> // 用于getcwd(),可选 #define SDL_MAIN_HANDLED #include <SDL2/SDL.h> #include <SDL2/SDL_ttf.h> #include <time.h> // 常量定义 const int WINDOW_WIDTH = 640; const int WINDOW_HEIGHT = 480; const int GRID_SIZE = 20; const int GAME_SPEED = 150; // 游戏状态变量 int snakeX[100], snakeY[100]; int snakeLength; int fruitX, fruitY; int score; int direction; int gameOver; TTF_Font* font = NULL; // SDL资源 SDL_Window* window = NULL; SDL_Renderer* renderer = NULL; // 初始化函数 bool init() { // 初始化SDL视频模块 if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("SDL初始化失败! SDL_Error: %s\n", SDL_GetError()); return false; } // 初始化SDL_ttf库 if (TTF_Init() == -1) { printf("SDL_ttf初始化失败! TTF_Error: %s\n", TTF_GetError()); return false; } // 打印当前工作目录(可选,用于验证字体路径) char cwd[1024]; if (getcwd(cwd, sizeof(cwd)) != NULL) { printf("当前工作目录: %s\n", cwd); } // 创建窗口 window = SDL_CreateWindow("Snake Game", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN); if (window == NULL) { printf("窗口创建失败! SDL_Error: %s\n", SDL_GetError()); return false; } // 创建渲染器 renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); if (renderer == NULL) { printf("渲染器创建失败! SDL_Error: %s\n", SDL_GetError()); return false; } // 加载字体(直接赋值给全局变量,避免局部覆盖) font = TTF_OpenFont("arial.ttf", 24); if (font == NULL) { printf("字体加载失败! TTF_Error: %s\n", TTF_GetError()); return false; } return true; } // 资源清理函数 void close() { // 释放字体资源 if (font != NULL) { TTF_CloseFont(font); font = NULL; } SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); TTF_Quit(); SDL_Quit(); } // 输入处理 void handleInput() { SDL_Event event; while (SDL_PollEvent(&event)) { if (event.type == SDL_QUIT) { gameOver = true; } else if (event.type == SDL_KEYDOWN) { switch (event.key.keysym.sym) { case SDLK_UP: case SDLK_w: if (direction != 2) direction = 0; break; case SDLK_DOWN: case SDLK_s: if (direction != 0) direction = 2; break; case SDLK_LEFT: case SDLK_a: if (direction != 1) direction = 3; break; case SDLK_RIGHT: case SDLK_d: if (direction != 3) direction = 1; break; } } } } // 生成水果位置 void generateFruit() { fruitX = rand() % (WINDOW_WIDTH / GRID_SIZE); fruitY = rand() % (WINDOW_HEIGHT / GRID_SIZE); } // 蛇移动逻辑 void moveSnake() { for (int i = snakeLength; i > 0; --i) { snakeX[i] = snakeX[i - 1]; snakeY[i] = snakeY[i - 1]; } switch (direction) { case 0: // 上 snakeY[0] -= 1; break; case 1: // 右 snakeX[0] += 1; break; case 2: // 下 snakeY[0] += 1; break; case 3: // 左 snakeX[0] -= 1; break; } // 边界碰撞检测 if (snakeX[0] < 0 || snakeX[0] >= WINDOW_WIDTH / GRID_SIZE || snakeY[0] < 0 || snakeY[0] >= WINDOW_HEIGHT / GRID_SIZE) { gameOver = true; } // 自碰撞检测 for (int i = 1; i < snakeLength; ++i) { if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) { gameOver = true; } } // 吃水果逻辑 if (snakeX[0] == fruitX && snakeY[0] == fruitY) { score += 1; snakeLength++; generateFruit(); } } // 渲染分数 void renderScore(){ SDL_Color textColor = {255,255,255}; char scoreText[20]; sprintf(scoreText, "Score: %d", score); SDL_Surface* surface = TTF_RenderText_Solid(font, scoreText, textColor); if (surface == NULL) { printf("渲染分数文本失败! TTF_Error: %s\n", TTF_GetError()); return; } SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); int textWidth = surface->w; int textHeight = surface->h; SDL_Rect textRect = {10, 10, textWidth, textHeight}; SDL_RenderCopy(renderer, texture, NULL, &textRect); SDL_FreeSurface(surface); SDL_DestroyTexture(texture); } // 渲染游戏画面 void render() { SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); // 渲染蛇 SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); for (int i = 0; i < snakeLength; ++i) { SDL_Rect rect = { snakeX[i] * GRID_SIZE, snakeY[i] * GRID_SIZE, GRID_SIZE, GRID_SIZE }; SDL_RenderFillRect(renderer, &rect); } // 渲染水果 SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); SDL_Rect fruitRect = { fruitX * GRID_SIZE, fruitY * GRID_SIZE, GRID_SIZE, GRID_SIZE }; SDL_RenderFillRect(renderer, &fruitRect); // 渲染分数 renderScore(); SDL_RenderPresent(renderer); } // 游戏结束界面 void showGameOver(){ bool quit=false; SDL_Event event; while (!quit){ // 清空背景 SDL_SetRenderDrawColor(renderer, 255,255,255,255); SDL_RenderClear(renderer); // 渲染"Game Over"文本 SDL_Color textColor = {0, 0, 255}; SDL_Surface* surface = TTF_RenderText_Solid(font, "Game Over!", textColor); if (surface == NULL) { printf("渲染游戏结束文本失败! TTF_Error: %s\n", TTF_GetError()); break; } SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); int textWidth = surface->w; int textHeight = surface->h; // 修正文本居中计算(原代码计算错误,导致文本位置偏移) SDL_Rect textRect = {(WINDOW_WIDTH - textWidth) / 2, (WINDOW_HEIGHT - textHeight) / 2, textWidth, textHeight}; SDL_RenderCopy(renderer, texture, NULL, &textRect); SDL_FreeSurface(surface); SDL_DestroyTexture(texture); // 渲染操作提示文本 surface = TTF_RenderText_Solid(font, "Press ESC to quit or R to restart", textColor); if (surface == NULL) { printf("渲染提示文本失败! TTF_Error: %s\n", TTF_GetError()); break; } texture = SDL_CreateTextureFromSurface(renderer, surface); textWidth = surface->w; textHeight = surface->h; SDL_Rect textRect1 = {(WINDOW_WIDTH - textWidth) / 2, (WINDOW_HEIGHT - textHeight) / 2 + 50, textWidth, textHeight}; SDL_RenderCopy(renderer, texture, NULL, &textRect1); SDL_FreeSurface(surface); SDL_DestroyTexture(texture); SDL_RenderPresent(renderer); // 处理输入 while(SDL_PollEvent(&event)){ if(event.type == SDL_QUIT){ quit = true; gameOver=true; } else if(event.type == SDL_KEYDOWN){ if(event.key.keysym.sym == SDLK_ESCAPE){ quit=true; gameOver=true; } else if (event.key.keysym.sym == SDLK_r){ quit=true; // 此处可添加重启游戏的逻辑 } } } } } // 主函数 int main(int argc, char* argv[]) { srand(time(NULL)); // 初始化 if (!init()) { return 1; } // 初始画面 SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); SDL_RenderPresent(renderer); // 初始化游戏状态 generateFruit(); snakeX[0] = WINDOW_WIDTH / (2 * GRID_SIZE); snakeY[0] = WINDOW_HEIGHT / (2 * GRID_SIZE); snakeLength = 1; direction = -1; gameOver = false; score = 0; Uint32 lastTime = SDL_GetTicks(); // 游戏主循环 while (!gameOver) { Uint32 currentTime = SDL_GetTicks(); Uint32 deltaTime = currentTime - lastTime; handleInput(); if (deltaTime >= GAME_SPEED) { moveSnake(); render(); lastTime = currentTime; } } // 显示游戏结束界面 showGameOver(); // 清理资源 close(); return 0; }
额外修复说明
- 修正了游戏结束界面中文本居中的计算错误(原代码用
WINDOW_HEIGHT - WINDOW_WIDTH得到负数,导致文本位置偏移)。 - 添加了文本渲染失败的错误检查,便于排查后续问题。
- 可选添加了当前工作目录打印,帮助确认字体文件是否在程序运行路径下。
内容的提问来源于stack exchange,提问作者ImaHUman2
相关产品推荐
相关产品推荐

