SDL学习

  1. 配置
    1. CMake配置SDL2
  2. 使用
    1. 基础使用 窗口 表面 事件
    2. 常用事件
    3. 文字输入
    4. 图片优化 && 图片缩放
    5. Texture && Render
    6. 加载 PNG IMG等
    7. 精灵移动

配置

CMake配置SDL2

cmake_minimum_required(VERSION 3.0)
project(SDL2Game)

set(CMAKE_CXX_STANDARD 17)

# SDL2 SDL_IMAGE都放在了一起
set(SDL2_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/SDL2/include)
set(SDL2_LIBRARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/SDL2/lib)

add_executable(SDL2Game main.cpp)
target_include_directories(SDL2Game PUBLIC ${SDL2_INCLUDE_DIR})
target_link_directories(SDL2Game PUBLIC ${SDL2_LIBRARY_DIR})
target_link_libraries(SDL2Game PUBLIC mingw32 SDL2main SDL2 SDL2_image)

使用

基础使用 窗口 表面 事件

#include <SDL2/SDL.h>
#include <iostream>

// main 函数被声明为了宏,所以argc和argv必须要有
// 这个函数会被SDL中内置的main函数调用
int main(int argc, char** argv)
{
    // SDL中的窗口
    SDL_Window* window = nullptr;

    // SDL中的表面
    SDL_Surface* window_surface = nullptr;
    SDL_Surface* img_surface = nullptr;

    SDL_Init(SDL_INIT_VIDEO);


    // 创建一个窗口
    window = SDL_CreateWindow("Test Name", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
            640, 480, SDL_WINDOW_SHOWN);

    // 获取窗口的表面
    window_surface = SDL_GetWindowSurface(window);
    // 加载图片
    img_surface = SDL_LoadBMP("../test.bmp");
    if (img_surface == nullptr)
    {
        std::cout << "load img error: " << SDL_GetError() << std::endl;
    }
    else
    {
        // 将图片绘制到窗口的表面上
        SDL_BlitSurface(img_surface, nullptr, window_surface, nullptr);
        SDL_UpdateWindowSurface(window);
    }

    // 基于事件的管理机制
    bool is_running = true;
    SDL_Event ev;
    while (is_running)
    {
        while (SDL_PollEvent(&ev) != 0)
        {
            if (ev.type == SDL_QUIT)
            {
                is_running = false;
            }
        }
        SDL_UpdateWindowSurface(window);
    }

    SDL_FreeSurface(img_surface);
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}

常用事件

// 退出
if (ev.type == SDL_QUIT)
{
    is_running = false;
}
// 按键按下和弹起
else if (ev.type == SDL_KEYUP || ev.type == SDL_KEYDOWN)
{
    std::cout << "key down: " << ev.key.keysym.sym << ", " << ev.key.timestamp << std::endl;
}
// 鼠标左键 鼠标右键 按下
else if (ev.type == SDL_MOUSEBUTTONUP || ev.type == SDL_MOUSEBUTTONDOWN)
{
    if (ev.button.button == SDL_BUTTON_LEFT || ev.button.button == SDL_BUTTON_RIGHT)
    {
        std::cout << "mouse button down: " <<
                    ev.button.button << ", " << ev.key.timestamp << std::endl;
    }
}
// 鼠标移动
else if (ev.type == SDL_MOUSEMOTION)
{
    std::cout << ev.motion.x << ", " << ev.motion.y << std::endl;
}

文字输入

SDL_StartTextInput();
std::string text;
while (is_running)
{
    while (SDL_PollEvent(&ev) != 0)
    {
        if (ev.type == SDL_QUIT)
        {
            is_running = false;
        }
        else if (ev.type == SDL_TEXTINPUT || ev.type == SDL_KEYDOWN)
        {
            // clear
            system("cls");
            if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_BACKSPACE &&
                text.length() > 0)
            {
                text = text.substr(0, text.length() - 1);
            }
            else if (ev.type == SDL_TEXTINPUT)
            {
                text += ev.text.text;
            }
            std::cout << text << std::endl;
        }
    }
    SDL_UpdateWindowSurface(window);
}
SDL_StopTextInput();

图片优化 && 图片缩放

SDL_Surface* OptimizedSurface(const std::string& filepath, SDL_Surface* windows_surface)
{
    SDL_Surface* image_surface = SDL_LoadBMP(filepath.c_str());
    if (!image_surface)
    {
        std::cout << "error: " << SDL_GetError() << std::endl;
        return nullptr;
    }
    auto converted_surface =
            SDL_ConvertSurface(image_surface, windows_surface->format, 0);
    SDL_FreeSurface(image_surface);
    return converted_surface;
}

// 图片缩放
window = SDL_CreateWindow("Test Name", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
            640, 480, SDL_WINDOW_SHOWN);
SDL_Surface* windows_surface = SDL_GetWindowSurface(window);
SDL_Surface* image = OptimizedSurface("../test.bmp", windows_surface);
SDL_Rect rect{0, 0, 640, 480};
SDL_BlitScaled(image, nullptr, windows_surface, &rect);

Texture && Render

这么多Destroy, 感觉可以上RAII了。

#include <SDL2/SDL.h>
#include <iostream>

SDL_Texture* LoadTexture(const std::string& filepath, SDL_Renderer* target_render)
{
    SDL_Surface* image_surface = SDL_LoadBMP(filepath.c_str());
    if (!image_surface)
    {
        std::cout << "error: " << SDL_GetError() << std::endl;
        return nullptr;
    }
    return SDL_CreateTextureFromSurface(target_render, image_surface);
}

// main 函数被声明为了宏,所以argc和argv必须要有
// 这个函数会被SDL中内置的main函数调用
int main(int argc, char** argv)
{
    // SDL中的窗口
    SDL_Window* window = nullptr;
    SDL_Init(SDL_INIT_VIDEO);


    // 创建一个窗口
    window = SDL_CreateWindow("Test Name", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
            640, 480, SDL_WINDOW_SHOWN);

    // SDL_RENDERER_ACCELERATED 硬件加速
    SDL_Renderer* render_target = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    SDL_Texture* texture = LoadTexture("../test.bmp", render_target);

    bool is_running = true;
    SDL_Event ev;
    while (is_running)
    {
        while (SDL_PollEvent(&ev) != 0)
        {
            if (ev.type == SDL_QUIT)
            {
                is_running = false;
            }
            // 清除
            SDL_RenderClear(render_target);
            // 将texture绘制到render上
            SDL_RenderCopy(render_target, texture, nullptr, nullptr);
            // 绘制
            SDL_RenderPresent(render_target);
        }
        SDL_UpdateWindowSurface(window);
    }

    SDL_DestroyWindow(window);
    SDL_DestroyRenderer(render_target);
    SDL_DestroyTexture(texture);
    SDL_Quit();
    return 0;
}

加载 PNG IMG等

需要使用SDLImage https://www.libsdl.org/projects/SDL_image/

这样即可使用IMG_Load加载图片

使用前需要进行初始化

auto init_flag = IMG_INIT_PNG | IMG_INIT_JPG;
if (IMG_Init(init_flag) != init_flag)
{
    std::cout << "init error" << std::endl;
}

精灵移动

https://retro-sprite-creator.nihey.org/character/new

https://web.archive.org/web/20141219071009/http://www.famitsu.com/freegame/tool/chibi/index1.html

#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <iostream>
#include <array>

SDL_Texture* LoadTexture(const std::string& filepath, SDL_Renderer* target_render)
{
    SDL_Surface* image_surface = IMG_Load(filepath.c_str());
    if (!image_surface)
    {
        std::cout << "error: " << SDL_GetError() << std::endl;
        return nullptr;
    }
    return SDL_CreateTextureFromSurface(target_render, image_surface);
}

// main 函数被声明为了宏,所以argc和argv必须要有
// 这个函数会被SDL中内置的main函数调用
int main(int argc, char** argv)
{
    const int FPS = 60;
    float frame_time = 0;
    uint32_t prev_time = 0;
    uint32_t current_time = 0;
    float delta_time = 0;
    float move_speed = 100.0f;

    SDL_Init(SDL_INIT_VIDEO);
    IMG_Init(IMG_INIT_PNG);

    SDL_Window*  window = SDL_CreateWindow("Test Name", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
            640, 480, SDL_WINDOW_SHOWN);

    // SDL_Renderer感觉是一个图层,index代表图层的下标从-1开始
    // SDL_RENDERER_ACCELERATED 硬件加速
    SDL_Renderer* render_target = SDL_CreateRenderer(window, -1,
            SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    // 图层背景色
    SDL_SetRenderDrawColor(render_target, 0xFF, 0, 0, 0);
    SDL_Texture* texture = LoadTexture("../sprite-sheet.png", render_target);

    int texture_width, texture_height, frame_width, frame_height;
    SDL_QueryTexture(texture, nullptr, nullptr, &texture_width, &texture_height);
    frame_width = texture_width / 3;
    frame_height = texture_height / 4;

    SDL_Rect player_rect{ 0, 0, frame_width, frame_height};
    SDL_Rect player_pos{ 0, 0, 32, 32};

    // 下 左 右 上
    std::array<int, 4> rect_y = {0, frame_width, frame_width * 2, frame_width * 3};

    bool is_running = true;
    SDL_Event ev;
    while (is_running)
    {
        prev_time = current_time;
        current_time = SDL_GetTicks();
        delta_time = (current_time - prev_time) / 1000.0f;

        while (SDL_PollEvent(&ev) != 0)
        {
            if (ev.type == SDL_QUIT)
            {
                is_running = false;
            }
        }
        const uint8_t* key_state = SDL_GetKeyboardState(nullptr);

        // 如果这里不强制转换 而是用float 向右和下移动 会比 向上和左移动 要慢
        int move_length = static_cast<int>(move_speed * delta_time);
        // float move_length = move_speed * delta_time;
        if (key_state[SDL_SCANCODE_RIGHT])
        {
            player_pos.x += move_length;
            player_rect.y = rect_y[2];
        }
        else if (key_state[SDL_SCANCODE_LEFT])
        {
            player_pos.x -= move_length;
            player_rect.y = rect_y[1];
        }
        else if (key_state[SDL_SCANCODE_UP])
        {
            player_pos.y -= move_length;
            player_rect.y = rect_y[3];
        }
        else if (key_state[SDL_SCANCODE_DOWN])
        {
            player_pos.y += move_length;
            player_rect.y = rect_y[0];
        }

        frame_time += delta_time;
        if (frame_time >= 0.25f)
        {
            frame_time = 0;
            player_rect.x += frame_width;
            if (player_rect.x >= texture_width)
            {
                player_rect.x = 0;
            }
        }
        // 清除图层
        SDL_RenderClear(render_target);
        // 将texture绘制到render上
        SDL_RenderCopy(render_target, texture, &player_rect, &player_pos);
        // 绘制图层
        SDL_RenderPresent(render_target);
    }

    SDL_DestroyWindow(window);
    SDL_DestroyRenderer(render_target);
    SDL_DestroyTexture(texture);
    SDL_Quit();
    return 0;
}