Minilop Retroware

Tutoriais de shoot 'em up · Parte 5 de 12

Vertical Shmup - Parte 5 - Uma estrutura para os inimigos

2 de agosto de 2022

Para começar, vou criar um sprite de nave extraterrestre e depois escrever algum código para o seu movimento.

Captura de ecrã do tutorial

Na primeira vaga de ataque quero que uma sequência destes tipos apareça na parte superior do ecrã, desça em linha reta e depois se desvie para um dos lados do ecrã.

Quero que a sequência de extraterrestres apareça em dois pontos da parte superior do ecrã, um à direita do centro e outro à esquerda.

A sequência da direita deve desviar-se para o limite direito, e a da esquerda para o limite esquerdo.

Mas, para começar, vamos resolver o movimento básico. Vamos usar o Sprite Type 2 para este extraterrestre:

MENU: EVENTS > SPRITE TYPE 2

IF IMAGE 2					; ALIEN 1 image
	SPRITEINK 4				; green

	IF DIRECTION = 0			; initial direction (setup)
		IF X <= 128			; is alien on left side of screen?
			LET DIRECTION = 1	; yes, set its direction to 1
		ELSE
			LET DIRECTION = 2	; no, set its direction to 2
		ENDIF
	ENDIF
	
	IF Y < 80				; is alien above the 80px y line?	
		SPRITEDOWN			; move down
		SPRITEDOWN			; and again (faster)
	ELSE						; alien is below the 80 px y line
		IF Y < 120			; is it betwwwn 80 and 119 y?
			IF DIRECTION = 1	; yes, is it on the left side?
				SPRITEDOWN	; yes, go down
				SPRITELEFT	; and go left
			ELSE				; no, its on the right side
				SPRITEDOWN	; so go down
				SPRITERIGHT	; and go right
			ENDIF
		ELSE					; alien is below the 120 y line
			IF DIRECTION = 1	; is it on the left side?	
				SPRITELEFT	; yes, go left
				SPRITELEFT	; and left again
				SPRITEDOWN	; and go down
			ELSE				; if its on the right side
				SPRITERIGHT	; go right
				SPRITERIGHT	; go right again
				SPRITEDOWN	; and go down
			ENDIF
		ENDIF
	ENDIF
	
							; BOUNDARY CHECKING
							; ===================
	IF X <= LEFTEDGE			; if alien reaches the left edge
		REMOVE				; remove it
		SUBTRACT 1 FROM S		; decrement the sprite counter	
	ENDIF
	
	IF X >= RIGHTEDGE			; if alien reaches the right edge
		REMOVE				; remove it
		SUBTRACT 1 FROM S		; decrement the sprite counter
	ENDIF
ENDIF

Agora vamos acrescentar dois destes tipos ao nosso ecrã: vamos pôr um à esquerda do centro, no topo, e outro à direita do centro, e atribuir a cada um o Sprite Type 2. O código acima deverá descobrir onde está cada um e definir o seu movimento em conformidade.

MENU: EDITOR > SPRITE POSITIONS

Captura de ecrã do tutorial

Vamos compilar o jogo e ver o que acontece

{INSERT VIDEO}

Está bem, não está mau, podemos refiná-los e melhorá-los mais tarde.

Mas primeiro temos um problema maior para resolver. Como fazemos para que apareça uma sequência destes tipos?

Podia simplesmente criar um ecrã para cada nível, desenhar alguns sprites, escrever código de movimento e usar as posições dos sprites para definir os seus pontos de partida, mas não me parece que isso fosse a utilização mais eficiente da memória disponível.

Vou adotar uma abordagem diferente: vou definir um sprite Spawner, um sprite invisível cuja função é colocar inimigos no ecrã no momento certo.

O Spawner precisa de ter propriedades próprias, como:

  1. Se está numa posição fixa ou se se move

  2. Que extraterrestres gera (a sua imagem e o seu tipo de sprite)

  3. Quanto tempo deve esperar entre cada aparição?

  4. Quantos extraterrestres deve gerar em cada vaga de ataque / nível?

Este é um cenário ideal para guardar cada uma destas propriedades numa tabela DATA e depois lê-las com READ conforme necessário.

Mas primeiro, vamos criar o sprite Spawner, criando um sprite novo com uma imagem em branco.

Captura de ecrã do tutorial

Antes de começarmos a programar o nosso Spawner vamos criar uma variável nova, 'L', que vai conter o nível do jogo: começará em 1, o primeiro nível, e avançará à medida que o jogador derrotar cada vaga de ataque.

MENU: EVENTS > GAME INITIALISATION

EVENT GAMEINIT

LET L = 1			; Level (starts at 1)

A seguir vamos definir o Spawner no Sprite Type 7 e escrever algum código:

EVENT SPRITETYPE7

						; SPAWNER
						; ============

IF SETTINGA = 0			; first time we run this
	LET JUMPSPEED = 0		; reset the enemies spawned counter
	LET SETTINGB = 0		; reset the timer
	RESTORE				; start from the beginning of the data table
	REPEAT L				; get the data for this level
	READ AIRBORNE			; get the spawner movement
	READ X				; get starting x position
	READ Y				; get starting y position
	READ J				; get the enemy sprite type
	READ K				; get the enemy image
	READ E				; how many enemies in this level?
	READ G				; delay between releasing each enemy
	ENDREPEAT
	ADD 1 TO SETTINGA      ; prevent this running again	
ENDIF


IF SETTINGB = G			; has timer reached the delay point?
	IF JUMPSPEED < E		; are there still enemies left to spawn?
		IF S <= 8	; are there 10 or less enemies already on screen?
			SPAWN J K			; spawn an enemy
			ADD 1 TO JUMPSPEED	; increment the number spawned
			SPAWNED				; switch to the enemy sprite
			LET DIRECTION = 0		; set its initial direction to 0
			ENDSPRITE			; retrun to the spawner
		ENDIF
	ELSE					; all enemies in this level have been spawned
		ADD 1 TO L		; get ready for next level
		IF L = 2			; have we reached the end of the levels?
			LET L = 1	; yes, start again
		ENDIF
		LET SETTINGA = 0	; GET DATA FOR NEXT LEVEL
	ENDIF
	LET SETTINGB = 0		; reset the timer
ENDIF

ADD 1 TO SETTINGB			; increment the timer


DATA  0 165 8 2 2 5 12 

Muito bem, é aqui que a coisa se complica um pouco, mas tem paciência comigo, vou tentar explicar da forma mais simples possível. Vamos usar algumas das funcionalidades menos conhecidas do MPAGD que não estão disponíveis no AGD normal nem sequer no AGDx, mas quando as perceberes garanto-te que vais começar a criar melhores jogos.

Estou a falar de READ e DATA. Usei-os extensivamente em todos os jogos que lancei: ajudaram-me a poupar memória, a escrever melhor código, a criar mecânicas novas e interessantes e muito mais. Na minha opinião, são as melhores funcionalidades do MPAGD.

O que o código acima faz, essencialmente, é:

  1. Quando o spawner aparece pela primeira vez, lê o primeiro número da tabela DATA e guarda-o na variável de sprite AIRBORNE (já explico!)

  2. Lê o segundo número da tabela de dados: será a posição X do Spawner

  3. Lê o terceiro número da tabela de dados: será a posição Y do Spawner

  4. Lê o quarto número da tabela de dados: dir-nos-á o número de Sprite Type do extraterrestre que será gerado

  5. Lê o quinto número da tabela de dados: dir-nos-á o número de imagem de sprite do extraterrestre que será gerado

  6. Lê o sexto número da tabela de dados: dir-nos-á quantos inimigos é preciso gerar nesta vaga de ataque / nível

  7. Lê o sétimo número da tabela de dados: dir-nos-á quanto tempo o spawner deve esperar antes de gerar o extraterrestre seguinte

A tabela DATA, de momento, só contém os dados da primeira vaga de ataque:

  1. 0 (AIRBORNE) Define o padrão de movimento do próprio spawner, embora não o vamos usar nesta primeira vez

  2. 165 (X) a coordenada x do spawner

  3. 8 (Y) a coordenada y do spawner

  4. 2 (J) o número de tipo de sprite do extraterrestre

  5. 2 (K) o número de imagem de sprite do extraterrestre

  6. 5 (E) o número de extraterrestres desta vaga de ataque

  7. 12 (G) o número de ciclos de cpu a esperar entre a geração de um extraterrestre e do seguinte

Antes de testarmos os nossos progressos, precisamos de inicializar o valor de SETTINGA a 0 quando o Spawner é gerado pela primeira vez

MENU: EVENTS > INITIALISE SPRITES

e acrescentar o código destacado:

EVENT INITSPRITE

ADD 1 TO S				; increment the sprite counter

IF TYPE 1				; PHOTON TORPEDO
	LET SETTINGB = 0		; initialise the distance counter
ENDIF

IF TYPE 7				; SPAWNER
	LET SETTINGA = 0		; RESET SETTINGA	
ENDIF

Vamos executá-lo e verificar se está tudo como esperado.

Sim, parece estar bem

Agora vamos acrescentar os dados da vaga seguinte. Desta vez vamos pôr o Spawner à esquerda do centro. No evento do teu spawner acrescenta/atualiza o código destacado, que vai acrescentar os dados da vaga / nível seguinte

EVENT SPRITETYPE7

						; SPAWNER
						; ============

IF SETTINGA = 0			; first time we run this
	LET JUMPSPEED = 0		; reset the enemies spawned counter
	LET SETTINGB = 0		; reset the timer
	RESTORE				; start from the beginning of the data table
	REPEAT L				; get the data for this level
	READ AIRBORNE			; get the spawner movement
	READ X				; get starting x position
	READ Y				; get starting y position
	READ J				; get the enemy sprite type
	READ K				; get the enemy image
	READ E				; how many enemies in this level?
	READ G				; delay between releasing each enemy
	ENDREPEAT
	ADD 1 TO SETTINGA      ; prevent this running again	
ENDIF


IF SETTINGB = G			; has timer reached the delay point?
	IF JUMPSPEED < E		; are there still enemies left to spawn?
		IF S <= 8	; are there 10 or less enemies already on screen?
			SPAWN J K			; spawn an enemy
			ADD 1 TO JUMPSPEED	; increment the number spawned
			SPAWNED				; switch to the enemy sprite
			LET DIRECTION = 0		; set its initial direction to 0
			ENDSPRITE			; retrun to the spawner
		ENDIF
	ELSE					; all enemies in this level have been spawned
		ADD 1 TO L		; get ready for next level
		IF L = 3			; have we reached the end of the levels?
			LET L = 1	; yes, start again
		ENDIF
		LET SETTINGA = 0	; GET DATA FOR NEXT LEVEL
	ENDIF
	LET SETTINGB = 0		; reset the timer
ENDIF

ADD 1 TO SETTINGB			; increment the timer


DATA  0 165 8 2 2 5 12 0 80 8 2 2 5 12 

Agora vamos compilá-lo outra vez:

{insert video}

Muito bem, tem bom aspeto: vemos a primeira vaga à esquerda e depois a segunda à direita, e o padrão repete-se em ciclo.

Mais tarde vamos ampliar as capacidades do Spawner, mas como podes ver no vídeo acima, o jogo tem um problema fundamental: não acontece nada quando os torpedos do jogador atingem os extraterrestres.

Por isso, vamos tratar disso a seguir...

PARTE 6: ELIMINAR EXTRATERRESTRES

Todos os tutoriais de shoot 'em up