Previous: Z88dk and C gotchas - episode 1

Next: Knuckling down

Finally some example code!

It took long enough (these things happened so much quicker before I got a life ;-) ) but here, at last, is some code making use of the most current version of z88dk and version 3 of Sprite Pack.

It’s mostly self-documenting, but there are a few places where it begs explanation, particularly when setting up sprites, so I’ve expanded heavily on the comments in the code.

Well, whaddya waiting for? Go download the source code and have a look (But don’t blame me if it makes your eyes go all funny).

Comments

Posted by Alvin at 5:22 am on May 19, 2007.

Hey Paul, google has found you and so have I :-) Just to answer some Qs in your source code:


/* set up a bunch of stuff for SpritePack internal operations
* Honestly I don’t know or care much what all this means. It’s lifted ….
* /

uchar idtypetbl[] = { … };
extern uchar sprdrawftbl[];

This is a trick to avoid having the compiler attach library code for all 32 sprite draw functions in SP1. The linker only attaches code to which your program makes reference — here you create references to the library sprite draw functions you’ll be using with the LIB declarations and the defw. Obviously if I had made reference to them in the library draw functions you’d get all 32 draw functions whether you wanted them or not!

The idtypetbl[] makes associations between sprite draw function IDs (a defined int from the header file) and entries in the sprdrawftbl[] table (function pointers). This allows constant IDs to be used to specify sprite draw functions in the sp1_CreateSpr() call, rather than an arbitrary constant which would vary from program to program, thus adding to readability.


/* ditto for the block allocator. It’s all rather black-boxy, but it works, and
* right now that’s all I need or care to know. More on this as I need it.
*/
BAQTBL(2)

The block allocator manages queues of available fixed size memory blocks. The blocks of each size are stored as a linked list in each queue. The queue table stores the head pointer for each memory queue. If you have two memory queues (as in above declaration) you need 4 bytes of space to store two head pointers — the macro declares a static array of size 4 bytes to store the head pointers.


* The pointer variable pl is used instead of referencing directly to
* player[p] because, for whatever reason, doing that caused all sorts of

Aha, player[p] is the struct itself so a reference to the getjoy member, eg, should be player[p].getjoy rather than the player[p]->getjoy you were trying. Yes, a little K&R review would help ;-)

[b]
* It is defined 3 high and 3 columns because of the way SP1 works
* internally. This is all cryptic and undocumented, save for one example
* with no source code comments.
[/b]

Yes, guilty as charged. The documentation will improve with time though. I also suffer from shiny object disease where something shiny always steals my attention and documentation is decidedly *not* shiny.

To understand this, imagine you have a 2×2 character sprite. If you draw the sprite at exact character coordinates, no problem, the sprite will appear correctly on screen. But suppose you shift the sprite right by 4 pixels. Then the sprite will occupy three characters horizontally. But if you tell the engine the sprite is only 2 chars wide, it will only draw 2 of those 3 chars. The solution is to create the sprite with an extra blank 3rd column, making it 3 chars wide and then always drawing the sprite as 3 chars wide. To avoid having to draw the sprite’s blank 3rd column when it is not needed, which would be wasteful, there is a xthresh member in the struct sp1_ss describing the sprite. This member indicates the minimum number of pixels the sprite should be shifted right before the 3rd column is drawn on screen. By default it is one pixel so that if the now 3-char wide sprite is drawn at an exact horizontal character coordinate (with 0 pixel shift) the last blank 3rd column will not be drawn. The same thing is happening vertically which is why you also need a blank 3rd row underneath the sprite.

Rather than define a blank 3rd horizontal column, you can use one of the RB (right border) draw functions for the last column, just as you’ve done. The RB draw functions know that their char square does not contain any sprite graphics at all and any sprite graphics it may have to draw only comes from the portion of the sprite shifted right into the character cell when the sprite is moved right by pixel amounts. In the sp1_AddColSpr() functions, you specified a ptr to sprite graphics of 0 for the last column because the RB draw function doesn’t even look at it. The LB (left border) draw function for the first column of the sprite takes advantage of the knowledge it has that there is no portion of the sprite to the left of that column so when the sprite is shifted right by pixel amounts it doesn’t have to worry about incoming sprite graphics from the char to the left. This means the draw function can be made quicker than the general case. You can see all these sprite draw functions in {sp1}/spectrum/sprites/draw . You can even write your own if you have a specialized effect in mind.


* The 48 and 0 in the Create and AddCol calls are badly documented black
* voodoo and tied in somehow with the offsets between column data. Someone
* care to explain how the hell it all works??? Alvin?

Yes, these numbers are actually the address of the sprite graphic for that column that gets stored into sp1_cs.def and sp1_cs.l_def for each character of the sprite (the sprite is broken into 8×8 pixel pieces, each of which is described by a struct sp1_cs when it is created). Now 0 and 48 are not actually the address in this case, they are the offset in bytes of each graphical column from the beginning of the sprite graphic. The address of the sprite graphic itself is stored in the sprite structure struct sp1_ss.frame. When the draw function for a specific sprite character square runs, it grabs the graphic offset from sp1_cs.def and *adds* the sprite frame address sp1_ss.frame to arrive at the absolute address of the sprite character’s graphic definition. This way, to specify a new sprite frame for animation purposes, you only need to modify the sp1_ss.frame member to point at the sprite frame address. This can be done by specifying a new frame address in the sp1_MoveSpr*() functions or by doing it manually by assigning a new value to sp1_ss.frame. Passing 0 as the frame address to the sp1_MoveSpr*() functions means the frame address will not be changed so that effectively you’ll be using the same frame as already stored in the sprite structure sp1_ss.

I hope this clears some things up! And keep at it — there will be a new 3d isometric engine added to z88dk soon enough and that’ll be something new and shiny to take some more attention away :-)

Posted by Chief Experimenter at 11:54 am on May 25, 2007.

Hey, good to hear from you (and quite an honor, I must say :-) )

That definitely clears up some of the unclear, especially around the Create and AddCol stuff, which fell into the “I know I really need to understand this but it has me utterly baffled” category.

I hear you on the documentation - a necessary evil, with emphasis very much on the evil. ;-) Apologies if any of my code comments came across as even slightly harsh in that regard =-o

Oh no…new stuff to play with? 3D isometric? Oh my… :-D

/me pushes the projected completion date back another 6 months…things were so much easier when I only had to ignore my homework to sit down and code all weekend. ;-)

Leave a comment

Spam is filtered, and in the unlikely event it survives will be removed. Comments with hyperlinks will be held for moderation. Spammers really stink, don't they?

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>