基于SDL2的简易坦克游戏问题排查:平滑转向与Shift加速移动异常修复
解决坦克游戏的移动问题
我来帮你搞定这两个移动相关的问题,先说说问题出在哪,再给你调整好的代码:
问题根源分析
- Shift按下后坦克停止:你现在只在
SDL_KEYDOWN事件触发时处理移动,但这个事件只在按键按下的瞬间触发一次。当你先按住WSAD(触发一次KEYDOWN)再按Shift,此时Shift的KEYDOWN事件会触发,但WSAD的KEYDOWN不会重复触发,所以移动逻辑没执行,坦克就停了。 - 移动有延迟不平滑:系统默认的按键重复有0.5秒左右的延迟,
SDL_KEYDOWN要等延迟后才会重复触发,导致你按下WSAD后,只有第一次触发时移动了一点,之后要等延迟才会继续移动,看起来就不流畅。
修改后的完整代码
#include <SDL2/SDL.h> #include <SDL2/SDL_image.h> #include <stdio.h> const int WINDOW_WIDTH = 960; //窗体宽度 const int WINDOW_HEIGHT = 640; //窗体高度 //使用旋转角度定义方向 const int UP = 0; const int DOWN = 180; const int LEFT = 270; const int RIGHT = 90; //------------------- typedef struct { int x; int y; int width; int height; int direction; SDL_Texture *texture; } Player; void RenderPlayer(Player player, SDL_Renderer *renderer) { SDL_Rect srcRect = {.x = 0, .y = 0, .w = player.width, .h = player.height}; SDL_Rect destRect = {.x = player.x, .y = player.y, .w = player.width / 2, .h = player.height / 2}; SDL_RenderCopyEx(renderer, player.texture, &srcRect, &destRect, player.direction, NULL, SDL_FLIP_NONE); } int main(int argc, char *argv[]) { SDL_Window *window; SDL_Renderer *renderer; if (SDL_Init(SDL_INIT_EVERYTHING)) { printf("SDL对象初始化失败!\n"); return -1; } if (!(window = SDL_CreateWindow("坦克大战", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_OPENGL))) { printf("窗体创建失败!\n"); return -1; } if (!(renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE))) { printf("渲染器创建失败!\n"); return -1; } SDL_SetRenderDrawColor(renderer, 76, 110, 150, 255); SDL_RenderClear(renderer); SDL_RenderPresent(renderer); SDL_Surface *surface = IMG_Load("Tank01.png"); if (!surface) { printf("像素集创建失败:%s\n", IMG_GetError()); return -1; } printf("宽度:%d\n高度:%d\n", surface->w, surface->h); Player player1 = {(WINDOW_WIDTH - surface->w/2) / 2, WINDOW_HEIGHT - surface->h/2, surface->w, surface->h, UP, SDL_CreateTextureFromSurface(renderer, surface)}; SDL_FreeSurface(surface); // 加载纹理后释放surface,避免内存泄漏 const Uint8 *state = SDL_GetKeyboardState(NULL); SDL_Event sdlEvent; int running = 1; // 控制帧率,避免移动速度随帧率变化 const int FPS = 60; const int frameDelay = 1000 / FPS; Uint32 frameStart; int frameTime; while (running) { frameStart = SDL_GetTicks(); // 处理事件(只处理退出类事件) while (SDL_PollEvent(&sdlEvent)) { switch (sdlEvent.type) { case SDL_QUIT: running = 0; break; case SDL_KEYDOWN: if (sdlEvent.key.keysym.sym == SDLK_ESCAPE) { running = 0; } break; } } // 实时计算当前速度 int speed = state[SDL_SCANCODE_LSHIFT] ? 8 : 4; // 实时检测WSAD按键,处理移动和方向 if (state[SDL_SCANCODE_W]) { player1.direction = UP; if (player1.y > 0) { player1.y -= speed; } } if (state[SDL_SCANCODE_S]) { player1.direction = DOWN; if (player1.y < WINDOW_HEIGHT - player1.height/2) { player1.y += speed; } } if (state[SDL_SCANCODE_A]) { player1.direction = LEFT; if (player1.x > 0) { player1.x -= speed; } } if (state[SDL_SCANCODE_D]) { player1.direction = RIGHT; if (player1.x < WINDOW_WIDTH - player1.width/2) { player1.x += speed; } } // 渲染 SDL_RenderClear(renderer); RenderPlayer(player1, renderer); SDL_RenderPresent(renderer); // 控制帧率 frameTime = SDL_GetTicks() - frameStart; if (frameDelay > frameTime) { SDL_Delay(frameDelay - frameTime); } } // 释放资源 SDL_DestroyTexture(player1.texture); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return 0; }
关键修改说明
- 移动逻辑移到主循环每帧:不再依赖
SDL_KEYDOWN事件处理移动,而是每帧通过SDL_GetKeyboardState实时检测按键状态,这样只要按键按住,每帧都会执行移动逻辑,解决了Shift按下后停止和移动延迟的问题。 - 动态计算速度:每帧都检查Shift是否按住,实时更新速度,不用再担心按键顺序的问题。
- 添加帧率控制:加入了FPS固定逻辑,避免不同设备帧率不同导致移动速度不一致的问题。
- 修复内存泄漏:加载纹理后释放了
surface,程序退出时也正确释放了所有SDL资源。 - 调整初始位置:修正了坦克初始位置的计算,让它更居中。
内容的提问来源于stack exchange,提问作者suyangzuo




