Repository navigation
Levels
Уровень - это картинка. Никакого своего формата, никакого текстового описания:
Level_LoadTileMap 0x0001261c читает .bmp пиксель за пикселем и получает
сетку проходимости.
| Уровень | Ресурс | Файл | Тема |
|---|---|---|---|
| 0 |
JB_RES_LEVEL_MAP_1 309 |
BITMAP/309.bmp |
ICE (0) |
| 1 |
JB_RES_LEVEL_MAP_2 333 |
BITMAP/333.bmp |
DESERT (1) |
| 2 |
JB_RES_LEVEL_MAP_3 334 |
BITMAP/334.bmp |
GRASS (4) |
Выбор - три сравнения подряд, причём третье level > 1, так что любой номер
больше единицы даёт третий уровень (0x00012628, 0x00012648, 0x00012654).
Если бы ни одно не сработало, оригинал ушёл бы читать неинициализированный
указатель (ldrne r8,[sp,#0x0] на 0x00012644); порт в этом случае просто
выходит.
for (row = 0; row < JB_GRID_ROWS; row++) { /* 1020 строк */
const uint16_t *p = src;
for (col = 0; col < JB_MAP_COLS; col++, p++) { /* 16 столбцов */
dst[col] = (*p != Color_Pack16If16bpp(screen, 0x00ffffffu));
if (*p == Color_Pack16If16bpp(screen, 0x000000ffu)) { /* чекпоинт */ }
}
dst += JB_MAP_COLS;
src += map1->w;
}Правила ровно два:
- белый пиксель -> 0, всё остальное -> 1. Единица значит «по этому тайлу можно катиться», ноль - провал (вода, трава, пропасть). Никаких промежуточных типов тайлов в игре нет.
-
чисто красный пиксель -> чекпоинт:
COLORREF 0x000000ff, то есть R = 0xff, G = B = 0. Он одновременно и тайл (не белый, значит проходимый), и метка.
Важная тонкость: шаг строки источника берётся у первого битмапа для всех
трёх. 0x000126c0 (ldr r0,[r9,#0x54]; ldr r3,[r0,#0x4]) читает ширину
g_levelMap1 независимо от выбранного уровня, поэтому 333 и 334 обязаны иметь
ту же ширину, что 309 - иначе карта «поедет по диагонали». Порт повторяет это
буквально.
16 x 1024 объявлено (g_mapCols 0x0002628c, g_mapRows 0x00026274), но
заполняется только 0x3fc = 1020 строк. Остальное - обнулённый хвост, и он
нужен: рисовальщик читает 17 строк вперёд от cam_row, а cam_row доходит до
0x3fe. Поэтому массив в порте выделен с запасом:
#define JB_GRID_ALLOC_ROWS (JB_GRID_ROWS + JB_VISIBLE_ROWS + 2) /* 1020 + 17 + 2 */Оригинал полагался на то, что за g_tileGrid 0x00031178 лежит ещё .bss;
порт делает это явным - см. Deviations.
Две таблицы по 50 элементов (g_checkpointX 0x000612b0,
g_checkpointY 0x000611e8, ровно 0xc8 байт друг от друга), индексируемые с
1, а не с 0. Счётчик красных пикселей в оригинале ничем не ограничен
(addeq r10,r10,#0x1 на 0x000126a4), то есть 51-й чекпоинт в картинке писал
бы за границу таблицы. Порт ограничивает обход - единственное отступление в
этом файле, и оно намеренное.
Как чекпоинт применяется при падении - в Physics (PlaceAtCheckpoint:
ball_x = (8 - x) - 0.5, ball_row = y - 0.2, только при cam_row > 0x46).
Stage_Begin (оригинал Level_Begin 0x0001376c) выводит из номера уровня всё
оформление:
theme = level;
if (theme == 2) theme = 4; /* GRASS */
if (theme == 3) theme = 1; /* DESERT */| Тема | Фон | Ресурс |
|---|---|---|
| ICE 0 | лёд | 321 |
| DESERT 1 | пустыня | 316 |
| FOREST 2 | лес | 322 |
| SKY 3 | небо | 324 |
| GRASS 4 | лёд (321) | - |
Цвет HUD - следствие темы: на льду (0,0,0), во всех остальных 250,250,250
(g_hudR/G/B 0x00026280..0x00026288). Этим же цветом красится маска мяча, так
что на первом уровне мяч тёмный, а дальше светлый - одна переменная на два
разных визуальных решения.
st->time_sec = -1; /* Level_Begin 0x0001376c */
st->time_min = 0;Отрицательная секунда - это пауза перед стартом: Stage_Frozen() возвращает
time_sec < 0, и jb_main.c на это время не вызывает физику
(WndProc 0x00020934 уходит сразу в Game_DrawFrame). Stage_Step прибавляет
секунду каждые 1000 мс и переносит в минуты на 0x3c.
Финиш - cam_row > 0x3fe (WndProc 0x00020920): пауза 1000 мс,
max_unlocked = level + 1, возврат в меню выбора уровня. Никакого «экрана
результатов» в игре нет.
Открыть BITMAP/309.bmp (или 333/334), сохранив ширину, и работать тремя
цветами: чёрный (любой не белый) - трасса, белый - провал, чисто
красный #FF0000 - чекпоинт. Строка 0 картинки - старт. Практические детали
и ограничения - в README.md репозитория.
Устройство порта
Сборка и происхождение