4

Neon Nexus: Digital Opposition

Commodore 64 • Phase 1 • Tier 1

Add your first enemy to Neon Nexus! Create a moving opponent with simple AI patterns while learning arrays and memory management through enemy position tracking and collision detection.

easy
⏱️ 30-45 minutes
💻 Code Examples
🛠️ Exercise
🎯

Learning Objectives

  • Create your first moving enemy with simple AI behavior
  • Learn arrays and memory management through enemy position tracking
  • Implement collision detection between player and enemy entities
  • Build autonomous enemy movement with basic AI patterns
  • Master multi-entity game programming on the C64
🧠

Key Concepts

Enemy entity creation and autonomous movement systems Arrays and memory management for tracking multiple entities Collision detection algorithms and boundary checking Basic AI patterns and autonomous behavior programming Multi-entity game loop management and coordination

Lesson 4: Neon Nexus - Digital Opposition

Your player needs a challenge! In the last lesson, you mastered keyboard input and smooth player movement. Today, you’ll add your first enemy - a digital opponent that moves autonomously around the arena, creating the foundation for interactive gameplay and collision detection!

Understanding Enemy Systems

Before diving into code, let’s understand what makes a good enemy:

  • Autonomous movement - moves without player input
  • Predictable behavior - follows consistent patterns
  • Collision detection - interacts with player and boundaries
  • Visual distinction - clearly different from the player

Building Your First Enemy

Step 1: Start with Your Movement Code

Create enemy.s starting with your working player movement from lesson 3:

; Neon Nexus - Lesson 4
; Adding our first enemy

*= $0801

; BASIC stub: 10 SYS 2061
!word next_line
!word 10
!byte $9e
!text "2061"
!byte 0
next_line:
!word 0

; Start of our program
start:
        jsr setup_arena
        jsr create_player
        
game_loop:
        ; Wait for raster sync
wait_raster:
        lda $d012
        cmp #$ff
        bne wait_raster
        
        jsr read_keyboard
        
        ; Only update if player moved
        lda player_moved
        beq game_loop
        
        jsr update_player
        lda #$00
        sta player_moved
        
        jmp game_loop

; Copy your working subroutines from lesson 3
setup_arena:
        ; Set border color to dark blue
        lda #$06     
        sta $d020
        
        ; Set background color to black
        lda #$00
        sta $d021
        
        ; Clear screen manually
        lda #$20        ; Space character
        ldx #$00
clear_loop:
        sta $0400,x     ; Screen page 1
        sta $0500,x     ; Screen page 2
        sta $0600,x     ; Screen page 3
        sta $0700,x     ; Screen page 4
        inx
        bne clear_loop
        
        rts

create_player:
        jsr calculate_screen_pos
        
        lda #$5a        ; Diamond character
        ldy #$00
        sta ($fb),y
        
        ; Set color (simplified version)
        lda #$07        ; Yellow
        sta $d800 + 500 ; Approximate center color
        
        rts

; Include all your keyboard and movement code from lesson 3
; (read_keyboard, update_player, calculate_screen_pos, store_old_position)

; Variables at end
player_x:     !byte 20
player_y:     !byte 12
old_player_x: !byte 20
old_player_y: !byte 12
move_delay:   !byte 0
player_moved: !byte 0

Build and test to ensure your foundation works:

acme -f cbm -o enemy.prg enemy.s
x64sc enemy.prg

Step 2: Add a Static Enemy

Let’s add an enemy that just appears on screen first. Add this new subroutine after create_player:

create_enemy:
        ; Create enemy at fixed position
        lda enemy_x
        sta player_x        ; Temporarily use player vars
        lda enemy_y
        sta player_y
        
        jsr calculate_screen_pos
        
        lda #$2a            ; Star character for enemy
        ldy #$00
        sta ($fb),y
        
        ; Set enemy color to red
        lda #$02            ; Red color
        sta $d800 + 200     ; Approximate enemy color position
        
        ; Restore player position
        lda #20
        sta player_x
        lda #12
        sta player_y
        
        rts

Update your start: routine to create the enemy:

start:
        jsr setup_arena
        jsr create_player
        jsr create_enemy    ; ADD THIS LINE
        
game_loop:
        ; ... rest stays the same

Add enemy variables at the very end, after your other variables:

; Enemy data
enemy_x:      !byte 10
enemy_y:      !byte 8

Build and test - you should see a red star enemy appear on screen!

Step 3: Add Basic Enemy Movement

Now let’s make the enemy move. Add this new subroutine:

update_enemy:
        ; Store enemy's old position
        lda enemy_x
        sta old_enemy_x
        lda enemy_y
        sta old_enemy_y
        
        ; Simple movement: move right
        inc enemy_x
        
        ; Check right boundary
        lda enemy_x
        cmp #39
        bne enemy_in_bounds
        
        ; Hit right edge - reset to left
        lda #0
        sta enemy_x
        
enemy_in_bounds:
        ; Clear old enemy position
        lda old_enemy_x
        sta player_x        ; Use player vars temporarily
        lda old_enemy_y
        sta player_y
        
        jsr calculate_screen_pos
        lda #$20            ; Space character
        ldy #$00
        sta ($fb),y
        
        ; Draw enemy at new position
        lda enemy_x
        sta player_x
        lda enemy_y
        sta player_y
        
        jsr calculate_screen_pos
        lda #$2a            ; Star character
        ldy #$00
        sta ($fb),y
        
        ; Restore player position
        lda #20
        sta player_x
        lda #12
        sta player_y
        
        rts

Add enemy movement variables:

; Enemy data
enemy_x:        !byte 10
enemy_y:        !byte 8
old_enemy_x:    !byte 10
old_enemy_y:    !byte 8
enemy_delay:    !byte 0

Step 4: Add Enemy to Game Loop

Update your game loop to move the enemy:

game_loop:
        ; Wait for raster sync
wait_raster:
        lda $d012
        cmp #$ff
        bne wait_raster
        
        jsr read_keyboard
        jsr update_enemy_movement   ; ADD THIS LINE
        
        ; Update player if moved
        lda player_moved
        beq check_enemy_moved
        
        jsr update_player
        lda #$00
        sta player_moved

check_enemy_moved:
        ; Update enemy if moved
        lda enemy_moved
        beq game_loop
        
        jsr update_enemy
        lda #$00
        sta enemy_moved
        
        jmp game_loop

Add enemy movement timing:

update_enemy_movement:
        ; Check enemy movement delay
        lda enemy_delay
        beq enemy_can_move
        dec enemy_delay
        rts
        
enemy_can_move:
        ; Set movement flag
        lda #$01
        sta enemy_moved
        
        ; Reset delay (slower than player)
        lda #$08        ; 8 frame delay
        sta enemy_delay
        
        rts

Add the enemy movement flag:

enemy_moved:    !byte 0

Build and test - the enemy should now move slowly from left to right!

Step 5: Add Collision Detection

Now for the exciting part - detecting when player and enemy collide. Add this subroutine:

check_collision:
        ; Check if player and enemy positions match
        lda player_x
        cmp enemy_x
        bne no_collision
        
        lda player_y
        cmp enemy_y
        bne no_collision
        
        ; Collision detected!
        jsr handle_collision
        
no_collision:
        rts

handle_collision:
        ; Flash border red briefly
        lda #$02        ; Red
        sta $d020
        
        ; Reset enemy position
        lda #0
        sta enemy_x
        lda #5
        sta enemy_y
        
        ; Reset player position
        lda #20
        sta player_x
        lda #12
        sta player_y
        
        ; Brief delay to show red border
        ldx #$50
collision_delay:
        nop
        dex
        bne collision_delay
        
        ; Restore border
        lda #$06        ; Blue
        sta $d020
        
        rts

Add collision checking to your game loop, after enemy movement:

check_enemy_moved:
        ; Update enemy if moved
        lda enemy_moved
        beq check_collision_detect
        
        jsr update_enemy
        lda #$00
        sta enemy_moved

check_collision_detect:
        jsr check_collision    ; ADD THIS LINE
        
        jmp game_loop

Step 6: Improve Enemy AI

Let’s make the enemy movement more interesting. Replace your update_enemy: subroutine:

update_enemy:
        ; Store enemy's old position
        lda enemy_x
        sta old_enemy_x
        lda enemy_y
        sta old_enemy_y
        
        ; Simple AI: move toward player
        lda player_x
        cmp enemy_x
        beq check_y_movement    ; Same X, check Y
        bcc move_enemy_left     ; Player is left
        
        ; Player is right
        inc enemy_x
        jmp enemy_movement_done
        
move_enemy_left:
        dec enemy_x
        jmp enemy_movement_done
        
check_y_movement:
        lda player_y
        cmp enemy_y
        beq enemy_movement_done ; Same position
        bcc move_enemy_up       ; Player is up
        
        ; Player is down
        inc enemy_y
        jmp enemy_movement_done
        
move_enemy_up:
        dec enemy_y
        
enemy_movement_done:
        ; Keep enemy in bounds
        lda enemy_x
        bpl check_enemy_x_max
        lda #0
        sta enemy_x
        
check_enemy_x_max:
        lda enemy_x
        cmp #40
        bcc check_enemy_y_min
        lda #39
        sta enemy_x
        
check_enemy_y_min:
        lda enemy_y
        bpl check_enemy_y_max
        lda #0
        sta enemy_y
        
check_enemy_y_max:
        lda enemy_y
        cmp #25
        bcc enemy_bounds_ok
        lda #24
        sta enemy_y
        
enemy_bounds_ok:
        ; Clear old enemy position
        lda old_enemy_x
        sta player_x
        lda old_enemy_y
        sta player_y
        
        jsr calculate_screen_pos
        lda #$20            ; Space
        ldy #$00
        sta ($fb),y
        
        ; Draw enemy at new position
        lda enemy_x
        sta player_x
        lda enemy_y
        sta player_y
        
        jsr calculate_screen_pos
        lda #$2a            ; Star
        ldy #$00
        sta ($fb),y
        
        ; Restore player position
        lda #20
        sta player_x
        lda #12
        sta player_y
        
        rts

Now the enemy will chase the player! This creates much more engaging gameplay.

Step 7: Add Visual Feedback

Let’s improve the collision visual feedback. Replace your handle_collision: subroutine:

handle_collision:
        ; Flash border and screen several times
        ldx #$08        ; Number of flashes
        
flash_loop:
        ; Flash red
        lda #$02        ; Red
        sta $d020
        sta $d021
        
        ; Short delay
        ldy #$30
red_delay:
        nop
        dey
        bne red_delay
        
        ; Flash blue
        lda #$06        ; Blue
        sta $d020
        lda #$00        ; Black
        sta $d021
        
        ; Short delay
        ldy #$30
blue_delay:
        nop
        dey
        bne blue_delay
        
        dex
        bne flash_loop
        
        ; Reset positions farther apart
        lda #5
        sta enemy_x
        lda #5
        sta enemy_y
        
        lda #30
        sta player_x
        lda #15
        sta player_y
        
        ; Force redraw of both entities
        lda #$01
        sta player_moved
        sta enemy_moved
        
        rts

Complete Working Examples

The complete working code for this lesson is available in our code samples repository:

📁 Lesson 4 Code Examples

All examples are tested and ready to assemble with ACME:

acme -f cbm -o enemy.prg complete.s
x64sc enemy.prg

Understanding Enemy AI

Simple AI Patterns

Our enemy uses basic AI logic:

  1. Compare positions - Where is the player relative to the enemy?
  2. Make decisions - Should we move left, right, up, or down?
  3. Take action - Move one step toward the player
  4. Repeat - Do this every few frames

The Chase Algorithm

; Compare X positions
lda player_x
cmp enemy_x
beq check_y     ; Same X position
bcc move_left   ; Player is left of enemy
                ; Player is right of enemy, so move right
inc enemy_x

This creates the illusion of intelligence - the enemy “knows” where the player is and actively pursues them!

Collision Detection

Our collision detection is simple but effective:

; Check if positions match exactly
lda player_x
cmp enemy_x
bne no_collision    ; Different X = no collision

lda player_y  
cmp enemy_y
bne no_collision    ; Different Y = no collision
                    ; Same X AND Y = collision!

Experiment and Enhance

Try these modifications:

  1. Different enemy speeds:

    lda #$04    ; Faster enemy (4 frame delay)
    lda #$10    ; Slower enemy (16 frame delay)
    
  2. Multiple enemies:

    ; Add enemy2_x, enemy2_y variables
    ; Create update_enemy2 subroutine
    ; Check collisions with both enemies
    
  3. Smarter AI patterns:

    ; Random movement occasionally
    ; Patrol patterns
    ; Avoid player instead of chasing
    
  4. Different collision effects:

    ; Score reduction
    ; Player teleportation
    ; Game over condition
    

Common Issues

Enemy moves too fast:

  • Increase the enemy_delay value
  • Make sure you’re using frame sync

Collision detection doesn’t work:

  • Check that you’re comparing the right variables
  • Make sure positions are updated before checking

Enemy gets stuck at edges:

  • Verify boundary checking logic
  • Ensure enemy can’t move to invalid positions

What You’ve Achieved

Created your first autonomous enemy with movement AI
Learned basic collision detection between game entities
Built enemy AI that responds to player position
Implemented multi-entity management in your game loop
Added visual feedback for game events

You now have a complete interactive game with player movement, enemy AI, and collision detection!

Coming Next

In Lesson 5, you’ll add energy weapons - projectiles that fire across the screen! You’ll learn:

  • Creating and animating projectiles
  • Advanced collision detection (projectile vs enemy)
  • Weapon firing mechanics
  • Multiple moving objects management

Your arena is about to become a combat zone!

Quick Reference

Enemy AI Pattern:

; 1. Compare positions
lda player_x
cmp enemy_x

; 2. Make decision  
bcc move_left    ; Branch if carry clear (player < enemy)

; 3. Take action
inc enemy_x      ; Move toward player

Collision Detection:

; Check X and Y coordinates match
lda player_x
cmp enemy_x
bne no_collision

lda player_y
cmp enemy_y
bne no_collision
; Collision detected!

Game Loop Structure:

  1. Read player input
  2. Update enemy AI
  3. Check collisions
  4. Redraw changed entities
  5. Repeat

Ready for weapons and combat? On to lesson 5!