Espamática
ZX SpectrumRetroZ80 Assembly

ZX Spectrum Assembly, Space Battle – 0x06 Enemies

In this chapter of ZX Spectrum Assembly, we will add the enemies.

We create the folder Step06, copy from Step05 loader.tap, const.asm, ctrl.asm, game.asm, graph.asm, int.asm, main.asm, print.asm, var.asm and make or make.bat.

Translation by Felipe Monge Corbalán

Tabla de contenidos

We define enemies

Enemies are moving objects, and as such we need to know their current and starting positions. In total we will have a maximum of twenty enemies on the screen, and we will use two bytes to specify the current position of the enemy and some other settings we need.

Open var.asm and add the enemy configuration after the frame definition.

ASM
; -------------------------------------------------------------------
; Enemy configuration
;
; 2 bytes per enemy.
; -------------------------------------------------------------------
; Byte 1               | Byte 2
; -------------------------------------------------------------------
; Bit 0-4: Y position  | Bit 0-4: Position X
; Bit 5:   Free        | Bit 5:   Free
; Bit 6:   Free        | Bit 6:   Direction X 0 = Left 1 = Right
; Bit 7:   Active 0/1  | Bit 7:   Direction Y 0 = Up   1 = Down
; -------------------------------------------------------------------
enemiesConfig:
db $96, $dd, $96, $d7, $96, $d1, $96, $cb, $96, $c5
db $93, $9d, $93, $97, $93, $91, $93, $8b, $93, $85
db $90, $dd, $90, $d7, $90, $d1, $90, $cb, $90, $c5
db $8d, $9d, $8d, $97, $8d, $91, $8d, $8b, $8d, $85
enemiesConfigIni:
db $96, $dd, $96, $d7, $96, $d1, $96, $cb, $96, $c5
db $93, $9d, $93, $97, $93, $91, $93, $8b, $93, $85
db $90, $dd, $90, $d7, $90, $d1, $90, $cb, $90, $c5
db $8d, $9d, $8d, $97, $8d, $91, $8d, $8b, $8d, $85
enemiesConfigEnd:

In the first byte we have the Y coordinate, bits zero to four, and whether the enemy is active or not, bit seven. In the second byte we have the X coordinate, bits zero to four, the horizontal direction, bit six, and the vertical direction, bit seven.

Bits six and seven of the second byte contain the direction of the enemy:

  • 00b $00 Left / Top
  • 01b $01 Left / Bottom
  • 10b $02 Right / Top
  • 11b $03 Right / Down

We add the definition of the enemy graphics before enemiesConfig.

ASM
; -------------------------------------------------------------------
; Enemy graphics
;
; 00 Up-Left
; 01 Up-Right
; 10 Down-Left
; 11 Down-Right
; -------------------------------------------------------------------
enemiesGraph: 
db $9f, $a0, $a1, $a2

If we look at the UDGs of the enemies, everything fits.

Going back to the enemy definition, we have twenty enemies in total, divided into four rows and five enemies per row.

The definition of the enemies from left to right and from top to bottom, remembering that we are working with inverted coordinates, is as follows:

HexadecimalBinaryDefinition
$96, $dd10010110, 11011101Active Line 22 Down/Right Column 29
$96, $d710010110, 11010111Active Line 22 Down/Right Column 23
$96, $d110010110, 11010001Active Line 22 Line/Right Column 17
$96, $cb10010110, 11001011Active Line 22 Down/Right Column 11
$96, $c510010110, 11000101Active Line 22 Down/Right Column 5
$93, $9d10010011, 10011101Active Line 19 Down/Left Column 29
$93, $9710010011, 10010111Active Line 19 Down/Left Column 23
$93, $9110010011, 10010001Active Line 19 Down/Left Column 17
$93, $8b10010011, 10001011Active Line 19 Down/Left Column 11
$93, $8510010011, 10000101Active Line 19 Down/Left Column 5
90, $dd10010000, 11011101Active Line 16 Down/Right Column 29
$90, $d710010000, 11010111Active Line 16 Down/Right Column 23
$90, $d110010000, 11010001Active Line 16 Down/Right Column 17
90, $cb10010000, 11001011Active Line 16 Down/Right Column 11
$90, $c510010000, 11000101Active Line 16 Down/Right Column 5
$8d, $9d10001101, 10011101Active Line 13 Down/Left Column 29
$8d, $9710001101, 10010111Active Line 13 Down/Left Column 23
$8d, $9110001101, 10010001Active Line 13 Down/Left Column 17
$8d, $8b10001101, 10001011Active Line 13 Down/Left Column 11
$8d, $8510001101, 10000101Active Line 13 Down/Left Column 5
ZX Spectrum Assembly, Space Battle

Once the graphics and their configuration have been defined, we go on to paint them.

We paint the enemies

The routine that paints the enemies is in print.asm.

ASM
PrintEnemies:
ld   a, $06 
call Ink

ld   hl, enemiesConfig
ld   d, $14

We load the yellow ink in A, LD A, $06, change the ink, CALL Ink, load the address of the enemy configuration in HL, LD HL, enemiesConfig, and the number of enemies in D, LD D, $14.

ASM
printEnemies_loop:
bit  $07, (hl)
jr   z, printEnemies_endLoop

Check if the enemy is active, BIT $07, (HL), if not jump, JR Z, printEnemies_endLoop.

ASM
push hl

ld   a, (hl)
and  $1f
ld   b, a

We keep the value of HL, PUSH HL, load the first byte of the enemy configuration into A, LD A, (HL), keep the Y coordinate, AND $1F, and load it into B, LD B, A.

ASM
inc  hl
ld   a, (hl)
and  $1f
ld   c, a
call At

We point HL to the second byte of the enemy configuration, INC HL, load the value into A, LD A, (HL), leave the X coordinate, AND $1F, load it into C, LD C, A, and position the cursor, CALL At.

ASM
ld   a, (hl)
and  $c0
rlca
rlca
ld   c, a
ld   b, $00

We load the second byte of the enemy configuration into A, LD A, (HL), leave the address bits, AND $c0, pass the value to bits zero and one, RLCA, RLCA, load it into C, LD C, A, and set B to zero, LD B, $00.

ASM
ld   hl, enemiesGraph
add  hl, bc
ld   a, (hl)
rst  $10

We load in HL the direction in which we define the enemy figures, LD HL, enemiesGraph, we add the direction of the enemy (left, up, etc.), ADD HL, BC, we load in A the enemy figure to be painted, LD A, (HL), and we paint it, RST $10.

ASM
pop  hl

printEnemies_endLoop:
inc  hl
inc  hl

dec  d
jr   nz, printEnemies_loop

ret

We take the value of HL, POP HL, point it to the first byte of the configuration of the next enemy, INC HL, INC HL, subtract one from D, DEC D, and continue until D is zero and we have traversed all the enemies. Finally we exit, RET.

The final aspect of the routine is as follows:

ASM
; -------------------------------------------------------------------
; Paint the enemies
;
; Alters the value of the AF, BC, D and HL registers.
; -------------------------------------------------------------------
PrintEnemies:
ld   a, $06                ; A = yellow ink 
call Ink                   ; Change ink

ld   hl, enemiesConfig     ; HL = enemy configuration
ld   d, $14                ; D = 20 enemies

printEnemies_loop:
bit  $07, (hl)             ; Active enemy?
jr   z, printEnemies_endLoop ; If not active, skip

push hl                    ; Preserves HL
ld   a, (hl)               ; A = 1st byte value
and  $1f                   ; A = Y coordinate
ld   b, a                  ; B = A

inc  hl                    ; HL = 2nd byte config
ld   a, (hl)               ; A = 2nd byte value
and  $1f                   ; A = X coordinate
ld   c, a                  ; C = A
call At                    ; Position cursor

ld   a, (hl)               ; A = 2nd byte value
and  $c0                   ; A = direction (left...)
rlca                       ; Sets value in bits 0 and 1
rlca
ld   c, a                  ; Load the value in C
ld   b, $00                ; Sets B to zero

ld   hl, enemiesGraph      ; HL = enemy graph
add  hl, bc                ; Add direction (left...)
ld   a, (hl)               ; A = enemy graph
rst  $10                   ; Paints it
pop  hl                    ; Retrieve HL

printEnemies_endLoop:
inc  hl                    ; HL = 1st byte configuration
inc  hl                    ; next enemy

dec  d                     ; D = D - 1
jr   nz, printEnemies_loop ; Loop as long as D != 0

ret

To test if this works, we add the following lines to main.asm, just before Main_loop:

ASM
ld   a, $01
call LoadUdgsEnemies
call PrintEnemies

We load level one into A, LD A, $01, load the level’s enemy graphics into udgsExtension, CALL LoadUdgsEnemies, and print them, CALL PrintEnemies.

We compile, load into the emulator and see the results.

We move the enemies

The enemies are not moved every iteration of the loop, as we do with the trigger, but every N interrupts. So we add another comment in the flag tag, at the top of main.asm.

ASM
; Bit 2 -> enemies must be moved 0 = No, 1 = Yes

The next thing to do is to set the boundaries of the screen as far as the enemies can go. We set them in const.asm.

ASM
; Enemies' boundaries
ENEMY_TOP_T: EQU COR_Y - MIN_Y
ENEMY_TOP_B: EQU COR_Y - MAX_Y + $01
ENEMY_TOP_L: EQU COR_X - MIN_X
ENEMY_TOP_R: EQU COR_X - MAX_X

The boundaries we have set are top, bottom, left and right.

For the enemies to move every N interrupts, and as in flags we will use a bit to indicate whether they should move or not, in the int.asm file we will activate this bit. We add the following line under SET $00, (HL):

ASM
set  02, (hl)

We are now ready to implement the routine that moves the enemies in game.asm.

ASM
MoveEnemies:
ld   hl, flags
bit  $02, (hl)
ret  z
res  2, (hl)

We load the address of the flags into HL, LD HL, flags. We must check if the enemy movement bit is set, BIT $02, (HL), and if it is not, we exit, RET Z. If it is set, we disable it so that it does not happen in the next iteration of Main_loop, RES $02, (HL).

ASM
ld   d, $14
ld   hl, enemiesConfig

moveEnemies_loop:
bit  $07, (hl)
jr   z, moveEnemies_ endLoop

We load the number of enemies in D, LD D, $14, the address of the configuration in HL, LD HL, enemiesConfig, see if the enemy is active, BIT $07, (HL). If the enemy is not active, we jump to the next one, JR Z, moveEnemies_loopEnd.

ASM
push hl

ld   a, (hl)
and  $1f
ld   b, a

inc  hl
ld   a, (hl)
and  $1f
ld   c, a

call DeleteChar

pop  hl

We keep the value of HL, PUSH HL, load the first byte of the enemy configuration into A, LD A, (HL), keep the Y-coordinate, AND $1F, and load it into B, LD B, A.

We point HL to the second byte of the enemy configuration, INC HL, load the value into A, LD A, (HL), keep the X-coordinate, AND $1F, and load it into C, LD C, A.

Delete the enemy, CALL DeleteChar, restore HL, POP HL.

ASM
ld   b, (hl)
inc  hl
ld   c, (hl)

We load the first byte of the enemy configuration into B, LD B, (HL), point HL to the second byte, INC HL, and load it into C, LD C, (HL).

ASM
moveEnemies_X:
ld   a, c
and  $1f

bit  $06, c
jr   nz, moveEnemies_X_right

We load the value of the second byte of the configuration into A, LD A, C, and keep the X coordinate, AND $1F.

We check the horizontal direction bit of the enemy, BIT $06, C. If it is set to one, the enemy moves to the right and jumps, JR Z, moveEnemies_X_right. If not, the enemy moves to the left.

ASM
moveEnemies_X_left:
inc  a
sub  ENEMY_TOP_L
jr   z, moveEnemies_X_leftChg

inc  c
jr   moveEnemies_Y

moveEnemies_X_leftChg:
set  $06, c
jr   moveEnemies_Y

We increment A and so point to the column to the left of the current one, INC A, subtract the top left, SUB ENEMY_TOP_L, and if the result is zero, it has reached the top and jumps to change direction, JR Z, moveEnemies_X_leftChg.

If the direction is not to be changed, point C to the column to the left of the current column, INC C, and jump to the vertical move handle, JR moveEnemies_Y.

If the direction is to be changed, set bit six of C to set the horizontal direction to the right, SET $06, C, and jump to the vertical move handle, JR moveEnemies_Y.

If the enemy does not move to the left, it will move to the right.

ASM
moveEnemies_X_right:
dec  a
sub  ENEMY_TOP_R
jr   z, moveEnemies_X_rightChg

dec  c
jr   moveEnemies_Y

moveEnemies_X_rightChg:
res  $06, c

We decrement A to point to the column to the right of the current one, DEC A, subtract the right top, SUB ENEMY_TOP_R, and if the result is zero, it has reached the top and jumps to change direction, JR Z, moveEnemies_X_rightChg.

If the direction is not to be changed, point C to the right column, DEC C, and jump to vertical movement, JR moveEnemies_Y.

If you want to change the direction, turn off bit six of C to set the horizontal direction to the left, RES $06, C, and start the vertical movement.

ASM
moveEnemies_Y:
ld   a, b
and  $1f
bit  $07, c
jr   nz, moveEnemies_Y_down

We load the value of the first byte of the configuration into A, LD A, B, and leave the Y coordinate, AND $1F.

We check bit seven of C to get the vertical direction, BIT $07, C, and if it is one, the enemy moves down, JR NZ, moveEnemies_Y_down, and jumps.

If the bit is zero, the enemy moves up.

ASM
moveEnemies_Y_up:
inc  a
sub  ENEMY_TOP_T
jr   z, moveEnemies_Y_upChg

inc  b
jr   moveEnemies_endMove

moveEnemies_Y_upChg:
set  $07, c
jr   moveEnemies_endMove

We increment A to point to the line above the current line, INC A, subtract the top, SUB ENEMY_TOP_T, and if it is zero, we have reached the top and must jump, JR Z, moveEnemies_Y_upChg, to change direction.

If we have not reached the top, we increment B to point to the line above the current line, INC B, and jump to the end of the loop, JR moveEnemies_endMove.

If the direction is to be changed, we set bit seven of C to change the direction down, SET $07, C, and jump to the end of the loop, JR moveEnemies_endMove.

If the enemy does not move up, it moves down.

ASM
moveEnemies_Y_down:
dec  a
sub  ENEMY_TOP_B
jr   z, moveEnemies_Y_downChg

dec  b
jr   moveEnemies_endMove

moveEnemies_Y_downChg:
res  $07, c

We decrement A to point to the line below the current one, DEC A, subtract the top from the bottom, SUB ENEMY_TOP_B, and if it is zero, we have reached the top and jump because we have to change direction, JR Z, moveEnemies_Y_downChg.

If we have not reached the top, we decrement B to point to the line below the current line, DEC B, and jump to the end of the loop, JR moveEnemies_endMove.

If we want to change the direction, we disable bit seven of C and increment the direction, RES $07, C.

ASM
moveEnemies_endMove:
ld   (hl), c
dec  hl
ld   (hl), b

moveEnemies_endLoop:
inc  hl
inc  hl
dec  d
jr   nz, moveEnemies_loop

We update in memory the second byte of the configuration, LD(HL), C, point HL to the first byte, DEC HL, and update in memory, LD(HL), B.

We point HL to the first byte of the next enemy configuration, INC HL, INC HL, decrement D, DEC D, and continue in the loop until D is zero and we have traversed all twenty enemies, JR Z, moveEnemies_loop.

ASM
moveEnemies_end:
call PrintEnemies

ret

We print the enemies with the new positions, CALL PrintEnemies, and exit with RET.

We have implemented the routine that moves the enemies, whose final appearance is as follows:

ASM
; -------------------------------------------------------------------
; Moves enemies.
;
; Alters the value of the AF, BC, D and HL registers.
; -------------------------------------------------------------------
MoveEnemies:
ld   hl, flags             ; HL = address flags
bit  $02, (hl)             ; Bit 2 active?
ret  z                     ; Not active, exits 
res  $02, (hl)             ; Disables bit 2

ld   d, $14                ; D = number of enemies (20)
ld   hl, enemiesConfig     ; HL = configuration address
moveEnemies_loop:
bit  $07, (hl)             ; Active enemy?
jr   z, moveEnemies_endLoop ; Not active, jumps

push hl                    ; Preserves HL
ld   a, (hl)               ; A = value 1st byte config
and  $1f                   ; A = Y coordinate
ld   b, a                  ; B = A

inc  hl                    ; HL = 2nd byte config
ld   a, (hl)               ; A = value 2nd byte config
and  $1f                   ; A = X coordinate
ld   c, a                  ; C = A

call DeleteChar            ; Deletes enemy
pop  hl                    ; Retrieve HL

ld   b, (hl)               ; B = value 1st byte config
inc  hl                    ; HL = 2nd byte config
ld   c, (hl)               ; C = 2nd byte config value

moveEnemies_X:
ld   a, c                  ; A = C
and  $1f                   ; A = X coordinate

bit  $06, c                ; Evaluates horizontal direction
jr   nz, moveEnemies_X_right ; != 0, right, jump

moveEnemies_X_left:
inc  a                     ; A = previous column 
sub  ENEMY_TOP_L           ; A = A - left stop
jr   z, moveEnemies_X_leftChg ; = 0, stop has been reached, skip

inc  c                     ; C = previous column
jr   moveEnemies_Y         ; Jump to vertical movement 

moveEnemies_X_leftChg:
set  $06, c                ; Horizontal dir = right
jr   moveEnemies_Y         ; Jump to vertical movement

moveEnemies_X_right:
dec  a                     ; A = back column
sub  ENEMY_TOP_R           ; A = A - right stop
jr   z, moveEnemies_X_rightChg ; = 0, has reached stop, skip

dec  c                     ; C = back column
jr   moveEnemies_Y         ; Jump to vertical movement

moveEnemies_X_rightChg:
res  $06, c                ; Horizontal dir = left

moveEnemies_Y:
ld   a, b                  ; A = first byte config
and  $1f                   ; A = Y coordinate
bit  $07, c                ; Evaluates vertical direction
jr   nz, moveEnemies_Y_down ; != 0, downwards, jump

moveEnemies_Y_up:
inc  a                     ; A = previous line
sub  ENEMY_TOP_T           ; A = A - top top
jr   z, moveEnemies_Y_upChg ; = 0, stop has been reached, skip

inc  b                     ; B = back line
jr   moveEnemies_endMove   ; Jump to end loop

moveEnemies_Y_upChg:
set  $07, c                ; Vertical dir = down
jr   moveEnemies_endMove   ; Jump to end loop

moveEnemies_Y_down:
dec  a                     ; A = back line
sub  ENEMY_TOP_B           ; A = A - stop below
jr   z, moveEnemies_Y_downChg ; = 0, has arrived, jump

dec  b                     ; Aim B at the back line
jr   moveEnemies_endMove   ; Jumps to the end of the loop

moveEnemies_Y_downChg:
res  $07, c                ; Vertical dir = top

moveEnemies_endMove:
ld   (hl), c               ; Update 2nd byte config
dec  hl                    ; HL = 1st byte config
ld   (hl), b               ; Update 1st byte config

moveEnemies_endLoop:
inc  hl
inc  hl                    ; HL 1st byte config
                           ; next enemy
dec  d                     ; D = D - 1
jr   nz, moveEnemies_loop  ; Until D = 0 (20 enemies)

moveEnemies_end:
call PrintEnemies          ; Paint enemies

ret

Now it’s time to see how the enemies move. Open main.asm and in the Main_loop tag, just below CALL MoveShip, add the following line:

ASM
call MoveEnemies

We compile, load in the emulator and see the results.

How’s it going? Are the enemies moving? Yes, they’re moving, but they’re moving too fast and the firing has slowed down. We need to slow down the movement of the enemies, and we’re going to do that from the interrupt routine, so let’s go to the int.asm file.

We are going to do something similar to what we did in ZX-Pong, we are going to add a counter at the end of the file to control when we activate the movement of the enemies.

ASM
countEnemy: db $00

Between the lines SET $00, (HL) and SET $02, (HL) we now implement the use of this counter.

ASM
ld   a, (countEnemy)
inc  a
ld   (countEnemy), a
sub  $02
jr   nz, Isr_end
ld   (countEnemy), a

We load the value of the counter into A, LD A, (countEnemy), increment A, INC A, and update the counter, LD (countEnemy), A, in memory. We subtract from A the value that the counter must reach to activate the movement, SUB $03, and if it has not reached it, we jump to the end of the routine, JR NZ, Isr_end.

If it has reached the value, we set it to zero, LD (countEnemy), A, and set the bit to move the enemies, SET $02, (HL).

Compile and load into the emulator. We have regained the firing speed and the enemies are still moving fast.

ZX Spectrum Assembly, Space Battle

We have all the elements of the game in motion.

In the next chapter of ZX Spectrum Assembly, we will include the collisions of the shot with the enemies, the enemies with the ship and the level changes.

Download the source code from here.

ZX Spectrum Assembly, Space Battle by Juan Antonio Rubio García.
Translation by Felipe Monge Corbalán.
This work is licensed to Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0).
Any comments are always welcome.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.plugin cookies

ACEPTAR
Aviso de cookies

Descubre más desde Espamática

Suscríbete ahora para seguir leyendo y obtener acceso al archivo completo.

Seguir leyendo