This is a blog dedicated to helping others learn from my progress in developing machine code games for the Sinclair ZX Spectrum using a Windows based PC.
Tuesday, 25 August 2009
Scrolling Map Project (Update)
Once I have reached a presentable state with it, I'll update Part 2.
Friday, 8 May 2009
Scrolling Map Project (Part 1)
I have always been a big fan of large scrolling map games, where the play area is much larger than the screen. Sonic the hedgehog is a good example. I suppose even Lunar Jet man could be classed as one of this games.
I then played a game on the spectrum called “Maze Mania” by Hewson Consultants and thought “How did they do that?”.
Discussion
There is an “animated” thread discussing this here: http://www.worldofspectrum.org/forums/showthread.php?t=24788
Basically, I observed that the main screen is 28 chars by 16 chars in size, moving 1 char (8 pixels) in all directions. Moving 1 char avoided any colour clash and looked really good. The game ran at 12.5 Frames per second, and looked pretty smooth.
Investigating the code using a disassembler I noticed
1. They drew the tiles to a back buffer
2. Drew the sprites in their correct positions over the top of the back buffer tiles
3. Copied the buffer to the main screen
The following diagram showed how I planned to implement this (I was aiming for 24x16) where Diagram 1 is the back buffer, 2 is the region to copy and 3 is the end result on the screen.
Back buffer
| |
Region To Copy | |
Display |
Copying the Back Buffer
In Maze Mania, in order to copy the back buffer to the screen I think they have a table for each of the lines (18 x 8 = 144 lines) containing the source address and the destination screen address. The SP is pointed to this table, and the values are "popped" into HL and DE. Then 28 x LDI's are done to copy from the source to the destination.
Here is my suggested code based on 16 rows (128) x 24 columns
HALT ; Wait for Top Of Scanline, in reality would set up IM2 or something
DI ; Disable Interrupts
LD (spstore),SP ; Store Stack Pointer
LD SP,table_of_source_and_dest_addresses ; Point SP to our table of source and destination (display) addresses
; Wait for scan line
LD BC,$0320 ; 800
loop:
DEC BC
LD A,B
OR C
JR NZ,loop
LD A,$80 ; 128 (16 Rows of 8 bytes)
line_loop:
POP DE ; Get DESTINATION (Display) from our table
POP HL ; Get SOURCE address from our table
LDI ; Copy Contents of (HL) to (DE) 24 times (24 columns)
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
DEC A ; Move to next row
JR NZ,line_loop ; repeat until all done
LD SP,(spstore) ; restore Stack Pointer
EI ; enable interrupts
An example of the "table_of_source_and_dest_addresses" then looks like this:
Code:
table_of_source_and_dest_addresses:
; Destination (Display), Source
defw $4000,$E500,%4100,$E600..... for all 144 lines (18 Rows)
Where the first 2 bytes are the screen destination, the next 2 are the back buffer (in this case starting at $E500)
This takes about 140,000 T-Cycles, so I reckon with some optimised Tile Writing and Sprite Code I could get 50/4 = 12.5 Frames per second, or at least thats my goal.
Writing Tiles
We need to write tiles to the back buffer. For starters, I have written code that writes them straight to the display.
The code reads from a map, converts to a graphics address and writes the tile to the display. The map is 16 x 16 squares (256). The Clear square is Tile 0, the Lines are Tile 1 and the solid block is Tile 3.
The following code when assembled will produce the following:
If you move the screen address around (de), or the map position (bc) you will see the tiles react accordingly.
; draw03.asm
; Draw a 4x4 block and fill the screen
; de = Screen address; Takes about 100,000 cycles to do this (8 columns * 4)
org 08000h
; Turn border black
xor a ; a = 0
out (254),a ; send out of port to set borderdi ; disable interrupts
ld de,04000h ; de points to screen (top left) ld bc,map ; bc points to map position (0,0)
ld a,5 ; 5 Rows x 4 = 24 rows
loop1: push af ; store counter push de ; store screen address (First column)ld a,8 ; 8 columns to draw
loop2: push af ; store column counterpush de ; store screen address
ld a,(bc) ; Get Map TILE at map Position
ld l,a ; e = tile
ld h,0; Multiply by 128 to get tile gfx address
rl l
rl hrl l
rl hrl l
rl hrl l rl h
rl l
rl hrl l
rl hrl l
rl h; add address to base gfx address
ld a,h add a,gfx/256
ld h,apush bc ; store map position
call drawblock ; draw the block
pop bc ; get map position backinc c ; increase map position (column)
pop de ; retrieve screen address
inc e ; Move screen address along 4 characters to next column
inc e
inc e
inc epop af ; get column counter
dec a ; and decreasejr nz,loop2 ; repeat until all columns drawn
pop de ; get original (first column) screen address back
ld a,80h ; 4 lines (32 * 4 = 128) add a,e ; add to screen address (e) ld e,a
jr nc,loop3 ; If we have not crossed the 256 byte border, ok to draw
ld a,d ; Move to next 2048k screen third
add a,8 ; By adding 8 to d, 001000 = 2048
ld d,aloop3:
ld a,c ; We have drawn 8 columns so move to next row (16 by 16 map) add a,8 ; add 8 columns to get to next row ld c,apop af ; get counter back
dec a ; decrease counter
jr nz,loop1 ; repeat if we are not at the bottom rowei
ret
drawblock:
; Start
ld (spstore),sp ; Store Stack Pointer
ld sp,hl ; SP = Graphics Block
ex de,hl ; hl now has screen address
ld a,4 ; 4 Rows of Chars
a1:; Store Counter
ex af,af';-------------------------------------
;This code is repeated 8 times; Get 4 bytes from GFX
pop de
pop bc; Store in screen position
ld (hl),e
inc l
ld (hl),d
inc l
ld (hl),c
inc l
ld (hl),b; Next screen line
inc h; Get 4 more and work back
pop de
pop bcld (hl),b
dec l
ld (hl),c
dec l
ld (hl),d
dec l
ld (hl),einc h
;-------------------------------------
pop de
pop bcld (hl),e
inc l
ld (hl),d
inc l
ld (hl),c
inc l
ld (hl),binc h
pop de
pop bcld (hl),b
dec l
ld (hl),c
dec l
ld (hl),d
dec l
ld (hl),einc h
pop de
pop bcld (hl),e
inc l
ld (hl),d
inc l
ld (hl),c
inc l
ld (hl),binc h
pop de
pop bcld (hl),b
dec l
ld (hl),c
dec l
ld (hl),d
dec l
ld (hl),einc h
pop de
pop bcld (hl),e
inc l
ld (hl),d
inc l
ld (hl),c
inc l
ld (hl),binc h
pop de
pop bcld (hl),b
dec l
ld (hl),c
dec l
ld (hl),d
dec l
ld (hl),einc h
; Work out next screen line
NextPixelLine:ld a,h
and 7
jr nz,npl_ok
ld a,h
sub 8
ld h,a
Next_Char_Line: ld a,l
add a,32
ld l,a
and 224
jr nz,npl_ok
ld a,h
add a,8
ld h,a; loop
npl_ok:ex af,af'
dec a
jr nz,a1; restore SP and return
ld sp,(spstore)ret
spstore: defw 0000
org 0e400h
map:
defb 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
defb 1,1,1,2,0,0,0,0,0,0,0,0,0,0,0,0
defb 0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0
defb 0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0org 0e500h
gfx:
defb %11111111,%11111111,%11111111,%11111111
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %10000000,000000,000000,000001
defb %11111111,%11111111,%11111111,%11111111defs 128,128
defs 128,255
end 08000h
Conclusion
So far, we have worked out in theory how the map will work and have developed code to
1. Copy the back buffer to the screen2. Draw tiles to the back buffer (The above code is direct to the screen)
The next step (Part 2 to follow) will add colour to the tiles and draw attributes as well as left, right, up and down controls to allow you to scroll through the map.
Further parts to this series will then draw masked sprites on top of the background.
Thursday, 29 January 2009
Fast Sprites using Pre-Shifted Data
I wrote this demo on September 15th 2006.
The purpose was to develop a sprite engine (XOR to remove/display) that allows developer to concentrate on creating graphics and animation and not worry about shifting bytes, or pixel perfect collision. I also wanted to get as many sprites on the screen at any one time by using the screen refresh to time drawing of each sprite.
Advantages
- No need for pre-shifted graphics (Frame 1,2 etc), saving memory space
- Pixel perfect collision
- Full screen
Timing
On each 1/50th of a second:
For each sprite (1-4):
- move
- remove
- check for collision
- draw
If more than 4 sprites, we handle it like this (1/25th second)
- LOOP 1
Process first 4 sprites (Remove, draw)
Draw ALL sprite attributes - LOOP 2
Process remaining sprites
This demo allows for the drawing of 8 sprites. We can do this before the scan line hits the main screen (whilst still in border) if we time sprites right:
The following sprites can appear on the following rows to beat scan line (Flicker/No Visibility):
- Sprites 1 and 2 can appear anywhere on screen
- Sprite 3 can appear on rows 7-22
- Sprite 4 can appear on rows 12-22
The same then applies for 5 and 6,7 and 8 on loop 2. Then, for all the extra time we have before the next screen refresh, we would fill in the remaining time of the second drawing with music, or other animation.
Colour
When the sprite is drawn, to avoid flicker, the screen attributes are
- Copied to the attribute buffer
- Sprite attributes are written to the buffer
- The attribute buffer is copied to the screen
Download
You can download the source code and TAP file from http://www.peargames.co.uk/downloads/fsprites.zip
- To run, set up your development environment as described in my previous post, and open “fsprites.asm”.
- In “ConText”, press F9 to compile, F10 to run in “Spectaculator”
Next Steps
I plan to write a detailed walkthrough of the code to explain how it all works, watch this space ;-) !
Wednesday, 28 January 2009
Setting up the Development Environment on a Windows PC
When I develop Machine Code games for the Sinclair ZX Spectrum, I use the following Development Tools:
- ConText Text Editor (http://www.contexteditor.org/)
- Pasmo Assembler (Pasmo)
- Spectaculator Emulator (http://www.spectaculator.com/)
Installing “Spectaculator”
Download and Install “Spectaculator” 30 Day Trial. I cannot recommend this emulator more highly, it is awesome
Installing “Context” Editor
The Editor allows me to write the code with Syntax Highlighting, as well as define events for key presses that assist me in running and testing my programs.
Download the editor (1.6 MB) from http://www.contexteditor.org/ andfollow the installation instructions:
Step 1:
Step 2:
And follow the wizard until it has installed
Install “Pasmo” Assembler
Next, download the Assembler from http://www.arrakis.es/~ninsesabe/pasmo/
I use the 0.5.3 version, downloadable as a ZIP from pasmo-0.5.3.zip
Once downloaded, Unzip and extract.
Install in a directory of your choice
Configure Context
- From the menu choose “Options -> Environment”. A dialog box will appear.
- Click the “Execute Keys” tab.
- Click the “Add” button, and add an entry for files with a “asm” extension.
Set up the “F9” Functionality.
This will allow the file to be compiled, and a “TAP” file which includes a BASIC loader will be created.
- Execute: Browse To and Select the Pasmo executable.
- Start in: %p (File path)
- Parameters: --tapbas %n %F.tap
- This will create a ZX Spectrum TAP file with BASIC loader, output to same filename with “TAP” extension.
- Use short DOS names for Pasmo.
- Output to compiler window.
- Scroll to last line for verbose output.
Set up the “F10” functionality
This will execute “Spectaculator” and pass in the compiled filename.
- Execute: Points to the Spectaculator executable.
- Start in: %p (File path)
- Parameters: %p\%F.tap
- This means Path, and Filename (tap)
- Use short DOS names for Emulator
Testing the Development Environment
Create the following test program in Context, and save to your project folder as “text.asm”.
org 08000h
ld hl,04000h
ld (hl),%10101010
ld de,04001h
ld bc,17FFh
ldir
ret
end 08000h
Press “F9” and the program should assemble.
Press “F10” to execute “Spectaculator” and you should get the following results:
Congratulations! You are now ready to develop Machine Code on your Windows PC!