assembly8-bitz80zxspectrum

Drawing two character sprite in Z80 assembly


Following on from First Steps in Z80 Assembly Language I'm trying to move a two high character sprite in assembler.

            ORG 30000         ; Origin

LASTK       EQU 23560         ; last key press (system variable)

PRINT       EQU 8252          ; This means the label PRINT equates to 8252.


            XOR a             ; quick way to load accumulator with zero.
            LD A, 2           ; set print channel to screen

            CALL 5633         ; Open channel two (ie, write to screen)
            LD HL, GFX        ; set up UDGs
            LD (23675), HL    ; where the UDG characters are stored.
            CALL 3503         ; clear the screen. CLS


MAINLP      CALL PRTPLAY      ; print player sprite
            
            HALT              ; Slow it down three times
            HALT
            HALT

            LD BC, $FEFE      ; load port address into BC, scan for right ("X")
            IN A, (C)         ; load port data into A
            AND %0000100      ; looking for X
            JR Z, GORIGHT     ; if Z is press, go right

            JR MAINLP         ; loop back to continue scanning


GORIGHT     LD A, (PLAYER+2)  ; if player is at right edge, don't continue
            CP 31
            JR Z, MAINLP      ; Jump Relative Zero
            CALL UNDRAW
            LD A, (PLAYER+2)  ; get player's X coordinate
            INC A             ; add 1
            LD (PLAYER+2), A
            JR MAINLP


PRTPLAY     LD DE, PLAYER           ; print player graphic
            LD BC, EOPLAYR-PLAYER
            CALL PRINT
            RET


UNDRAW      LD A, " "            ; change graphic to empty space
            LD (PLAYER+3), A     ; store it
            CALL PRTPLAY         ; undraw graphic from screen
            LD A, 144            ; change graphic back to normal
            LD (PLAYER+3), A     ; store it


            RET ; return to basic!

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

            ; Player x, y 
PLAYER      DEFB 22, 12, 15, 144 ; print at Y, X, char 144 UDG (A)
            DEFB 22, 13, 15, 145 ; print at Y+1, X, char 145 UDG (B)
            EOPLAYR EQU $

            ; Graphics UDG Character
GFX         DEFB 6, 62, 124, 52, 62, 60, 24, 60
            DEFB 126, 126, 247, 251, 60, 118, 110, 119
            

The Manic Miner sprite is drawn ok. However when "x" is pressed to go right, only the top half moves. Which either means the undraw isn't working or the bottom character isn't incrementing. I'm very new to assembler and tried to unpick where I had gone wrong. I suspect it's where DEFB are explicitly told to be on 144 & 145, but the undraw is on 144 only. However that should be covered by LD BC, EOPLAYR-PLAYER. Confused.


Solution

  • So you tried to extend the book example by yourself... excellent, this way you will learn most.

    Your suspicion is partly correct, the PRTPLAY does print all bytes.

    So it prints first AT control code placing cursor at (x,12), then it prints UDG character 'A' 144, then another AT control code placing cursor at (x,13) and UDG character 'B' 145.

    The UNDRAW does modify only the top-character 144 to space, so it prints space over top of the sprite and prints again UDG 'B' over the bottom, keeping the sprite visible.

    To fix this (in the same mundane way how original works):

    UNDRAW      LD A, " "            ; change graphic to empty space
                LD (PLAYER+3), A     ; replace UDG A 144
                LD (PLAYER+7), A     ; replace UDG B 145
                CALL PRTPLAY         ; undraw graphic from screen
                LD A, 144            ; change graphic back to normal
                LD (PLAYER+3), A     ; store it
                LD A, 145
                LD (PLAYER+7), A
                RET                  ; return to caller
    

    And similarly the routine patching the X coordinate of AT control code has to patch two places now, as there are two AT control codes in your print string:

    GORIGHT     LD A, (PLAYER+2)  ; if player is at right edge, don't continue
                CP 31
                JR Z, MAINLP      ; Jump Relative Zero
                CALL UNDRAW
                LD A, (PLAYER+2)  ; get player's X coordinate
                INC A             ; add 1
                LD (PLAYER+2), A  ; update X position of upper char 144
                LD (PLAYER+6), A  ; update X position of lower char 145
                JR MAINLP
    

    You may want to go this kind of tutorial calling ROM using BASIC PRINT routines for graphics as quickly as possible and look for more advanced tutorials drawing directly into video RAM without ROM, as this is example here is both very slow in terms of performance (would start choking if you would try to animate few more sprites like this, while with optimised assembly you can move like 20+ 8x16 sprites per single frame), and becomes very unwieldy if you want to do larger sprites and suddenly you have to patch many AT control codes and many gfx-characters (144,145), plus it becomes further headache when you need clipping at sprite edges (and you need to print only part of the characters, but not all of them).

    I mean you should still try to grasp what the tutorial is doing and how, it's decent tutorial overall and you can exercise your Z80 assembly skill on it, but from the view point of demo-scener doing ZX demos for many years (and aiming for high performance full-screen gfx tricks) - this code is good only to learn how to not write ZX code. :)

    Also it may be somewhat easier to follow this tutorial if you are familiar with ZX BASIC and how PRINT works in BASIC. If you are completely unaware of ZX BASIC and you are just learning Z80 assembly, then this kind of print routines is probably even more confusing then code writing directly into video RAM without ROM routines.