## News:

Started by scheeba, 15:38, 28 January 10

0 Members and 1 Guest are viewing this topic.

#### scheeba

Hi all, was wondering if anyone here could help me out.

I'm using z88dk with cpcrslib, and can get sprites loaded and displaying at 0xc000 just fine. However I'm trying to get them to display at other x/y co-ordinates and I'm having trouble figuring out how the screen address is calculated on the cpc. Any tips on how to convert an x,y co-ordinate on a mode1 screen into an address would be great, thanks.

#### Bryce

http://www.cpcwiki.eu/index.php/Programming:Fast_Sprites

There might be some tips for you in there.

Bryce.

#### scheeba

I read that and a couple of other bits n' pieces, but it all seems well above my level as well as being much more than I need. I just need some reference I can use when placing images onscreen, I don't need to calculate movement or anything. Thanks anyway though.

#### Grim

The screen address can be calculated with something like this:
Quotescreenaddr = screenbase + (y AND 7)*&800 + int(y/8)*2*R1 + int(x/M)
with:
x and y your 2D coordinate.

R1 = value of CRTC register 1 (eg. 40 with the default firmware settings)

M = 2 (for screen mode 0, 2 pixels/byte)
M = 4 (for screen mode 1, 4 pixels/byte)
M = 8 (for screen mode 2, 8 pixels/byte)

screenbase = &C000

#### robcfg

As far as I remember, the screen memory in the cpc is not linear, but character based.

Each character has 8 rows, so the first 80 bytes of memory would be 320 pixels in mode 1, that is the first scanline of the first row of characters. Then, instead of the second scanline of the 1st row of characters, you have the 1st scanline of the second line of characters, and so on until the you get the 1st scanline of the 8th line of characters. Then you begin with the second scanline of the 1st line of chars, etc...

I also think that every 2000 bytes, you have to jump to the next 2048 byte block.

Also, in mode 1 there are 4 pixels packed in one byte, so you'll have to get the byte where the pixel is and unpack it to get the value or pack it to modify the value.

Maybe I'm mistaken, but that's what I remember, hope this helps.

#### scheeba

That's certainly enough for me to start playing around with things some more, thanks for the advice all!

#### robcfg

#6
I put up a little example to clarify things a bit:

`10 MODE 120 FOR L=0 to 730 color=25535 IF (L mod 2) = 1 THEN color=24040 POKE &C000+(2048*L),color50 NEXT L60 GOTO 60`

Pixels in mode 1 are stored as 2 bit values within a byte. The byte look like this "12341234", being the 1st bit and the 5th bit the two bits of the first point, and so on. The value of the 2 bits indicate the pen (or ink, I don't remember well) that should be used to paint the pixel.

Try to change the 240 for a 53, you'll see the four colors together.

#### redbox

I think of the CPC video RAM as being arranged as a venetian blind - if you watch of a tape loading on the 464 you can see how the memory is arranged by watching the picture being loaded into &C000.

To work out the next character line on the screen, you add &50 to the screen address (so &C000 becomes &C050).  To work out the next pixel line down on the screen, you add &800 (so &C000 becomes &C800).  You then just need something to work out if the address needs to be wrapped around to the start again if the addition goes over &FFFF (as the screen RAM is &C000 to &FFFF).

This routine will plot a simple sprite for you and is easy to understand:

`org &8000ld hl,&4000 ;address of sprite datald de,&C000 ;screen address ld b,32 ;height in pixelsld c,8 ;width in bytescall spriteret.spritepush de           ;preserve screen addresspush bc           ;and the dimensionsld b,0               ;BC now equals the width in bytesldir                   ;copy the bytes!pop bc             ;get back the dimensionspop de             ;and then the screen addressex de,hl           ;to move DE onto the next screen line,call nline          ;we use the routine to move HL onto theex de,hl           ;the next line, but swap DE and HL either sidedjnz sprite       ;if not all lines done, loop backret.nline:  ld a,8add a,h           ;add 8 to H (the high byte)ld h,aret nc             ;return if no overflowpush de         ;otherwise preserve DEld de,&C050add hl,de       ;add &C000+&50 to HLpop de           ;and get DE back again!ret`

#### scheeba

Outstanding, cheers. My sprites seem to be doing what I tell them to at last, it's all starting to make sense now. I'll get back to work then, +rep for all

#### mr_lou

Quote from: scheeba on 19:05, 28 January 10
Outstanding, cheers. My sprites seem to be doing what I tell them to at last, it's all starting to make sense now. I'll get back to work then, +rep for all

Are you coding a game? Lemme know when you need music.

#### fano

Quote from: mr_lou on 10:50, 31 January 10
Are you coding a game? Lemme know when you need music.
off topic but I may need you.Have a bit of free time for me ?
"NOP" is the perfect program : short , fast and (known) bug free

#### scheeba

Quote from: mr_lou on 10:50, 31 January 10
Are you coding a game? Lemme know when you need music.
Cool, thanks for the offer, I may well take you up on that! I'm playing with a couple of game ideas at the moment, I'll post some shots and stuff once I have some decent mockups and/or prototypes together. It'll probably be a little while until I have anything good to go, but I'll definitely need some help with the soundtrack.

#### Ygdrazil

Screenaddreses on the CPC can be quite confusion in the beginning but when been doing a while its real simple!!

Looking forward to som nice sprites moving around!

/Ygdrazil

Quote from: scheeba on 15:20, 31 January 10
Cool, thanks for the offer, I may well take you up on that! I'm playing with a couple of game ideas at the moment, I'll post some shots and stuff once I have some decent mockups and/or prototypes together. It'll probably be a little while until I have anything good to go, but I'll definitely need some help with the soundtrack.

#### redbox

The problem with this sprite routine (working demo below) is that when you use it to plot across the screen memory boundary (i.e. it goes over &FFFF and loops back round) the first pixel line of the sprite at the top of the screen is not printed due to the nline routine simply adding &C050, and not looking for &FFFF+1 (&0000) and if true just adding &C000.  This, of course, creates problems when you've been scrolling the screen...

Can anyone suggest a better way of doing the nline routine to accommodate this...?

` org &8000 ld a,0 ;mode 0 call &BC0E ld bc,0 ;border 0 call &BC38 ld hl,&B90C ;address of sprite data (use firmware area just to plot something to see) ld de,&C380 ;screen address ld b,160 ;height in pixels ld c,4 ;width in bytes call sprite caLL &BB18 ;wait for keypress retsprite: push de         ;preserve screen address        push bc         ;and the dimensions        ld b,0          ;BC now equals the width in bytes        ldir            ;copy the bytes!        pop bc          ;get back the dimensions        pop de          ;and then the screen address        ex de,hl        ;to move DE onto the next screen line,        call nline      ;we use the routine to move HL onto the        ex de,hl        ;the next line, but swap DE and HL either side        djnz sprite     ;if not all lines done, loop back        retnline:  ld a,8        add a,h         ;add 8 to H (the high byte)        ld h,a        ret nc          ;return if no overflow        push de         ;otherwise preserve DE        ld de,&C050        add hl,de       ;add &C000+&50 to HL        pop de          ;and get DE back again!        ret`

#### Axelay

Quote from: redbox on 06:26, 01 February 10
The problem with this sprite routine (working demo below) is that when you use it to plot across the screen memory boundary (i.e. it goes over &FFFF and loops back round) the first pixel line of the sprite at the top of the screen is not printed due to the nline routine simply adding &C050, and not looking for &FFFF+1 (&0000) and if true just adding &C000.  This, of course, creates problems when you've been scrolling the screen...

Can anyone suggest a better way of doing the nline routine to accommodate this...?

When you cross that screen end boundary you are also crossing the &800 byte boundary that makes up each set of 8 horizontal pixel lines in each character row.  If the screen has been scrolled so byte &ffff is visible, the byte below it should be &c04f on a 40 character wide screen, so instead of adding &c050, it should be &b850, as nline always adds &800 to start with.  Otherwise, you will skip one pixel line as you describe.

Here's that code with nline modified, but it might be wrong, it can probably be done better, but it seemed to work!

` org &8000;   ld a,0      ;mode 0;   call &BC0E   ld bc,0      ;border 0   call &BC38   ld hl,&B90C   ;address of sprite data (use firmware area just to plot something to see)   ld de,&C380   ;screen address   ld b,160   ;height in pixels   ld c,4      ;width in bytes   call sprite      caLL &BB18   ;wait for keypress   retsprite: push de         ;preserve screen address        push bc         ;and the dimensions        ld b,0          ;BC now equals the width in bytes        ldir            ;copy the bytes!        pop bc          ;get back the dimensions        pop de          ;and then the screen address        ex de,hl        ;to move DE onto the next screen line,        call nline      ;we use the routine to move HL onto the        ex de,hl        ;the next line, but swap DE and HL either side        djnz sprite     ;if not all lines done, loop back        retnline:  ld a,8        add a,h         ;add 8 to H (the high byte)        ld h,a        ret nc          ;return if no overflow        push de         ;otherwise preserve DE        ld a,&07        ; if hl was &ffb0 or higher        cp h            ; before &800 was added        jr nz,NextLine  ; it was the last char line        ld a,&af        ; when screen is 40 chars wide        cp l        jr nc,NextLine        ld de,&c050-&800 ; if it was the last char line        jr TopLine       ; subtract the &800 already added.NextLine        ld de,&c050.TopLine        add hl,de       ;add &C000+&50 to HL        pop de          ;and get DE back again!        ret`

However, if you are h/w scrolling the screen you also need to consider that those 8 separate &800 byte pixel rows all loop.  As far as the video h/w is concerned, the byte to the right of &c7ff is &c000 for example, the LDIR used here is going to drop down a pixel row by moving to &c800.

#### arnoldemu

Quote from: redbox on 06:26, 01 February 10
The problem with this sprite routine (working demo below) is that when you use it to plot across the screen memory boundary (i.e. it goes over &FFFF and loops back round) the first pixel line of the sprite at the top of the screen is not printed due to the nline routine simply adding &C050, and not looking for &FFFF+1 (&0000) and if true just adding &C000.  This, of course, creates problems when you've been scrolling the screen...

Can anyone suggest a better way of doing the nline routine to accommodate this...?

` org &8000 ld a,0 ;mode 0 call &BC0E ld bc,0 ;border 0 call &BC38 ld hl,&B90C ;address of sprite data (use firmware area just to plot something to see) ld de,&C380 ;screen address ld b,160 ;height in pixels ld c,4 ;width in bytes call sprite caLL &BB18 ;wait for keypress retsprite: push de         ;preserve screen address        push bc         ;and the dimensions        ld b,0          ;BC now equals the width in bytes        ldir            ;copy the bytes!        pop bc          ;get back the dimensions        pop de          ;and then the screen address        ex de,hl        ;to move DE onto the next screen line,        call nline      ;we use the routine to move HL onto the        ex de,hl        ;the next line, but swap DE and HL either side        djnz sprite     ;if not all lines done, loop back        retnline:  ld a,8        add a,h         ;add 8 to H (the high byte)        ld h,a        ret nc          ;return if no overflow        push de         ;otherwise preserve DE        ld de,&C050        add hl,de       ;add &C000+&50 to HL        pop de          ;and get DE back again!        ret`

next line: (also works for screen at &40)
`ld a,hadd a,8ld h,aand &38ret nzld a,hand &c0ld c,ald a,ladd a,&50ld l,ald a,hadc a,&8and 7or cld h,aret`

only need to do this every 2 bytes:
`next_byte: inc hlld a,hand &7ret nzor lret nzld a,hsub 8ld h,aret`
My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

#### redbox

#16
Quote from: Axelay on 11:49, 01 February 10
Here's that code with nline modified, but it might be wrong, it can probably be done better, but it seemed to work!

This is along the lines of what I was thinking and a good example of a fix - thanks!

Quote from: Axelay on 11:49, 01 February 10
However, if you are h/w scrolling the screen you also need to consider that those 8 separate &800 byte pixel rows all loop.  As far as the video h/w is concerned, the byte to the right of &c7ff is &c000 for example, the LDIR used here is going to drop down a pixel row by moving to &c800.

Yes, I have come across this when I was using the hardware scrolling.  My thinking was that after scrolling the screen around once totally you have &C000 as the top line again, and if you start writing to &C800+ you are printing 1 pixel line down.  Therefore, I look to see if the screen address has reached &C800 and then reset it to &C000 if true:

` inc de ;increase screen address by 1 byte for next time ld a,d ;load a with high byte of screen address and &c8 ;is it &C8 yet (we have scrolled right round as &C800 is one pixel line down) jp pe,notyet ;no, not yet so jump to update screen address else... ld de,&c000 ;yes, so reset screen address to &C000 ld (scrloc),de ;store it in pointer jp main_loop ;do the loop againnotyet: ld (scrloc),de ;store increased screen location jp main_loop ;loop round again`

This works in my example, where I am scrolling the whole screen and printing a sprite on the right far line every time.

#### redbox

Quote from: arnoldemu on 14:46, 01 February 10
only need to do this every 2 bytes:

I would like to try this code too - which two bytes do you need to call this routine after?

#### arnoldemu

Quote from: redbox on 15:36, 01 February 10
I would like to try this code too - which two bytes do you need to call this routine after?

Well it works like this:

generic code for drawing a box to screen (no transparency/mask):
`ld de,spriteld hl,scr_addrld b,16 ;; heightld c,8 ;; widthheight_loop: push bcpush hlwidth_loop: ld a,(de)   ;; pixel datald (hl),a ;; screeninc decall next_bytedec cjr nz,width_looppop hlcall next_linepop bcdjnz height_loop`

if the screen address is even (e.g. bit 0 is 0) then you can do this:

`...ld a,(de)ld (hl),ainc deinc lld a,(de)ld (hl),ainc decall next_byte...`
if the screen address is odd then you can do this:

`...ld a,(de)ld (hl),ainc decall next_byteld a,(de)ld (hl),ainc l...`

So what this basically means is that you don't have to call next_byte for ever byte you write to the screen. if you know your screen address is odd/even and you want to unroll your sprite drawing code you can choose the specific routine to call and this ends up being a bit faster.

also, because the screen is effectively scrolled 2 bytes at a time this works fine.

You will find that if you use the next_byte for every byte you plot, drawing software sprites like this on a hardware scrolling screen eats up cpu time.

The next_byte routine works but as you can see is not fast

Drawing sprites, and even drawing sprites on a hardware scrolling screen is not necessarily fast

My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

#### redbox

Quote from: arnoldemu on 15:50, 01 February 10
So what this basically means is that you don't have to call next_byte for ever byte you write to the screen. if you know your screen address is odd/even and you want to unroll your sprite drawing code you can choose the specific routine to call and this ends up being a bit faster.

Ah, I see what you mean and I understand the theory of this routine now.

However, when I combine it all into one piece of code it doesn't work...:

` org &8000 ld a,0      ;mode 0 call &BC0E ld bc,0      ;border 0 call &BC38 ld de,sprite ld hl,scr_addr ld b,160 ;; height ld c,4 ;; widthheight_loop: push bc push hlwidth_loop: ld a,(de)    ;; pixel data ld (hl),a ;; screen inc de call next_byte dec c jr nz,width_loop pop hl call next_line pop bc djnz height_loop call &BB18 retnext_byte: inc hl ld a,h and &7 ret nz or l ret nz ld a,h sub 8 ld h,a retnext_line: ld a,h add a,8 ld h,a and &38 ret nz ld a,h and &c0 ld c,a ld a,l add a,&50 ld l,a ld a,h adc a,&8 and 7 or c ld h,a retsprite: dw &B90Cscr_addr: dw &C380`

#### arnoldemu

Quote from: redbox on 17:05, 01 February 10
Ah, I see what you mean and I understand the theory of this routine now.

However, when I combine it all into one piece of code it doesn't work...:
what does it do? the width is in bytes (so this means sprites are multiple of 4 pixels in mode 1, multiple of 2 pixels in mode 0 and multiple of 8 pixels in mode 2).

The code looks correct to me.
My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

#### redbox

Quote from: arnoldemu on 10:46, 02 February 10
The code looks correct to me.

It was calculating the next character (not pixel) row down incorrectly.  I will go through it and try to see what the problem is.