libbgi: спрайты — кэш адреса кадра, DDA+asm тик, Y-сортировка со слоями
Оптимизации A+B (профиль rpgwalk-15: активная часть кадра 410К → 307К
тактов из 430080; лимит спрайтов 16×16 на стабильные 48.8 fps: 14 → ~21):
- (B) sprite_t.src/stride — готовый адрес кадра: считают только
sprite_frame (теперь функция, одно умножение на СМЕНУ кадра) и тикер
(±an_step БАЙТ инкрементально); блит-ядра принимают src+stride,
img/sx/sy из сигнатуры ушли. Блит 177К → 146К на кадр.
- (A) тик 100К → 37.7К: tween переформулирован Брезенхэм → беззнаковый
DDA (mv_rem/mv_acc, «приехали» = rem==0 — без знаковых 16-бит
сравнений), tick_move и tick_anim — ручной asm (SDCC спиллит такие
функции в IX-фрейм ~100 обращений; C-реструктуризации не помогали —
проверено кодогеном). Биты an_flags переименованы по категориям
(_SPR_STRIP_HORZ, _SPR_PP_BACK).
Y-сортировка (gfx_sprite_ysort, идеи пользователя — 8-бит ключ,
персистентность):
- painter's algorithm по ключу {layer:8, clamp_y:8}; поле
sprite_t.layer (в КОНЦЕ структуры — asm-офсеты не сдвигает): слои
сцены в одном массиве/одном sprite_update;
- ПЕРСИСТЕНТНАЯ asm-таблица {key16, ptr16}: resort порядка прошлого
кадра (почти линейно), rebuild при смене arr/count; массив
приложения не трогается; ~28К/15 спрайтов (с нуля было 44К);
- компоненты YSORT_Y/YSORT_LAYER отключаемы независимо масками ключа
(без ветвлений в сортировщике); ВНИМАНИЕ: mode=1 значит Y-only,
полный порядок = YSORT_Y|YSORT_LAYER;
- funcptr-DCE: выключено = код и таблица не линкуются (rpgwalk −190 Б);
- ПРАВИЛО в sprite.h: два sprite_update на страницу запрещены (heal
второй группы стирает спрайты первой — ОЗУ-копия чистая).
Попутные фиксы:
- libbgi/Makefile: .rel зависят от заголовков (HDRS) — stale .rel со
старой раскладкой sprite_t молча ломал рантайм;
- rpgprof: --memory small (перерос tiny: BSS вылезал за W2 → мгновенный
«Unexpected application termination»; mkexe это пока не ловит);
- tests/spranim: проверки переведены на кэш src, добавлены T6 (reframe
после тикера) и T7 (Y-сортировка: порядок, слои, персистентный
resort, LAYER-only) — 7/7 PASS в MAME.
Доки: §9д — новый бюджет (19.5К/спрайт), §9е — ПЛАН FPS-делителя
(frame pacing, gfx_set_fps_div); TODO — дизайн цепочки irq-обработчиков.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+366
-75
@@ -2,93 +2,384 @@
|
||||
* _sprite_tick — тикер авто-анимаций (§9г): вызывается из sprite_update
|
||||
* (раз на КАДР, до heal/блит-проходов — dirty должен взводиться раньше).
|
||||
* Кадровая анимация и tween-перемещение НЕЗАВИСИМЫ (идут одновременно);
|
||||
* тикаются и у скрытых спрайтов («время идёт», ветка дороже).
|
||||
* тикаются и у скрытых спрайтов («время идёт»).
|
||||
*
|
||||
* Кадровая: смена кадра = ±an_step к оси ленты (sy; при _SPR_ANIM_HORZ
|
||||
* — sx) — БЕЗ умножения; wrap цикла — прыжок оси в an_lo (посчитан
|
||||
* sprite_anim'ом один раз). Маятник разворачивается НА границе (…2,3,
|
||||
* 2,1,0,1…, без удвоения крайних). ANIM_ONCE замирает на last:
|
||||
* SPR_ANIM_ON снимается, SPR_ANIM_DONE взводится.
|
||||
* ГОРЯЧИЙ ПУТЬ (профиль rpgwalk 2026-07-13: тик ел 7.3К тактов/спрайт —
|
||||
* №2 после блита; переписан 2026-07-14):
|
||||
* - один ранний выход на спрайт без активных анимаций;
|
||||
* - декремент таймеров — в цикле, до call'а: недозревший таймер не
|
||||
* платит за вызов хелпера;
|
||||
* - сама смена кадра/порция перемещения — в static-хелперах
|
||||
* tick_anim/tick_move: читаемость + SDCC работает с ЛОКАЛЬНЫМИ
|
||||
* копиями полей в регистрах вместо IX-обращений (~19Т каждое)
|
||||
* на каждый чих;
|
||||
* - кадровая анимация двигает КЭШ АДРЕСА кадра (s->src ± an_step
|
||||
* байт; wrap — прыжок на готовый an_src0) — ни выбора оси, ни
|
||||
* пиксельных смещений: всё посчитано sprite_anim'ом один раз.
|
||||
*
|
||||
* Перемещение: раз в mv_speed кадров — порция ≤mv_step итераций
|
||||
* классического Брезенхэма (err-аккумулятор: большая ось шагает каждую
|
||||
* итерацию, меньшая подтягивается — нелинейные 2/3/2/3 сами собой,
|
||||
* деления нет). Цель достигнута — SPR_MOVE_ON→off, SPR_MOVE_DONE.
|
||||
* Кадровая: маятник разворачивается НА границе (…2,3,2,1,0,1… — без
|
||||
* удвоения крайних); ANIM_ONCE замирает на last: SPR_ANIM_ON снимается,
|
||||
* SPR_ANIM_DONE взводится. sx/sy НЕ обновляются (см. sprite.h: их
|
||||
* абсолютно переустановит следующий sprite_frame/sprite_anim).
|
||||
*
|
||||
* Перемещение: раз в mv_speed кадров — порция ≤mv_step шагов главной
|
||||
* оси беззнакового DDA (минорная подтягивается аккумулятором —
|
||||
* нелинейные 2/3/2/3 сами собой, деления нет; знаковых 16-бит
|
||||
* сравнений Брезенхэма нет — на Z80 они были главной статьёй расхода).
|
||||
* Цель достигнута (mv_rem == 0) — SPR_MOVE_ON→off, SPR_MOVE_DONE.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
#include <sprite.h>
|
||||
|
||||
/* Одна смена кадра кадровой анимации (таймер уже дозрел).
|
||||
*
|
||||
* РУЧНОЙ ASM (2026-07-14): та же болезнь SDCC, что у tick_move —
|
||||
* C-версия спиллилась в IX-фрейм (~120 обращений, ~4-5К тактов на
|
||||
* вызов); asm ~1К. При медленных анимациях (speed ≥ 5) вклад мал,
|
||||
* но при speed 1-2 tick_anim зовётся на каждый кадр для каждого
|
||||
* спрайта и становился главной статьёй тика.
|
||||
*
|
||||
* C-эквивалент (семантика 1:1, тест tests/spranim T1/T2/T3/T6):
|
||||
*
|
||||
* s->an_timer = s->an_speed;
|
||||
* if (!(fl & PP_BACK)) { // вперёд
|
||||
* if (fr < last) { fr++; src += step; }
|
||||
* else if (fl & ONCE) { ON→off, DONE→on; return; } // не грязнить
|
||||
* else if (fl & PP) { BACK→on; // разворот на границе:
|
||||
* if (fr > first) { fr--; src -= step; } else нет-шага; }
|
||||
* else ..LOOP.. { fr = first; src = an_src0; }
|
||||
* } else { // маятник, обратно
|
||||
* if (fr > first) { fr--; src -= step; }
|
||||
* else { BACK→off; // разворот на first
|
||||
* if (fr < last) { fr++; src += step; } else нет-шага; }
|
||||
* }
|
||||
* если шаг был: an_frame = fr; s->src = src; flags |= DIRTY;
|
||||
*
|
||||
* Офсеты полей sprite_t (см. пробник в шапке tick_move):
|
||||
* src=2 flags=16 an_flags=18 an_frame=19 an_first=20 an_last=21
|
||||
* an_speed=22 an_timer=23 an_step=24 an_src0=26
|
||||
* Биты an_flags: ON=0x01 DONE=0x02 PP_BACK=0x10 PP=0x40 ONCE=0x80.
|
||||
* Вход __sdcccall(1): s → HL. IX callee-saved.
|
||||
*/
|
||||
static void tick_anim(sprite_t *s) __naked
|
||||
{
|
||||
(void)s;
|
||||
__asm
|
||||
push ix
|
||||
push hl
|
||||
pop ix ; IX = s
|
||||
|
||||
;; an_timer = an_speed — перезарядить темп
|
||||
ld a, 22 (ix)
|
||||
ld 23 (ix), a
|
||||
|
||||
ld b, 19 (ix) ; B = fr (текущий кадр)
|
||||
ld c, 18 (ix) ; C = an_flags
|
||||
bit 4, c ; _SPR_PP_BACK?
|
||||
jr NZ, ta_backward
|
||||
|
||||
;; ---- ход вперёд (LOOP/ONCE всегда; маятник — фаза «туда») ----
|
||||
ld a, b
|
||||
cp a, 21 (ix) ; CF = (fr < an_last)
|
||||
jr NC, ta_at_last
|
||||
inc b ; шаг вперёд: fr++
|
||||
call ta_src_fwd ; src += an_step
|
||||
jr ta_commit ; commit
|
||||
ta_at_last:
|
||||
;; fr == last: конец отрезка — по режиму
|
||||
bit 7, c ; _SPR_ANIM_ONCE?
|
||||
jr Z, ta_not_once
|
||||
;; ONCE: замереть на last, отчитаться, НЕ грязнить
|
||||
ld a, c
|
||||
and a, #0xFE ; ~SPR_ANIM_ON
|
||||
or a, #0x02 ; SPR_ANIM_DONE
|
||||
ld 18 (ix), a
|
||||
jr ta_done ; выход без dirty
|
||||
ta_not_once:
|
||||
bit 6, c ; _SPR_ANIM_PP?
|
||||
jr Z, ta_loop_wrap
|
||||
;; маятник: разворот на границе — сразу шаг назад
|
||||
;; (…2,3,2,… — крайний кадр не удваивается)
|
||||
set 4, c ; _SPR_PP_BACK
|
||||
ld 18 (ix), c
|
||||
ld a, 20 (ix) ; an_first
|
||||
cp a, b ; CF = (first < fr)
|
||||
jr NC, ta_done ; лента из 1 кадра — шага нет
|
||||
dec b
|
||||
call ta_src_back ; src -= an_step
|
||||
jr ta_commit
|
||||
ta_loop_wrap:
|
||||
;; LOOP: wrap — прыжок на заранее посчитанный first
|
||||
ld b, 20 (ix) ; fr = an_first
|
||||
ld l, 26 (ix)
|
||||
ld h, 27 (ix) ; HL = an_src0
|
||||
ld 2 (ix), l
|
||||
ld 3 (ix), h ; src = an_src0
|
||||
jr ta_commit_fr ; commit (src уже записан)
|
||||
|
||||
;; ---- маятник, фаза «обратно» ----
|
||||
ta_backward:
|
||||
ld a, 20 (ix) ; an_first
|
||||
cp a, b ; CF = (first < fr)
|
||||
jr NC, ta_turn_fwd
|
||||
dec b ; шаг назад
|
||||
call ta_src_back ; src -= an_step
|
||||
jr ta_commit
|
||||
ta_turn_fwd:
|
||||
;; на first: разворот — сразу шаг вперёд
|
||||
res 4, c ; снять _SPR_PP_BACK
|
||||
ld 18 (ix), c
|
||||
ld a, b
|
||||
cp a, 21 (ix) ; CF = (fr < an_last)
|
||||
jr NC, ta_done ; лента из 1 кадра — шага нет
|
||||
inc b
|
||||
call ta_src_fwd ; src += an_step
|
||||
|
||||
;; ---- commit: кадр сменился ----
|
||||
ta_commit: ; (src записан хелпером)
|
||||
ta_commit_fr:
|
||||
ld 19 (ix), b ; an_frame = fr
|
||||
ld a, 16 (ix)
|
||||
or a, #0x0C ; flags |= _SPR_DIRTY
|
||||
ld 16 (ix), a
|
||||
ta_done:
|
||||
pop ix
|
||||
ret
|
||||
|
||||
;; ---- хелперы: s->src ± an_step; клоббер A,DE,HL ----
|
||||
ta_src_fwd: ; src += an_step
|
||||
ld l, 2 (ix)
|
||||
ld h, 3 (ix)
|
||||
ld e, 24 (ix)
|
||||
ld d, 25 (ix)
|
||||
add hl, de
|
||||
ld 2 (ix), l
|
||||
ld 3 (ix), h
|
||||
ret
|
||||
ta_src_back: ; src -= an_step
|
||||
ld l, 2 (ix)
|
||||
ld h, 3 (ix)
|
||||
ld e, 24 (ix)
|
||||
ld d, 25 (ix)
|
||||
or a, a ; CF = 0
|
||||
sbc hl, de
|
||||
ld 2 (ix), l
|
||||
ld 3 (ix), h
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
/* Одна порция tween-перемещения (таймер уже дозрел): до mv_step шагов
|
||||
* главной оси DDA (см. sprite_moveto: вся арифметика беззнаковая,
|
||||
* «приехали» = mv_rem == 0 — сравнений координат нет вообще).
|
||||
*
|
||||
* РУЧНОЙ ASM (2026-07-14): SDCC на этой функции спиллил ВСЁ в IX-фрейм
|
||||
* (~100 IX-обращений по ~19Т, ~6.5К тактов на вызов при порции в один
|
||||
* шаг) — и никакие реструктуризации C этого не лечили. asm держит
|
||||
* горячее в регистрах: ~2К тактов на вызов.
|
||||
*
|
||||
* C-эквивалент (семантика 1:1, тест tests/spranim):
|
||||
*
|
||||
* uint8_t n = s->mv_step; // порция...
|
||||
* if (n > s->mv_rem) n = s->mv_rem; // ...не длиннее остатка
|
||||
* s->mv_rem -= n;
|
||||
* s->mv_timer = s->mv_speed;
|
||||
* mins = 0; // шагов минорной оси
|
||||
* for (k = n; k--; ) { // DDA-аккумулятор
|
||||
* acc += dmin;
|
||||
* if (acc >= dmaj) { acc -= dmaj; mins++; }
|
||||
* }
|
||||
* major_coord += sgnmaj*n; minor_coord += sgnmin*mins;
|
||||
* s->flags |= _SPR_DIRTY;
|
||||
* if (!s->mv_rem) { MOVE_ON→off; MOVE_DONE→on; }
|
||||
*
|
||||
* Офсеты полей sprite_t — сверены offsetof-пробником по фактическому
|
||||
* кодогену SDCC 4.5 (поля пакуются подряд, выравнивания нет):
|
||||
* x=6 y=8 flags=16 an_flags=18 mv_speed=28 mv_timer=29 mv_step=30
|
||||
* mv_rem=35 mv_acc=37 mv_dmaj=39 mv_dmin=41 mv_ymaj=43
|
||||
* mv_sgnmaj=44 mv_sgnmin=45
|
||||
* ПРИ ИЗМЕНЕНИИ ПОРЯДКА ПОЛЕЙ В sprite.h — ПЕРЕСЧИТАТЬ (см. заметку
|
||||
* у sprite_t). Вход __sdcccall(1): s → HL. IX callee-saved.
|
||||
*/
|
||||
static void tick_move(sprite_t *s) __naked
|
||||
{
|
||||
(void)s;
|
||||
__asm
|
||||
push ix
|
||||
push hl
|
||||
pop ix ; IX = s
|
||||
|
||||
;; mv_timer = mv_speed — перезарядить темп
|
||||
ld a, 28 (ix)
|
||||
ld 29 (ix), a
|
||||
|
||||
;; B = n = min(mv_step, mv_rem); HL = rem
|
||||
ld b, 30 (ix)
|
||||
ld l, 35 (ix)
|
||||
ld h, 36 (ix)
|
||||
ld a, h
|
||||
or a, a
|
||||
jr NZ, tm_n_ok ; rem >= 256 — заведомо >= n
|
||||
ld a, l
|
||||
cp a, b
|
||||
jr NC, tm_n_ok ; rem >= n — порция целиком
|
||||
ld b, l ; хвост маршрута короче: n = rem
|
||||
tm_n_ok:
|
||||
;; rem -= n (заём в старший байт вручную)
|
||||
ld a, l
|
||||
sub a, b
|
||||
ld l, a
|
||||
jr NC, tm_rem_stored
|
||||
dec h
|
||||
tm_rem_stored:
|
||||
ld 35 (ix), l
|
||||
ld 36 (ix), h
|
||||
|
||||
push bc ; сохранить n (в B) на время цикла
|
||||
|
||||
;; ---- DDA минорной оси: n прогонов аккумулятора ----
|
||||
;; HL = acc, DE = dmin, C = mins (счётчик шагов минорной), B = k
|
||||
ld l, 37 (ix)
|
||||
ld h, 38 (ix)
|
||||
ld e, 41 (ix)
|
||||
ld d, 42 (ix)
|
||||
ld c, #0
|
||||
tm_dda_loop:
|
||||
add hl, de ; acc += dmin
|
||||
;; беззнаковое acc >= dmaj?
|
||||
ld a, l
|
||||
sub a, 39 (ix)
|
||||
ld a, h
|
||||
sbc a, 40 (ix)
|
||||
jr C, tm_dda_next ; acc < dmaj — минорная стоит
|
||||
;; acc -= dmaj; mins++
|
||||
ld a, l
|
||||
sub a, 39 (ix)
|
||||
ld l, a
|
||||
ld a, h
|
||||
sbc a, 40 (ix)
|
||||
ld h, a
|
||||
inc c
|
||||
tm_dda_next:
|
||||
djnz tm_dda_loop
|
||||
ld 37 (ix), l
|
||||
ld 38 (ix), h
|
||||
|
||||
ld e, c ; E = mins
|
||||
pop bc ; B = n
|
||||
|
||||
;; ---- применить порцию к координатам ----
|
||||
;; sgn* = ±1: rlca выносит знак в CF (0xFF → CF=1)
|
||||
ld a, 43 (ix) ; ymaj?
|
||||
or a, a
|
||||
jr NZ, tm_ymaj
|
||||
|
||||
;; --- главная ось X (поле +6), минорная Y (+8) ---
|
||||
ld a, 44 (ix) ; sgnmaj
|
||||
rlca
|
||||
ld a, b ; A = n
|
||||
jr NC, tm_xmaj_n_set
|
||||
neg ; A = -n
|
||||
tm_xmaj_n_set:
|
||||
push ix
|
||||
pop hl
|
||||
ld bc, #6
|
||||
add hl, bc ; HL = &x
|
||||
call tm_add16 ; x += ±n
|
||||
ld a, e
|
||||
or a, a
|
||||
jp Z, tm_mark_dirty ; минорная не шагала (далеко: jp)
|
||||
ld a, 45 (ix) ; sgnmin
|
||||
rlca
|
||||
ld a, e ; A = mins
|
||||
jr NC, tm_xmaj_min_set
|
||||
neg
|
||||
tm_xmaj_min_set:
|
||||
push ix
|
||||
pop hl
|
||||
ld bc, #8
|
||||
add hl, bc ; HL = &y
|
||||
call tm_add16 ; y += ±mins
|
||||
jp tm_mark_dirty
|
||||
|
||||
;; --- главная ось Y (+8), минорная X (+6) ---
|
||||
tm_ymaj:
|
||||
ld a, 44 (ix) ; sgnmaj
|
||||
rlca
|
||||
ld a, b
|
||||
jr NC, tm_ymaj_n_set
|
||||
neg
|
||||
tm_ymaj_n_set:
|
||||
push ix
|
||||
pop hl
|
||||
ld bc, #8
|
||||
add hl, bc ; HL = &y
|
||||
call tm_add16 ; y += ±n
|
||||
ld a, e
|
||||
or a, a
|
||||
jr Z, tm_mark_dirty
|
||||
ld a, 45 (ix) ; sgnmin
|
||||
rlca
|
||||
ld a, e
|
||||
jr NC, tm_ymaj_min_set
|
||||
neg
|
||||
tm_ymaj_min_set:
|
||||
push ix
|
||||
pop hl
|
||||
ld bc, #6
|
||||
add hl, bc ; HL = &x
|
||||
call tm_add16 ; x += ±mins
|
||||
|
||||
tm_mark_dirty:
|
||||
;; flags |= _SPR_DIRTY (0x0C) — порция всегда двигает major
|
||||
ld a, 16 (ix)
|
||||
or a, #0x0C
|
||||
ld 16 (ix), a
|
||||
;; rem == 0 → приехали: MOVE_ON снять, MOVE_DONE взвести
|
||||
ld a, 35 (ix)
|
||||
or a, 36 (ix)
|
||||
jr NZ, tm_done
|
||||
ld a, 18 (ix)
|
||||
and a, #0xFB ; ~SPR_MOVE_ON
|
||||
or a, #0x08 ; SPR_MOVE_DONE
|
||||
ld 18 (ix), a
|
||||
tm_done:
|
||||
pop ix
|
||||
ret
|
||||
|
||||
;; ---- хелпер: *(int16 @HL) += sign_extend(A); клоббер A,BC ----
|
||||
tm_add16:
|
||||
ld c, a
|
||||
add a, a ; CF = знак A
|
||||
sbc a, a ; A = 0x00 / 0xFF
|
||||
ld b, a ; BC = знак-расширенная дельта
|
||||
ld a, (hl)
|
||||
add a, c
|
||||
ld (hl), a
|
||||
inc hl
|
||||
ld a, (hl)
|
||||
adc a, b
|
||||
ld (hl), a
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void _sprite_tick(void *arr, uint8_t count)
|
||||
{
|
||||
sprite_t *s = (sprite_t *)arr;
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0; i < count; i++, s++) {
|
||||
/* ---- кадровая анимация ---- */
|
||||
if ((s->an_flags & SPR_ANIM_ON) && --s->an_timer == 0) {
|
||||
int *axis = (s->an_flags & _SPR_ANIM_HORZ) ? &s->sx : &s->sy;
|
||||
uint8_t moved = 1;
|
||||
uint8_t fl = s->an_flags;
|
||||
|
||||
s->an_timer = s->an_speed;
|
||||
if (!(s->an_flags & _SPR_ANIM_BACK)) {
|
||||
if (s->an_frame < s->an_last) {
|
||||
s->an_frame++;
|
||||
*axis += s->an_step;
|
||||
} else if (s->an_flags & _SPR_ANIM_ONCE) {
|
||||
s->an_flags &= (uint8_t)~SPR_ANIM_ON;
|
||||
s->an_flags |= SPR_ANIM_DONE;
|
||||
moved = 0;
|
||||
} else if (s->an_flags & _SPR_ANIM_PP) {
|
||||
s->an_flags |= _SPR_ANIM_BACK; /* разворот на границе */
|
||||
if (s->an_frame > s->an_first) {
|
||||
s->an_frame--;
|
||||
*axis -= s->an_step;
|
||||
} else
|
||||
moved = 0; /* лента из 1 кадра */
|
||||
} else { /* ANIM_LOOP: wrap */
|
||||
s->an_frame = s->an_first;
|
||||
*axis = s->an_lo;
|
||||
}
|
||||
} else { /* маятник, назад */
|
||||
if (s->an_frame > s->an_first) {
|
||||
s->an_frame--;
|
||||
*axis -= s->an_step;
|
||||
} else {
|
||||
s->an_flags &= (uint8_t)~_SPR_ANIM_BACK;
|
||||
if (s->an_frame < s->an_last) {
|
||||
s->an_frame++;
|
||||
*axis += s->an_step;
|
||||
} else
|
||||
moved = 0;
|
||||
}
|
||||
}
|
||||
if (moved)
|
||||
s->flags |= _SPR_DIRTY;
|
||||
}
|
||||
|
||||
/* ---- tween-перемещение ---- */
|
||||
if ((s->an_flags & SPR_MOVE_ON) && --s->mv_timer == 0) {
|
||||
uint8_t n = s->mv_step;
|
||||
int x = s->x, y = s->y;
|
||||
|
||||
s->mv_timer = s->mv_speed;
|
||||
while (n--) {
|
||||
int e2;
|
||||
if (x == s->mv_tx && y == s->mv_ty)
|
||||
break;
|
||||
e2 = s->mv_err * 2;
|
||||
if (e2 >= s->mv_dy) { s->mv_err += s->mv_dy; x += s->mv_sgnx; }
|
||||
if (e2 <= s->mv_dx) { s->mv_err += s->mv_dx; y += s->mv_sgny; }
|
||||
}
|
||||
if (x != s->x || y != s->y) {
|
||||
s->x = x;
|
||||
s->y = y;
|
||||
s->flags |= _SPR_DIRTY;
|
||||
}
|
||||
if (x == s->mv_tx && y == s->mv_ty) {
|
||||
s->an_flags &= (uint8_t)~SPR_MOVE_ON;
|
||||
s->an_flags |= SPR_MOVE_DONE;
|
||||
}
|
||||
}
|
||||
/* Типичный спрайт в кадре НЕ дозрел ни до смены кадра, ни до
|
||||
* шага — держим этот путь коротким: одна проверка флагов +
|
||||
* декремент(ы) таймера. */
|
||||
if (!(fl & (SPR_ANIM_ON | SPR_MOVE_ON)))
|
||||
continue;
|
||||
if ((fl & SPR_ANIM_ON) && --s->an_timer == 0)
|
||||
tick_anim(s);
|
||||
if ((fl & SPR_MOVE_ON) && --s->mv_timer == 0)
|
||||
tick_move(s);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user