Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
G4Play.c
/* |
File: G4Play.c |
Contains: xxx put contents here xxx |
Version: xxx put version here xxx |
Copyright: © 1998 by Apple Computer, Inc., all rights reserved. |
File Ownership: |
DRI: xxx put dri here xxx |
Other Contact: xxx put other contact here xxx |
Technology: xxx put technology here xxx |
Writers: |
(BWS) Brent Schorsch |
(sjb) Steve Bollinger |
Change History (most recent first): |
<5> 7/1/98 BWS fix comment |
<4> 7/1/98 sjb Update to CWPro 2 |
<3> 6/18/98 sjb InputSprocket.h comes from <> place |
*/ |
//============================================================================ |
//---------------------------------------------------------------------------- |
// Play.c |
//---------------------------------------------------------------------------- |
//============================================================================ |
// This (rather large) file handles all player routines while a game is inÉ |
// progress. It gets the player's input, moves the player, tests for collisionsÉ |
// and generally handles the "main game loop". Enemies and actually drawingÉ |
// the graphics to the screen are handled in other files. |
#include "G4Externs.h" |
#if GENERATINGPOWERPC |
#include <InputSprocket.h> |
#endif |
#include <ToolUtils.h> |
#include <TextUtils.h> |
#include <Timer.h> |
#define kFlapImpulse 48 |
#define kGlideImpulse 12 |
#define kAirResistance 2 |
#define kMaxHVelocity 192 |
#define kMaxVVelocity 512 |
#define kNumLightningStrikes 5 |
void SetUpLevel (void); |
void ResetPlayer (Boolean); |
void OffAMortal (void); |
void DoCommandKey (void); |
void GetPlayerInput (void); |
void HandlePlayerIdle (void); |
void HandlePlayerFlying (void); |
void HandlePlayerWalking (void); |
void HandlePlayerSinking (void); |
void HandlePlayerFalling (void); |
void HandlePlayerBones (void); |
void MovePlayer (void); |
void CheckTouchDownCollision (void); |
void CheckPlatformCollision (void); |
void KeepPlayerOnPlatform (void); |
void CheckLavaRoofCollision (void); |
void SetAndCheckPlayerDest (void); |
void HandleLightning (void); |
void FinishLightning (void); |
void HandleCountDownTimer (void); |
void CheckHighScore (void); |
void HandleLava(void); |
void DrawLava(void); |
playerType thePlayer; |
enemyType theEnemies[kMaxEnemies]; |
KeyMap theKeys; |
Rect platformRects[6], touchDownRects[6], enemyRects[24]; |
Rect enemyInitRects[5]; |
long theScore, wasTensOfThousands; |
short numLedges, beginOnLevel, levelOn, livesLeft, lightH, lightV; |
short lightningCount, numEnemies, countDownTimer; |
Boolean playing, pausing, flapKeyDown, evenFrame; |
Boolean doEnemyFlapSound, doEnemyScrapeSound; |
extern handInfo theHand; |
extern prefsInfo thePrefs; |
extern Rect playerRects[11], mainWindowRect; |
extern short numOwls; |
extern Boolean quitting, openTheScores; |
extern CGrafPtr workSrcMap; |
short oldFrameRate; |
short currentFrameRate; |
UnsignedWide frameTime; |
short numScoreFloater = 0; |
scoreFloaterType theScoreFloater[kMaxScoreFloater]; |
// |
OSErr EasyJoystick(Boolean *left, Boolean *right, Boolean *button); |
OSErr EasyJoystick(Boolean *left, Boolean *right, Boolean *button) |
{ |
// remember a little across calls |
static ISpElementReference lastXAxis = 0; |
static Boolean lastLeft = false; |
static Boolean lastRight = false; |
static Boolean lastButton = false; |
int maxEvents = 10; // process only ten events per round |
ISpElementEvent theEvent; // our event |
Boolean wasEvent = true; |
OSErr err = noErr; |
ISpElementListReference globalList; |
ISpElementInfo info; |
*left = lastLeft; |
*right = lastRight; |
*button = lastButton; |
if (ISpGetGlobalElementList == nil) |
{ |
return -1; |
} |
err = ISpGetGlobalElementList(&globalList); |
while(maxEvents > 0) |
{ |
err = ISpElementList_GetNextEvent(globalList, sizeof(theEvent), &theEvent, &wasEvent); |
if ((wasEvent == false) || (err)) |
{ |
break; |
} |
ISpElement_GetInfo(theEvent.element, &info); |
// get information about this element since we are not doing any configuration |
// we need to know the about labels and kinds |
switch(info.theKind) |
{ |
case kISpElementKind_Button: |
{ |
Boolean newValue; |
// figure out the new value for the boolean |
if (theEvent.data) |
{ |
newValue = true; |
} |
else |
{ |
newValue = false; |
} |
*button = newValue; |
} |
break; |
case kISpElementKind_DPad: |
{ |
// we'll support using any DPad to move |
if (1) // (info.theLabel == kISpElementLabel_Pad_Move || info.theLabel == kISpElementLabel_Pad_Move_Horiz) |
{ |
// a movement DPad so they might be using |
// a console style direction pad so turn |
// that into axis style data |
lastXAxis = nil; |
switch(theEvent.data) |
{ |
case kISpPadIdle: |
case kISpPadUp: |
case kISpPadDown: |
*left = false; |
*right = false; |
break; |
case kISpPadLeft: |
case kISpPadUpLeft: |
case kISpPadDownLeft: |
*left = true; |
*right = false; |
break; |
case kISpPadUpRight: |
case kISpPadRight: |
case kISpPadDownRight: |
*left = false; |
*right = true; |
break; |
} |
} |
} |
break; |
case kISpElementKind_Axis: |
{ |
// if it is an axis find out if it is the |
// x or y axis style data and use that. |
if (info.theLabel == kISpElementLabel_Axis_XAxis || info.theLabel == kISpElementLabel_Axis_Rudder) |
{ |
lastXAxis = theEvent.element; |
} |
ISpElement_Flush(theEvent.element); |
} |
break; |
} |
maxEvents--; |
} |
// poll the last meaningful axis we had on the way out |
// just to make sure we had good data |
if (lastXAxis != nil) |
{ |
UInt32 xAxis; |
double calc; |
*left = false; |
*right = false; |
ISpElement_GetSimpleState(lastXAxis, &xAxis); |
calc = xAxis; |
calc -= kISpAxisMiddle; |
calc /= kISpAxisMaximum; |
if (calc > 0.2) |
{ |
*right = true; |
} |
else if (calc < -0.2) |
{ |
*left = true; |
} |
} |
return err; |
} |
//============================================================== Functions |
//-------------------------------------------------------------- InitNewGame |
// This funciton sets up variables and readies for a new game. It is calledÉ |
// only when a the user selects "New Game" - during the course of a game, itÉ |
// is not called again. |
void InitNewGame (void) |
{ // Initialize a number of game variables. |
countDownTimer = 0; // Zero count down timer. |
numLedges = 3; // Initial number of ledges (platforms). |
beginOnLevel = 1; // Ledge (platform) the player is on (center ledge). |
levelOn = 0; // Game level on (first level). |
livesLeft = kInitNumLives; // Number of player lives remaining. |
theScore = 0L; // Player's score (a long - can go to 2 billion). |
playing = TRUE; // Flag playing. |
pausing = FALSE; // Not paused. |
evenFrame = TRUE; // Set an initial state for evenFrame. |
wasTensOfThousands = 0L; // Used for noting when player gets an extra life. |
numOwls = 4; // Number of "owl" enemies for this level. |
numPixelShatter = 0; // no pixels exploding currently |
numScoreFloater = 0; // no score floaters |
InitHandLocation(); // Get the mummy hand down in the lava. |
theHand.mode = kLurking; // Flag the hand in "lurking" mode. |
ResetPlayer(TRUE); // Initialize all player variables and put on ledge. |
UpdateLivesNumbers(); // Display number of lives remaining on screen. |
UpdateScoreNumbers(); // Display the player's score (zero at this point). |
UpdateLevelNumbers(); // Display the level (wave) the player is on. |
DumpBackToWorkMap(); // Copy background offscreen to "work" offscreen. |
SetUpLevel(); // Set up platforms for first level (wave). |
GenerateEnemies(); // Prepare all enemies for this level. |
} |
//-------------------------------------------------------------- SetUpLevel |
// Primarily, this function is called to set up the ledges for theÉ |
// current level (wave) the player is on. It determines how manyÉ |
// are required and then draws these offscreen. It also flashesÉ |
// the obelisks and strikes the lightning. |
void SetUpLevel (void) |
{ |
short wasLedges, waveMultiple; |
Boolean validLightning[6]; |
int invalidLightning; |
for(invalidLightning = 0; invalidLightning < 6; invalidLightning++) |
{ |
validLightning[invalidLightning] = false; |
} |
KillOffEye(); // Return eye to the aether. |
wasLedges = numLedges; // Remember number of ledges. |
waveMultiple = levelOn % 5; // Waves repeat every 5th wave (but harder!). |
switch (waveMultiple) // See which of the 5 we're on. |
{ |
case 0: // Waves 0, 5, 10, É |
numLedges = 5; // have 5 ledges (platforms) on screen. |
validLightning[5] = true; |
break; |
case 1: // Waves 1, 6, 11, É |
numLedges = 6; // are up to 6 ledges (platforms) on screen. |
validLightning[5] = true; |
break; |
case 2: // Waves 2, 7, 12, É |
numLedges = 5; // return to 5 ledges (platforms) on screen. |
validLightning[5] = true; |
break; |
case 3: // Waves 3, 8, 13, É |
numLedges = 3; // drop to 3 ledges (platforms) on screen. |
validLightning[3] = true; |
validLightning[4] = true; |
break; |
case 4: // Waves 4, 9, 14, É |
numLedges = 6; // and return to 6 ledges (platforms) on screen. |
validLightning[3] = true; |
validLightning[4] = true; |
validLightning[5] = true; |
break; |
} |
if (wasLedges != numLedges) // No need to redraw if platforms are unchanged. |
DrawPlatforms(numLedges); |
#if 0 |
// These are the platforms. See diagram for numbering. |
SetRect(&platformRects[0], 206, 424, 433, 438); //_______________ |
SetRect(&platformRects[1], -256, 284, 149, 298); // |
SetRect(&platformRects[2], 490, 284, 896, 298); //--3-- --4-- |
SetRect(&platformRects[3], -256, 105, 149, 119); // --5-- |
SetRect(&platformRects[4], 490, 105, 896, 119); //--1-- --2-- |
SetRect(&platformRects[5], 233, 190, 407, 204); //_____--0--_____ |
#endif |
{ |
int toast; |
if (levelOn == 0) |
{ |
for(toast = 0; toast < 6; toast++) |
{ |
validLightning[toast] = true; |
} |
} |
for(toast = 0; toast < 6; toast++) |
{ |
Rect r = platformRects[toast]; |
int diff = (r.right - r.left) / 4; |
int subtoast; |
if (!validLightning[toast]) |
{ |
continue; |
} |
r.right = r.left + diff; |
for(subtoast = 0; subtoast < 4; subtoast++) |
{ |
unsigned long tickWait = TickCount() + 3L; |
PlayExternalSound(kLightningSound, kLightningPriority); |
DSpContext_GetBackBuffer( gTheContext, kDSpBufferKind_Normal, &workSrcMap ); |
GetPlayerInput(); // Get the player's input (keystrokes). |
MovePlayer(); // Move the player's bird. |
HandleLava(); |
DumpBackToWorkMap(); // clear the screen |
DrawBanner(); |
DrawLava(); |
DrawPlayer(); |
// lightning |
lightningCount = 1; |
lightH = (r.left + r.right) / 2; |
lightV = (r.top + r.bottom) / 2; |
HandleLightning(); |
// pixel explosions |
StartPixelShatterRect(&r, 0, 0, kShatterPlatformLightning); |
HandlePixelShatter(); |
DrawPixelShatter(); |
DSpContext_SwapBuffers( gTheContext, NULL, NULL ); |
r.left += diff; |
r.right += diff; |
evenFrame = !evenFrame; |
do // Wait for 2 Ticks to pass to keep fast Macs at bay. |
{ |
} |
while (TickCount() < tickWait); |
} |
} |
} |
UpdateLevelNumbers(); // Display the current level on screen. |
} |
//-------------------------------------------------------------- ResetPlayer |
// This function prepares the player - it places the player and his/her mountÉ |
// in their proper starting location (depending on which platform they are toÉ |
// begin on), and it sets all the player's variables to their initial state. |
void ResetPlayer (Boolean initialPlace) |
{ |
short location; |
thePlayer.srcNum = 5; // Set which graphic (frame) the player is to use. |
thePlayer.frame = 320; // This variable will be used as a coutndown timer. |
if (initialPlace) // If "initialPlace" is TRUE, É |
location = 0; // the player is to begin on the lowest platform. |
else // Otherwise, a random location is chosen. |
location = RandomInt(numLedges); |
switch (location) // Move player horizontally and vertically to theirÉ |
{ // proper location (based on ledge # they're on). |
case 0: |
thePlayer.h = 296 << 4; // Bottom center ledge. |
thePlayer.v = 377 << 4; // We're scaling by 16. |
break; |
case 1: |
thePlayer.h = 102 << 4; // Lower left ledge. |
thePlayer.v = 237 << 4; |
break; |
case 2: |
thePlayer.h = 489 << 4; // Lower right ledge. |
thePlayer.v = 237 << 4; |
break; |
case 3: |
thePlayer.h = 102 << 4; // Top left ledge. |
thePlayer.v = 58 << 4; |
break; |
case 4: |
thePlayer.h = 489 << 4; // Top right ledge. |
thePlayer.v = 58 << 4; |
break; |
case 5: |
thePlayer.h = 296 << 4; // Top central ledge. |
thePlayer.v = 143 << 4; |
break; |
} |
// Assign destination rectangle. |
thePlayer.dest = playerRects[thePlayer.srcNum]; |
ZeroRectCorner(&thePlayer.dest); |
OffsetRect(&thePlayer.dest, thePlayer.h >> 4, thePlayer.v >> 4); |
thePlayer.wasDest = thePlayer.dest; |
thePlayer.hVel = 0; // Player initially has no velocity. |
thePlayer.vVel = 0; |
thePlayer.facingRight = TRUE; // We're facing to the right. |
thePlayer.flapping = FALSE; // We're not flapping our wings initially. |
thePlayer.wrapping = FALSE; // We can't be wrapping around the edge of the screen. |
thePlayer.clutched = FALSE; // The hand ain't got us. |
thePlayer.mode = kIdle; // Our mode is "idle" - waiting to be "born". |
if (lightningCount == 0) // Prepare for a lightning display to "birth" us. |
{ |
lightH = thePlayer.dest.left + 24; |
lightV = thePlayer.dest.bottom - 24; |
lightningCount = kNumLightningStrikes; |
} |
} |
//-------------------------------------------------------------- OffAMortal |
// Alas, 'tis here that a player is brought who loses a life. |
void OffAMortal (void) |
{ |
livesLeft--; // Decrememnt number of player lives left. |
if (livesLeft > 0) // Indeed, are there lives remaining? |
{ |
ResetPlayer(FALSE); // Good, start a new one off. |
UpdateLivesNumbers(); // Make note of the number of lives remaining. |
} |
else // Otherwise, we are at the dreaded "Game Over". |
playing = FALSE; // Set flag to drop us out of game loop. |
} |
//-------------------------------------------------------------- DoCommandKey |
// This function handles the case when the user has held down the commandÉ |
// key. Note, this only applies to input when a game is in session - otherwiseÉ |
// a standard event loop handles command keys and everything else. |
void DoCommandKey (void) |
{ |
if (BitTst(&theKeys, kEKeyMap)) // Test for "command - E"É |
{ |
playing = FALSE; // which would indicate "End Game". |
} |
else if (BitTst(&theKeys, kPKeyMap)) // Otherwise, see if it's "command - P". |
{ |
pausing = TRUE; // This means the player is pausing the game. |
MenusReflectMode(); // Gray-out menus etc. |
} |
else if (BitTst(&theKeys, kQKeyMap)) // Or perhaps the player hit "command - Q". |
{ |
playing = FALSE; // Set flag to drop out of game loop. |
quitting = TRUE; // Set flag to drop out of Glypha. |
} |
} |
//-------------------------------------------------------------- GetPlayerInput |
// This function looks for keystrokes when a game is underway. We don't useÉ |
// the more conventional event routines (like GetNextEvent()), because they'reÉ |
// notoriously slow, allow background tasks, introduce possible INIT problems,É |
// and we don't have to. Instead, we'll rely on GetKeys() (which has its ownÉ |
// set of problems - but we deal with them). |
void GetPlayerInput (void) |
{ |
thePlayer.flapping = FALSE; // Assume we're not flapping. |
thePlayer.walking = FALSE; // Assume too we're not walking. |
GetKeys(theKeys); // Get the current keyboard keymap. |
// InputSprocket! |
{ |
Boolean left = false; |
Boolean right = false; |
Boolean button = false; |
EasyJoystick(&left, &right, &button); |
if (button) { BitSet(&theKeys, kDownArrowKeyMap); } |
if (left) { BitSet(&theKeys, kLeftArrowKeyMap); } |
if (right) { BitSet(&theKeys, kRightArrowKeyMap); } |
} |
if (BitTst(&theKeys, kCommandKeyMap)) // See first if command key downÉ |
DoCommandKey(); // and handle those seperately. |
else // If not command key, continue. |
{ // Look for one of the two "flap" keys. |
if ((BitTst(&theKeys, kSpaceBarMap)) || (BitTst(&theKeys, kDownArrowKeyMap))) |
{ |
if (thePlayer.mode == kIdle) // Handle special case when player is idle. |
{ |
thePlayer.mode = kWalking; // Set the player's mode now to walking. |
thePlayer.frame = 0; // Used to note "state" of walking. |
} // Otherwise, if player is flying or walkingÉ |
else if ((thePlayer.mode == kFlying) || (thePlayer.mode == kWalking)) |
{ |
if (!flapKeyDown) // If flap key was not down last frameÉ |
{ // (this is to prevent "automatic fire"). |
// Give player lift. |
thePlayer.vVel -= kFlapImpulse; |
flapKeyDown = TRUE; // Note that the flap key is down. |
// Play the "flap" sound. |
PlayExternalSound(kFlapSound, kFlapPriority); |
// Set player flag to indicate flapping. |
thePlayer.flapping = TRUE; |
} |
} |
} |
else |
flapKeyDown = FALSE; // If flap key not down, remember this. |
// Test now for one of three "right" keys. |
if ((BitTst(&theKeys, kRightArrowKeyMap) || |
BitTst(&theKeys, kSKeyMap) || |
BitTst(&theKeys, kQuoteMap)) && |
(thePlayer.hVel < kMaxHVelocity)) |
{ |
if (thePlayer.mode == kIdle) // Handle special case when player idle. |
{ // They are to begin walking (no longer idle). |
thePlayer.mode = kWalking; |
thePlayer.frame = 0; |
} |
else if ((thePlayer.mode == kFlying) || (thePlayer.mode == kWalking)) |
{ // If flying or walking, player moves right. |
if (!thePlayer.facingRight) // If facing left, player does an about face. |
{ |
thePlayer.facingRight = TRUE; |
if (thePlayer.clutched) |
{ |
thePlayer.dest.left += 18; |
thePlayer.dest.right += 18; |
thePlayer.h = thePlayer.dest.left << 4; |
thePlayer.wasH = thePlayer.h; |
thePlayer.wasDest = thePlayer.dest; |
} |
} // Otherwise, if facing right alreadyÉ |
else |
{ // If flying, add to their horizontal velocity. |
if (thePlayer.mode == kFlying) |
thePlayer.hVel += kGlideImpulse; |
else // If walking, set flag to indicate a step. |
thePlayer.walking = TRUE; |
} |
} |
} // Test now for one of three "left" keys. |
else if ((BitTst(&theKeys, kLeftArrowKeyMap) || |
BitTst(&theKeys, kAKeyMap) || |
BitTst(&theKeys, kColonMap)) && |
(thePlayer.hVel > -kMaxHVelocity)) |
{ |
if (thePlayer.mode == kIdle) // Handle special case when player idle. |
{ |
thePlayer.mode = kWalking; |
thePlayer.frame = 0; |
} |
else if ((thePlayer.mode == kFlying) || (thePlayer.mode == kWalking)) |
{ // If flying or walking, player moves left. |
if (thePlayer.facingRight) // If facing right, player does an about face. |
{ // Flag player facing left. |
thePlayer.facingRight = FALSE; |
if (thePlayer.clutched) // Handle case where player gripped by hand. |
{ // An about face handled a bit differently. |
thePlayer.dest.left -= 18; |
thePlayer.dest.right -= 18; |
thePlayer.h = thePlayer.dest.left << 4; |
thePlayer.wasH = thePlayer.h; |
thePlayer.wasDest = thePlayer.dest; |
} |
} |
else // Otherwise, player already facing left. |
{ // So player will move left. |
if (thePlayer.mode == kFlying) |
thePlayer.hVel -= kGlideImpulse; |
else |
thePlayer.walking = TRUE; |
} |
} |
} |
} |
} |
//-------------------------------------------------------------- HandlePlayerIdle |
// Following are a number of functions handling the player's different "modes". |
// This first function handles the player when in "idle" mode. When idle, theÉ |
// player is standing on a platform - having just been "born". This is when theÉ |
// player is in a "safe" mode - meaning no enemy can kill them. The player remainsÉ |
// in idle mode until they hit a key to flap or move or until a timer (thePlayer.frame)É |
// counts down to zero. |
void HandlePlayerIdle (void) |
{ |
thePlayer.frame--; // Count down the timer. |
if (thePlayer.frame == 0) // See if timer has reached zero yet. |
thePlayer.mode = kWalking; // If so, player is no longer idle. |
SetAndCheckPlayerDest(); // Keep player on platform. |
} |
//-------------------------------------------------------------- HandlePlayerFlying |
// This function handles a player in "flying" mode. In flying mode, the playerÉ |
// is alive and not standing/walking on any platform. A plyaer remains in flyingÉ |
// mode until the player dies (collides unfavorably with an enemy), is caught byÉ |
// the hand, or comes near the top of a platform (in which case they land andÉ |
// switch to walking mode). While in flying mode, gravity pulls the player downÉ |
// while friction acts to slow the player down. |
void HandlePlayerFlying (void) |
{ |
if (thePlayer.hVel > 0) // If player has a positive hori. velocityÉ |
{ // subtract frictional constant from velocity. |
thePlayer.hVel -= kAirResistance; |
if (thePlayer.hVel < 0) // Don't let it go negative (otherwise, youÉ |
thePlayer.hVel = 0; // can get a "yo-yo" effect set up). |
} |
else if (thePlayer.hVel < 0) // Otherwise, if horizontal velocity negativeÉ |
{ // add firctional constant to hori. velocity. |
thePlayer.hVel += kAirResistance; |
if (thePlayer.hVel > 0) |
thePlayer.hVel = 0; |
} |
thePlayer.vVel += kGravity; // Add gravity to player's vertical velocity. |
if (thePlayer.vVel > kMaxVVelocity) // Don't allow player to fall too fast. |
thePlayer.vVel = kMaxVVelocity; |
else if (thePlayer.vVel < -kMaxVVelocity) |
thePlayer.vVel = -kMaxVVelocity; // And don't allow player to climb too fast. |
thePlayer.h += thePlayer.hVel; // Add velocities to players position. |
thePlayer.v += thePlayer.vVel; |
// Now we determine which graphic to use. |
if (thePlayer.facingRight) // There are the set of right-facing graphics. |
{ |
thePlayer.srcNum = 1; // Assume standard right-facing graphic. |
if (thePlayer.vVel < -kDontFlapVel) // Now we jump through a series of hoopsÉ |
{ // simply to determine whether we'll useÉ |
if (thePlayer.flapping) // the graphic of the player with the wingsÉ |
thePlayer.srcNum = 0; // up (srcNum = 0) or with the wings downÉ |
else // (srcNum = 1). |
thePlayer.srcNum = 1; |
} |
else if (thePlayer.vVel > kDontFlapVel) |
{ |
if (thePlayer.flapping) |
thePlayer.srcNum = 1; |
else |
thePlayer.srcNum = 0; |
} |
else if (thePlayer.flapping) |
thePlayer.srcNum = 0; |
} |
else // If the player is facing leftÉ |
{ // We jump through a similar set of hoopsÉ |
thePlayer.srcNum = 2; // this time choosing between srcNum = 2 É |
if (thePlayer.vVel < -kDontFlapVel) // and srcNum = 3. |
{ |
if (thePlayer.flapping) |
thePlayer.srcNum = 3; |
else |
thePlayer.srcNum = 2; |
} |
else if (thePlayer.vVel > kDontFlapVel) |
{ |
if (thePlayer.flapping) |
thePlayer.srcNum = 2; |
else |
thePlayer.srcNum = 3; |
} |
else if (thePlayer.flapping) |
thePlayer.srcNum = 3; |
} |
SetAndCheckPlayerDest(); // Check for wrap-around, etc. |
CheckLavaRoofCollision(); // See if player hit top or bottom of screen. |
CheckPlayerEnemyCollision(); // See if player hit an enemy. |
CheckPlatformCollision(); // See if player collided with platform. |
CheckTouchDownCollision(); // See if player has landed on platform. |
} |
//-------------------------------------------------------------- HandlePlayerWalking |
// This function handles a player in "walking" mode. They remain in this modeÉ |
// until they walk off a platform's edge, flap to lift off the platform, orÉ |
// collide unfavorably with an enemy (die). While in walking mode, we need onlyÉ |
// determine which frame of animation to display (if the player is taking steps)É |
// and check for the usual set of collisions. |
void HandlePlayerWalking (void) |
{ |
short desiredHVel; |
if (thePlayer.walking) // This means user is actively holding downÉ |
{ // the left or right key. |
if (evenFrame) // Now we jump through a number of hoopsÉ |
{ // in order to get a semi-realisticÉ |
if (thePlayer.facingRight) // "stepping" animation going. We take stepsÉ |
{ // only on "even frames". |
if (thePlayer.srcNum == 4) |
desiredHVel = 208; |
else |
desiredHVel = 128; |
} |
else |
{ |
if (thePlayer.srcNum == 7) |
desiredHVel = -208; |
else |
desiredHVel = -128; |
} |
if (thePlayer.hVel < desiredHVel) |
{ |
thePlayer.hVel += 80; // Move player right. |
if (thePlayer.hVel > desiredHVel) |
{ // This is the case where player is walking. |
thePlayer.hVel = desiredHVel; |
PlayExternalSound(kWalkSound, kWalkPriority); |
} |
else |
{ // In this case, player is skidding. |
PlayExternalSound(kScreechSound, kScreechPriority); |
StartPixelShatter((thePlayer.dest.left + thePlayer.dest.right) / 2, |
thePlayer.dest.bottom, thePlayer.hVel, thePlayer.vVel, kShatterPlatformScrape); |
} |
} |
else |
{ |
thePlayer.hVel -= 80; // Move player to the left. |
if (thePlayer.hVel < desiredHVel) |
{ // Player is stepping to left. |
thePlayer.hVel = desiredHVel; |
PlayExternalSound(kWalkSound, kWalkPriority); |
} |
else |
{ // Player is skidding to a stop. |
PlayExternalSound(kScreechSound, kScreechPriority); |
StartPixelShatter((thePlayer.dest.left + thePlayer.dest.right) / 2, |
thePlayer.dest.bottom, thePlayer.hVel, thePlayer.vVel, kShatterPlatformScrape); |
} |
} |
} |
} |
else // If user is not actively holding down theÉ |
{ // left or right key, bring player to a stop. |
thePlayer.hVel -= thePlayer.hVel / 4; |
if ((thePlayer.hVel < 4) && (thePlayer.hVel > -4)) |
thePlayer.hVel = 0; // If close to zero (within 4), stop player. |
else // Othewrwise, play the skidding sound. |
PlayExternalSound(kScreechSound, kScreechPriority); |
} |
if (thePlayer.vVel > kMaxVVelocity) // Keep player from moving too quicklyÉ |
thePlayer.vVel = kMaxVVelocity; // left or right. |
else if (thePlayer.vVel < -kMaxVVelocity) |
thePlayer.vVel = -kMaxVVelocity; |
thePlayer.h += thePlayer.hVel; // Move player horizontally and verticallyÉ |
thePlayer.v += thePlayer.vVel; // by the corresponding velocity. |
if (thePlayer.walking) // "If player holding down left or right keysÉ". |
{ |
if (evenFrame) // Here's where we toggle between the twoÉ |
{ // frames of "stepping" animation. |
if (thePlayer.facingRight) |
thePlayer.srcNum = 9 - thePlayer.srcNum; |
else |
thePlayer.srcNum = 13 - thePlayer.srcNum; |
} |
} |
else // If the player not holding down keysÉ |
{ // draw the player just standing there. |
if (thePlayer.facingRight) |
thePlayer.srcNum = 5; |
else |
thePlayer.srcNum = 6; |
} |
SetAndCheckPlayerDest(); // Check for wrap-around and all that. |
CheckTouchDownCollision(); // See if player still on platform. |
KeepPlayerOnPlatform(); // Don't let player "sink through" ledge. |
CheckPlayerEnemyCollision(); // See if player hit an enemy. |
} |
//-------------------------------------------------------------- HandlePlayerSinking |
// When the player is in "sinking" mode, they are on a one-way ticket to death. |
// The player is sinking into the lava. We put the player into this mode (ratherÉ |
// than kill them outright) so that we can have a number of frames of them slowlyÉ |
// slipping beneath the surface of the lava. When the get below the surface ofÉ |
// the lava, they will be officially "killed" and a new player will be "born", |
void HandlePlayerSinking (void) |
{ |
thePlayer.hVel = 0; // Don't allow horizontal motion. |
thePlayer.vVel = 16; // They will sink at this constant rate. |
if (thePlayer.dest.top > kLavaHeight) // See if they slipped below the surface. |
OffAMortal(); // If they did, kill 'em. |
thePlayer.v += thePlayer.vVel; // Otherwise, move them down a notch. |
SetAndCheckPlayerDest(); // Check for wrap-around, etc. |
} |
//-------------------------------------------------------------- HandlePlayerFalling |
// "Falling" refers to a player who is dead already but is still careeningÉ |
// down the screen as a skeleton. If (when) the player lands on a ledge theyÉ |
// will turn into a pile of bones for a short duration. If instead they fallÉ |
// into the lava, they'll sink. In any event, it is then that they areÉ |
// officially pronounced dead and a new player is born. |
void HandlePlayerFalling (void) |
{ |
if (thePlayer.hVel > 0) // Handle horizontal air resistance. |
{ |
thePlayer.hVel -= kAirResistance; |
if (thePlayer.hVel < 0) |
thePlayer.hVel = 0; |
} |
else if (thePlayer.hVel < 0) |
{ |
thePlayer.hVel += kAirResistance; |
if (thePlayer.hVel > 0) |
thePlayer.hVel = 0; |
} |
thePlayer.vVel += kGravity; // Add in effect of gravity. |
if (thePlayer.vVel > kMaxVVelocity) // Keep player from falling too fast. |
thePlayer.vVel = kMaxVVelocity; |
else if (thePlayer.vVel < -kMaxVVelocity) |
thePlayer.vVel = -kMaxVVelocity; |
thePlayer.h += thePlayer.hVel; // Move player's x and y (h and v)É |
thePlayer.v += thePlayer.vVel; // by amount of velocity in each direction. |
SetAndCheckPlayerDest(); // Check for wrap-around, etc. |
CheckLavaRoofCollision(); // See if they hit roof or lava. |
CheckPlatformCollision(); // See if they crashed to a ledge. |
} |
//-------------------------------------------------------------- HandlePlayerBones |
// This is when the player is just a static pile of bones on a platform. TheyÉ |
// have been killed by an enemy and now are waiting to slip away so that a newÉ |
// player can be born. |
void HandlePlayerBones (void) |
{ |
if (evenFrame) // To slow it down a bit, action only occursÉ |
{ // on the even frames. |
thePlayer.frame--; // Drop the counter down by one. |
if (thePlayer.frame == 0) // When counter reaches zero, player officially dies. |
OffAMortal(); |
else // Otherwise, player's bones are sinking. |
thePlayer.dest.top = thePlayer.dest.bottom - thePlayer.frame; |
} |
} |
//-------------------------------------------------------------- MovePlayer |
// This function is the sort of "master movement" function. It looksÉ |
// at what mode a player is in and calls the appropriate function fromÉ |
// above. Arcade games (at least this one) tend to be very "modal" inÉ |
// this way. It's the actions of the user and the enemies in the gameÉ |
// that cause the player's mode to move from one state to another. |
void MovePlayer (void) |
{ |
switch (thePlayer.mode) // Check the "mode" the player is in. |
{ |
case kIdle: // Invulnerable - standing there - just born. |
HandlePlayerIdle(); |
break; |
case kFlying: // Flapping, floating, airborne. |
HandlePlayerFlying(); |
break; |
case kWalking: // On terra firma. Standing or walking on ledge. |
HandlePlayerWalking(); |
break; |
case kSinking: // Trapped in the lava - going down. |
HandlePlayerSinking(); |
break; |
case kFalling: // Dead - a skeleton falling to earth. |
HandlePlayerFalling(); |
break; |
case kBones: // Dead - a static pile of bones on a ledge. |
HandlePlayerBones(); |
break; |
} |
} |
//-------------------------------------------------------------- CheckTouchDownCollision |
// This function determines whether or not the player is landed on a ledge. |
// It does this by doing a rectangle collision between the player's boundingÉ |
// rectangle and an imaginary rectangle enclosing an area above the ledges. |
// I call these imaginary rectangles "touchDownRects[]". The trick was thatÉ |
// you don't want the player to have to "hit" the top of a ledge in order toÉ |
// land on it - there is an arbitrary distance above a ledge where, if the playerÉ |
// is within this area, the legs ought to come out and the player flagged asÉ |
// walking. As well, this same function is used for a walking player to seeÉ |
// if they are still on the ledge (they may walk off the edge). |
void CheckTouchDownCollision (void) |
{ |
Rect testRect, whoCares; |
short i, offset; |
Boolean sected; |
sected = FALSE; // Assume not on ledge. |
for (i = 0; i < numLedges; i++) // Go through all ledges. |
{ |
testRect = touchDownRects[i]; // Here's the imaginary rect. |
if (thePlayer.mode == kWalking) // We need an offset if player walkingÉ |
OffsetRect(&testRect, 0, 11); // since the player graphic is taller. |
if (SectRect(&thePlayer.dest, &testRect, &whoCares)) |
{ // Does the player's rect intersect? |
if (thePlayer.mode == kFlying) // Okay, it does, is the player airborne? |
{ |
StartPixelShatter((thePlayer.dest.right+thePlayer.dest.left)/2, |
thePlayer.dest.bottom, |
thePlayer.hVel, |
thePlayer.vVel, kShatterPlatformScrape); |
thePlayer.mode = kWalking; // Put player into walking mode. |
if (thePlayer.facingRight) // Assign correct graphic for player. |
thePlayer.srcNum = 5; |
else |
thePlayer.srcNum = 6; |
if (thePlayer.vVel > 0) // Stop player from falling further. |
thePlayer.vVel = 0; |
thePlayer.dest.bottom += 11; // "Grow" player's bounding rect. |
thePlayer.wasDest.bottom += 11; |
// Move player so standing on top of ledge. |
offset = thePlayer.dest.bottom - testRect.bottom - 1; |
thePlayer.dest.bottom -= offset; |
thePlayer.dest.top -= offset; |
thePlayer.v = thePlayer.dest.top << 4; |
// Play brief collision sound. |
PlayExternalSound(kGrateSound, kGratePriority); |
} |
sected = TRUE; // Make note that we've landed. |
} |
} |
if (!sected) // Now, if we didn't collideÉ |
{ // were we walking? |
if (thePlayer.mode == kWalking) // Did we walk off the ledge? |
{ |
thePlayer.mode = kFlying; // Set player to flying mode. |
thePlayer.dest.bottom -= 11; // Resize player's bounding rect. |
thePlayer.wasDest.bottom -= 11; |
} |
} |
} |
//-------------------------------------------------------------- CheckPlatformCollision |
// Unlike the above function, this one tests the player's bounding rect againstÉ |
// the bounding rect of each ledge (not an imaginary rect above the ledge). ThisÉ |
// function is primarily for (then) collisions off the bottom and sides of theÉ |
// ledges. In this way, the ledges are "solid" - not able to be passed through. |
void CheckPlatformCollision (void) |
{ |
Rect hRect, vRect, whoCares; |
short i, offset; |
for (i = 0; i < numLedges; i++) // Walk through all ledges. |
{ // Test rectangle overlap. |
if (SectRect(&thePlayer.dest, &platformRects[i], &whoCares)) |
{ // If player intersecting ledgeÉ |
hRect.left = thePlayer.dest.left; // Create our special test rect. |
hRect.right = thePlayer.dest.right; |
hRect.top = thePlayer.wasDest.top; |
hRect.bottom = thePlayer.wasDest.bottom; |
// Determine if the player hit theÉ |
// top/bottom of the ledge or theÉ |
// sides of the ledge. |
if (SectRect(&hRect, &platformRects[i], &whoCares)) |
{ // We're fairly sure the player hitÉ |
// the left or right edge of ledge. |
PlayExternalSound(kGrateSound, kGratePriority); |
StartPixelShatter( (thePlayer.dest.right+thePlayer.dest.left)/2, |
thePlayer.dest.bottom, |
thePlayer.hVel, |
thePlayer.vVel, kShatterPlatformHit); |
if (thePlayer.h > thePlayer.wasH) // If player was heading rightÉ |
{ // player will bounce to left. |
offset = thePlayer.dest.right - platformRects[i].left; |
thePlayer.dest.left -= offset; |
thePlayer.dest.right -= offset; |
thePlayer.h = thePlayer.dest.left << 4; |
if (thePlayer.hVel > 0) // We bounce back with 1/2 our vel. |
thePlayer.hVel = -(thePlayer.hVel >> 1); |
else |
thePlayer.hVel = thePlayer.hVel >> 1; |
} // Else if player was heading leftÉ |
else if (thePlayer.h < thePlayer.wasH) |
{ // player will bounce right. |
offset = platformRects[i].right - thePlayer.dest.left; |
thePlayer.dest.left += offset; |
thePlayer.dest.right += offset; |
thePlayer.h = thePlayer.dest.left << 4; |
if (thePlayer.hVel < 0) // We bounce back with 1/2 our vel. |
thePlayer.hVel = -(thePlayer.hVel >> 1); |
else |
thePlayer.hVel = thePlayer.hVel >> 1; |
} // Play impact sound. |
} |
else // It doesn't look like we hit theÉ |
{ // the left or right edge of ledge. |
vRect.left = thePlayer.wasDest.left; |
vRect.right = thePlayer.wasDest.right; |
vRect.top = thePlayer.dest.top; |
vRect.bottom = thePlayer.dest.bottom; |
// So we'll test top/bottom collision. |
if (SectRect(&vRect, &platformRects[i], &whoCares)) |
{ // We've decided we've hit top/bottom. |
if (thePlayer.wasV < thePlayer.v) |
{ // If we were heading down (hit top)É |
// keep player on top of ledge. |
offset = thePlayer.dest.bottom - platformRects[i].top; |
thePlayer.dest.top -= offset; |
thePlayer.dest.bottom -= offset; |
thePlayer.v = thePlayer.dest.top << 4; |
// Play collision sound. |
if (thePlayer.vVel > kDontFlapVel) |
{ |
StartPixelShatter( (thePlayer.dest.right+thePlayer.dest.left)/2, |
thePlayer.dest.bottom, |
thePlayer.hVel, |
thePlayer.vVel, kShatterPlatformScrape); |
PlayExternalSound(kGrateSound, kGratePriority); |
} |
// If we were falling bones (dead)É |
if (thePlayer.mode == kFalling) |
{ // we'll bounce. |
if ((thePlayer.dest.right - 16) > platformRects[i].right) { |
thePlayer.hVel = 16; |
if (thePlayer.vVel > 0) |
thePlayer.vVel = -(thePlayer.vVel >> 1); |
else |
thePlayer.vVel = thePlayer.vVel >> 1; |
} |
else if ((thePlayer.dest.left + 16) < platformRects[i].left) |
{ |
thePlayer.hVel = -16; |
if (thePlayer.vVel > 0) |
thePlayer.vVel = -(thePlayer.vVel >> 1); |
else |
thePlayer.vVel = thePlayer.vVel >> 1; |
} |
else // If we were nearly stoppedÉ |
{ // turn into pile of bones. |
PlayExternalSound(kBoom1Sound, kBoom1Priority); |
thePlayer.vVel = 0; |
thePlayer.mode = kBones; |
thePlayer.frame = 22; |
thePlayer.dest.top = thePlayer.dest.bottom - 22; |
thePlayer.v = thePlayer.dest.top << 4; |
thePlayer.srcNum = 10; |
} |
} |
else // Okay, if we weren't falling bonesÉ |
{ // bounce the player (-1/2 vel.). |
if (thePlayer.vVel > 0) |
thePlayer.vVel = -(thePlayer.vVel >> 1); |
else |
thePlayer.vVel = thePlayer.vVel >> 1; |
} |
} // If the player was instead moving upÉ |
else if (thePlayer.wasV > thePlayer.v) |
{ // the player likely hit the bottom ofÉ |
// the ledge. Keep player below ledge. |
StartPixelShatter( (thePlayer.dest.right+thePlayer.dest.left)/2, |
thePlayer.dest.bottom, |
thePlayer.hVel, |
thePlayer.vVel, kShatterPlatformHit); |
offset = platformRects[i].bottom - thePlayer.dest.top; |
thePlayer.dest.top += offset; |
thePlayer.dest.bottom += offset; |
thePlayer.v = thePlayer.dest.top << 4; |
// Play collision sound. |
PlayExternalSound(kGrateSound, kGratePriority); |
if (thePlayer.vVel < 0) // Bounce player down (-1/2 vel.). |
thePlayer.vVel = -(thePlayer.vVel >> 1); |
else |
thePlayer.vVel = thePlayer.vVel >> 1; |
} |
} |
} |
} |
} |
} |
//-------------------------------------------------------------- KeepPlayerOnPlatform |
// This is an alignment function. It is called only if the player is standing orÉ |
// walking on a ledge. It is designed to keep the player's mount's (bird's)É |
// feet firmly planted on the ledge. Consider that, with the addition of gravityÉ |
// to a player's downward velocity, there is a problem where the player can appearÉ |
// to slowly sink down through the ledge. There may be any number of methods youÉ |
// might want to try to prevent this from becoming a problem in the first place, É |
// but my experience has been that all the methods I've tried have flaws - correctingÉ |
// for those flaws points out other flaws and you start getting a messy sort ofÉ |
// patchwork. Should you ever get it to work, the mess that is your function has comeÉ |
// to resemble the Knot of ????. |
void KeepPlayerOnPlatform (void) |
{ |
Rect whoCares; |
short i, offset; |
for (i = 0; i < numLedges; i++) // For each ledge for this waveÉ |
{ // test for a collision. |
if ((SectRect(&thePlayer.dest, &platformRects[i], &whoCares)) && (thePlayer.vVel > 0)) |
{ // If collided (player sinking), forceÉ |
// player to top of ledge. |
offset = thePlayer.dest.bottom - platformRects[i].top - 1; |
thePlayer.dest.top -= offset; |
thePlayer.dest.bottom -= offset; |
thePlayer.v = thePlayer.dest.top * 16; |
} |
} |
if (thePlayer.vVel > 0) // Set player's vertical velocity to zero. |
thePlayer.vVel = 0; |
} |
//-------------------------------------------------------------- CheckLavaRoofCollision |
// This is a simple high/low test to see if the player has either bounced offÉ |
// the roof of the "arena" or dipped down into the lava below. |
void CheckLavaRoofCollision (void) |
{ |
short offset; |
if (thePlayer.dest.bottom > kLavaHeight) // See if player in lava. |
{ |
if (thePlayer.mode == kFalling) // If falling (dead), "Splash!" |
PlayExternalSound(kSplashSound, kSplashPriority); |
else // If flying (alive), "Yeow!" |
PlayExternalSound(kBirdSound, kBirdPriority); |
{ |
short left = thePlayer.dest.left; |
short right = thePlayer.dest.right; |
short bottom = thePlayer.dest.bottom; |
short delta = (right - left); |
short temp = left * 32; |
short splashItr; |
for(splashItr = 0; splashItr < 32; splashItr++) |
{ |
StartPixelShatter(temp / 32, bottom, (thePlayer.hVel * 0.75), -(thePlayer.vVel * 0.75), kShatterLavaSplash); |
temp += delta; |
} |
} |
thePlayer.mode = kSinking; // Irregardless, player is now sinking. |
} |
else if (thePlayer.dest.top < kRoofHeight) // See if player hit roof. |
{ // Move player to below roof. |
offset = kRoofHeight - thePlayer.dest.top; |
thePlayer.dest.top += offset; |
thePlayer.dest.bottom += offset; |
thePlayer.v = thePlayer.dest.top * 16; |
// Play collision sound. |
PlayExternalSound(kGrateSound, kGratePriority); |
thePlayer.vVel = thePlayer.vVel / -2; // Rebound player (-1/2 vel.). |
} |
} |
//-------------------------------------------------------------- SetAndCheckPlayerDest |
// This function keeps our player's screen coordinates and "scaled" coordinatesÉ |
// in agreement. As well, it checks for wrap-around and handles it. |
void SetAndCheckPlayerDest (void) |
{ |
short wasTall, wasWide; |
// Remember width and height of player. |
wasTall = thePlayer.dest.bottom - thePlayer.dest.top; |
wasWide = thePlayer.dest.right - thePlayer.dest.left; |
// Convert scaled coords to screen coords. |
thePlayer.dest.left = thePlayer.h >> 4; |
thePlayer.dest.right = thePlayer.dest.left + wasWide; |
thePlayer.dest.top = thePlayer.v >> 4; |
thePlayer.dest.bottom = thePlayer.dest.top + wasTall; |
if (thePlayer.dest.left > 640) // Has player left right side of arena? |
{ // Wrap player back to left side of screen. |
OffsetRect(&thePlayer.dest, -640, 0); |
thePlayer.h = thePlayer.dest.left << 4; |
OffsetRect(&thePlayer.wasDest, -640, 0); |
} |
else if (thePlayer.dest.right < 0) // Else, has player left left side of screen? |
{ // Wrap player around to right side of screen. |
OffsetRect(&thePlayer.dest, 640, 0); |
thePlayer.h = thePlayer.dest.left << 4; |
OffsetRect(&thePlayer.wasDest, 640, 0); |
} |
} |
//-------------------------------------------------------------- HandleLightning |
// Lightning is handled here. Obelisks are flashed, lightning is generated, É |
// lighting strikes, and the lightning counter decremented. This is prettyÉ |
// nice - we can just set "lightningCount" to a non-zero number and thisÉ |
// function will strike lightning every fram until the counter returns to zero. |
void HandleLightning (void) |
{ |
Boolean doLightning = false; |
short hLoc; |
short vLoc; |
if (thePlayer.electrical > 0) |
{ |
lightningCount = 0; |
hLoc = (thePlayer.dest.left + thePlayer.dest.right) / 2; |
vLoc = (thePlayer.dest.top + thePlayer.dest.bottom) / 2; |
thePlayer.electrical--; |
doLightning = true; |
} |
else if (lightningCount > 0) // Is lightning to strik this frame? |
{ |
lightningCount--; |
hLoc = lightH; |
vLoc = lightV; |
doLightning = true; |
} |
if (doLightning) |
{ |
GenerateLightning(hLoc, vLoc); |
StrikeLightningWork(); |
} |
} |
//-------------------------------------------------------------- HandleCountDownTimer |
// This is a pretty boring function. It is here so that when one level ends,É |
// the next one does begin immediately. It gives the player a few seconds ofÉ |
// breathing time. Essentially, to engage it, we need merely set "countDownTimer"É |
// to a positive number. Each frame the counter gets decremented. When itÉ |
// reaches zero, the level is advanced to the next wave. |
void HandleCountDownTimer (void) |
{ |
if (countDownTimer == 0) // If already zero, do nothing. |
return; |
else // Otherwise, if greater than zeroÉ |
{ |
countDownTimer--; // decrememnt counter. |
if (countDownTimer == 0) // Did it just hit zero? |
{ |
countDownTimer = 0; // Well, just to be sure (dumb line of code). |
levelOn++; // Increment the level (wave) we're on. |
UpdateLevelNumbers(); // Display new level on screen. |
SetUpLevel(); // Set up the platforms. |
GenerateEnemies(); // Ready nemesis. |
} |
} |
} |
//-------------------------------------------------------------- PlayGame |
// Here is the "core" of the "game loop". When a player has elected toÉ |
// begin a game, Glypha falls into this function and remains in a loopÉ |
// herein until the player either quits, or loses their last "bird". |
// Each pass through the main loop below constitutes one "frame" of the game. |
void PlayGame (void) |
{ |
Point offsetPt; |
UnsignedWide thisTime; |
offsetPt.h = 0; // Set up ShieldCursor() point. |
offsetPt.v = 20; |
oldFrameRate = 0; |
Microseconds(&frameTime); |
frameTime.lo += 1000000; |
do // Main game loop!!!! |
{ |
Microseconds(&thisTime); |
if ((thisTime.hi > frameTime.hi) || |
((thisTime.hi == frameTime.hi) && (thisTime.lo > frameTime.lo))) |
{ |
UpdateLevelNumbers(); // update the level, using for frame rate |
oldFrameRate = currentFrameRate; |
currentFrameRate = 0; |
Microseconds(&frameTime); |
frameTime.lo += 1000000; |
} |
else |
{ |
currentFrameRate++; |
} |
MovePlayer(); // Move the player's bird. |
MoveEnemies(); // Move all sphinx enemies. |
HandleHand(); // Handle the mummy hand (may do nothing). |
HandleEye(); // Handle eye (probably will do nothing). |
HandlePixelShatter(); |
HandleScoreFloaters(); |
HandleLava(); |
DSpContext_GetBackBuffer( gTheContext, kDSpBufferKind_Normal, &workSrcMap ); |
DumpBackToWorkMap(); // clear the screen |
DrawFrame(); // Draw the whole scene for this frame. |
HandleLightning(); |
DSpContext_SwapBuffers( gTheContext, NULL, NULL ); |
evenFrame = !evenFrame; // Toggle "evenFrame" variable. |
GetPlayerInput(); // Get the player's input (keystrokes). |
HandleCountDownTimer(); // Handle countdown (may do nothing). |
} |
while ((playing) && (!pausing)); // Stay in loop until dead, paused or quit. |
if ((!playing) && (!quitting)) // If the player died! |
{ // Then play some sweet music. |
PlayExternalSound(kMusicSound, kMusicPriority); |
CheckHighScore(); // And see if they're on the high scores. |
} |
MenusReflectMode(); // Set the menus grayed-out state correctly. |
FlushEvents(everyEvent, 0); // Flush any events in the queue. |
} |
//-------------------------------------------------------------- CheckHighScore |
// This function handles testing to see if the player's score is in the É |
// high scores. If that is the case, the function prompts the user forÉ |
// a name to enter, and sorts and stores off the new score list. |
void CheckHighScore (void) |
{ |
#define kHighNameDialogID 130 |
Str255 placeStr, tempStr; |
DialogPtr theDial; |
short i, item; |
Boolean leaving; |
if (theScore > thePrefs.highScores[9]) // To see if on high scores, we needÉ |
{ // merely see if the last guy is beat out. |
openTheScores = TRUE; // Will automatically bring up high scores. |
// Play some congratulatory music. |
PlayExternalSound(kBonusSound, kMusicPriority - 1); |
i = 8; // Find where new score fits in list. |
while ((theScore > thePrefs.highScores[i]) && (i >= 0)) |
{ // We'll bump everyone down as we look. |
thePrefs.highScores[i + 1] = thePrefs.highScores[i]; |
thePrefs.highLevel[i + 1] = thePrefs.highLevel[i]; |
PasStringCopy(thePrefs.highNames[i], thePrefs.highNames[i + 1]); |
i--; |
} |
i++; // i is our place in list (zero based). |
thePrefs.highScores[i] = theScore; // Pop the new score in place. |
thePrefs.highLevel[i] = levelOn + 1; // Drop in the new highest level. |
NumToString((long)i + 1L, placeStr); // Convert place to a string to displayÉ |
ParamText(placeStr, "\p", "\p", "\p"); // in the dialog (via ParamText()). |
InitCursor(); // Show cursor. |
CenterDialog(kHighNameDialogID); // Center the dialog and then bring it up. |
theDial = GetNewDialog(kHighNameDialogID, 0L, kPutInFront); |
SetPort((GrafPtr)theDial); |
ShowWindow((GrafPtr)theDial); // Make dialog visible. |
DrawDefaultButton(theDial); // Draw outline around "Okay" button. |
FlushEvents(everyEvent, 0); // Flush any events queued up. |
// Put a default name in text edit box. |
SetDialogString(theDial, 2, thePrefs.highName); |
SelectDialogItemText(theDial, 2, 0, 1024); // Select the whole text edit string. |
leaving = FALSE; // Flag for noting when player hit "Okay". |
while (!leaving) // Simple modal dialog loop. |
{ |
ModalDialog(0L, &item); // Use standard filtering. |
if (item == 1) // If player hit the "Okay" buttonÉ |
{ // Get the name entered in text edit box. |
GetDialogString(theDial, 2, tempStr); |
// Copy the name into high score list. |
PasStringCopyNum(tempStr, thePrefs.highNames[i], 15); |
PasStringCopy(thePrefs.highNames[i], thePrefs.highName); |
leaving = TRUE; // We're gone! |
} |
} |
DisposeDialog(theDial); // Clean up. |
} |
else // But if player didn't get on high scoresÉ |
openTheScores = FALSE; // no need to rub their face in it. |
} |
void HandlePixelShatter(void) |
{ |
int i = 0; |
while(i < numPixelShatter) |
{ |
if (thePixelShatter[i].duration == 0) |
{ |
thePixelShatter[i] = thePixelShatter[numPixelShatter]; |
numPixelShatter--; |
continue; |
} |
thePixelShatter[i].duration--; |
thePixelShatter[i].h += thePixelShatter[i].hVel; |
thePixelShatter[i].v += thePixelShatter[i].vVel; |
thePixelShatter[i].hVel += RandomInt(5) - 2; |
thePixelShatter[i].vVel += kGravity + RandomInt(5) - 2; |
if ( (thePixelShatter[i].type == kShatterLavaBubble) || |
(thePixelShatter[i].type == kShatterLavaSplash) || |
(thePixelShatter[i].type == kShatterLavaBurst)) |
{ |
thePixelShatter[i].color += RandomInt(3) - 1; |
if (thePixelShatter[i].color > 220) |
{ |
thePixelShatter[i].color = 220; |
} |
else if (thePixelShatter[i].color < 216) |
{ |
thePixelShatter[i].color = 216; |
} |
} |
else if ((thePixelShatter[i].type == kShatterPlatformLightning)) |
{ |
if (thePixelShatter[i].duration > 18) |
{ |
thePixelShatter[i].color = 5; |
} |
else if (thePixelShatter[i].duration > 12) |
{ |
thePixelShatter[i].color = 47; |
} |
else if (thePixelShatter[i].duration > 6) |
{ |
thePixelShatter[i].color = 89; |
} |
else |
{ |
thePixelShatter[i].color = 255; |
} |
} |
else if ( (thePixelShatter[i].type == kShatterObeliskTip) || |
(thePixelShatter[i].type == kShatterLightningDust) || |
(thePixelShatter[i].type == kShatterPlayerDeath) || |
(thePixelShatter[i].type == kShatterEnemyDeath) || |
(thePixelShatter[i].type == kShatterPlayerEnemyScrape)) |
{ |
// white 0 |
// yellow 1,2,3,4,5 |
// orange 11, 17, 23, 29 |
// red 35, ... 216, 218, 220, 222, 223 |
// black 255 |
unsigned char blackBodyTable[17] = { 0, // white |
1,2,3,4,5, // yellow (5) |
11,17,23,29, // orange (4) |
35, 216, 218, 220, 222, 223, // red (6) |
255 // black |
}; |
if (thePixelShatter[i].duration >= 17) |
{ |
thePixelShatter[i].color = 0; |
} |
else |
{ |
thePixelShatter[i].color = blackBodyTable[thePixelShatter[i].duration]; |
} |
} |
else |
{ |
if (thePixelShatter[i].duration > 23) |
{ |
thePixelShatter[i].color = 0; |
} |
else |
{ |
thePixelShatter[i].color = 255 - (thePixelShatter[i].duration / 2); |
} |
} |
thePixelShatter[i].h %= (640 * 16) ; |
if ((thePixelShatter[i].h < 0) || (thePixelShatter[i].h >= (640 * 16)) || |
(thePixelShatter[i].v < 0) || (thePixelShatter[i].v >= (480 * 16))) |
{ |
thePixelShatter[i] = thePixelShatter[numPixelShatter]; |
numPixelShatter--; |
continue; |
} |
i++; |
} |
} |
void StartPixelShatterRect(Rect *r, short dH, short dV, short type) |
{ |
StartPixelShatter(r->left, r->top, dH, dV, type); |
StartPixelShatter(r->left, r->bottom, dH, dV, type); |
StartPixelShatter(r->right, r->top, dH, dV, type); |
StartPixelShatter(r->right, r->bottom, dH, dV, type); |
StartPixelShatter((r->left + r->right) / 2, (r->top + r->bottom) / 2, dH, dV, type); |
} |
void StartPixelShatter(short h, short v, short hVel, short vVel, short type) |
{ |
signed short hOffset[9] = {0,16,16,0,-16,-16,-16,0,16}; |
signed short vOffset[9] = {0,0,-16,-16,-16,0,16,16,16}; |
int itr; |
unsigned char color; |
h *= 16; |
v *= 16; |
if ((numPixelShatter + 10) >= kMaxPixelShatter) |
{ |
return; |
} |
if ( (type == kShatterLavaBubble) || |
(type == kShatterLavaSplash) || |
(type == kShatterLavaBurst) || |
(type == kShatterPlayerDeath) || |
(type == kShatterEnemyDeath)) |
{ |
color = 215; |
} |
else if (type == kShatterPlatformLightning) |
{ |
color = 5; |
} |
else |
{ |
color = 0; |
} |
for(itr = 0; itr < 9; itr++) |
{ |
thePixelShatter[numPixelShatter].h = h + hOffset[itr]; |
thePixelShatter[numPixelShatter].v = v + vOffset[itr]; |
thePixelShatter[numPixelShatter].h = h + hOffset[itr] + RandomInt(20) - 10; |
thePixelShatter[numPixelShatter].v = v + vOffset[itr] + RandomInt(20) - 10; |
thePixelShatter[numPixelShatter].hVel = hVel + hOffset[itr]; |
thePixelShatter[numPixelShatter].vVel = vVel + vOffset[itr]; |
thePixelShatter[numPixelShatter].color = color; |
if ((type == kShatterLavaSplash) || (type == kShatterPlatformLightning)) |
{ |
thePixelShatter[numPixelShatter].duration = 400; |
} |
else if ((type == kShatterPlatformScrape) || (type == kShatterPlatformHit)) |
{ |
thePixelShatter[numPixelShatter].duration = RandomInt(15) + 10; |
} |
else |
{ |
thePixelShatter[numPixelShatter].duration = RandomInt(21) + 20; |
} |
thePixelShatter[numPixelShatter].type = type; |
numPixelShatter++; |
} |
} |
void HandleScoreFloaters(void) |
{ |
int i = 0; |
while(i < numScoreFloater) |
{ |
if (theScoreFloater[i].duration == 0) |
{ |
theScoreFloater[i] = theScoreFloater[numScoreFloater]; |
numScoreFloater--; |
continue; |
} |
theScoreFloater[i].location.v -= 2; |
theScoreFloater[i].duration--; |
i++; |
} |
} |
void StartScoreFloater(unsigned long score, Point where) |
{ |
if (where.h > 419) { where.h = 419; } |
if (where.v > 459) { where.v = 549; } |
if (where.h < 0) { where.h = 0;} |
if (where.v < 0) { where.v = 0; } |
theScoreFloater[numScoreFloater].score = score; |
theScoreFloater[numScoreFloater].location = where; |
theScoreFloater[numScoreFloater].duration = 20; |
numScoreFloater++; |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-10-14