Skip to content

Input Edge Detection

Detect newly-pressed buttons, ignoring held inputs. Essential for menus and single-action triggers.

inputjoystickkeyboardedge-detection

Overview

Raw input tells you if a button is pressed right now. Edge detection tells you if a button was just pressed this frame — it wasn’t pressed last frame, but is pressed now. This prevents a single button press from triggering multiple times.

Without edge detection: holding fire spawns bullets every frame. With edge detection: holding fire spawns one bullet, then nothing until released and pressed again.

Algorithm

current_input: byte      ; This frame's input state
previous_input: byte     ; Last frame's input state
new_presses: byte        ; Buttons just pressed this frame

read_input_with_edges:
    ; Save last frame's state
    previous_input = current_input

    ; Read new input (platform-specific)
    current_input = read_hardware_input()

    ; Find newly pressed buttons:
    ; New press = pressed now AND NOT pressed before
    new_presses = current_input AND (NOT previous_input)

    return

Pseudocode

; Constants (bits in input byte)
BTN_UP    = %00000001
BTN_DOWN  = %00000010
BTN_LEFT  = %00000100
BTN_RIGHT = %00001000
BTN_FIRE  = %00010000

; Variables
current_input: byte = 0
previous_input: byte = 0
new_presses: byte = 0

; Call once per frame, before game logic
update_input:
    previous_input = current_input
    current_input = read_joystick()   ; Platform-specific
    new_presses = current_input AND (NOT previous_input)
    return

; Check if fire was JUST pressed (not held)
if new_presses AND BTN_FIRE:
    spawn_bullet()

; Check if fire is HELD (continuous fire)
if current_input AND BTN_FIRE:
    charge_weapon()

; Check if up was JUST pressed (menu navigation)
if new_presses AND BTN_UP:
    menu_selection = menu_selection - 1

Implementation Notes

6502:

update_input:
    lda current_input
    sta previous_input
    jsr read_joystick      ; Result in A
    sta current_input

    ; new_presses = current AND (NOT previous)
    lda previous_input
    eor #$FF               ; Invert (NOT)
    and current_input
    sta new_presses
    rts

; Usage
check_fire:
    lda new_presses
    and #BTN_FIRE
    beq .no_fire
    jsr spawn_bullet
.no_fire:

Z80:

update_input:
    ld a,(current_input)
    ld (previous_input),a
    call read_keyboard      ; Result in A
    ld (current_input),a

    ; new_presses = current AND (NOT previous)
    ld b,a
    ld a,(previous_input)
    cpl                     ; Invert (NOT)
    and b
    ld (new_presses),a
    ret

Trade-offs

AspectCost
CPU~15-25 extra cycles per frame
Memory2 extra bytes (previous + new_presses)
LatencyNone — detection is same frame as press

When to use: Menu navigation, single-shot weapons, jump triggers, pause toggle.

When to avoid: Continuous actions like movement or autofire (use raw input instead).

Variations

Released detection: Find buttons just released this frame

new_releases = previous_input AND (NOT current_input)

Double-tap detection: Track timing between presses

if new_presses AND BTN_RIGHT:
    if frames_since_last_right < 15:
        trigger_dash()
    frames_since_last_right = 0
else:
    frames_since_last_right = frames_since_last_right + 1