Repository navigation
Physics
Весь шаг физики - Player_Step() в jb_player.c, прямой перенос тела
WM_TIMER из WndProc 0x000208f8..0x00021268. Три координаты, три скорости,
никаких коллизий кроме одной проверки байта тайла.
| Поле | Глобал оригинала | Смысл |
|---|---|---|
ball_x |
g_ballX 0x00064b0c |
поперёк трассы, в тайлах |
ball_row |
g_ballRow 0x00064b08 |
вдоль трассы, в строках сетки |
ball_y |
g_ballY 0x00064b10 |
высота над трассой |
ball_prev_x |
g_ballPrevX 0x0006118c |
ball_x предыдущего кадра |
vel_x, vel_z, vel_y
|
0x000649d4 / 0x000649cc / 0x000649d8
|
скорости |
spin |
g_ballSpin 0x00064994 |
фаза вращения |
ball_y - настоящая высота: пока она больше нуля, мяч в воздухе. ball_row
одновременно и позиция, и камера (см. Track-and-camera).
Базовая величина - accel = 7.0f * dt (kAccelX 0x000262ac). Дальше
асимметрия, которую легко потерять при «аккуратной» переписи:
if (left_down) vel_x += accel * 2.0f;
else if (left_released) { vel_x -= accel; if (vel_x <= 0) { vel_x = 0; left_released = 0; } }
if (down_down) vel_z -= (vel_z >= 0.0f) ? accel * 2.0f : accel;- Нажатие даёт двойное ускорение, отпускание - одинарное торможение. Разгон вдвое быстрее, чем остановка.
- Торможение живёт на отдельном флаге
*_released, который ставится вWM_KEYUPи снимается сам, когда скорость дошла до нуля. Просто «нет нажатия» ничего не тормозит. - Тормоз (
down) даёт двойную величину, только если мяч ещё едет вперёд; при движении назад - одинарную.
Есть и ловушка, унаследованная целиком: гашение по оси Z заперто на флаге
z_axis_damped (DAT_00064940). В оригинале у этого глобала нет ни одной
записи, в порте он выставлен в 0 и не меняется, поэтому ветки затухания
up/down не выполняются никогда - vel_z держится, пока его не сбросят
явно. Порт хранит эту логику как есть, а не «исправляет».
ball_row += vel_z * dt;
ball_x += vel_x * dt;
ball_y += vel_y * dt;
if (ball_x > (float)map_cols) ball_x = (float)map_cols; /* 0x00020ecc */
if (ball_x <= -16.0f) ball_x = -16.0f; /* 0x00020ef4 */ball_row < 0 обнуляется вместе с vel_z - назад за старт уехать нельзя.
Одна проверка на кадр, и та с двумя странностями:
row = (int)((double)ball_row + 0.58);
col = (int)(ball_prev_x + (ball_prev_x > 0.0f ? 1.0f : 0.0f));
tile = tile_grid[8 + row * map_cols - col];-
0.58 - не «примерно половина». Это точный
doubleиз литерального пула (0x3fe28f5c/0x28f5c28fпо адресам0x0021119c/0x000211a4); мяч «стоит» чуть впереди своей строки. - Индекс идёт вычитанием столбца от базы
+8: сетка читается зеркально относительно центра (WndProc 0x00020f74индексирует&DAT_00031180, то естьg_tileGrid + 8). - Столбец вне
1..map_colsдаёт тот же результат, что пустой тайл (0x00020fbc/0x00020fe0ведут в общийLAB_00020fa4).
Пустой тайл под мячом при ball_y <= 0 означает одно из двух:
if (auto_jump == 1 && ball_y == 0.0f) vel_y = JB_JUMP_IMPULSE; /* автопрыжок */
else fall_flag = 1; /* падение */auto_jump (g_autoJump 0x00026430) по умолчанию 1 и переключается в меню -
это тот самый пункт настроек, а не отладочный флаг.
case JB_KEY_JUMP:
if (vel_y == 0.0f) vel_y = JB_JUMP_IMPULSE; /* 10.0, 0x000262b4 */
else jump_buffered = 1;Сравнение с нулём точное: прыжок в воздухе не работает, но нажатие не теряется - оно запоминается и тратится в момент касания:
if (ball_y < 0.0f) {
landed = 1; ball_y = 0.0f; vel_y = 0.0f;
if (jump_buffered) { jump_buffered = 0; vel_y = JB_JUMP_IMPULSE; }
}Гравитация - vel_y -= 20.0f * dt (kGravity 0x00026434), и только при
vel_y != 0.0: лежащий на трассе мяч не «набирает» отрицательную скорость.
Флаг landed порт поднимает здесь, а звук отбоя играет jb_main.c - ровно
там, где оригинал звал hssSpeaker::playSound перед записью 0.0 в g_ballY.
if (fall_flag == 1) {
vel_y = -11.0f;
ball_y -= dt * 11.0f;
if (ball_y < -20.0f) Player_Respawn(st);
}Player_Respawn (0x00012f18) ставит ball_y = 5.0, ball_x = 0,
ball_row = 0, vel_y = -1.0, гасит остальные скорости и сбрасывает все
клавиши. Потом - поиск чекпоинта (PlaceAtCheckpoint):
- работает только при
cam_row > 0x46(cmp r0,#0x46); - граница -
cam_row - 0x14, перебираются чекпоинты с индекса 1, пока их строка меньше границы, и берётся последний подходящий; -
ball_x = (8 - checkpoint_x[k]) - 0.5f- снова зеркальное «8 минус столбец», плюс полтайла к центру; -
ball_row = checkpoint_y[k] - 0.2f- чуть перед линией.
Камеру Player_Respawn не трогает: оригинал выводит её из g_ballRow на
следующем WM_TIMER, порт делает то же в конце функции.
spin = (int)(vel_z * dt * 350.0f + (float)spin); /* 0x00021094 */и заворачивается в 0..0x240 двумя беззнаковыми делениями - вверх на
0x000210f4, вниз на 0x000210cc. Дальше jb_ball.c превращает фазу в кадр:
frame = (int)((double)spin * 0.03125); /* 1/32 */
src_x = (frame * 70) % (9 * 70); /* 0x276 = 630 */0x240 = 576, 576 / 32 = 18, полоса шириной 0x276 = 630, поэтому за один
цикл спина полоса из девяти кадров прокручивается дважды.
Мяч рисуется не как картинка, а как маска: Blit_Glyph с цветом
hud_r/g/b (250, g_hudR 0x00026280) - в 331.bmp лежит только покрытие.
Тень - отдельный спрайт 74x48, рисуется лишь при ball_y >= 0.0, со своим
смещением 40.0 + ball_y * 6.0 (0x0001ec38, 0x0001ec80), поэтому «отрыв»
тени от мяча в прыжке - оригинальное поведение, а не артефакт порта.
Оба блита сдвинуты влево от центра: -0x23 для мяча, -0x25 для тени
(0x0001ef08, 0x0001ecac).
Устройство порта
Сборка и происхождение