Version of Robotron arcade game using LPC1768, a Gameduino shield, a serial EEPROM (for high scores), two microswitch joysticks and two buttons plus a box to put it in. 20 levels of mayhem.
Dependencies: 25LCxxx_SPI CommonTypes Gameduino mbed
LevelNormal.cpp
- Committer:
- RichardE
- Date:
- 2013-06-08
- Revision:
- 6:8bbdb70bc11c
- Parent:
- 5:0b0651ac7832
- Child:
- 7:e72691603fd3
File content as of revision 6:8bbdb70bc11c:
/* * SOURCE FILE : LevelNormal.cpp * * Definition of class LevelNormal. * Base class for all "normal" levels. * i.e. Levels that are not special attract modes * but have enemies who are trying to kill you * and so on. * */ // Define this for debugging messages. #define CHATTY #ifdef CHATTY #include "mbed.h" extern Serial pc; #endif #include "LevelNormal.h" #include "GameObjectLocator.h" #include "FrameCounter.h" #include "SpriteNumber.h" #include "ArenaConst.h" // Current instance being processed. LevelNormal *LevelNormal::currentInstance; /***************/ /* CONSTRUCTOR */ /***************/ LevelNormal::LevelNormal() { } /**************/ /* DESTRUCTOR */ /**************/ LevelNormal::~LevelNormal() { } /********************/ /* INITIALISE LEVEL */ /********************/ // Pass pointer to Gameduino to draw on in gd. void LevelNormal::InitialiseLevel( Gameduino *gd ) { // Note that if you re-arrange the following code you may need to adjust the // SpriteNumber enumeration in SpriteNumber.h. UInt8 spriteNumber = FirstEnemySprite; // Initialise enemies. GameObject::InitialiseAll( DataForLevel->Enemies, LevelData::MaxEnemies, &spriteNumber ); // Initialise humans. spriteNumber = FirstHumanSprite; GameObject::InitialiseAll( DataForLevel->Humans, LevelData::MaxHumans, &spriteNumber ); // Use next free sprite number for player. player->SpriteNumber = PlayerSprite; // Do futher initialisation for all enemies. #if 0 EnemyObject *object; for( UInt8 e = 0; e < LevelData::MaxEnemies; ++e ) { object = (EnemyObject*)DataForLevel->Enemies[ e ]; if( object != (EnemyObject*)NULL ) { // Get enemy to chase the player. object->SetChaseObject( player ); // Pass array of all enemies to this enemy. object->Enemies = DataForLevel->Enemies; // If enemy is a brain then tell it about the humans to chase. if( object->GetEnemyType() == Brain ) { ((BrainObject*)object)->HumansToChase = DataForLevel->Humans; } } } #endif // Put player in the centre of the arena. player->Xco = PLAYER_START_X; player->Yco = PLAYER_START_Y; // Kill off all player's bullets. player->KillAllBullets( gd ); // Kill off all explosions. // ExplosionManager::Instance.KillAllExplosions(); } /**********************************/ /* DRAW SCORE AND NUMBER OF LIVES */ /**********************************/ void LevelNormal::DrawScoreAndLives( void ) { GDExtra::WriteBCDNumber( gd, 16, 0, player->Score, 8 ); // Display number of lives but limit this to 20 lives displayed. UInt8 lives = ( player->Lives > 20 ) ? 20 : player->Lives; gd->fill( Gameduino::RAM_PIC + VISIBLE_CHAR_WIDTH - lives, MiniPlayer, lives ); } /******************/ /* DRAW THE LEVEL */ /******************/ void LevelNormal::DrawLevel( void ) { // Set screen background to black. gd->wr( Gameduino::BG_COLOR, Gameduino::RGB( 0, 0, 0 ) ); // Clear the screen to zero characters. GDExtra::ClearScreen( gd, TransparentChar ); // Hide all sprties. GDExtra::HideAllSprites( gd ); // Display level number. GDExtra::WriteProgString( gd, 0, 0, StringData::LevelString ); GDExtra::WriteUInt16( gd, 6, 0, LevelNumber, 10, 2 ); // Display score. GDExtra::WriteProgString( gd, 10, 0, StringData::ScoreString ); // Update score and lives. DrawScoreAndLives(); // Draw border around screen. CharFrame::Draw( gd, ARENA_BORDER_X, ARENA_BORDER_Y, ARENA_BORDER_WIDTH, ARENA_BORDER_HEIGHT ); } /************************************************/ /* HANDLE COLLISIONS BETWEEN HUMANS AND ENEMIES */ /************************************************/ // Pass index of human in level's humans array in humanIndex. // Pass sprite number of sprite that it hit in spriteNumber. void LevelNormal::HandleHumanCollision( UInt8 humanIndex, UInt8 spriteNumber ) { #if 0 // Point to array of enemy object pointers. GameObject **enemies = currentInstance->DataForLevel->Enemies; EnemyObject *enemy; UInt8 enemyIndex, mutantIndex; // Find an enemy with given sprite number. if( GameObject::FindSpriteNumber( enemies, LevelData::MaxEnemies, spriteNumber, &enemyIndex ) ) { // Found enemy. Check if it squashes humans. enemy = (EnemyObject*)enemies[ enemyIndex ]; // Get hold of the human that is doomed. GameObject **humans = currentInstance->DataForLevel->Humans; HumanObject *human = (HumanObject*)humans[ humanIndex ]; // Human must be walking around. Not rescued or already dead. if( human->CurrentState == HumanObject::WalkingAbout ) { if( enemy->SquashesHumans ) { // Change human to dead state. human->CurrentState = HumanObject::Dead; // Make a noise. SoundManager::Instance.PlaySound( Sounds::HumanDies, 0, 0 ); } else if( enemy->GetEnemyType() == Brain ) { // Kill human by inserting a null into humans array. humans[ humanIndex ] = (GameObject*)NULL; // Find a free slot for a new enemy. if( GameObject::FindUnusedObject( enemies, LevelData::MaxEnemies, &mutantIndex ) ) { // Write a pointer to a mutant with the same index as the human that just died // into the enemy array. MutantObject *mutant = currentInstance->DataForLevel->Mutants + humanIndex; enemies[ mutantIndex ] = mutant; // Initialise mutant at coordinates of human and chasing the player. mutant->Start( human, currentInstance->player ); // Make a noise. // TODO : SoundManager::Instance.PlaySound( Sounds::HumanMutates, 0, 0 ); } else { // Could not find a free slot for a new enemy so just erase the human sprite. GDExtra::HideSprite( human->SpriteNumber ); } } } } #endif } /********************************************************/ /* HANDLE COLLISIONS BETWEEN PLAYER BULLETS AND ENEMIES */ /********************************************************/ // Pass index of bullet in player's bullet array in bulletIndex. // Pass sprite number of sprite that it hit in spriteNumber. void LevelNormal::HandleBulletCollision( UInt8 bulletIndex, UInt8 spriteNumber ) { #if 0 // Point to array of enemy object pointers. GameObject **enemies = currentInstance->DataForLevel->Enemies; EnemyObject *enemy; UInt8 enemyIndex; // Find an enemy with given sprite number. if( GameObject::FindSpriteNumber( enemies, LevelData::MaxEnemies, spriteNumber, &enemyIndex ) ) { // Found enemy. Check if it is indestructable. enemy = (EnemyObject*)enemies[ enemyIndex ]; if( enemy->HitPoints != EnemyObject::Indestructable ) { // Enemy is not indestructable. Decrement hit points and die when it reaches zero. enemy->HitPoints--; if( enemy->HitPoints == 0 ) { // Kill enemy by inserting a NULL into enemies array. enemies[ enemyIndex ] = (GameObject*)NULL; // Hide the enemy sprite. GDExtra::HideSprite( enemy->SpriteNumber ); // Add points to player's score. currentInstance->player->AddToScore( enemy->GetPoints() ); } } // Tell enemy it has been hit by a bullet. enemy->RegisterHitByBullet(); // Kill off the bullet. currentInstance->player->KillBullet( bulletIndex ); // Make a noise. SoundManager::Instance.PlaySound( Sounds::Explosion, 0, 0 ); // Start explosion animation using coordinates of enemy. ExplosionManager::Instance.StartExplosion( enemy->Xco, enemy->Yco ); } #endif } /*********************************************************/ /* CHECK FOR COLLISIONS BETWEEN PLAYER AND OTHER OBJECTS */ /*********************************************************/ // Pass pointer to a flag that will be set true if player is dead in isDead parameter. void LevelNormal::CheckPlayerCollisions( bool *isDead ) { #if 0 UInt8 enemyIndex, humanIndex; // Check if player sprite has hit another sprite. UInt8 hitSpriteNumber = GD.rd( COLLISION + player->SpriteNumber ); // If you get 0xFF then no collision found. if( hitSpriteNumber != 0xFF ) { // Check for collision with an enemy. if( GameObject::FindSpriteNumber( DataForLevel->Enemies, LevelData::MaxEnemies, hitSpriteNumber, &enemyIndex ) ) { // Hit an enemy. Player is dead. *isDead = true; } // Check for collision with a human that has not already been rescued or killed. else if( GameObject::FindSpriteNumber( DataForLevel->Humans, LevelData::MaxHumans, hitSpriteNumber, &humanIndex ) ) { HumanObject *human = (HumanObject*)DataForLevel->Humans[ humanIndex ]; if( human->CurrentState == HumanObject::WalkingAbout ) { // Change human state to rescued. human->CurrentState = HumanObject::Rescued; // Give player 50 points (in BCD!). player->AddToScore( 0x50 ); // Make a noise. SoundManager::Instance.PlaySound( Sounds::RescueHuman, 0, 0 ); } } } #endif } /***********************************************************************************/ /* WAIT UNTIL SLOT FREE FOR A NEW SOUND, PLAY IT AND WAIT FOR ALL SOUNDS TO FINISH */ /***********************************************************************************/ // Pass sound to play in soundToPlay parameter. void LevelNormal::PlaySoundAndWait( const UInt8 *soundToPlay ) { #if 0 // Keep trying to play sound until it works and meanwhile // keep currently playing sounds going. while( ! SoundManager::Instance.PlaySound( soundToPlay, 0, 0 ) ) { // Update sound manager. SoundManager::Instance.Update(); // Wait for frame flyback. GD.waitvblank(); } // Now wait until all sounds have finished. while( SoundManager::Instance.CountSoundsPlaying() > 0 ) { // Update sound manager. SoundManager::Instance.Update(); // Wait for frame flyback. GD.waitvblank(); } #endif } /*************/ /* PLAY LOOP */ /*************/ // Returns code indicating how level ended. // This method should be called from the Play method after the // level data has been initialised and the return value returned // by the Play method. Level::LevelExitCode LevelNormal::PlayLoop( void ) { // Do nothing if Gameduino has not been specified, level data is NULL or player has not been specified. if( ( gd != (Gameduino*)NULL ) || ( DataForLevel != (LevelData*)NULL ) || ( player == (PlayerObject*)NULL ) ) { // Point static pointer to current instance. currentInstance = this; // Do some initialisation first. InitialiseLevel( gd ); // Redraw the screen. DrawLevel(); // Wait for frame flyback once before entering loop so collision data is recalculated. // At this point there should not be any sprites on the screen so no collisions // should be found. gd->waitvblank(); // Repeat until all enemies are dead or player is dead. bool allEnemiesAreDead = false; bool playerIsDead = false; bool gameIsOver = false; bool firstDraw = true; while( ! allEnemiesAreDead && ! gameIsOver ) { // Update sound manager. // SoundManager::Instance.Update(); // Wait for frame flyback. gd->waitvblank(); // Check for collisions between player and other objects. CheckPlayerCollisions( &playerIsDead ); #if 0 // Check for collisions between humans and enemies that squash. GameObject::FindCollisions( DataForLevel->Humans, LevelData::MaxHumans, &LevelNormal::HandleHumanCollision ); // Check for collisions between player bullets and enemies. GameObject::FindCollisions( player->GetBullets(), BulletManager::MaxBullets, &LevelNormal::HandleBulletCollision ); #endif // Redraw the player's score and number of lives. DrawScoreAndLives(); #if 0 // Draw all the enemies. GameObject::DrawAll( DataForLevel->Enemies, LevelData::MaxEnemies ); // Draw all the humans. GameObject::DrawAll( DataForLevel->Humans, LevelData::MaxHumans ); // Draw all the explosions. GameObject::DrawAll( ExplosionManager::Instance.GetExplosions(), ExplosionManager::MaxExplosions ); #endif // Draw the player. player->Draw( gd ); // Draw the player's bullets. GameObject::DrawAll( gd, player->GetBullets(), BulletManager::MaxBullets ); // Increment the frame counter. FrameCounter++; // After first redraw play level start sound and wait for it to end. if( firstDraw ) { // PlaySoundAndWait( Sounds::StartLevel ); firstDraw = false; } // If player was killed then play death march and wait for it to finish. if( playerIsDead ) { #ifdef CHATTY pc.puts( "Player got killed.\r\n" ); #endif // Player got killed. // PlaySoundAndWait( Sounds::PlayerDead ); // One less life for player. if( player->Lives > 0 ) { player->Lives--; } // Game is over when player has no more lives. gameIsOver = ( player->Lives == 0 ); // If game is not over then re-initialise level using any remaining enemies. if( ! gameIsOver ) { #ifdef CHATTY pc.puts( "Game is over.\r\n" ); #endif // Remove all objects that do not survive a level restart (like enemy bullets). GameObject::RemoveUnretainedObjects( DataForLevel->Enemies, LevelData::MaxEnemies ); InitialiseLevel( gd ); DrawLevel(); gd->waitvblank(); playerIsDead = false; firstDraw = true; } } else { #if 0 // Move all the enemies and check if all dead. allEnemiesAreDead = ! GameObject::MoveAll( DataForLevel->Enemies, LevelData::MaxEnemies ); // If there are still some enemies alive then check if those that remain are indestructable. // If only indestructable enemies survive then act as if all enemies are dead. // You need to do this or you would never be able to complete a level that had indestructable // enemies on it. if( ! allEnemiesAreDead ) { allEnemiesAreDead = EnemyObject::AreAllIndestructable( (const EnemyObject**)DataForLevel->Enemies, LevelData::MaxEnemies ); } // Move all the humans. GameObject::MoveAll( DataForLevel->Humans, LevelData::MaxHumans ); // Move (update) all the explosions. GameObject::MoveAll( ExplosionManager::Instance.GetExplosions(), ExplosionManager::MaxExplosions ); #endif // Read the player's controls. player->ReadControls(); // Move the player. player->Move(); // Move the player's bullets. GameObject::MoveAll( player->GetBullets(), BulletManager::MaxBullets ); } } // Player completed level or game is over. return gameIsOver ? GameOver : Completed; } else { // Level data or player were not specified. return Completed; } }