Thursday 3 October 2024

Bipboi Returns

 

In 2009 Cronosoft released my game for the 48k Spectrum Bipboi on Cassette

I felt a real sense of satisfaction finally releasing a game for the ZX Spectrum, I got my 16k Issue 2 mail order Speccy in December 1982, so it only took 27 years.

During the development I got great support from Shaun Bebbington who wrote for Micro Mart back in the day, for the Retro section, I still have the original mags somewhere in the loft.

Now, a further 15 years on from then, Shaun asked the question on Facebook “Will there be a sequel”.  This timed exactly with a quick sketch I did this morning on my car window:



And so, I have been meaning to write another game for the Speccy, and today I begin work on Bipboi Returns,  

I will document progress here, stay tuned, the stars of Bip must be saved once again!

Thursday 1 June 2017

Using Visual Studio Code as your Editor on a Mac

I have managed to get VS Code to work for my development

Download Visual Studio Code

I am assuming you are using PASMO as your command line assembler, but the following tutorial should work with your command line assembler of choice.

To set up PASMO on a Mac, see this awesome tutorial

Create the Test Project
Like all my Angular and Node development, I now create a new folder for each spectrum project.

I also create a "DIST" subfolder, for the assembled TAP file to live.

Once you have created it, from VS Code choose "Open Folder" and point to the newly created folder.

You should see no files, except for a "DIST" folder

Create a test "hello..asm" file (Shoutout to Juho Vähä-Herttua)

org $8000

; Define some ROM routines
cls EQU $0D6B
opench EQU $1601
print EQU $203C

; Define our string to print
string:
db 'Hello world!',13

start:
; Clear screen
call cls

; Open upper screen channel
ld a,2
call opench

; Print string
ld de,string
ld bc,13
call print

; Return to the operating system
ret

end start 


Configure tasks.json
To create your tasks.json file, follow this tutorial (Just need to complete the "Hello World" bit)

This part assumes that "Pasmo" is in your environments $PATH and can be called from anywhere.

If not, you will need to add it to your environment path. Replace the default task in task.json with the following
{
"command": "pasmo",
"args": ["--tapbas", "-d", "${fileBasename}", "dist/${fileBasenameNoExtension}.tap"],
"showOutput": "always"


Edit your keybindings.json (VS->Preferences->Key Mappings->keybindings.jscon) with:

{
"key": "cmd+q",
"command": "workbench.action.tasks.runTask",
"args": "pasmo"


Go Go Go
Select the file you want to assemble ("hello.asm") and then do CMD+Q, and it will assemble your code and put the TAP file in your DIST folder.

Now open with FUSE, SPECTACULATOR etc and you are away. I

Tuesday 2 February 2016

I'm back

i finally have recovered my log in details to my blog!

Expect an update soon

All is good here, and looking to pick up my ZX Spectrum development ASAP, especially since the delivery of my recreated spectrum!

Thursday 25 November 2010

Return to the Spectrum

After more than a year away from the Speccy scene, developing iPhone apps and the like, I have decided to return and continue work on the large map tile games.

Thanks for your patience, watch this space

Tuesday 25 August 2009

Scrolling Map Project (Update)

Just a quick post to let all who are following that the game based on the Maze theory is starting to kick off in earnest.

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:


image

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 border


di ; 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 counter

push 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 h


rl l

rl h


rl l

rl h


rl l rl h


rl l

rl h


rl l

rl h


rl l

rl h



; add address to base gfx address

ld a,h add a,gfx/256

ld h,a


push bc ; store map position

call drawblock ; draw the block

pop bc ; get map position back


inc 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 e


pop af ; get column counter

dec a ; and decrease


jr 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,a



loop3:

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,a



pop af ; get counter back

dec a ; decrease counter


jr nz,loop1 ; repeat if we are not at the bottom row



ei



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 bc



ld (hl),b

dec l


ld (hl),c


dec l


ld (hl),d


dec l


ld (hl),e



inc h



;-------------------------------------



pop de

pop bc



ld (hl),e

inc l


ld (hl),d


inc l


ld (hl),c


inc l


ld (hl),b



inc h



pop de

pop bc



ld (hl),b

dec l


ld (hl),c


dec l


ld (hl),d


dec l


ld (hl),e



inc h

pop de


pop bc



ld (hl),e

inc l


ld (hl),d


inc l


ld (hl),c


inc l


ld (hl),b



inc h



pop de

pop bc



ld (hl),b

dec l


ld (hl),c


dec l


ld (hl),d


dec l


ld (hl),e



inc h

pop de


pop bc



ld (hl),e

inc l


ld (hl),d


inc l


ld (hl),c


inc l


ld (hl),b



inc h



pop de

pop bc



ld (hl),b

dec l


ld (hl),c


dec l


ld (hl),d


dec l


ld (hl),e



inc 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,0



org 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,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,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,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,000001


defb %10000000,000000,000000,000001


defb %10000000,000000,000000,000001


defb %11111111,%11111111,%11111111,%11111111



defs 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 screen
2. 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.

My goal is to document the complete development of this game, so stay posted and constructive comments are always welcome.

You can download the source and “tap” image for the spectrum for this article here

*** ADDITION ***

You can download a work in progress of step 2 here. Use Q,A,O & P to navigate the map.

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.

image

 

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):

  1. Sprites 1 and 2 can appear anywhere on screen
  2. Sprite 3 can appear on rows 7-22
  3. 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

  1. Copied to the attribute buffer
  2. Sprite attributes are written to the buffer
  3. 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 ;-) !