/* * example of controlling two sprites z88dk + SP1 * Paul Glover http://alien-experiment.com/ * 2006.11.27 * * * Use defined keys and Sinclair joystick to move a pair of static sprites * around over a background. * * "Clicks" if edge of screen is reached. * * Use sound.h for beep, input.h for controls, spectrum.h for Speccy-specific * stuff, balloc.h for block allocator (used by SP1), sprites/sp1.h for sprite * handling. * * Compile with: * zcc +zx -vn z88dk-control.c -o z88dk-control.bin -create-app -lsp1 -lballoc * */ #include #include #include #include #include /* put stack pointer at 54016. This value is used by SPv3's example code, but * I've also found lower values (below 32768) worked too. */ #pragma output STACKPTR=54016 /* top movement speed */ #define TOPSPEED 4 /* 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 * wholesale from the example program again. It'll probably become clearer if * the Sprite Pack documentation is ever updated. SPv2 docs might be at least * partly relevant. */ uchar idtypetbl[] = { SP1_ID_MASK2, 0, SP1_ID_MASK2_LB, 1, SP1_ID_MASK2_RB, 2, 0, 0 }; extern uchar sprdrawftbl[]; #pragma asm ._sprdrawftbl defw SP1Mask2Data defw SP1Mask2LBData defw SP1Mask2RBData LIB SP1Mask2Data, SP1Mask2LBData, SP1Mask2RBData #pragma endasm /* 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) /* mem allocation using block allocator from balloc.h */ void *u_malloc(uint size) { if (size == sizeof(struct sp1_ss)) return ba_Malloc(0); return ba_Malloc(1); } void u_free(void *addr) { ba_Free(addr); } /* define clip area as whole display */ struct sp1_Rect drawarea = {0, 0, 32, 24}; /* 24 rows, 32 columns, starting at 0,0 */ /* * define struct for all characters (player and AI) * AI would use a getjoy function which does AI stuff and returns movement as * if a player had controlled it. This would allow player control of other * characters, and AI or script control of the player for a demo routine, with * no changes to the movement code. */ struct character { /* sprite (and on screen position info) */ struct sp1_ss *sprite; uchar *frameoffset; /* offset in memory from sprite frame data used at initial sprite creation, to frame data for animation */ /* user defined keys and per character joystick input fn */ struct in_UDK keys; void *getjoy; /* velocity */ char dx; char dy; }; /* background hash pattern */ uchar hash[] = {0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa}; /* main sprite */ unsigned char man[] = { /* 16x16, mask/graph interleave, no attrs. 2 columns of 2 characters. * * note on layout: SP1 requires some extra data which is used for sprite * pixel-resolution movement. If movement is only to be done in * whole-character cell increments, then the blank cells aren't required. * * One character-cell of solid mask and no graphic is required before each * sprite. * * One character-cell of solid mask and no graphic is required at the * bottom of each character-cell column of the sprite data. * * The graphic is defined as a 2x2 cell (16x16) graphic, but has to be * defined as 3 high and with 3 columns. * * See definition code in main() * */ 255, 0,255, 0,255, 0,255, 0, /* needed for sprite routines */ 255, 0,255, 0,255, 0,255, 0, /* needed for sprite routines */ 192, 0,128, 31, 0, 32, 0, 76, /* col1 (man+16) */ 0, 82, 0, 86, 0, 76, 0, 64, 0, 82, 0, 88, 0, 87, 0, 85, 0, 79, 0, 32,128, 31,192, 0, 255, 0,255, 0,255, 0,255, 0, /* needed for sprite routines */ 255, 0,255, 0,255, 0,255, 0, /* needed for sprite routines */ 3, 0, 1,248, 0, 4, 0, 50, /* col2 (man+64, col1+48) */ 0, 74, 0, 90, 0, 50, 0, 2, 0, 74, 0, 26, 0,234, 0, 90, 0,242, 0, 12, 1,248, 3, 0, 255, 0,255, 0,255, 0,255, 0, /* needed for sprite routines */ 255, 0,255, 0,255, 0,255, 0 /* needed for sprite routines */ }; void main () { void *temp; /* for balloc */ uchar input, p; /* input bits from getjoy, and iterator for player array */ struct character player[2], *pl; /* two players, and a pointer to refer to one */ /* disable interrupts */ #pragma asm di #pragma endasm /* initialize some memory for Sprite Pack to use */ ba_Init(2); temp = ba_AddMem(0, 10, sizeof(struct sp1_ss), 0xc000); ba_AddMem(1, 90, sizeof(struct sp1_cs), temp); /* initialize 1-bit sound */ bit_open(); /* configure keyboard QAOPM for player 1 * The pointer variable pl is used instead of referencing directly to * player[p] because, for whatever reason, doing that caused all sorts of * memory corruption and spectacular crashes */ pl = &player[0]; pl->getjoy = in_JoyKeyboard; /* calling getjoy will really use keyboard fn */ pl->keys.left = in_LookupKey('o'); pl->keys.right = in_LookupKey('p'); pl->keys.up = in_LookupKey('q'); pl->keys.down = in_LookupKey('a'); pl->keys.fire = in_LookupKey('m'); /* configure sinclair js 1 for player 2 */ pl++; pl->getjoy = in_JoySinclair1; /* calling getjoy will really use IF2 #1 function */ /* initialize sp1, setting up display with black border and backdrop */ border(BLACK); sp1_Initialize(SP1_IFLAG_MAKE_ROTTBL | SP1_IFLAG_OVERWRITE_TILES | SP1_IFLAG_OVERWRITE_DFILE, idtypetbl, sprdrawftbl, INK_BLACK | PAPER_WHITE, ' '); /* use defined hash pattern instead of ROM space character */ sp1_TileEntry(' ', hash); /* make sure that updatenow updates entire screen first time around */ sp1_Invalidate(&drawarea); /* set up sprite data, pointed to by *player * mask/graphic pairs, 2x2 tiles. See graphic data for more details. * * 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. * * The SprId2Types used are for software rotated masked sprites (2BYTE), * with the leftmost column needing MASK2_LB set, the middle column needing * MASK2, and the rightmost column needing MASK2_RB. * * The '3' in the CreateSpr is how many rows high the sprite is, in * character cells. * * 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? * * */ for (p = 0; p != 2; p++) { pl = &player[p]; pl->sprite = sp1_CreateSpr(sp1_SprId2Type(SP1_ID_MASK2_LB) | SP1_TYPE_2BYTE, 3, 0, p); sp1_AddColSpr(pl->sprite, sp1_SprId2Type(SP1_ID_MASK2), 48, p); sp1_AddColSpr(pl->sprite, sp1_SprId2Type(SP1_ID_MASK2_RB), 0, p); /* initial position - this is not in pixels, but character cell and * pixel rotation relative to the cell boundary */ sp1_MoveSprAbs(pl->sprite, &drawarea, man + 16, 10, 8 + (16 * p), 0, 0); /* no frame offset - we aren't animating */ pl->frameoffset = (uchar *) 0; /* make sure player isn't in motion */ pl->dx = pl->dy = 0; } /* main loop */ while (1) { /* update changed parts of display */ sp1_UpdateNow(); /* loop for all players (two, here) */ for (p = 0; p != 2; p++) { /* set the pointer for this player's data structure */ pl = &player[p]; /* read joystick, keys, script or AI */ input = (pl->getjoy)(&pl->keys); /* set velocity from inputs - FIRE stops motion */ pl->dx += (((input & in_RIGHT) > 0) - ((input & in_LEFT) > 0)); pl->dy += (((input & in_DOWN) > 0) - ((input & in_UP) > 0)); if (pl->dx > TOPSPEED) pl->dx = TOPSPEED; if (pl->dx < -TOPSPEED) pl->dx = -TOPSPEED; if (pl->dy > TOPSPEED) pl->dy = TOPSPEED; if (pl->dy < -TOPSPEED) pl->dy = -TOPSPEED; if (input & in_FIRE) pl->dx = pl->dy = 0; /* move the pl sprite relatively */ sp1_MoveSprRel(pl->sprite, &drawarea, pl->frameoffset, 0, 0, pl->dy, pl->dx); /* check for edge of screen * a note: we check the sprite structure directly, comparing against * character column & row, plus a pixel on the right. * Checking against the left side isn't needed, as it wraps around * from 0 to 255 - the row and column are stored unsigned char. * * Incidentally, it makes a lot of sense to use the internal sprite * position info, as it's there anyway and the sprite move routines * which use it are the best method for sprite movement (no, * really?). * * A pixel position could be pulled out with something like: * x = pl->sprite->col * 8 + pl->sprite->hrot; * y = pl->sprite->row * 8 + pl->sprite->vrot & 0x07; * */ if (pl->sprite->col > 29 && pl->sprite->hrot > 0) { /* reverse direction and pull sprite back within screen * boundary, all in one hit (gotta love C!) * * We have a 32 column area of interest, a 2 column wide * sprite, and we want it to be able to enter column 30 at 0 * pixels rotation (which will place it flush right with the * screen edge) but GO NO FURTHER. */ sp1_MoveSprRel(pl->sprite, &drawarea, pl->frameoffset, 0, 0, 0, pl->dx = -pl->dx); /* make a click noise, and reset the border afterwards */ bit_click(); border(BLACK); } if (pl->sprite->row > 21 && pl->sprite->vrot > 128) { /* for 2-byte type sprites, vrot bit 7 is always = 1 */ /* this is exactly the same as for horizontal. 'Nuff Said. */ sp1_MoveSprRel(pl->sprite, &drawarea, pl->frameoffset, 0, 0, pl->dy = -pl->dy, 0); bit_click(); border(BLACK); } } /* wait 1 frame, uninterrupted by keypresses */ in_Wait(1); } }