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 комментариев

avatar
«Демомейкинг — искусство наебать зрителя» (ц)
С играми всё сложнее… и интереснее )
avatar
знаешь, да. тут совсем другой подход, это логика.
Но! Демомейкерское знание работы с экраном очень помогает сделать игру как насыщеннее, так и визуально мощнее.
avatar
до завершеноого вида и публикации в широкие массы я довел только вроде это.

Не то, чтобы особо сложная работа (уровни не генерятся, их мне рисовали на тетрадных листах друзья-товарищи). Ослеживание и отрисовка пути тупо в три прохода (там как раз двух мало ))… Исходники утеряны (упаковал в zxzip, дискету отдал на оцинковку куму-надо-знает-кому).
avatar
вполне хорошая игрушка :)
уже двух девочек видел :)
avatar
В следующей части посмотрим на башни, часть будет попроще.
Ещё остаётся большая и сложная часть, связанная с UI.
  • VBI
  • 0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.