Skip to content

Graphics

Владислав edited this page Sep 5, 2026 · 1 revision

Графика

Вся отрисовка идёт в один 16-битный буфер и делается собственными блиттерами - ни SDL_Renderer, ни SDL_Surface в игровой логике нет. SDL получает готовый кадр один раз за проход: Platform_Present() заливает его в текстуру и выводит.

Поверхность и спрайт

typedef struct {
    uint16_t *pixels;
    int       bpp;
    int       fmt;      /* 0x80 = RGB565, 0x40 = RGB555 */
    int       x_pitch;  /* шаг между пикселями в элементах uint16_t */
    int       y_pitch;  /* шаг между строками */
} jb_surface;

typedef struct {
    uint16_t *pixels;
    int       w;        /* и ширина, и шаг строки источника */
    int       h;
} jb_sprite;

Оригинал читал приёмник как массив int по фиксированным индексам: [0] пиксели, [6] bpp, [7] формат, [9] шаг по X, [10] шаг по Y (Blit_Core 0x00021a98). Спрайт - блок 0xc байт из Sprite_LoadResourceBitmap 0x00021a04: пиксели, ширина, высота, причём ширина работает и как шаг строки.

Отсечение - три глобала (0x000269e0 / 0x000269e4 / 0x000269e8) с начальными значениями 240 / 320 / 320; в порте это jb_clip_w, jb_clip_h, jb_clip_h_row.

Цвет

Функция Оригинал Смысл
Color_Pack16 0x00023bb4 COLORREF (0x00BBGGRR) -> RGB565
Color_Pack16If16bpp 0x00024030 то же, но 0, если приёмник не 16 bpp
Color_Blend 0x00023c3c смешивание, процент 0..100
Color_MaskTint 0x00023ddc тинт по маске

Две тонкости, из-за которых легко получить «почти правильную» картинку:

  • Color_Blend взвешивает пиксель ПРИЁМНИКА, а не источника. Процент 0 отдаёт источник без изменений. Перепутанное направление даёт визуально похожий, но не тот результат на воде и на свечении.
  • В Color_MaskTint покрытие берётся из СИНЕГО канала источника, а (r,g,b) - это цвет, которым красят. Так работает и шрифт, и тень мяча: в битмапе лежит маска, цвет приходит аргументом.

Ключ прозрачности - 0xffff означает «ключа нет» (JB_NO_KEY).

Блиттеры

Десять штук; каждый - обёртка над ядром, отличается только тем, как считает шаги по источнику и что делает с пикселем.

Функция Оригинал Что делает
Blit_NoKey 0x00023a3c копия 1:1, без ключа
Blit 0x00023b78 копия 1:1 с ключом
Blit_Core 0x00021a98 ядро: копия с ключом и явными src_x/src_y
Blit_Keyed 0x000239f8 копия с ключом и blend
Blit_Glyph 0x000239ac по маске, цветом (r,g,b) - шрифт, тень
Blit_TileH 0x00023aa8 одна строка приёмника, масштаб по X
Blit_TileHV 0x00023a7c масштаб по X и Y в прямоугольник w x h
Blit_TileH_Ofs 0x00023b44 Blit_TileH + Color_Blend на каждый пиксель
Blit_TileHV_Ofs 0x00023ad4 Blit_TileHV + Color_Blend
Blit_TileHV_Glyph 0x00023b08 Blit_TileHV + тинт по маске, без ключа

Blit_TileH возвращает номер выбранной строки таблицы масштаба - трасса использует это возвращаемое значение, поэтому его нельзя «упростить» до void.

Таблицы масштаба

Gfx_BuildScaleTables() (оригинал Gfx_CreateBackBuffer 0x00021698) строит три таблицы шагов - для каждой целевой ширины заранее посчитано, как идти по источнику:

Таблица Источник Шаг таблицы Ширины
0x00068518 100 px 200 1..199
0x00065ed0 64 px 70 1..63
0x00064b48 44 px 50 1..49

Первая - основная для тайлов трассы: тайл шириной 100 пикселей растягивается на любую ширину строки без делений в кадре. Отсюда же характерный «пиксельный» вид дальних строк - шаг по источнику берётся из таблицы, а не считается с плавающей точкой.

Четвёртая таблица, вертикальная, живёт в jb_track.c: Gfx_BuildTexVStep() заполняет 299 строк по 300 элементов (g_texVStep 0x00035238), строка h содержит h значений - шаги по вертикали для строки высотой h.

Загрузка картинок

Оригинал держал битмапы в ресурсах PE и брал их LoadBitmapW по числовому id. Порт читает те же id как файлы: id 254 -> BITMAP/254.bmp (jb_assets.c), а jb_bmp.c разбирает .bmp в jb_sprite с конвертацией в формат приёмника. Корень ассетов - рядом с исполняемым файлом либо JUMPYBALL_ASSETS; на Android чтение идёт через SDL_RWops прямо из APK.

Соответствие «id ресурса -> что это» полностью выписано в jb_consts.h, от JB_RES_FONT (254) до JB_RES_BALL (331) - см. Levels и Track-and-camera.

Шрифты

Их два, и они устроены по-разному (Font_Load 0x0001f4d8):

  • размер 1: BITMAP/254.bmp, границы глиф берутся сканированием строки 0 на парные маркеры 0xff00ff;
  • размер 2: BITMAP/332.bmp, границы - столбцы, чёрные на всю высоту листа.

Оба заполняют одни и те же таблицы ширин и X-смещений (g_glyphWidth 0x00027298, g_glyphX 0x00027698). Меню зовёт размер 1 (Screen_Set 0x00013678), игра - размер 2 (Player_Respawn 0x00012f18), поэтому HUD в игре крупнее.

Clone this wiki locally