Sprites will kill me. Help

ZX80, ZX 81, ZX Spectrum, TS2068 and other clones
Post Reply
Emlyn_Hughes
New member
Posts: 2
Joined: Mon Feb 02, 2009 12:15 am

Sprites will kill me. Help

Post by Emlyn_Hughes »

Hi. I'm struggling to find out some way to put a 3x3 coloured sprite on screen. So far I got a monochrome sprite.
I'm trying to give color with:

uchar n;
void addColour(struct sp1_cs *cs)
{
// . . .
// more clauses
// . . .
else if (n==9)
cs->attr = INK_BLACK | PAPER_MAGENTA;
}

sp1_IterateSprChar(my.sprite, addColour);

but the color it shows it's wrong most of the time.


I would thank some little v3.0 working sample, but any kind of help is welcome.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

Hi Emlyn,

I guess I'll have to take this one :-) Sorry for the slow response -- I have been really busy the last few months and have not done much with these things lately but I still check in occasionally.

You will find some simple example programs in z88dk/libsrc/sprites/software/sp1/spectrum/examples and the first example involving coloured sprites is ex4a.c. You can also browse these files online:

http://z88dk.cvs.sourceforge.net/viewvc ... /examples/

These examples are meant to be read in order as a sort of tutorial. Unfortunately they only really scratch the surface of what the engine can do but hopefully some insight will be gained into how things work nevertheless. There has been a bugfix or two since 1.8 was released but if I remember right the bugs only affected background tile printing using the sp1_PrintString() function. If one of the example programs crashes, you'll have to grab the updated files from cvs to be able to compile it. Pre-compiled tap files are already contained in the examples directory so you know it should work with updated files.

There are two ways to colour sprites. The first method is inherited from splib and is contained in most of the example code you will see on the web and is how you are trying to do it in the code snippet you posted. The second method is faster and is designed to be used inside the main game loop, if desired. Either way is fine and you should use whatever makes sense.

METHOD #1 (inherited from splib2)

When sprites are created, the graphic is divided into individual 8x8 pixel squares (characters, in fact). Each of those character squares is described by a "struct sp1_cs", the definition of which can be extracted from the sp1.h header file:

Code: Select all

struct sp1_cs {                       // "char structs" - 24 bytes - Every sprite is broken into pieces fitting into a tile, each of which is described by one of these

   struct sp1_cs     *next_in_spr;    // +0  BIG ENDIAN ; next sprite char within same sprite in row major order (MSB = 0 if none)

   struct sp1_update *update;         // +2  BIG ENDIAN ; tile this sprite char currently occupies (MSB = 0 if none)

   uchar              plane;          // +4  plane sprite occupies, 0 = closest to viewer
   uchar              type;           // +5  bit 7 = 1 occluding, bit 6 = 1 last column, bit 5 = 1 last row, bit 4 = 1 clear pixelbuffer
   uchar              attr_mask;      // +6  attribute mask logically ANDed with underlying attribute, default = 0xff for transparent
   uchar              attr;           // +7  sprite colour, logically ORed to form final colour, default = 0 for transparent

   void              *ss_draw;        // +8  struct sp1_ss + 8 bytes ; points at code embedded in sprite struct sp1_ss

   uchar              res0;           // +10 typically "LD HL,nn" opcode
   uchar             *def;            // +11 graphic definition pointer
   uchar              res1;           // +13 typically "LD IX,nn" opcode
   uchar              res2;           // +14
   uchar             *l_def;          // +15 graphic definition pointer for sprite character to left of this one
   uchar              res3;           // +17 typically "CALL nn" opcode
   void              *draw;           // +18 & draw function for this sprite char

   struct sp1_cs     *next_in_upd;    // +20 BIG ENDIAN ; & sp1_cs.attr_mask of next sprite occupying the same tile (MSB = 0 if none)
   struct sp1_cs     *prev_in_upd;    // +22 BIG ENDIAN ; & sp1_cs.next_in_upd of previous sprite occupying the same tile

};
As you can see, two of the properties of each sprite character square are "attr" and "attr_mask" which controls the colour of the sprite character.

The idea will be to use the "sp1_IterateSprChar()" function to visit all the "struct sp1_cs" structures of the sprite and change the "attr" and "attr_mask" members of each sprite character. "sp1_IterateSprChar()" will traverse the sprite in row-major order, calling a function you supply with the current "struct sp1_cs" being visited as one of the arguments.

So if your sprite is 4 characters wide and 2 characters tall as in:

ABCD
EFGH

"sp1_IterateSprChar()" will call your function with the "struct sp1_cs" corresponding to the 8x8 sprite char A, followed by B, followed by C, etc... then E, F, ..., and finally H.

In sp1, "sp1_IterateSprChar()" also counts which sprite char it is currently visiting in the second argument of the function it calls. This count will be zero for char A, 1 for char B, etc.

So, an example:

Code: Select all

void addColour(uint count, struct sp1_cs *cs)
{
   // . . .
   // more clauses
   // . . .
   else if (count==9)
   {
    cs->attr = INK_BLACK | PAPER_MAGENTA;
    cs->attr_mask = 0;
   }
}

...

struct sp1_ss *s;

s = sp1_CreateSpr(SP1_DRAW_MASK2LB, SP1_TYPE_2BYTE, 3, 0, i);  // some statements to create a 3x3 sprite
sp1_AddColSpr(s, SP1_DRAW_MASK2, 0, 48, i);
sp1_AddColSpr(s, SP1_DRAW_MASK2RB, 0, 0, i);

sp1_IterateSprChar(s, addColour);   // colour the sprite by visiting the "struct sp1_cs" making up sprite "s" in row-major order
Your code fragment included a global variable "n" to keep track of which character square was being visited. This is no longer necessary as sp1 does the counting for you internally.

The problem of not getting the right colours to display I think was occurring because you weren't setting the "attr_mask" member. In sp1, colours are also masked to allow you to do INK-only sprites, PAPER-only sprites, etc. Set bits in the "attr_mask" determine which attribute bits underneath the sprite are kept and mixed (ORed) with the "attr" colour of the sprite character. With "attr_mask=0" we will be completely overwriting the underlying background colour. "attr_mask=255" is the default which keeps the underlying background colour. Again, this is new in sp1 and the code snippet you posted looks like it came from am splib2 program; splib2 didn't support attribute masks.

An "addColour()" function like the one above can be a little unwieldly. You may prefer, instead, to colour the sprite using values stored in an array:

Code: Select all

uchar clr[9] = {INK_BLUE | PAPER_YELLOW,....,INK_GREEN | PAPER_YELLOW};

void addColour(uint count, struct sp1_cs *cs)
{
   cs->attr_mask = 0;   // replace underlying background colour
   cs->attr = clr[count];
}

...

sp1_IterateSprChar(s, addColour);
If you have several sprites you need to colour you can share the "addColour" function by making use of a global variable like so:

Code: Select all

uchar clrA[9] = {INK_BLUE | PAPER_YELLOW,....,INK_GREEN | PAPER_YELLOW};
uchar clrB[9] = {INK_YELLOW | PAPER_BLUE,....,INK_YELLOW | PAPER_GREEN};

uchar *clr;
void addColour(uint count, struct sp1_cs *cs)
{
   cs->attr_mask = 0;   // replace underlying background colour
   cs->attr = clr[count];
}

...
clr = clrA;
sp1_IterateSprChar(s1, addColour);

clr = clrB;
sp1_IterateSprChar(s2, addColour);
This sort of code is probably best used outside the main loop since iterating over the sprite characters in a sprite is a (relatively) slow operation. Inside the main loop we want code to be as quick as possible. So in sp1, a faster way to colour sprites was added.

METHOD #2

Method #2 uses three new sprite functions dedicated to colouring a sprite:

void sp1_GetSprClrAddr(struct sp1_ss *s, uchar **sprdest);
Writes the address of the "attr_mask" member of each "struct sp1_cs" of the sprite into the array "sprdest". You supply an array 2*R*C bytes in size (R = # sprite rows, C = # sprite columns, R*C = # sprite characters) and this function writes into it the addresses of the "attr_mask" members.

void sp1_PutSprClr(uchar **sprdest, struct sp1_ap *src, uchar n);
Copies "attr_mask / attr" pairs from the array "src" into the "struct sp1_cs" members stored in the "sprdest" array previously created by a call to "sp1_GetSprClrAddr()". This is a fast way to "blit" a new colour into an existing sprite.

void sp1_GetSprClr(uchar **sprsrc, struct sp1_ap *dest, uchar n);
Writes the current colour of the sprite into the "attr_mask / attr" pairs of array "dest" using the "struct sp1_cs" members stored in the "sprdest" array previously created by a call to "sp1_GetSprClrAddr()". This is a fast way to read the current colours of the sprite into an array you supply.

So the idea is to call "sp1_GetSprClrAddr()" before the game loop starts and inside the game loop you can quickly colour a sprite using the "sp1_PutSprClr()" call.

Hopefully this makes sense. Dedicated functions were added to speed up the process as I thought someone may want to quickly recolour a sprite when shifted so as to minimize colour clash. You can see how far a sprite is shifted right within a character square by examining the "struct sp1_ss" member "hrot" and likewise the vertical down-shift in pixels can be found in the member "vrot" (see the sp1.h header file for the definition of a "struct sp1_ss").


I hope that helps :-)
Post Reply