Форум программистов, компьютерный форум, киберфорум
8Observer8
Войти
Регистрация
Восстановить пароль

SDL3 для Web (WebAssembly): Синхронизация спрайтов SDL3 и тел Box2D

Запись от 8Observer8 размещена 04.03.2026 в 00:03. Обновил(-а) 8Observer8 04.03.2026 в 00:05
Показов 2092 Комментарии 0

Содержание блога

Финальная демка в браузере. Итоговый код: finish-sync-physics-sprites-sdl3-c.zip

На первой гифке отладочные линии отключены, а на второй включены:


Вспомогательные линии нужны только для разработки и отладки, а пользователь не должен их видеть. Пользователь должен видеть спрайты, а линии будут скрыты. Во время отладки будет рисовать линии поверх спрайтов. Спрайты должны рисоваться по тем же координатам и с теми же углами поворота, как и коллайдеры. Box2D имеет специальные функции для считывания координат и углов поворота физических тел, то есть коллайдеров.

Подключение библиотек к стартовому примеру



  • Скачайте пример: start-sync-physics-sprites-sdl3-c.zip
  • Примечание 1. Стартовый пример написан по пошаговой инструкции: Подключение Box2D v3, физика и отрисовка коллайдеров
  • Примечание 2. Разница только в одной строке в файле CMakeLists.txt, где вместо SDL 3.4.0 подключается более новая версия SDL 3.4.2:
    Bash
    1
    
    set(SDL3_DIR "C:/libs/SDL3-devel-3.4.2-wasm/lib/cmake/SDL3")
  • Так выглядит код стартового примера:

    CMakeLists.txt

    Bash
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    cmake_minimum_required(VERSION 3.21)
    project(start-sync-physics-sprites-sdl3-c)
     
    # Задаем название будущего приложения (в Windows это был бы app.exe, а в вебе будет app.js / app.wasm)
    add_executable(app)
     
    # Устанавливаем стандарт C
    set(CMAKE_C_STANDARD 11)
     
    # Указываем точное местоположение конфигурационных файлов библиотек
    # Имя переменной должно строго соответствовать: НазваниеБиблиотеки_DIR
    set(SDL3_DIR "C:/libs/SDL3-devel-3.4.2-wasm/lib/cmake/SDL3")
    set(box2d_DIR "C:/libs/box2d-3.1.1-wasm/lib/cmake/box2d")
     
    # Проверяем наличие библиотек в системе
    # Если она не будет найдены, CMake прервет настройку с ошибкой
    # REQUIRED - переводится, как «обязательно» или «требуется»
    find_package(SDL3 REQUIRED)
    find_package(box2d REQUIRED)
     
    # Привязываем библиотеки к нашему приложению (настройка линковки и путей include)
    target_link_libraries(app PRIVATE box2d::box2d SDL3::SDL3)
     
    # Добавляем исходный код к проекту
    target_sources(app
        PRIVATE
        src/main.c
    )
    src/main.c

    C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    
    #define SDL_MAIN_USE_CALLBACKS 1 // Use the callbacks instead of main()
     
    #include "debug-drawer.h"
    #include <SDL3/SDL.h>
    #include <SDL3/SDL_main.h>
    #include <box2d/box2d.h>
    #include <stdio.h>
     
    #define TIME_STEP (1.0f / 60.0f)
    #define MAX_FRAME_TIME 0.25f
     
    static SDL_Window *window = NULL;
    SDL_Renderer *renderer = NULL;
    static b2WorldId worldId;
    static b2DebugDraw debugDrawer;
     
    const SDL_PixelFormatDetails *format = NULL;
    float ppm = 30.f; // Pixels per meter
     
    static float accumulator = 0.0f;
    static Uint64 last_ticks = 0;
     
    SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
    {
        if (!SDL_Init(SDL_INIT_VIDEO))
        {
            SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
            return SDL_APP_FAILURE;
        }
     
        if (!SDL_CreateWindowAndRenderer("Example", 400, 400, 0, &window, &renderer))
        {
            SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
            return SDL_APP_FAILURE;
        }
     
        SDL_SetRenderVSync(renderer, 1);
     
        // Создаём физический мир Box2D с гравитацией
        b2Vec2 gravity = { 0.f, 9.8f };
        b2WorldDef worldDef = b2DefaultWorldDef();
        worldDef.gravity = gravity;
        worldId = b2CreateWorld(&worldDef);
     
        // Выводим значение гравитации в консоль
        // Получаем текущее значение гравитации
        b2Vec2 currentGravity = b2World_GetGravity(worldId);
        // Выводим компоненты x и y
        printf("Текущее значение гравитации: x = %.2f, y = %.2f\n", currentGravity.x, currentGravity.y);
     
        debugDrawer = b2DefaultDebugDraw();
        debugDrawer.drawShapes = true;
        debugDrawer.DrawSolidPolygonFcn = drawSolidPolygon;
        debugDrawer.DrawSolidCircleFcn = drawSolidCircle;
     
        // Floor
        b2BodyDef floorBodyDef = b2DefaultBodyDef();
        floorBodyDef.type = b2_staticBody;
        floorBodyDef.position = (b2Vec2) { 200.f / ppm, 300.f / ppm };
        b2BodyId floorBodyId = b2CreateBody(worldId, &floorBodyDef);
        b2Polygon floorShape = b2MakeBox((300.f / 2.f) / ppm, (20.f / 2.f) / ppm);
        b2ShapeDef floorShapeDef = b2DefaultShapeDef();
        b2ShapeId floorShapeId = b2CreatePolygonShape(floorBodyId, &floorShapeDef, &floorShape);
     
        // Square
        b2BodyDef squareBodyDef = b2DefaultBodyDef();
        squareBodyDef.type = b2_dynamicBody;
        squareBodyDef.position = (b2Vec2) { 201.f / ppm, 100.f / ppm };
        squareBodyDef.enableSleep = false;
        b2BodyId squareBodyId = b2CreateBody(worldId, &squareBodyDef);
        b2Polygon squareShape = b2MakeBox((30.f / 2.f) / ppm, (30.f / 2.f) / ppm);
        b2ShapeDef squareShapeDef = b2DefaultShapeDef();
        b2ShapeId squareShapeId = b2CreatePolygonShape(squareBodyId, &squareShapeDef, &squareShape);
     
        // Circle
        b2BodyDef circleBodyDef = b2DefaultBodyDef();
        circleBodyDef.type = b2_dynamicBody;
        circleBodyDef.position = (b2Vec2) { 200.f / ppm, 150.f / ppm };
        circleBodyDef.enableSleep = false;
        b2BodyId circleBodyId = b2CreateBody(worldId, &circleBodyDef);
        b2Circle circleCircle = {
            .center = { 0.0f, 0.0f }, // <--- ВАЖНО: 0,0 означает, что центр круга совпадает с центром тела
            .radius = 20.f / ppm      // Радиус в метрах
        };
        b2ShapeDef circleShapeDef = b2DefaultShapeDef();
        circleShapeDef.density = 1.0f;
        b2ShapeId circleShapeId = b2CreateCircleShape(circleBodyId, &circleShapeDef, &circleCircle);
     
        // Get the pixel format
        SDL_Surface *surface = SDL_GetWindowSurface(window);
        format = SDL_GetPixelFormatDetails(surface->format);
     
        last_ticks = SDL_GetTicks();
     
        return SDL_APP_CONTINUE;
    }
     
    SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
    {
        if (event->type == SDL_EVENT_QUIT)
        {
            return SDL_APP_SUCCESS;
        }
     
        // --- DESKTOP ---
        if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN)
        {
            if (event->button.which != SDL_TOUCH_MOUSEID)
            {
                const char *btnName = (event->button.button == SDL_BUTTON_LEFT) ? "Левая" : "Правая";
                printf("%s кнопка (Мышь): x=%.0f, y=%.0f\n", btnName, event->button.x, event->button.y);
            }
        }
     
        // --- MOBILE / WASM (Touch) ---
        if (event->type == SDL_EVENT_FINGER_DOWN)
        {
            int w, h;
            SDL_GetWindowSize(window, &w, &h);
     
            float pixelX = event->tfinger.x * w;
            float pixelY = event->tfinger.y * h;
     
            // Проверяем количество пальцев на устройстве (touchId берем из события)
            int numFingers = 0;
            SDL_GetTouchFingers(event->tfinger.touchID, &numFingers);
     
            if (numFingers <= 1)
            {
                printf("Левая кнопка (Тап 1 пальцем): x=%.2f, y=%.2f\n", pixelX, pixelY);
            }
            else
            {
                printf("Правая кнопка (Тап несколькими): x=%.2f, y=%.2f\n", pixelX, pixelY);
            }
        }
     
        return SDL_APP_CONTINUE;
    }
     
    SDL_AppResult SDL_AppIterate(void *appstate)
    {
        Uint64 current_ticks = SDL_GetTicks();
     
        float frameTime = (float)(current_ticks - last_ticks) / 1000.0f;
        last_ticks = current_ticks;
     
        if (frameTime > MAX_FRAME_TIME)
        {
            frameTime = MAX_FRAME_TIME;
        }
     
        accumulator += frameTime;
     
        while (accumulator >= TIME_STEP)
        {
            // Step physics
            b2World_Step(worldId, TIME_STEP, 3);
     
            accumulator -= TIME_STEP;
        }
     
        // Clear the screen
        SDL_SetRenderDrawColor(renderer, 100, 100, 100, 255);
        SDL_RenderClear(renderer);
     
        b2World_Draw(worldId, &debugDrawer);
     
        // Update the screen
        SDL_RenderPresent(renderer);
        return SDL_APP_CONTINUE;
    }
     
    void SDL_AppQuit(void *appstate, SDL_AppResult result)
    {
        // Удаляем физический мир Box2D
        if (b2World_IsValid(worldId))
        {
            b2DestroyWorld(worldId);
        }
     
        // SDL will clean up the window/renderer for us
    }
    src/debug-drawer.h

    C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    
    #ifndef DEBUG_DRAWER_H
    #define DEBUG_DRAWER_H
     
    #include <SDL3/SDL.h>
    #include <box2d/box2d.h>
     
    extern SDL_Renderer *renderer;
    extern const SDL_PixelFormatDetails *format;
    extern float ppm;
     
    static void drawSolidPolygon(b2Transform transform,
        const b2Vec2 *vertices, int vertexCount,
        float radius, b2HexColor color, void *context)
    {
        (void)radius;
        (void)context;
     
        // Extract RGB
        Uint8 r, g, b;
        SDL_GetRGB(color, format, NULL, &r, &g, &b);
        // Draw a collider rectangle with lines
        SDL_SetRenderDrawColor(renderer, r, g, b, SDL_ALPHA_OPAQUE);
        for (int i = 0; i < vertexCount; ++i)
        {
            int next_index = (i + 1 == vertexCount) ? 0 : i + 1;
            b2Vec2 p0 = b2TransformPoint(transform, vertices[i]);
            b2Vec2 p1 = b2TransformPoint(transform, vertices[next_index]);
            float x0 = p0.x * ppm;
            float y0 = p0.y * ppm;
            float x1 = p1.x * ppm;
            float y1 = p1.y * ppm;
            SDL_RenderLine(renderer, x0, y0, x1, y1);
        }
     
        // --- Tip: Draw orientation line ---
        // Compute polygon center
        float cx = 0.f, cy = 0.f;
        for (int i = 0; i < vertexCount; ++i)
        {
            b2Vec2 p = b2TransformPoint(transform, vertices[i]);
            cx += p.x;
            cy += p.y;
        }
        cx = (cx / vertexCount) * ppm;
        cy = (cy / vertexCount) * ppm;
     
        // Midpoint of second edge (vertex[1] → vertex[2])
        b2Vec2 p1 = b2TransformPoint(transform, vertices[1]);
        b2Vec2 p2 = b2TransformPoint(transform, vertices[2]);
        float mx = (p1.x + p2.x) * 0.5f * ppm;
        float my = (p1.y + p2.y) * 0.5f * ppm;
     
        // Draw center → edge midpoint line (acts like "direction tip")
        SDL_RenderLine(renderer, cx, cy, mx, my);
    }
     
    static void drawSolidCircle(b2Transform transform, float radius,
        b2HexColor color, void *context)
    {
        (void)radius;
        (void)context;
     
        float angle = 0.f;
        const int numberOfSegments = 20;
        const float angleStep = 360.f / numberOfSegments;
     
        // Extract RGB
        Uint8 r, g, b;
        SDL_GetRGB(color, format, NULL, &r, &g, &b);
        // Draw a collider rectangle with lines
        SDL_SetRenderDrawColor(renderer, r, g, b, SDL_ALPHA_OPAQUE);
     
        float x = radius * cos(angle * SDL_PI_F / 180.f);
        float y = radius * sin(angle * SDL_PI_F / 180.f);
        b2Vec2 p0 = b2TransformPoint(transform, (b2Vec2) { x, y });
        float x0 = p0.x * ppm;
        float y0 = p0.y * ppm;
        angle += angleStep;
     
        for (int i = 0; i < numberOfSegments; ++i)
        {
            float x = radius * cos(angle * SDL_PI_F / 180.f);
            float y = radius * sin(angle * SDL_PI_F / 180.f);
            b2Vec2 p1 = b2TransformPoint(transform, (b2Vec2) { x, y });
            float x1 = p1.x * ppm;
            float y1 = p1.y * ppm;
            SDL_RenderLine(renderer, x0, y0, x1, y1);
            x0 = x1;
            y0 = y1;
            angle += angleStep;
            if (angle >= 360.f)
            {
                angle = 0.f;
            }
        }
     
        // --- Tip: Draw orientation line ---
        // Circle center
        b2Vec2 c = b2TransformPoint(transform, (b2Vec2) { 0.f, 0.f });
        float cx = c.x * ppm;
        float cy = c.y * ppm;
     
        // Direction = transform applied to (radius, 0)
        b2Vec2 orient = b2TransformPoint(transform, (b2Vec2) { radius, 0.f });
        float ox = orient.x * ppm;
        float oy = orient.y * ppm;
     
        // Draw center → orientation marker
        SDL_RenderLine(renderer, cx, cy, ox, oy);
    }
     
    #endif // DEBUG_DRAWER_H
  • Результатом стартового примеря являеются: падающие круговой и квадратный коллайдер и статическая платформа:
    Название: finish-box2d-v3-wasm-sdl3-c.gif
Просмотров: 1422

Размер: 387.2 Кб
  • Запустим стартовый пример, а для этого скачаем и подключим библиотеки SDL3 и Box2D
  • Откройте папку со стартовым примером в какой-нибудь редакторе кода, например, в Notepad++ или в Sublime Text 4 (ST4): https://www.sublimetext.com/download
  • Скачайте библиотеку SDL3 для Wasm: SDL3-devel-3.4.2-wasm.zip
  • Скачайте библиотеку Box2D v3 для Wasm: box2d-3.1.1-wasm.zip
  • Извлеките содержимое архив выше в какую-нибудь новую общую папку, например, с именем "libs" на какой-нибудь диск, например, на C:
    Название: f7ed3a1f-0d5e-4e03-9aaa-ee8163534e78.png
Просмотров: 1423

Размер: 4.1 Кб
  • Откройте папку "SDL3-devel-3.4.2-wasm", перейдите в "lib/cmake/SDL3" и скопируйте абсолютный путь:


  • В файле CMakeLists.txt стартового примера замените в переменной SDL3_DIR путь на свой, который скопировали выше:

    Bash
    1
    
    set(SDL3_DIR "C:/libs/SDL3-devel-3.4.2-wasm/lib/cmake/SDL3")
  • Откройте папку "box2d-3.1.1-wasm", перейдите в "lib/cmake/box2d" и скопируйте абсолютный путь:


  • Замените в переменной box2d_DIR путь на свой, если необходимо:

    Bash
    1
    
    set(SDL3_DIR "C:/libs/box2d-3.1.1-wasm/lib/cmake/box2d")
  • По следующей инструкции проведём тестовую сборку, чтобы убедиться, что библиотеки подключены и пример работает

Тестовая сборка и тестовый запуск стартового примера в браузере на локальном хостинге



  • Откройте консоль (CMD) в корне стартового примера. Для этого можете просто в адресной строке папки (там где путь) написать "cmd" (без кавычек) и нажать Enter
  • В CMD введите команду для конфигурирования:
    Bash
    1
    
    config-web
  • Примечание. "config-web" - это имя bash-файла (батника), который находится в корне папки стартового примера
  • В CMD введите команду сборки проекта:
    Bash
    1
    
    build-web
  • Собранные файлы (app.js и app.wasm) будут скопированы в папку "public/js" (папка "public" лежит в корне проекта)
  • В корне проекта запустите локальный сервер командой:
    Bash
    1
    
    http-server -c-1
  • Примечание 1. Будут выведены адреса:
    Bash
    1
    2
    3
    4
    
    Available on:
      http://192.168.1.65:8080
      http://127.0.0.1:8080
    Hit CTRL-C to stop the server
  • Примечание 2. Адрес 192.168.1.65:8080 можно использовать для запуска приложения на мобильном устройстве, если у вас есть Wi-Fi - просто вводите этот адрес в браузере мобильного устройства и приложение запустится. Если интересен запуск на мобильном устройстве, то пройдите пошаговую инструкцию: Основы отладки веб-приложений на SDL3 по USB и Wi-Fi
  • Откройте новую вкладку в браузере. Если у вас Chrome или Edge, то нажмите Ctrl+Shift+J, чтобы открыть консоль браузера для контроля вывода информации. Если у вас FireFox, то нажмите Ctrl+Shift+K
  • Перейдите по адресу:
    Bash
    1
    
    localhost:8080
  • Примечание. Обновлять страницу после повторной сборки проекта лучше с очисткой кэша браузера. Для этого (обязательно с открытой консолью браузера) в браузере Chrome нужно найти в левом верхнем углу браузера кнопку обновления страницы (круговая стрелка), нажать по ней правой кнопкой мыши и выбрать "Empty Cache and Hard Reload"
  • Мы убедились, что тестовый пример запускается нормально. Если вы что-то меняете в коде (в файлах main.c или CMakeLists.txt), то достаточно ввести команду для сборки build-web (в CMD нажать стрелку-вверх и Enter) и обновить вкладку в браузере. Удобнее всего, чтобы локальный хостинг был запущен в одном окне CMD, а сборку делать в другом окне CMD

Загружаем спрайты в наше приложение



На сайте Kenney (https://kenney.nl) большое количество бесплатных паков с изображениями для игр.

  • Скачайте следующий пак под названием Physics со спрайтами и тайлами на сайте Kenney: https://kenney.nl/assets/physics-assets
  • Примечание. На случай, если ссылка выше не открывается, то я прикрепил пак Kenney архивом: kenney_physics-assets.zip (2.46 MB)
  • В корне стартового примера создайте папку "assets", а в ней папку "sprites"
  • Скопируйте в папку "assets/sprites" следующие файлы спрайтов:
    • kenney_physics-assets/PNG/Aliens/alienGreen_square.png
    • kenney_physics-assets/PNG/Aliens/alienYellow_round.png
    • kenney_physics-assets/PNG/Other/grass.png
  • Откройте файл CMakeLists.txt в редакторе кода и скопируйте в него (в конец файла) код для встраивания всех трёх файлов изображений в файл "app.wasm":
    Bash
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    # Встраиваем изображения в файл app.wasm
    if (EMSCRIPTEN)
        target_link_options("app" PRIVATE "SHELL:--embed-file \"${CMAKE_CURRENT_SOURCE_DIR}/assets/sprites/alienGreen_square.png@/assets/sprites/alienGreen_square.png\"") 
        set_property(TARGET "app" APPEND PROPERTY LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/assets/sprites/alienGreen_square.png")
     
        target_link_options("app" PRIVATE "SHELL:--embed-file \"${CMAKE_CURRENT_SOURCE_DIR}/assets/sprites/alienYellow_round.png@/assets/sprites/alienYellow_round.png\"") 
        set_property(TARGET "app" APPEND PROPERTY LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/assets/sprites/alienYellow_round.png")
     
        target_link_options("app" PRIVATE "SHELL:--embed-file \"${CMAKE_CURRENT_SOURCE_DIR}/assets/sprites/grass.png@/assets/sprites/grass.png\"") 
        set_property(TARGET "app" APPEND PROPERTY LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/assets/sprites/grass.png")
    endif()
  • Файлы изображений добавлены в проект. Таким же образом вы можете добавлять и другие файлы: звуковые файлы, файлы шрифтов, XML/JSON-файлы и т.д.
  • Откройте файл src/main.c в редакторе кода
  • Добавьте три глобальные переменные для хранения текстур:
    C
    1
    2
    3
    
    static SDL_Texture *alien_green_square_texture = NULL;
    static SDL_Texture *alien_yellow_round_texture = NULL;
    static SDL_Texture *grass_texture = NULL;
  • Сразу же, чтобы не забыть освободить ресурсы, перейдём в конец файла "main.c" и добавим код удаления текстур перед завершением программы, в функцию SDL_AppQuit():
    C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    void SDL_AppQuit(void *appstate, SDL_AppResult result)
    {
        if (alien_green_square_texture)
        {
            SDL_DestroyTexture(alien_green_square_texture);
            alien_green_square_texture = NULL;
        }
     
        if (alien_yellow_round_texture)
        {
            SDL_DestroyTexture(alien_yellow_round_texture);
            alien_yellow_round_texture = NULL;
        }
     
        if (grass_texture)
        {
            SDL_DestroyTexture(grass_texture);
            grass_texture = NULL;
        }
     
        // Удаляем физический мир Box2D
        if (b2World_IsValid(worldId))
        {
            b2DestroyWorld(worldId);
        }
     
        // SDL will clean up the window/renderer for us
    }
  • Перед функцией инициализации SDL_AppInit() добавьте функцию загрузки текстуры:
    C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    SDL_Texture* LoadTexture(const char* path)
    {
        SDL_Surface* surface = SDL_LoadPNG(path);
        if (!surface)
        {
            SDL_Log("Load failed (%s): %s", path, SDL_GetError());
            return NULL;
        }
        SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
        SDL_DestroySurface(surface);
        return texture;
    }
  • Добавьте следующий год загрузки файлов-текстур в теле функции инициализации, то есть в теле функции SDL_AppInit() после функции SDL_SetRenderVSync():
    C
    1
    2
    3
    
        alien_green_square_texture = LoadTexture("./assets/sprites/alienGreen_square.png");
        alien_yellow_round_texture = LoadTexture("./assets/sprites/alienYellow_round.png");
        grass_texture = LoadTexture("./assets/sprites/grass.png");
  • В функции SDL_AppIterate() добавьте код вывода изображения для тестового запуска после функции SDL_RenderClear(), до перед функций b2World_Draw(), чтобы в будущем отладочные линии коллайдеров рисовались поверх текстуры:
    C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    SDL_AppResult SDL_AppIterate(void *appstate)
    {
        // ..
     
        // Clear the screen
        SDL_SetRenderDrawColor(renderer, 100, 100, 100, 255);
        SDL_RenderClear(renderer);
     
        SDL_FRect srcRect = { 0, 0, 70, 70 };
        SDL_FRect destRect = { 150, 250, 100, 100 };
        SDL_RenderTexture(renderer, alien_green_square_texture, &srcRect, &destRect);
     
        b2World_Draw(worldId, &debugDrawer);
     
        // Update the screen
        SDL_RenderPresent(renderer);
     
        return SDL_APP_CONTINUE;
    }
  • Сделайте сборку проекта командой "build-web" и обновите страницу браузера
  • Мы видим, что изображение выводится, а отладочные линии коллайдеров выводятся поверх изображения:
    Название: 023c18f4-590d-4bbe-842c-8ff0995c2d70.png
Просмотров: 1423

Размер: 4.4 Кб

Синхронизация спрайтов SDL3 и тел Box2D



Нам нужно выводить спрайты с теми же координатами и углами повотора, что и тела (коллайдеры). У Box2D есть специальные функции для того, чтобы считать координаты тела (коллайдера): b2Body_GetTransform() и угла: b2Rot_GetAngle(). Найдите эти функции через поиск в правом верхнем углу в официальной документации: https://box2d.org/documentation/, чтобы привыкать работать с документаций.

Вот что по этому поводу написал Gemini

Для того чтобы спрайт «приклеился» к физическому телу, нам нужно в каждом кадре спрашивать у Box2D: «Где сейчас находится тело и под каким углом оно повернуто?».

Вот пошаговый план, как это внедрить в ваш код.
  1. Как получить данные из Box2D. В Box2D версии 3.0 (которую вы используете) позиция и поворот извлекаются через функцию b2Body_GetTransform.
    • Позиция: Извлекается в метрах, поэтому умножаем на ppm.
    • Угол: Извлекается в радианах. Для SDL_RenderTextureRotated нам нужно перевести их в градусы.
  2. Изменения в коде. Чтобы не загромождать SDL_AppIterate, лучше всего создать вспомогательную функцию для отрисовки. Добавьте эту функцию перед SDL_AppIterate:
    C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    void RenderBody(b2BodyId bodyId, SDL_Texture* texture, float width, float height) {
        // 1. Получаем трансформацию тела из Box2D
        b2Transform transform = b2Body_GetTransform(bodyId);
        b2Vec2 pos = transform.p;
        float angleRadians = b2Rot_GetAngle(transform.q);
        
        // 2. Конвертируем метры в пиксели и радианы в градусы
        float centerX = pos.x * ppm;
        float centerY = pos.y * ppm;
        float angleDegrees = angleRadians * (180.0f / SDL_PI_F);
     
        // 3. Определяем прямоугольник отрисовки
        // SDL рисует от верхнего левого угла, поэтому смещаем координаты на половину размера
        SDL_FRect destRect = {
            centerX - (width / 2.0f),
            centerY - (height / 2.0f),
            width,
            height
        };
     
        // 4. Отрисовка с учетом поворота
        // NULL в качестве центра означает вращение вокруг центра destRect
        SDL_RenderTextureRotated(renderer, texture, NULL, &destRect, (double)angleDegrees, NULL, SDL_FLIP_NONE);
    }
  3. Обновляем переменные. Чтобы функция RenderBody могла дотянуться до ID тел, сделайте их статическими (перенесите из SDL_AppInit в начало файла к остальным static переменным):
    C
    1
    2
    3
    
    static b2BodyId floorBodyId;
    static b2BodyId squareBodyId;
    static b2BodyId circleBodyId;
  4. Обновляем цикл отрисовки. Теперь в SDL_AppIterate замените ваш тестовый блок отрисовки на вызов новой функции:
    C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    SDL_AppResult SDL_AppIterate(void *appstate)
    {
        // ... (код обновления физики остается прежним) ...
     
        // Очистка экрана
        SDL_SetRenderDrawColor(renderer, 50, 50, 50, 255);
        SDL_RenderClear(renderer);
     
        // Рисуем спрайты поверх физических тел
        // Размеры берем те же, что задавали при создании коллайдеров (умноженные на 2, т.к. b2MakeBox берет полуобхват)
        RenderBody(floorBodyId, grass_texture, 300.f, 20.f);
        RenderBody(squareBodyId, alien_green_square_texture, 30.f, 30.f);
        RenderBody(circleBodyId, alien_yellow_round_texture, 40.f, 40.f); // диаметр = радиус 20 * 2
     
        // Если нужно видеть линии коллайдеров для проверки - оставляем:
        // b2World_Draw(worldId, &debugDrawer);
     
        SDL_RenderPresent(renderer);
        return SDL_APP_CONTINUE;
    }

Почему это работает:
  • Центрирование: Box2D считает позицию тела по его центру масс. SDL по умолчанию рисует от угла. Мы вычитаем width / 2, чтобы "центр" картинки совпал с "центром" физического объекта.
  • Синхронизация: Поскольку RenderBody вызывается сразу после b2World_Step, визуальная картинка никогда не будет отставать от физики.
  • Вращение: SDL_RenderTextureRotated — это мощная функция SDL3, которая берет на себя всю математику поворота текстуры на GPU.


  • Используем код Gemini выше. Добавьте функцию RenderBody() перед SDL_AppIterate():
    C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    void RenderBody(b2BodyId bodyId, SDL_Texture *texture, float width, float height)
    {
        // 1. Получаем трансформацию тела из Box2D
        b2Transform transform = b2Body_GetTransform(bodyId);
        b2Vec2 pos = transform.p;
        float angleRadians = b2Rot_GetAngle(transform.q);
     
        // 2. Конвертируем метры в пиксели и радианы в градусы
        float centerX = pos.x * ppm;
        float centerY = pos.y * ppm;
        float angleDegrees = angleRadians * (180.0f / SDL_PI_F);
     
        // 3. Определяем прямоугольник отрисовки
        // SDL рисует от верхнего левого угла, поэтому смещаем координаты на половину размера
        SDL_FRect destRect = {
            centerX - (width / 2.0f),
            centerY - (height / 2.0f),
            width,
            height
        };
     
        // 4. Отрисовка с учетом поворота
        // NULL в качестве центра означает вращение вокруг центра destRect
        SDL_RenderTextureRotated(renderer, texture, NULL, &destRect, (double)angleDegrees, NULL, SDL_FLIP_NONE);
    }
  • Идентификаторы тел делаем глобальными:
    C
    1
    2
    3
    
    static b2BodyId floorBodyId;
    static b2BodyId squareBodyId;
    static b2BodyId circleBodyId;
  • Примечание. Не забудьте удалить b2BodyId перед floorBodyId, squareBodyId и circleBodyId в функции SDL_AppInit()
  • Добавьте код рисования пола плиткой перед SDL_AppIterate():
    C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    void RenderFloorTiled(b2BodyId bodyId, SDL_Texture *texture, float floorWidth, float floorHeight)
    {
        // 1. Получаем позицию из Box2D (в метрах)
        b2Vec2 pos = b2Body_GetPosition(bodyId);
     
        // 2. Переводим координаты центра из метров в пиксели
        float centerX = pos.x * ppm;
        float centerY = pos.y * ppm;
     
        // 3. Целевой прямоугольник для отрисовки всего пола
        SDL_FRect dstRect = {
            centerX - (floorWidth / 2.0f),
            centerY - (floorHeight / 2.0f),
            floorWidth,
            floorHeight
        };
     
        // 4. Вызываем исправленную функцию с 5 аргументами (добавлен 1.0f как масштаб)
        // Параметры: renderer, texture, src_rect (NULL = вся текстура), scale, dst_rect
        SDL_RenderTextureTiled(renderer, texture, NULL, 1.0f, &dstRect);
    }
  • В функции SDL_AppIterate() измените и добавьте код таким образом:
    C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
        // Clear the screen
        SDL_SetRenderDrawColor(renderer, 100, 100, 100, 255);
        SDL_RenderClear(renderer);
     
        SDL_FRect srcRect = { 0, 0, 70, 70 };
        SDL_FRect destRect = { 150, 250, 100, 100 };
     
        // Рисуем ПОЛ плиткой (Tiled)
        RenderFloorTiled(floorBodyId, grass_texture, 300.f, 20.f);
     
        // Рисуем спрайты поверх физических тел
        // Размеры берем те же, что задавали при создании коллайдеров (умноженные на 2, т.к. b2MakeBox берет полуобхват)
        RenderBody(squareBodyId, alien_green_square_texture, 30.f, 30.f);
        RenderBody(circleBodyId, alien_yellow_round_texture, 40.f, 40.f); // диаметр = радиус 20 * 2
     
        b2World_Draw(worldId, &debugDrawer);
  • Примечание. Читайте комментарии в коде
  • Текущий код в src/main.c:
    C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    
    #define SDL_MAIN_USE_CALLBACKS 1 // Use the callbacks instead of main()
     
    #include "debug-drawer.h"
    #include <SDL3/SDL.h>
    #include <SDL3/SDL_main.h>
    #include <box2d/box2d.h>
    #include <stdio.h>
     
    #define TIME_STEP (1.0f / 60.0f)
    #define MAX_FRAME_TIME 0.25f
     
    static SDL_Window *window = NULL;
    SDL_Renderer *renderer = NULL;
    static b2WorldId worldId;
    static b2DebugDraw debugDrawer;
     
    static SDL_Texture *alien_green_square_texture = NULL;
    static SDL_Texture *alien_yellow_round_texture = NULL;
    static SDL_Texture *grass_texture = NULL;
     
    const SDL_PixelFormatDetails *format = NULL;
    float ppm = 30.f; // Pixels per meter
     
    static float accumulator = 0.0f;
    static Uint64 last_ticks = 0;
     
    static b2BodyId floorBodyId;
    static b2BodyId squareBodyId;
    static b2BodyId circleBodyId;
     
    SDL_Texture *LoadTexture(const char *path)
    {
        SDL_Surface *surface = SDL_LoadPNG(path);
        if (!surface)
        {
            SDL_Log("Load failed (%s): %s", path, SDL_GetError());
            return NULL;
        }
        SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
        SDL_DestroySurface(surface);
        return texture;
    }
     
    SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
    {
        if (!SDL_Init(SDL_INIT_VIDEO))
        {
            SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
            return SDL_APP_FAILURE;
        }
     
        if (!SDL_CreateWindowAndRenderer("Example", 400, 400, 0, &window, &renderer))
        {
            SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
            return SDL_APP_FAILURE;
        }
     
        SDL_SetRenderVSync(renderer, 1);
     
        alien_green_square_texture = LoadTexture("./assets/sprites/alienGreen_square.png");
        alien_yellow_round_texture = LoadTexture("./assets/sprites/alienYellow_round.png");
        grass_texture = LoadTexture("./assets/sprites/grass.png");
     
        // Создаём физический мир Box2D с гравитацией
        b2Vec2 gravity = { 0.f, 9.8f };
        b2WorldDef worldDef = b2DefaultWorldDef();
        worldDef.gravity = gravity;
        worldId = b2CreateWorld(&worldDef);
     
        // Выводим значение гравитации в консоль
        // Получаем текущее значение гравитации
        b2Vec2 currentGravity = b2World_GetGravity(worldId);
        // Выводим компоненты x и y
        printf("Текущее значение гравитации: x = %.2f, y = %.2f\n", currentGravity.x, currentGravity.y);
     
        debugDrawer = b2DefaultDebugDraw();
        debugDrawer.drawShapes = true;
        debugDrawer.DrawSolidPolygonFcn = drawSolidPolygon;
        debugDrawer.DrawSolidCircleFcn = drawSolidCircle;
     
        // Floor
        b2BodyDef floorBodyDef = b2DefaultBodyDef();
        floorBodyDef.type = b2_staticBody;
        floorBodyDef.position = (b2Vec2) { 200.f / ppm, 300.f / ppm };
        floorBodyId = b2CreateBody(worldId, &floorBodyDef);
        // Половина ширины 150, половина высоты 10. Итого весь бокс 300x20 пикселей.
        b2Polygon floorShape = b2MakeBox((300.f / 2.f) / ppm, (20.f / 2.f) / ppm);
        b2ShapeDef floorShapeDef = b2DefaultShapeDef();
        b2ShapeId floorShapeId = b2CreatePolygonShape(floorBodyId, &floorShapeDef, &floorShape);
     
        // Square
        b2BodyDef squareBodyDef = b2DefaultBodyDef();
        squareBodyDef.type = b2_dynamicBody;
        squareBodyDef.position = (b2Vec2) { 201.f / ppm, 100.f / ppm };
        squareBodyDef.enableSleep = false;
        squareBodyId = b2CreateBody(worldId, &squareBodyDef);
        b2Polygon squareShape = b2MakeBox((30.f / 2.f) / ppm, (30.f / 2.f) / ppm);
        b2ShapeDef squareShapeDef = b2DefaultShapeDef();
        b2ShapeId squareShapeId = b2CreatePolygonShape(squareBodyId, &squareShapeDef, &squareShape);
     
        // Circle
        b2BodyDef circleBodyDef = b2DefaultBodyDef();
        circleBodyDef.type = b2_dynamicBody;
        circleBodyDef.position = (b2Vec2) { 200.f / ppm, 150.f / ppm };
        circleBodyDef.enableSleep = false;
        circleBodyId = b2CreateBody(worldId, &circleBodyDef);
        b2Circle circleCircle = {
            .center = { 0.0f, 0.0f }, // <--- ВАЖНО: 0,0 означает, что центр круга совпадает с центром тела
            .radius = 20.f / ppm      // Радиус в метрах
        };
        b2ShapeDef circleShapeDef = b2DefaultShapeDef();
        circleShapeDef.density = 1.0f;
        b2ShapeId circleShapeId = b2CreateCircleShape(circleBodyId, &circleShapeDef, &circleCircle);
     
        // Get the pixel format
        SDL_Surface *surface = SDL_GetWindowSurface(window);
        format = SDL_GetPixelFormatDetails(surface->format);
     
        last_ticks = SDL_GetTicks();
     
        return SDL_APP_CONTINUE;
    }
     
    SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
    {
        if (event->type == SDL_EVENT_QUIT)
        {
            return SDL_APP_SUCCESS;
        }
     
        // --- DESKTOP ---
        if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN)
        {
            if (event->button.which != SDL_TOUCH_MOUSEID)
            {
                const char *btnName = (event->button.button == SDL_BUTTON_LEFT) ? "Левая" : "Правая";
                printf("%s кнопка (Мышь): x=%.0f, y=%.0f\n", btnName, event->button.x, event->button.y);
            }
        }
     
        // --- MOBILE / WASM (Touch) ---
        if (event->type == SDL_EVENT_FINGER_DOWN)
        {
            int w, h;
            SDL_GetWindowSize(window, &w, &h);
     
            float pixelX = event->tfinger.x * w;
            float pixelY = event->tfinger.y * h;
     
            // Проверяем количество пальцев на устройстве (touchId берем из события)
            int numFingers = 0;
            SDL_GetTouchFingers(event->tfinger.touchID, &numFingers);
     
            if (numFingers <= 1)
            {
                printf("Левая кнопка (Тап 1 пальцем): x=%.2f, y=%.2f\n", pixelX, pixelY);
            }
            else
            {
                printf("Правая кнопка (Тап несколькими): x=%.2f, y=%.2f\n", pixelX, pixelY);
            }
        }
     
        return SDL_APP_CONTINUE;
    }
     
    void RenderBody(b2BodyId bodyId, SDL_Texture *texture, float width, float height)
    {
        // 1. Получаем трансформацию тела из Box2D
        b2Transform transform = b2Body_GetTransform(bodyId);
        b2Vec2 pos = transform.p;
        float angleRadians = b2Rot_GetAngle(transform.q);
     
        // 2. Конвертируем метры в пиксели и радианы в градусы
        float centerX = pos.x * ppm;
        float centerY = pos.y * ppm;
        float angleDegrees = angleRadians * (180.0f / SDL_PI_F);
     
        // 3. Определяем прямоугольник отрисовки
        // SDL рисует от верхнего левого угла, поэтому смещаем координаты на половину размера
        SDL_FRect destRect = {
            centerX - (width / 2.0f),
            centerY - (height / 2.0f),
            width,
            height
        };
     
        // 4. Отрисовка с учетом поворота
        // NULL в качестве центра означает вращение вокруг центра destRect
        SDL_RenderTextureRotated(renderer, texture, NULL, &destRect, (double)angleDegrees, NULL, SDL_FLIP_NONE);
    }
     
    void RenderFloorTiled(b2BodyId bodyId, SDL_Texture *texture, float floorWidth, float floorHeight)
    {
        // 1. Получаем позицию из Box2D (в метрах)
        b2Vec2 pos = b2Body_GetPosition(bodyId);
     
        // 2. Переводим координаты центра из метров в пиксели
        float centerX = pos.x * ppm;
        float centerY = pos.y * ppm;
     
        // 3. Целевой прямоугольник для отрисовки всего пола
        SDL_FRect dstRect = {
            centerX - (floorWidth / 2.0f),
            centerY - (floorHeight / 2.0f),
            floorWidth,
            floorHeight
        };
     
        // 4. Вызываем исправленную функцию с 5 аргументами (добавлен 1.0f как масштаб)
        // Параметры: renderer, texture, src_rect (NULL = вся текстура), scale, dst_rect
        SDL_RenderTextureTiled(renderer, texture, NULL, 1.0f, &dstRect);
    }
     
    SDL_AppResult SDL_AppIterate(void *appstate)
    {
        Uint64 current_ticks = SDL_GetTicks();
     
        float frameTime = (float)(current_ticks - last_ticks) / 1000.0f;
        last_ticks = current_ticks;
     
        if (frameTime > MAX_FRAME_TIME)
        {
            frameTime = MAX_FRAME_TIME;
        }
     
        accumulator += frameTime;
     
        while (accumulator >= TIME_STEP)
        {
            // Step physics
            b2World_Step(worldId, TIME_STEP, 3);
     
            accumulator -= TIME_STEP;
        }
     
        // Clear the screen
        SDL_SetRenderDrawColor(renderer, 100, 100, 100, 255);
        SDL_RenderClear(renderer);
     
        SDL_FRect srcRect = { 0, 0, 70, 70 };
        SDL_FRect destRect = { 150, 250, 100, 100 };
     
        // Рисуем ПОЛ плиткой (Tiled)
        RenderFloorTiled(floorBodyId, grass_texture, 300.f, 20.f);
     
        // Рисуем спрайты поверх физических тел
        // Размеры берем те же, что задавали при создании коллайдеров (умноженные на 2, т.к. b2MakeBox берет полуобхват)
        RenderBody(squareBodyId, alien_green_square_texture, 30.f, 30.f);
        RenderBody(circleBodyId, alien_yellow_round_texture, 40.f, 40.f); // диаметр = радиус 20 * 2
     
        b2World_Draw(worldId, &debugDrawer);
     
        // Update the screen
        SDL_RenderPresent(renderer);
        return SDL_APP_CONTINUE;
    }
     
    void SDL_AppQuit(void *appstate, SDL_AppResult result)
    {
        if (alien_green_square_texture)
        {
            SDL_DestroyTexture(alien_green_square_texture);
            alien_green_square_texture = NULL;
        }
     
        if (alien_yellow_round_texture)
        {
            SDL_DestroyTexture(alien_yellow_round_texture);
            alien_yellow_round_texture = NULL;
        }
     
        if (grass_texture)
        {
            SDL_DestroyTexture(grass_texture);
            grass_texture = NULL;
        }
     
        // Удаляем физический мир Box2D
        if (b2World_IsValid(worldId))
        {
            b2DestroyWorld(worldId);
        }
     
        // SDL will clean up the window/renderer for us
    }
  • После сборки командой "build-web" и обновления вкладки браузера вы увидите, что графика рисуется внутри коллайдеров:
    Название: finish-sync-physics-sprites-sdl3-c-debug-draw.gif
Просмотров: 5014

Размер: 153.3 Кб
  • Для production нужно отключить рисование коллайдеров, то есть закомментировать эту строку:
    C
    1
    
        // b2World_Draw(worldId, &debugDrawer);
  • Коллайдеры скрыты:
    Название: finish-sync-physics-sprites-sdl3-c.gif
Просмотров: 5037

Размер: 156.7 Кб
  • Для сборки в Release поменяйте Debug на Release в файле config-web.bat
    Bash
    1
    
    emcmake cmake -S . -B dist -DCMAKE_BUILD_TYPE=Release
  • Размер сборки в Release:
    • app.js - 185 KB
    • app.wasm - 916 KB
  • Выполните команды в консоле: "config-web" и "build-web". Теперь папку "public" вы можете загрузить на бесплатный хостинг: GitHub Pages, Firebase и т.д.
  • Итоговый код: finish-sync-physics-sprites-sdl3-c.zip

Миниатюры

Нажмите на изображение для увеличения
Название: 94c24eb3-f142-4548-86f7-8cf4236c00e5.png
Просмотров: 1451
Размер:	12.5 Кб
ID:	11688
Нажмите на изображение для увеличения
Название: c92c8509-a6a5-4679-a466-c82eff7e4ec2.png
Просмотров: 1455
Размер:	9.1 Кб
ID:	11689
Нажмите на изображение для увеличения
Название: 9df82320-e0f3-42f7-92c6-c97caac14db6.png
Просмотров: 1458
Размер:	14.3 Кб
ID:	11692
Вложения
Тип файла: zip start-sync-physics-sprites-sdl3-c.zip (6.7 Кб, 108 просмотров)
Тип файла: zip SDL3-devel-3.4.2-wasm.zip (1.22 Мб, 54 просмотров)
Тип файла: zip box2d-3.1.1-wasm.zip (149.2 Кб, 75 просмотров)
Тип файла: zip kenney_physics-assets.zip (2.46 Мб, 50 просмотров)
Тип файла: zip finish-sync-physics-sprites-sdl3-c.zip (16.5 Кб, 108 просмотров)
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Новые блоги и статьи
SDL3 для Desktop (MinGW): Рисуем цветные прямоугольники с помощью рисовальщика SDL3 на Си и C++
8Observer8 17.03.2026
Содержание блога Финальные проекты на Си и на C++: finish-rectangles-sdl3-c. zip finish-rectangles-sdl3-cpp. zip
Символические и жёсткие ссылки в Linux.
algri14 15.03.2026
Существует два типа ссылок — символические и жёсткие. Ссылка в Linux — это запись в каталоге, которая может указывать либо на inode «файла-ИСТОЧНИКА», тогда это будет «жёсткая ссылка» (hard link),. . .
[Owen Logic] Поддержание уровня воды в резервуаре количеством включённых насосов: моделирование и выбор регулятора
ФедосеевПавел 14.03.2026
Поддержание уровня воды в резервуаре количеством включённых насосов: моделирование и выбор регулятора ВВЕДЕНИЕ Выполняя задание на управление насосной группой заполнения резервуара,. . .
делаю науч статью по влиянию грибов на сукцессию
anaschu 13.03.2026
прикрепляю статью
SDL3 для Desktop (MinGW): Создаём пустое окно с нуля для 2D-графики на SDL3, Си и C++
8Observer8 10.03.2026
Содержание блога Финальные проекты на Си и на C++: hello-sdl3-c. zip hello-sdl3-cpp. zip Результат:
Установка CMake и MinGW 13.1 для сборки С и C++ приложений из консоли и из Qt Creator в EXE
8Observer8 10.03.2026
Содержание блога MinGW - это коллекция инструментов для сборки приложений в EXE. CMake - это система сборки приложений. Здесь описаны базовые шаги для старта программирования с помощью CMake и. . .
Как дизайн сайта влияет на конверсию: 7 решений, которые реально повышают заявки
Neotwalker 08.03.2026
Многие до сих пор воспринимают дизайн сайта как “красивую оболочку”. На практике всё иначе: дизайн напрямую влияет на то, оставит человек заявку или уйдёт через несколько секунд. Даже если у вас. . .
Модульная разработка через nuget packages
DevAlt 07.03.2026
Сложившийся в . Net-среде способ разработки чаще всего предполагает монорепозиторий в котором находятся все исходники. При создании нового решения, мы просто добавляем нужные проекты и имеем. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru