/********************************* - A M A T E U R W A R S - ********************************** AKA Nick's First Attempt To Code a NY "game... Started: 24/5/98 (13:36) v1.0: 6/7/98 (21:55) Finished: ??? NB: This is *NOT* my GDUK entry :-) WRITTEN FOR PAL TVs (revenge! revenge!) ---------------------------------- Nick Ferguson, Edinburgh 6 July, 1998 nickf@saqnet.co.uk http://www.netyaroze-europe.com/~rookie1 http://www.saqnet.co.uk/users/nickf http://www.n64gazetta.com ---------------------------------- TIMELINE: 24/5/98 Initial goal - 2 sprites moving about the screen in response to input from both pads... (DID IT - 23:30)!!! 25/5/98 Goal - set up collision detection between the two sprites and improve boundaries... (DID IT - 11:30 29/5/98)!!! 29/5/98 Goal - add a pretty backdrop (a sprite!) and change the player sprites to something a bit cooler. (DID IT at last - 16:30 31/5/98)!!! 1/6/98 Goal - add some animation to the ships. Ideas: Gunfire? Rockets? Direction changes - rotation vs animation? (ADDED ship rotation for turns - 25/6/98) (BIG 3 WEEK BREAK DUE TO UNIVERSITY WORK) 25/6/98 Goal - add bullets/laser fire to ships. (and try to remember how Yaroze works after 3 weeks...) (KIND OF DID IT eventually - 1/7/98) 2/7/98 Goal - rewriting program to be a bit more sensible, coherent and generally more moral. (MUCHO IMPROVED 3/7/98) 3/7/98 Goal - get lasers firing properly (ie not taking up huge amounts of space in memory). (WAHEY! LASERS AHOY 4/7/98) 4/7/98 Goal - add transparent energy bars to the top of the screen for each player sprite, work out the collision detection to apply to the lasers on the ships and *maybe* even get the lasers to decrement the counter...(whew!) (DONE IT! 5/7/98) 5/7/98 Goal - add a scores counter and make a loop so that there is a Game Over screen and the program can be looped...generally tidy it up and get v1.0 FINISHED!!! (DONE IT! 6/7/98) Goals for (much?) later: add explosion animations to ships... add decent homedone font for menu/announcements? add titlescreen? transparent lasers? add laser recharge bar? Or "superlaser" - to be fired only once per round? KNOWN BUGS: A single laser usually fires when a new round begins. Presumably this is a hang-up from the last round, but if everything is reset (which I *think* it is) then how does it happen??? THANKS TO: Ira Rainey & Scott Campbell for their sprite tutorials and code John Ward for his understandable-if-uncommented "Spacies" source James Shaughnessy for the brill Gravitation Source and SCEE and Net Yaroze for estranging my girlfriend ;-/ SPECIAL THANKS TO MR FROSTY FOR HIS BUG-HUNTING EXPERTISE (!), PEEKS AT THE SBF SOURCE, AND FCONTROL v1.5 CONTROLLER LIBRARY! (Visit the omni-talented Mr F at the URL below:) http://www.netyaroze-europe.com/~mrfrosty *********************************/ /* HEADER FILES */ #include // Hooray for LIBPS! #include "fcont1_5.h" // fcontrol v1.5 (from Mr Frosty) #include "sincos.h" // sin/cos lookup tables #define SPRITE_CNT (MAX_LASERS*2+5) // sprite count #define SCREEN_W 320 // setup screen stuff. Dur. #define SCREEN_H 240 #define player1Add 0x80090000 // memory address of player sprites #define player2Add 0x80092000 #define laser1Add 0x800a7000 // memory add. of laser sprites #define laser2Add 0x800a9000 #define BGAdd 0x80094000 // memory add. of background #define OT_LENGTH 2 //Ordering Table length /*GAME CONTROL DEFINITIONS */ // You can play about with these if you want :-) #define MAX_SPEED 12288 // max speed of player ship *4096 #define ACCEL_RATE 128 // acceleration pixels per frame *4096 (decelerate is half accel) #define LASERSPEED 20480 // controls the speed of lasers 'n stuff (default=5) #define LASER_GAP 10 // this is the frame time gap between each laser fired (default=10) #define MAX_LASERS 20 // the max number of lasers "fireable" (is that a word?) at a time (default=20) #define PLAYER_LIFE 80 // the length of players' energy bars (default=80) #define LASER_DAMAGE 5 // the damage done to energy bars by laser damage (default=5) // I wouldn't change these... #define TOP_WALL (13<<12) // collision boundaries for the "arena" (*4096) #define BOTTOM_WALL (225<<12) #define LEFT_WALL (15<<12) #define RIGHT_WALL (305<<12) /* TYPEDEF STRUCT DEFINITIONS */ struct ROBOT { u_long x, y; // integer screen coords (*4096) long rotate; // direction 0=up, 4096 = 1 degree short speed; // +'ve or -'ve (*4096) u_long timer; // Timer for fire repeat rate } Robot1, Robot2; struct LASER { u_long x, y; // integer screen coords (*4096) long rotate; // direction 0=up, 4096 = 1 degree fixed at launch u_short speed; // +'ve or -'ve (*4096) fixed at launch speed u_short launched; // launched flag } LASER1[MAX_LASERS], LASER2[MAX_LASERS]; /* PROTOTYPE FUNCTIONS */ void InitialiseGraphics(void); // I-N-I-T-I-A-L-I-S-E.... G-R-A-P-H-I-C-S. Oh! void InitialiseOTs(void); // You can guess what this does, too. void FSetPortBuffers(void); // Setup pad reading routine (Courtesy of Mr Frosty) void InitialisePlayers(void); // Sets up those SEXY player sprites and THAT funky BG... void InitialiseLasers(void); // Set up the lasers (hidden under the ships) void InitialiseLife(void); // Set up players' energy bars void GameOverCheck(void); // If game is over, prompts for and then restarts void ReadPads(void); // Reads. The. Pads. Using FCONTROL v1.5 (1.6 now available!!!) void MoveShips(void); // Moves. The. Ships. (Good names, huh?) void RotateShips(void); // Actually, this DOESN'T rotate the ships. // Oh, OK then, I'm lying. It does. void ResetAllLasers(); // Resets lasers to prevent any strays on next game void LaunchLaser(int player); // Launch a player laser void MoveLasers(void); // Hey, it's a secret :-) void CheckLaserCollision(void); // Try typing that in a hurry (checks laser collision) int CheckLasers1(int num); // Checks for laser collision between P1 lasers and P2 int CheckLasers2(int num); // Checks for laser collision between P2 lasers and P1 void PrintScores(void); // Prints da scores // Complex (ahem) collision detection functions, using the old "bounding box" method... int CheckCollision(long P1x_minus, long P1y_minus, long P1x_plus, long P1y_plus, long P2x_minus, long P2y_minus, long P2x_plus, long P2y_plus); int Check(); /* GLOBALS 'N STUFF */ GsOT WorldOT[2]; GsOT_TAG OTTags[2][1<>12); player1.y = (Robot1.y>>12); player1.w = (timData.pw*2); player1.h = (timData.ph); player1.tpage = GetTPage(1,0,timData.px,timData.py); player1.u = 0; player1.v = 0; player1.cx = timData.cx; player1.cy = timData.cy; player1.r = 128; player1.g = 128; player1.b = 128; player1.mx = player1.w/2; player1.my = player1.h/2; player1.scalex = ONE; player1.scaley = ONE; player1.rotate = Robot1.rotate; // Setup PLAYER2 Robot2.x = ((3*SCREEN_W/4)<<12); Robot2.y = ((SCREEN_H/2)<<12); Robot2.rotate = (270<<12); Robot2.speed = 0; Robot2.timer = 0; GsGetTimInfo((u_long*)(player2Add+4),&timData2); rect.x=timData2.px; rect.y=timData2.py; rect.w=timData2.pw; rect.h=timData2.ph; LoadImage(&rect, timData2.pixel); rect.x=timData2.cx; rect.y=timData2.cy; rect.w=timData2.cw; rect.h=timData2.ch; LoadImage(&rect, timData2.clut); player2.attribute = (1<<24); player2.x = (Robot2.x>>12); player2.y = (Robot2.y>>12); player2.w = (timData2.pw*2); player2.h = (timData2.ph); player2.tpage = GetTPage(1,0,timData2.px,timData2.py); player2.u = 0; player2.v = 0; player2.cx = timData2.cx; player2.cy = timData2.cy; player2.r = 128; player2.g = 128; player2.b = 128; player2.mx = player2.w/2; player2.my = player2.h/2; player2.scalex = ONE; player2.scaley = ONE; player2.rotate = Robot2.rotate; //Setup Background (need to setup 2 sprites) GsGetTimInfo((u_long *)(BGAdd+4),&timData3); rect.x = 320; //hard code the TIM position rect.y = 0; //ditto rect.w = timData3.pw; rect.h = timData3.ph; LoadImage(&rect, timData3.pixel); rect.x=timData3.cx; rect.y=timData3.cy; rect.w=timData3.cw; rect.h=timData3.ch; LoadImage(&rect, timData3.clut); // Initialise Sprite Structure! // First half of the BG sprite (sprites can only be 256x256) BG_left.attribute = (1<<24); // middling 8-bitter BG_left.x = 0; BG_left.y = 0; BG_left.w = timData3.pw; BG_left.h = timData3.ph; BG_left.tpage = GetTPage(1,0,320,0); BG_left.u = 0; BG_left.v = 0; BG_left.r = 128; BG_left.g = 128; BG_left.b = 128; BG_left.mx = 0; BG_left.my = 0; BG_left.scalex = ONE; BG_left.scaley = ONE; BG_left.rotate = 0; BG_left.cx = timData3.cx; BG_left.cy = timData3.cy; // other half of the BG sprite BG_right.attribute = (1<<24); BG_right.x = 160; BG_right.y = 0; BG_right.w = timData3.pw; BG_right.h = timData3.ph; BG_right.tpage = GetTPage(1,0,400,0); BG_right.u = 32; BG_right.v = 0; BG_right.r = 128; BG_right.g = 128; BG_right.b = 128; BG_right.mx = 0; BG_right.my = 0; BG_right.scalex = ONE; BG_right.scaley = ONE; BG_right.rotate = 0; BG_right.cx = timData3.cx; BG_right.cy = timData3.cy; DrawSync(0); } void InitialiseLasers() { RECT rect; GsIMAGE timData; // Using a pointer to initialise the lasers purely to get // used to them and not let them scare the shit out of me :-) GsSPRITE *sp; int i; //for loops // Setup LASER1 GsGetTimInfo((u_long*)(laser1Add+4),&timData); rect.x=timData.px; rect.y=timData.py; rect.w=timData.pw; rect.h=timData.ph; LoadImage(&rect, timData.pixel); rect.x=timData.cx; rect.y=timData.cy; rect.w=timData.cw; rect.h=timData.ch; LoadImage(&rect, timData.clut); for(sp=laser1,i=0;iattribute = (1<<24); sp->x = player1.x; sp->y = player1.y; sp->w = (timData.pw*4); sp->h = (timData.ph); sp->tpage = GetTPage(0,0,timData.px,timData.py); sp->u = 0; sp->v = 0; sp->cx = timData.cx; sp->cy = timData.cy; sp->r = 128; sp->g = 128; sp->b = 128; sp->mx = sp->w/2; sp->my = sp->h/2; sp->scalex = ONE; sp->scaley = ONE; sp->rotate = 0; } // Setup LASER2 GsGetTimInfo((u_long*)(laser2Add+4),&timData); rect.x=timData.px; rect.y=timData.py; rect.w=timData.pw; rect.h=timData.ph; LoadImage(&rect, timData.pixel); rect.x=timData.cx; rect.y=timData.cy; rect.w=timData.cw; rect.h=timData.ch; LoadImage(&rect, timData.clut); for(sp=laser2,i=0;iattribute = (1<<24); sp->x = player2.x; sp->y = player2.y; sp->w = (timData.pw*4); sp->h = (timData.ph); sp->tpage = GetTPage(0,0,timData.px,timData.py); sp->u = 0; sp->v = 0; sp->cx = timData.cx; sp->cy = timData.cy; sp->r = 128; sp->g = 128; sp->b = 128; sp->mx = sp->w/2; sp->my = sp->h/2; sp->scalex = ONE; sp->scaley = ONE; sp->rotate = 0; } DrawSync(0); } void InitialiseLife() { P1_LIFE.attribute = (1<<30); P1_LIFE.x = 10; P1_LIFE.y = 10; P1_LIFE.w = PLAYER_LIFE; P1_LIFE.h = 10; P1_LIFE.r = 0; P1_LIFE.g = 180; P1_LIFE.b = 20; P2_LIFE.attribute = (1<<30); P2_LIFE.x = 310-PLAYER_LIFE; P2_LIFE.y = 10; P2_LIFE.w = PLAYER_LIFE; P2_LIFE.h = 10; P2_LIFE.r = 0; P2_LIFE.g = 180; P2_LIFE.b = 20; } /*********************** MAIN FUNCTIONS ***********************/ void ReadPads() //reads both pad inputs, and defines control variables as true or false depending... { SPAD1 = FSPadRead(PORT_ONE); SPAD2 = FSPadRead(PORT_TWO); if(SPAD1) { if(SPAD1 & SPADr1 && Robot1.timer++ > LASER_GAP) { Robot1.timer = 0; LaunchLaser(1); } if(SPAD1 & SPADstart) P1_start=1; else P1_start=0; } if(SPAD2) { if(SPAD2 & SPADr1 && Robot2.timer++ > LASER_GAP) { Robot2.timer = 0; LaunchLaser(2); } if(SPAD2 & SPADstart) P2_start=1; else P2_start=0; } } void MoveShips() { // PLAYER 1 if (Robot1.x >= LEFT_WALL && Robot1.x <= RIGHT_WALL && Robot1.y >= TOP_WALL && Robot1.y <= BOTTOM_WALL) { if (SPAD1 & SPADcr) // accelerate { if (Robot1.speed < MAX_SPEED) Robot1.speed += ACCEL_RATE; } else // decelerate { if (Robot1.speed > 0) Robot1.speed -= ACCEL_RATE/2; } if (SPAD1 & SPADsq) // brake/reverse { if (Robot1.speed > -MAX_SPEED) Robot1.speed -= ACCEL_RATE; } else // decelerate reverse { if (Robot1.speed < 0) Robot1.speed += ACCEL_RATE/2; } // Move ship forward by component values (basic trig) Robot1.x += ((Robot1.speed * SIN[Robot1.rotate>>12])>>12); Robot1.y -= ((Robot1.speed * COS[Robot1.rotate>>12])>>12); } else // HIT wall { Robot1.speed = 0; limitRange(Robot1.x, LEFT_WALL, RIGHT_WALL); limitRange(Robot1.y, TOP_WALL, BOTTOM_WALL); } // Set sprite position player1.x = (Robot1.x>>12); player1.y = (Robot1.y>>12); if(Check()) // stop if a collision with player2 occurs { Robot1.speed = 0; } // SAME THING FOR PLAYER 2, basically. if (Robot2.x >= LEFT_WALL && Robot2.x <= RIGHT_WALL && Robot2.y >= TOP_WALL && Robot2.y <= BOTTOM_WALL) { if (SPAD2 & SPADcr) // accelerate { if (Robot2.speed < MAX_SPEED) Robot2.speed += ACCEL_RATE; } else // decelerate { if (Robot2.speed > 0) Robot2.speed -= ACCEL_RATE/2; } if (SPAD2 & SPADsq) // brake/reverse { if (Robot2.speed > -MAX_SPEED) Robot2.speed -= ACCEL_RATE; } else // decelerate reverse (confused yet?) { if (Robot2.speed < 0) Robot2.speed += ACCEL_RATE/2; } // Move ship forward by component values Robot2.x += ((Robot2.speed * SIN[Robot2.rotate>>12])>>12); Robot2.y -= ((Robot2.speed * COS[Robot2.rotate>>12])>>12); } else // HIT wall { Robot2.speed = 0; limitRange(Robot2.x, LEFT_WALL, RIGHT_WALL); limitRange(Robot2.y, TOP_WALL, BOTTOM_WALL); } // Set sprite position player2.x = (Robot2.x>>12); player2.y = (Robot2.y>>12); if(Check()) { Robot2.speed = 0; } } void RotateShips() { // Set PLAYER 1 direction depending on speed and pad input if (Robot1.speed > 0) { if (SPAD1 & SPADleft) Robot1.rotate -= Robot1.speed; else if (SPAD1 & SPADright) Robot1.rotate += Robot1.speed; } else if (Robot1.speed < 0) { if (SPAD1 & SPADright) Robot1.rotate += Robot1.speed; if (SPAD1 & SPADleft) Robot1.rotate -= Robot1.speed; } // Set PLAYER 2 direction depending on speed and pad input if (Robot2.speed > 0) { if (SPAD2 & SPADleft) Robot2.rotate -= Robot2.speed; else if (SPAD2 & SPADright) Robot2.rotate += Robot2.speed; } else if (Robot2.speed < 0) { if (SPAD2 & SPADright) Robot2.rotate += Robot2.speed; if (SPAD2 & SPADleft) Robot2.rotate -= Robot2.speed; } // Keep rotation within legal values (for sin/cos LUTs) (limitRange is no good here) if (Robot1.rotate < 0) Robot1.rotate += (360<<12); // add 360 degrees else if (Robot1.rotate > (360<<12)) Robot1.rotate -= (360<<12); // minus 360 degrees if (Robot2.rotate < 0) Robot2.rotate += (360<<12); // add 360 degrees else if (Robot2.rotate > (360<<12)) Robot2.rotate -= (360<<12); // minus 360 degrees // Set sprites player1.rotate = Robot1.rotate; player2.rotate = Robot2.rotate; } void LaunchLaser(int player) { int i; if (player == 1) { for (i=0; i>12); laser1[i].y = (LASER1[i].y>>12); break; // escape FOR loop } } } else if (player == 2) { for (i=0; i>12); laser2[i].y = (LASER2[i].y>>12); break; // escape FOR loop } } } } void MoveLasers() // lasers travel on their own in the direction they were fired { int i; for (i=0; i 0 && LASER1[i].x < (SCREEN_W<<12) && LASER1[i].y > 0 && LASER1[i].y < (SCREEN_H<<12)) { LASER1[i].x += ((LASER1[i].speed * SIN[(LASER1[i].rotate>>12)])>>12); LASER1[i].y -= ((LASER1[i].speed * COS[(LASER1[i].rotate>>12)])>>12); // Set sprite position laser1[i].x = (LASER1[i].x>>12); laser1[i].y = (LASER1[i].y>>12); } else // Laser off screen { LASER1[i].launched = 0; } } if (LASER2[i].launched) { if (LASER2[i].x > 0 && LASER2[i].x < (SCREEN_W<<12) && LASER2[i].y > 0 && LASER2[i].y < (SCREEN_H<<12)) { LASER2[i].x += ((LASER2[i].speed * SIN[(LASER2[i].rotate>>12)])>>12); LASER2[i].y -= ((LASER2[i].speed * COS[(LASER2[i].rotate>>12)])>>12); // Set sprite position laser2[i].x = (LASER2[i].x>>12); laser2[i].y = (LASER2[i].y>>12); } else // Laser off screen { LASER2[i].launched = 0; } } } } void ResetAllLasers() { int i; for (i=0;i= P1x_minus) & (P2y_minus <= P1y_plus) & (P2y_plus >= P1y_minus)) return(1); else return(0); } // for checking the inter-player collision... int Check() { return(CheckCollision((player1.x)-((player1.w/2)-3), (player1.y)-((player1.h/2)-3), (player1.x)+((player1.w/2)-3), (player1.y)+((player1.h/2)-3), (player2.x)-((player2.w/2)-3), (player2.y)-((player2.h/2)-3), (player2.x)+((player2.w/2)-3), (player2.y)+((player2.h/2)-3) )); } void CheckLaserCollision() { int i; for(i=0;i PLAYER_LIFE) P2_LIFE.w = 0; LASER1[i].launched = 0; } if(LASER2[i].launched && CheckLasers2(i)) //if P2's laser hits P1 { P1_LIFE.w -= LASER_DAMAGE; if(P1_LIFE.w > PLAYER_LIFE) P1_LIFE.w = 0; LASER2[i].launched = 0; } } } int CheckLasers1(int num) { return(CheckCollision(laser1[num].x-((laser1[num].w/2)-2), laser1[num].y-((laser1[num].h/2)-4), laser1[num].x+((laser1[num].w/2)-2), laser1[num].y+((laser1[num].h/2)-4), player2.x-((player2.w/2)-3), player2.y-((player2.h/2)-3), player2.x+((player2.w/2)-3), player2.y+((player2.h/2)-3) )); } int CheckLasers2(int num) { return(CheckCollision(laser2[num].x-((laser2[num].w/2)-2), laser2[num].y-((laser2[num].h/2)-4), laser2[num].x+((laser2[num].w/2)-2), laser2[num].y+((laser2[num].h/2)-4), player1.x-((player1.w/2)-3), player1.y-((player1.h/2)-3), player1.x+((player1.w/2)-3), player1.y+((player1.h/2)-3) )); } void GameOverCheck() //handles Game Over stuff { while(!((P1_start)||(P2_start))) //pause until P1 or P2 hits start { ReadPads(); } GAMEOVER=0; // the game is no longer "over" InitialisePlayers(); //re-initialise sprites, boxes and movement. InitialiseLife(); ResetAllLasers(); } void PrintScores() { FntLoad(960, 256); FntOpen( 15, 227, 320, 256, 0, 1024); FntPrint("P1 SCORE: %d P2 SCORE: %d",(P1_SCORE/2),(P2_SCORE/2)); //easier just to print on one long line // Note that the P1_SCORE etc must be divided by two. This is because the scores are incremented by one // TWICE after a win (ie they go up by 2). This is fixable in a more elegant way, but I'm getting tired :-) FntFlush(-1); } /**************************************** I HOPE YOU LEARNT SOMETHING FROM THIS! (KEEP UP THE GOOD WORK...) NICK F WILL RETURN WITH - C H E M I C A L W A R F A R E - (It's not big. It's not clever. Just fun.) (DUE SEPTEMBER 1998) *****************************************/