Captain Drexx изнутри. Часть 3. Creeps
Содержание цикла «Captain Drexx изнутри»
Приходим мы в Основной цикл:
game_loop
ld a,1
ld (screen_ready),a
dec a
ld (int_calk+1),a
game_pause ld a,0
or a
jr nz,gl_halt
; ld a,2
; out (#fe),a
call restore_view
call base_damage_view
end_wave ld a,1
or a
call nz,enemy_create
; ld a,3
; out (#fe),a
call enemy_pos
; ld a,4
; out (#fe),a
call spr_store
; ld a,5
; out (#fe),a
call spr_view
; ld a,6
; out (#fe),a
call towers_fire
; ld a,7
; out (#fe),a
call tower_fires_view
xor a
ld (screen_ready),a
; out (#fe),a
gl_halt halt
gl_halt1 ld a,(int_calk+1)
cp 2
jr nc,game_loop
halt
jp game_loop
Итак, что происходит в основном? устанавливаем признак что происходит отрисовка и пересчёт всех данных, это необходимо для того, что-бы отрисовка была в теневом экране, а показывался основной.
По нажатию на пробел мы переходим в режим паузы, который позволяет ставить башни, апгрейдится, думать куда бежать и где взять денег :)
Первым делом — восстанавливаем экран restore_view под крипами, смотрим на возможное повреждение базы и отрисовываем её base_damage_view, если у нас закончились крипы (всех перебили) — создаём новую волну крипов или переходим на новый уровень.
Пересчитываем позиции крипов enemy_pos и сохраняем экран под ними в текущий буффер spr_store, и можно их отрисовывать: spr_view.
После этого проводим проверки действия башен towers_fire — возможно крипы оказываются в зоне поражения, будем стрелять! tower_fires_view — ну, стреляем! отрисовка попадания / стрельбы башен.
всё, все действия основного цикла выполнены, нужно ровнять скорость работы основного цикла.
Один фрейм для игры — сильно быстро, ровняем до 2х (не меньше, а при большом количестве крипов и отключенном турбо будет вообще много).
Давайте рассмотрим, как собственно сохраняется / восстанавливается экран под крипами:
back_sprites — буффер для хранения спрайтов экрана, максимум #800 байт на всех крипов, тут нужно аккуратно с ними :)
Экранов у нас два, соответственно мы снимаем данные то из одного, то из второго и ложим в свои непересекующиеся буффера.
Выглядит это следующим образом:
back_sprites equ #6000
back_sprites_offset equ #800
spr_store ld hl,back_sprites
ld de,back_sprites_offset
ld a,(active_screen)
cp #40
jr z,s_stor1
add hl,de
s_stor1 ld (store_view_adr+1),hl
ld a,7
call page
ld ix,enemy
ld a,(enemy_count)
ld b,a
s_stor ld a,(ix+0) ; #ff - killed
inc a
jr nz,stor_count
ld a,(ix+1)
or a
jr z,stor_count2
stor_count ld l,(ix+5)
ld h,(ix+6)
dec hl
ld a,h
or l
jr nz,stor_count2
ld e,(ix+3)
ld d,(ix+4)
push bc
push de
call store_view
pop de
pop bc
stor_count2 ld de,12
add ix,de
djnz s_stor
ret
; position on way, num_spr, napravlenie, scr_adr, wait low,high, type, energy, fired
; 0 1 2 3,4 5,6 7 8,9 10
enemy
ds 11*128
Пробегаем по данным крипов enemy, если крип убит (позиция на пути = #ff) — идём дальше.
Если он сука жив, смотрим — он уже появился на экране или нет. Если да — берём его адрес в экране и сохраняем:
store_view
store_view_adr ld hl,back_sprites
ld (hl),e
inc hl
ld (hl),d
inc hl
ex hl,de
ld a,(active_screen)
; xor #80
add h
ld h,a
ld b,#10
s_v1 push hl
ld a,(hl)
ld (de),a
inc l
inc de
ld a,(hl)
ld (de),a
inc l
inc de
ld a,(hl)
ld (de),a
inc l
inc de
ld a,(hl)
ld (de),a
inc de
pop hl
call inch
djnz s_v1
ex de,hl
ld (store_view_adr+1),hl
ld (hl),#ff
inc hl
ld (hl),#ff
ret
Складываем адрес в экране, дальше валим содержиме экрана в буфер. Признаком конца всех данных у нас является два #ff. Получаем список с перемешанными адресами и данными, на одного крипа уходит #42 байта.
Соответственно, для восстановления мы снимаем из нужного буфера адрес и дальше ложим в экран полученные данные:
restore_view ld hl,back_sprites
ld a,(active_screen)
cp #40
jr z,r_v1
ld de,back_sprites_offset
add hl,de
r_v1 ld (r_s0+1),hl
; ld a,7
; call page
r_s0 ld hl,0
ld e,(hl)
inc hl
ld d,(hl)
inc hl
ld a,e
cp #ff
jr nz,r_s_c1
ld a,d
cp #ff
ret z
r_s_c1 di
ld (rest_v_s+1),sp
ld sp,hl
ex de,hl
ld a,(active_screen)
; xor #80
add h
ld h,a
ld c,l
ld b,#10
r_s1 pop de
ld (hl),e
inc l
ld (hl),d
inc l
pop de
ld (hl),e
inc l
ld (hl),d
ld l,c
INC h
LD A,h
AND 7
jr NZ,rvsn
LD A,l
ADD A,32
LD l,A
jr C,rvsn
LD A,h
SUB 8
LD h,A
rvsn ld c,l
djnz r_s1
ld hl,0
add hl,sp
ld (r_s0+1),hl
rest_v_s ld sp,0
ei
jr r_s0
А теперь давайте посмотрим как двигаются крипы enemy_pos:
enemy_pos ld ix,enemy
ld a,1
ld (end_wave+1),a
ld a,(enemy_count)
ld b,a
enemy_pos1 push bc
ld a,(ix+0) ; #ff - killed
inc a
jr nz,enemy_pos20
ld a,(ix+1)
or a
jp z,end_calk
xor a
ld (end_wave+1),a ; флаг обработки врагов
jp end_calk
Сначала поставим флаг признака того что волна у нас закончена, все крипы уничтожены.
Проходим по данным крипов, с проверкой — убит ли текущий, если хоть одна падла жива — снимаем признак.
enemy_pos20 ld l,(ix+5)
ld h,(ix+6)
dec hl
ld a,h
or l
jr z,begin_calk
ld (ix+5),l
ld (ix+6),h
xor a
ld (end_wave+1),a ; флаг обработки врагов
jp end_calk
Очередная проверка задержки на выход крипа, у всех крипов счётчик выхода постоянно декрементится.
Если у крипа счётчик = 0001 — он в деле, отрабатываем его движение:
begin_calk xor a
ld (end_wave+1),a
ld a,(ix+11) ; freezed
or a
jr z,begin_calk2
dec a
ld (ix+11),a
rra
jr c,end_calk
Проверка на заморозку крипа, если крип приморожен, он движется через раз — в два раза медленнее.
begin_calk2 ld d,#ff
ld a,(ix+1) ; num_spr
inc a
and num_enemy_sprites
ld (ix+1),a
jr nz,en
Увеличиваем счётчик спрайтов крипа — крип сделает движение
ld a,(ix+0)
inc a
way_enemy_end cp 0
jr nz,we
enemy_find_exit ld a,#ff ;dont calc this enemy
ld (ix+0),a
ld a,#20
ld (red_lives_counter+1),a
call fill_lives_atr
ld a,(lives)
dec a
ld (lives),a
jp z,end_game
xor a
ld (view_scores_update_flag+1),a
ld (base_damage_view+1),a
call dec_enemy_counter
push ix
ld a,enemy_finish_sound
call AFXPLAY
pop ix
jr end_calk
Проверка на достижение крипом базы — база повреждена, ставим признак крипу (его больше считать не будем), подсвечиваем базу (урон!), уменьшаем жизни (вот оно, бессмертие!!!), и если жизней нет — конец игры.
Так же уменьшаем счётчик врагов, и проигрываем звук повреждения базы.
we ld (ix+0),a
; ld a,(ix+2)
; ld (move_old+1),a
ld d,(ix+2)
ld l,a
ld h,high way
ld a,(hl) ; new napravlenie
ld (ix+2),a
Здесь мы вычисляем направление движения крипа по пути и пересчитываем спрайт крипа по типу направления движения:
; position on way, num_spr, napravlenie, scr_adr, wait low,high, type, energy, fired, slowed
; 0 1 2 3,4 5,6 7 8,9 10, 11
en ld a,(ix+2)
; napravlenie: 1-right, 0-left, 2-down, 3-up
or a
call z,spr_adr_left
ld a,(ix+2)
cp 1
call z,spr_adr_right
ld a,(ix+2)
cp 2
call z,spr_adr_down
ld a,(ix+2)
cp 3
call z,spr_adr_up
end_calk pop bc
ld de,12
add ix,de
dec b
jp nz,enemy_pos1
jp view_scores_update_flag
перебираем все данные крипов пока они не закончатся ( ld a,(enemy_count): ld b,a в начале)
spr_adr_up
ld a,d
cp 0
call z,spr_adr_right_correct
ld e,(ix+3)
ld d,(ix+4)
call decd
call decd
ld (ix+3),e
ld (ix+4),d
ret
; napravlenie: 1-right, 0-left, 2-down, 3-up
spr_adr_down
ld a,d
cp 0
call z,spr_adr_right_correct
ld l,(ix+3)
ld h,(ix+4)
call inch
call inch
ld (ix+3),l
ld (ix+4),h
ret
spr_adr_right
ld a,(ix+1)
or a
ret nz
spr_adr_left_correct
ld a,(ix+3)
inc a
inc a
ld (ix+3),a
ret
; napravlenie: 1-right, 0-left, 2-down, 3-up
spr_adr_left
ld a,d
cp 2
ret z
ld a,d
cp 3
ret z
ld a,(ix+1)
or a ; or a
ret nz
spr_adr_right_correct
ld a,(ix+3)
dec a
dec a
ld (ix+3),a
ret
— корректировка движения крипов.
А сейчас рассмотрим как выводятся крипы spr_view:
spr_view ld ix,enemy
ld a,(enemy_count)
ld b,a
s_view push bc
ld a,(ix+0)
inc a
jr nz,s_view2
ld a,(ix+1)
or a
jp z,ns_view
dec (ix+1)
jr killed
Очередная проверка, нужно ли выводить крипа. Напомним себе структуру данных:
; position on way, num_spr, napravlenie, scr_adr, wait low,high, type, energy, fired, slowed
; 0 1 2 3,4 5,6 7 8,9 10, 11
смотрим счётчик ожидания вывода:
s_view2 ld l,(ix+5)
ld h,(ix+6)
dec hl
ld a,h
or l
jp nz, ns_view
ld a,(ix+10)
or a
jr z,s_view1
dec (ix+10)
ld hl,fired
ld de,#400
ld a,(ix+2) ; napravlenie
or a
jr z,fire_spr_view
add hl,de
cp 1
jr z,$+5
killed ld hl,fired_dwn
fire_spr_view ld a,1
ld (en_page+1),a ; page 1 for explode
ld a,(ix+1)
jr s_view0
смотрим, в случае если крип бомбят башни, нужно отображать не его, а взрыв на его месте.
s_view1 ld a,(ix+7) ; enemy
ld e,a
ld d,0
ld hl,enemy_pages
add hl,de
ld a,(hl)
ld (en_page+1),a
sla e
ld hl,enemy_gfx
add hl,de
ld a,(hl)
inc hl
ld h,(hl)
ld l,a
push hl
ld a,(ix+2) ; napravlenie
add a,a
ld e,a
ld d,0
ld hl,enemy_moves
add hl,de
ld a,(hl)
inc hl
ld h,(hl)
ld l,a
pop de
add hl,de
только что пересчитали, какой именно спрайт нужен для вывода
данные для пересчёта:
enemy_gfx dw enemy_sprite1,enemy_sprite2,enemy_sprite3,enemy_sprite4,enemy_sprite5,enemy_sprite6
enemy_pages db 1,1,1,6,6,6
; sprite moves: right (4*32*4) left down up
enemy_moves dw #400,0,#800,#c00
enemy_lives db 4, 12, 22, 36, 64,128
page 1
org #c000
enemy_sprite1 incbin "bin/kb.bin"
enemy_sprite2 incbin "bin/egg.bin"
enemy_sprite3 incbin "bin/_javi_dog.bin"
fired incbin "bin/_explode.bin"
fired_dwn equ fired+#800
Вычисляем адрес:
ld a,(ix+1)
s_view0
push hl
ld l,a
ld h,0
add hl,hl
add hl,hl
add hl,hl
add hl,hl
add hl,hl
add hl,hl
add hl,hl ; 48 bytes per sprite, x128
pop de
add hl,de
di
en_page ld a,#1
call page
Начинаем отрисовку. для этого сначала перебрасываем данные спрайта в буффер в нижней памяти:
ld (view_spr_s+1),sp
ld (view_spr_s2+1),sp
ld sp,hl
ld hl,sprbuffer
dup 4*#16
pop bc
ld (hl),c
inc hl
ld (hl),b
inc hl
edup
view_spr_s2 ld sp,0
LD A,7
call page
vsnrscr ld sp,sprbuffer
ld e,(ix+3)
ld d,(ix+4)
ld a,(active_screen)
; xor #80
add d
ld d,a
Данные получены, начинаем накладывать спрайт на экран. Спрайт лежит следующим образом: байт маски, байт данных, соответственно снимаем со стека данные спрайта и накладываем на экран по маске:
ld b,#10
vsnr ld c,e
dup 3
pop hl
ld a,(de)
and l
or h
ld (de),a
inc e
edup
pop hl
ld a,(de)
and l
or h
ld (de),a
ld e,c
INC d
LD A,d
AND 7
jr NZ,vsn
LD A,e
ADD A,32
LD e,A
jr C,vsn
LD A,d
SUB 8
LD d,A
vsn djnz vsnr
view_spr_s ld sp,0
ei
ns_view pop bc
ld de,12
add ix,de
dec b
jp nz, s_view
ret
отрисовка крипа закончена, переходим к следующему.
На сегодня всё, дальше будем посмотреть как работают башни.
5 комментариев
С играми всё сложнее… и интереснее )
Но! Демомейкерское знание работы с экраном очень помогает сделать игру как насыщеннее, так и визуально мощнее.
Не то, чтобы особо сложная работа (уровни не генерятся, их мне рисовали на тетрадных листах друзья-товарищи). Ослеживание и отрисовка пути тупо в три прохода (там как раз двух мало ))… Исходники утеряны (упаковал в zxzip, дискету отдал на оцинковку куму-надо-знает-кому).
уже двух девочек видел :)
Ещё остаётся большая и сложная часть, связанная с UI.