Skip to content
Game 5 Unit 17 of 128 1 hr learning time

Custom Character Set

Design and load custom characters for improved visual quality and a more polished game appearance.

13% of Ink War

Phase 2 begins! The game is playable—now we make it polished. Custom characters are the foundation of distinctive Spectrum graphics.

Run It

Assemble and run:

pasmonext --sna inkwar.asm inkwar.sna

Unit 17 Screenshot

Version shows “PHASE 2 V0.1”.

Why Custom Characters?

The ROM character set is functional but generic. Every Spectrum program that uses it looks the same. Custom characters let you:

BenefitExample
Brand identityDistinctive logo and UI
Better bordersClean lines instead of blocky defaults
Special symbolsGame-specific icons
Visual polishProfessional appearance

The Spectrum Character System

Characters are 8×8 pixel bitmaps stored sequentially:

Character 'A' (8 bytes):
Byte 0: %00111100  →    ****
Byte 1: %01000010  →   *    *
Byte 2: %01000010  →   *    *
Byte 3: %01111110  →   ******
Byte 4: %01000010  →   *    *
Byte 5: %01000010  →   *    *
Byte 6: %01000010  →   *    *
Byte 7: %00000000  →

The ROM charset lives at $3D00 and contains 96 printable characters (space through to ©).

Installing a Custom Charset

The approach:

  1. Reserve RAM for 768 bytes (96 chars × 8 bytes)
  2. Copy ROM charset to RAM at startup
  3. Modify specific characters as needed
  4. Point print routine at custom charset
; Reserve space for custom charset
custom_charset: defs    768, 0

install_charset:
            ; Copy ROM charset to our RAM area
            ld      hl, $3D00           ; ROM charset (space onwards)
            ld      de, custom_charset
            ld      bc, 768             ; 96 characters * 8 bytes
            ldir
            ret

Updated Print Routine

Our print routine now reads from the custom charset instead of ROM:

print_char:
            ; Calculate character data address
            sub     32              ; ASCII to charset offset
            ld      l, a
            ld      h, 0
            add     hl, hl
            add     hl, hl
            add     hl, hl          ; HL = (char-32) * 8
            ld      de, custom_charset
            add     hl, de          ; HL = source address

            ; ... rest of display file calculation unchanged

The key change is using custom_charset instead of the ROM address.

Designing Custom Characters

For borders, consider:

Corner (top-left):      Horizontal:         Vertical:
%11111111  ████████    %00000000           %11000000  ██
%11000000  ██          %00000000           %11000000  ██
%11000000  ██          %11111111  ████████ %11000000  ██
%11000000  ██          %11111111  ████████ %11000000  ██
%11000000  ██          %11111111  ████████ %11000000  ██
%11000000  ██          %11111111  ████████ %11000000  ██
%11000000  ██          %00000000           %11000000  ██
%11000000  ██          %00000000           %11000000  ██

These create cleaner, more professional-looking borders than using attribute-only blocks.

Memory Considerations

The charset uses 768 bytes of RAM. On a 48K Spectrum with 32K available (above the ROM), this is acceptable. Position it carefully:

$8000 (32768) - Program start
...
custom_charset - 768 bytes for characters
board_state    - 64 bytes for game state
variables      - Game variables

The Complete Code

What You’ve Learnt

  • Character memory - 8×8 bitmaps, 8 bytes per character
  • Custom charset installation - Copy ROM to RAM, modify as needed
  • Print routine update - Point at RAM charset instead of ROM
  • Visual identity - Foundation for distinctive graphics

What’s Next

Unit 18 animates the cursor with colour cycling—making it easier to see and more visually engaging.

What Changed

Unit 16 → Unit 17