You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

基于SDL2的简易坦克游戏问题排查:平滑转向与Shift加速移动异常修复

解决坦克游戏的移动问题

我来帮你搞定这两个移动相关的问题,先说说问题出在哪,再给你调整好的代码:

问题根源分析

  1. Shift按下后坦克停止:你现在只在SDL_KEYDOWN事件触发时处理移动,但这个事件只在按键按下的瞬间触发一次。当你先按住WSAD(触发一次KEYDOWN)再按Shift,此时Shift的KEYDOWN事件会触发,但WSAD的KEYDOWN不会重复触发,所以移动逻辑没执行,坦克就停了。
  2. 移动有延迟不平滑:系统默认的按键重复有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

火山引擎 最新活动