Содержание блога
Если вы откроете примеры для начинающих на официальном репозитории SDL3 в папке: examples, то вы увидите, что все примеры используют следующие четыре обязательные функции, а привычная функция main() отсутствует:
- SDL_AppInit (Initialization) - эта функция срабатывает один раз самой первой.
- SDL_AppEvent (Event Handler) - эта функция срабатывает каждый раз, когда происходит какое-то событие, например, нажатие клавиши, движение мышью, клик мышью, изменение размеров окна и т.д.
- SDL_AppIterate (Main Loop) - эта функция срабатывает множество раз в секунду. Здесь можно рассчитать dt (delta time) и сделать анимацию, скорость которой не будет зависеть от числа кадров в секунду, который поддерживает монитор.
- SDL_AppQuit (Clean up) - эта функция сработает один раз перед закрытием приложения. Здесь нужно располагать код для освобождения ресурсов.
Эти функции позволяют собирать один и тот же код для Android, Desktop и WebAssembly без лишних #ifdef и дублирования логики — например, без специфичного для браузера цикла при сборке через Emscripten.
Рассмотрим пример 01-clear в файле clear.c. Я попросил Gemini 3 перевести комментарии на русский, не меняя ничего в коде, даже отступы. Прочитайте этот код. Далее я пошагово опишу, как запустить его на Android:
| 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
| /*
* Этот пример кода создает окно и рендерер SDL, а затем очищает
* окно разным цветом в каждом кадре, так что вы фактически получите окно,
* цвет которого плавно меняется.
*
* Этот код является общественным достоянием. Не стесняйтесь использовать его в любых целях!
*/
#define SDL_MAIN_USE_CALLBACKS 1 /* использовать функции обратного вызова вместо main() */
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
/* Мы будем использовать этот рендерер для отрисовки в этом окне в каждом кадре. */
static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
/* Эта функция запускается один раз при запуске. */
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
SDL_SetAppMetadata("Example Renderer Clear", "1.0", "com.example.renderer-clear");
if (!SDL_Init(SDL_INIT_VIDEO)) {
SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
if (!SDL_CreateWindowAndRenderer("examples/renderer/clear", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) {
SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
SDL_SetRenderLogicalPresentation(renderer, 640, 480, SDL_LOGICAL_PRESENTATION_LETTERBOX);
return SDL_APP_CONTINUE; /* продолжаем выполнение программы! */
}
/* Эта функция запускается, когда происходит новое событие (ввод мыши, нажатия клавиш и т. д.). */
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
if (event->type == SDL_EVENT_QUIT) {
return SDL_APP_SUCCESS; /* завершаем программу, сообщая ОС об успешном завершении. */
}
return SDL_APP_CONTINUE; /* продолжаем выполнение программы! */
}
/* Эта функция запускается один раз за кадр и является сердцем программы. */
SDL_AppResult SDL_AppIterate(void *appstate)
{
const double now = ((double)SDL_GetTicks()) / 1000.0; /* преобразуем миллисекунды в секунды. */
/* выбираем цвет для кадра, который будем рисовать. Трюк с синусоидой заставляет цвета плавно сменять друг друга. */
const float red = (float) (0.5 + 0.5 * SDL_sin(now));
const float green = (float) (0.5 + 0.5 * SDL_sin(now + SDL_PI_D * 2 / 3));
const float blue = (float) (0.5 + 0.5 * SDL_sin(now + SDL_PI_D * 4 / 3));
SDL_SetRenderDrawColorFloat(renderer, red, green, blue, SDL_ALPHA_OPAQUE_FLOAT); /* новый цвет, полная непрозрачность. */
/* очищаем окно цветом отрисовки. */
SDL_RenderClear(renderer);
/* выводим свежеочищенный рендер на экран. */
SDL_RenderPresent(renderer);
return SDL_APP_CONTINUE; /* продолжаем выполнение программы! */
}
/* Эта функция запускается один раз при завершении работы. */
void SDL_AppQuit(void *appstate, SDL_AppResult result)
{
/* SDL сама очистит окно/рендерер за нас. */
} |
|
Далее мы по шагам рассмотрим, как собрать этот пример в WASM, запустить в браузере локально и загрузить на бесплатный хостинг Vercel из командной строки.
Если у вас не установлены Emscripten SDK и CMake, то установите их по следующей инструкции: Установка Emscripten SDK (emsdk) и CMake
- Скачайте стартовый пример: hello-wasm-sdl3-c.zip
- Проверьте работоспособность этого примера. В инструкции по установке Emscripten SDK мы запускали этот пример командами:
| Bash | 1
2
3
| config-web
build-web
http-server -c-1 |
|
- Перейдите в браузер и введите адрес: localhost:8080 и нажмите Enter
- Вы увидите результат работы программы в консоли браузера, если нажмёте: Ctrl+Shift+J в Chrome и Edge, либо Ctrl+Shift+K в FireFox
- Запустите в корне папки проекта вторую консоль, чтобы дальше можно было переконфигурировать и собирать проект в этой консоли, а в первой консоли пусть запущен локальные сервер
- Откройте скаченный пример в каком-нибудь редакторе кода, например, Notepad++, а лучше в Sublime Text 4: https://www.sublimetext.com/download
- Скачайте архив SDL3-devel-3.4.0-wasm.zip. Эта библиотека была собрана из исходников с помощью Emscripten SDK 4.0.15
- Создайте на каком-нибудь локальном диске папку "libs", например, на диске C и разархивируйте скаченный архив в папку "libs"
- Укажем путь к этой библиотеке в файле CMakeLists.txt. Добавим find_package и target_link_libraries. Читайте комментарии:
CMakeLists.txt
| Code | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| cmake_minimum_required(VERSION 3.21)
project(hello-wasm-sdl3-c)
# Задаем название будущего приложения (в Windows это был бы app.exe, а в вебе будет app.js / app.wasm)
add_executable(app)
# Устанавливаем стандарт C
set(CMAKE_C_STANDARD 11)
# Подсказываем CMake, где искать конфигурационные файлы библиотеки SDL3
set(SDL3_DIR "C:/libs/SDL3-devel-3.4.0-wasm/lib/cmake/SDL3")
# Загружаем настройки пакета SDL3 (параметры компиляции и пути к заголовкам)
find_package(SDL3 REQUIRED)
# Привязываем SDL3 к нашему приложению (настройка линковки и путей include)
target_link_libraries(app PRIVATE SDL3::SDL3)
# Добавляем исходный код к проекту
target_sources(app
PRIVATE
src/main.c
) |
|
- Сейчас в примере в файле main.c такой код:
| C | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
| #include <stdio.h>
#include <emscripten.h>
void loop()
{
// Пусто
}
int main()
{
printf("Привет из WebAssembly на Vercel!\n");
emscripten_set_main_loop(loop, 0, 1); // Это не даст программе закрыться
return 0;
} |
|
- Заменим этот код на код из официального примера, который был показан в начале этого сообщения (пример 01-clear)
- Откройте в редакторе кода файл "public/index.html" и добавьте элемент <canvas> и удалите элемент <title> (потому что Title задаются в функции SDL_CreateWindowAndRenderer - в первом аргументе этой функции):
index.html
| PHP/HTML | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
| <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<canvas id="canvas"></canvas>
<script async src="./js/app.js"></script>
</body> |
|
- Введите команды конфигурирования и сборки (на самом деле можно команду build-web, а конфигурирование произойдёт по автомату, потому что CMake проверяет не был ли изменён файл CMakeLists.txt)
- Перейдите в браузер и введите адрес: localhost:8080 и нажмите Enter
- Обратите внимание, что локальный сервер http-server выводит адреса, например, у меня:
| Code | 1
2
3
4
5
| Available on:
http://100.116.245.193:8080
http://192.168.56.1:8080
http://127.0.0.1:8080
Hit CTRL-C to stop the server[*]Вы можете взять телефон, который подключён к Wi-Fi и ввести в его браузере адрес 192.168.56.1:8080 и приложение запустится на Android[*]Загрузите это веб-приложение на бесплатный хостинг Vercel по следующей инструкции: [URL="https://www.cyberforum.ru/blogs/416874/10735.html"]Установка Emscripten SDK (emsdk) и CMake[/URL] (пролистайте до "Загрузка веб-приложения на бесплатный хостинг Vercel") |
|
|