Assembly Language Fundamentals

Intermediate
Version:
1.0

Sprite Collisions

Introduction

In the previous lesson, we created projectile sprites that could move across the screen independently.
This introduced the idea of separate game objects, each with their own position and behaviour.

Now it’s time to make those objects interact with one another.
In this lesson, we’ll introduce collision detection — the method a game uses to decide when two sprites have touched.

We’ll start with a simple system that detects when the player sprite overlaps with another sprite on the screen. When that happens, the background colour will change to yellow as visual proof that a collision occurred.

This lesson lays the groundwork for more advanced interactions, such as projectiles hitting targets, enemies colliding with the player, and item pickups.

How Collision Detection Works

Every sprite on the Tatung Einstein has an X and Y position in video memory. These values represent the top-left corner of its visible 8×8 pixel area.

If you imagine drawing a small invisible box around each sprite, then a collision occurs when the two boxes overlap.

This method is called bounding box collision detection, and it’s widely used because it’s both simple and fast.
Instead of checking each individual pixel, the program just compares the positions of the boxes.

For two sprites — for example, the player and a barrier — a collision happens if:

Player’s right edge > Barrier’s left edge
AND
Player’s left edge  < Barrier’s right edge
AND
Player’s bottom edge > Barrier’s top edge
AND
Player’s top edge    < Barrier’s bottom edge

If all four of those conditions are true at the same time, the two boxes overlap.

Storing the Sprite Positions

We already store each sprite’s position in memory using simple byte variables:

PlayerX:  DEFB 80
PlayerY:  DEFB 80
BarrierX: DEFB 160
BarrierY: DEFB 80

These hold the screen positions for both sprites.
The update routines copy these values to the video chip (the VDP) each frame, so the visible sprites always match the positions stored in RAM.

By using these memory values for collision testing, we can perform all the game logic safely in the CPU without interfering with the VDP.

Step-by-Step: The Collision Routine

The routine called CheckCollision does four key things:

  1. Loads both sprites’ positions into registers
    The player’s coordinates are placed into registers B and C (for X and Y), and the barrier’s coordinates are placed into D and E.

LD A,(PlayerX)
LD B,A            ; B = Player X
LD A,(PlayerY)
LD C,A            ; C = Player Y
LD A,(BarrierX)
LD D,A            ; D = Barrier X
LD A,(BarrierY)
LD E,A            ; E = Barrier Y

  1. Checks for horizontal overlap
    We test whether the right edge of one sprite passes the left edge of the other, and vice versa.
    Because each sprite is 8 pixels wide, we add 8 to the X position when calculating the right-hand edge.

LD A,D        ; Barrier X
ADD A,8       ; Barrier right edge
CP B          ; Compare with Player X
JP C,NoCollision   ; If Barrier is completely left of Player → no overlap

LD A,B        ; Player X
ADD A,8       ; Player right edge
CP D          ; Compare with Barrier X
JP C,NoCollision   ; If Player is completely left of Barrier → no overlap

  1. These two comparisons handle the left and right sides of the bounding boxes.
    If either sprite is completely to one side of the other, there’s no chance of collision, so we can exit early.
  2. Checks for vertical overlap
    The same logic is used for the Y coordinates to handle the top and bottom of the boxes.

LD A,E        ; Barrier Y
ADD A,8       ; Barrier bottom edge
CP C          ; Compare with Player Y
JP C,NoCollision   ; Barrier is above Player → no overlap

LD A,C        ; Player Y
ADD A,8       ; Player bottom edge
CP E          ; Compare with Barrier Y
JP C,NoCollision   ; Player is above Barrier → no overlap

  1. Again, if either condition is true, the boxes don’t overlap vertically.
  2. If all checks pass, a collision has occurred
    If we reach this point, it means the X and Y ranges overlap.
    We can now trigger whatever event we want — in this case, a simple colour change.

CollisionDetected:
   CALL ChangeScreen
   RET

If any of the earlier comparisons find no overlap, the routine jumps to NoCollision and simply returns without doing anything.

The Visual Feedback Routine

The subroutine ChangeScreen modifies the colours in VDP Register 7, which controls the overall screen ink and paper colours.

ChangeScreen:
   LD A,11         ; yellow background, black ink
   OUT (9),A
   LD A,135        ; 128 + 7 selects Register 7
   OUT (9),A
   RET

This is a quick and easy way to show that a collision has taken place.
When the player sprite touches the barrier sprite, the background instantly turns yellow.

You can later replace this effect with anything you like — for example:

Full Program Listing

Here’s the complete working example that ties everything together.
Move the player sprite using the W, A, S, and D keys.
When the player collides with the barrier, the background colour changes to yellow.

ORG 256

     ; -----------------------
     ; Enable Graphics Mode II
     ; ----------------------

      ; Set Register 0 values

      LD   A,2
      OUT  (9),A
      LD   A,128
      OUT  (9),A

      ; Set Register 1 values

      LD   A,224
      OUT  (9),A
      LD   A,129
      OUT  (9),A


     ; ---------------------------
     ; Set Foreground / Background
     ; ---------------------------

     LD   A, 240        ; 11110000 (white foreground, black background)
     OUT  (9), A        ; Send to Port 9
     LD   A, 135        ; 128 (bit 7 set) + 7 (register 7)
     OUT  (9), A        ; Send to Port 9

     ; -----------------
     ; Write Blank Tiles
     ; -----------------

     ; Top Third Blank Tile

     LD   A, 0            ; Load zero into A register
     OUT  (9), A          ; Send to Port 9
     LD   A, 64           ; Load 64 into A register
     OUT  (9), A          ; Send to Port 9

     LD A, 0            ; Load 170 into A
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8

     ; Middle Third Blank Tile

     LD   A, 0            ; Load zero into A register
     OUT  (9), A          ; Send to Port 9
     LD   A, 72           ; Load 72 into A register
     OUT  (9), A          ; Send to Port 9

     LD A, 0            ; Load 170 into A
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8

     ; Bottom Third Blank Tile

     LD   A, 0            ; Load zero into A register
     OUT  (9), A          ; Send to Port 9
     LD   A, 80           ; Load 80 into A register
     OUT  (9), A          ; Send to Port 9

     LD A, 0              ; Load 170 into A
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8
     OUT (8), A           ; Send to Port 8


     ; --------------------------------
     ; Clear Name Table with Blank Tile
     ; --------------------------------

     LD A, 0            ; Low Byte
     OUT (9), A         ; Send to Port 9
     LD A, 120          ; High Byte
     OUT (9), A         ; Send to Port 9

     LD BC, 768         ; Set Counter for table address space

ClearLoop:
     LD A, 0            ; Load zero into A register
     OUT (8), A         ; Send to Port 8
     DEC BC             ; Decrement BC Counter
     LD A, B            ; Load B into A register
     OR C               ; Bitwise OR to compare
     JP NZ, ClearLoop    ; Jump to start unless BC is zero


      ; ---------------------
      ; Define Sprite Pattern
      ; ---------------------


      ; Player Sprite 0

      LD A,0
      OUT (9),A
      LD A,88
      OUT (9),A

      LD A,%00011000 : OUT (8),A
      LD A,%00111100 : OUT (8),A
      LD A,%01111110 : OUT (8),A
      LD A,%11111111 : OUT (8),A
      LD A,%11111111 : OUT (8),A
      LD A,%01111110 : OUT (8),A
      LD A,%00111100 : OUT (8),A
      LD A,%00011000 : OUT (8),A

      ; Barrier Sprite 0

      LD A,8
      OUT (9),A
      LD A,88
      OUT (9),A

      LD A,%11111111 : OUT (8),A
      LD A,%11111111 : OUT (8),A
      LD A,%11111111 : OUT (8),A
      LD A,%11111111 : OUT (8),A
      LD A,%11111111 : OUT (8),A
      LD A,%11111111 : OUT (8),A
      LD A,%11111111 : OUT (8),A
      LD A,%11111111 : OUT (8),A


MainLoop:

     CALL UpdatePlayerSprite
     CALL UpdateBarrierSprite
     CALL CheckCollision
     CALL CheckKeys
     JP MainLoop


UpdatePlayerSprite:

      LD A,0
      OUT (9),A
      LD A,123
      OUT (9),A

      LD A,(PlayerY)
      OUT (8),A
      LD A,(PlayerX)
      OUT (8),A
      LD A, 0
      OUT (8),A
      LD A,15
      OUT (8),A

      RET

UpdateBarrierSprite:

      LD A,4
      OUT (9),A
      LD A,123
      OUT (9),A

      LD A,(BarrierY)
      OUT (8),A
      LD A,(BarrierX)
      OUT (8),A
      LD A, 1
      OUT (8),A
      LD A,13
      OUT (8),A

      RET


CheckKeys:
     CALL Delay

     RST 8
     DEFB 181

     CP 0          ; No Key Pressed
     JP Z, MainLoop

     ; W Pressed
     CP 87
     JP Z, MoveUp

     ; A Pressed
     CP 65
     JP Z, MoveLeft

     ; S Pressed
     CP 83
     JP Z, MoveDown

     ; D Pressed
     CP 68
     JP Z, MoveRight


     RET


MoveUp:
     LD A, (PlayerY)
     DEC A
     LD (PlayerY), A
     JP MainLoop

MoveLeft:
     LD A, (PlayerX)
     DEC A
     LD (PlayerX), A
     JP MainLoop

MoveDown:
     LD A, (PlayerY)
     INC A
     LD (PlayerY), A
     JP MainLoop

MoveRight:
     LD A, (PlayerX)
     INC A
     LD (PlayerX), A
     JP MainLoop

     RET

CheckCollision:
   ; Load Player X and Y
   LD A,(PlayerX)
   LD B,A            ; B = PlayerX
   LD A,(PlayerY)
   LD C,A            ; C = PlayerY

   ; Load Barrier X and Y
   LD A,(BarrierX)
   LD D,A            ; D = BarrierX
   LD A,(BarrierY)
   LD E,A            ; E = BarrierY

   ; Check if PlayerX < BarrierX + 8
   LD A,D
   ADD A,8
   CP B
   JP C,NoCollision   ; PlayerX >= BarrierX + 8 → no overlap (right side missed)

   ; Check if BarrierX < PlayerX + 8
   LD A,B
   ADD A,8
   CP D
   JP C,NoCollision   ; BarrierX >= PlayerX + 8 → no overlap (left side missed)

   ; Check if PlayerY < BarrierY + 8
   LD A,E
   ADD A,8
   CP C
   JP C,NoCollision   ; Player below Barrier

   ; Check if BarrierY < PlayerY + 8
   LD A,C
   ADD A,8
   CP E
   JP C,NoCollision   ; Barrier above Player

   ; If all checks passed, they overlap

CollisionDetected:
   CALL ChangeScreen   ; optional feedback
   RET

NoCollision:
   RET


ChangeScreen:
   LD A,11            ; yellow foreground, black background
   OUT (9),A
   LD A,135
   OUT (9),A
   RET

Delay:
  LD BC,2000      ; adjust this number for smooth speed
DLoop:
  DEC BC
  LD A,B
  OR C
  JP NZ,DLoop
  RET


PlayerX:
     DEFB 80
PlayerY:
     DEFB 80
BarrierX:
     DEFB 160
BarrierY:
     DEFB 80


      JP $            ; JP Idefinitely

How to Experiment

Try adjusting the coordinates of the barrier in memory:

BarrierX: DEFB 120
BarrierY: DEFB 120

This moves the barrier closer to the player so you can test collision sooner.

You can also increase the “size” of the collision area by changing the value added in the comparisons (for example, ADD A,16 instead of ADD A,8), which makes the boxes larger and easier to hit.

Why This Technique Is Important

This type of collision system is simple but powerful.
It forms the basis for every type of interaction in a game — collecting items, being hit by enemies, bouncing off walls, and more.

Because it works purely with the values stored in memory, it’s completely safe for the Einstein’s VDP. You can calculate collisions, reactions, and even score updates in CPU space before updating the display.

Later, we’ll extend this same idea to detect when projectiles hit other sprites, allowing your player to shoot at targets.

Summary

In the next lesson, we’ll take this a step further by combining projectiles and collision detection — so your fired shots can interact with barriers and enemies, creating the first building block of a complete game.

Previous Module
Next Module