#include "View.h" #include "CameraBase.h" #include "SimpleConsoleOverlay.h" #include "IMGUIControls.h" #include "Engine.h" #include "Physics.h" #include "Model.h" #include "Controller.h" #include "EventBase.h" #include "Game.h" #include "ShipEntity.h" #include "AsteroidEntity.h" #include "AsteroidsEvents.h" #include "RocketEntity.h" #include #include // #define DRAW_BOUNDARIES #ifdef DRAW_BOUNDARIES #include "coll2d.h" #include "DrawingsGL.h" #endif using namespace std; namespace asteroids { int View::OnInit (int argc, char* argv[]) { ViewBase::OnInit (argc, argv); /* // We want the console mConsoleOverlay = boost::shared_ptr (new Engine::SimpleConsoleOverlay); // We also want to display the log bar mConsoleOverlay->SetDrawLogBar (true); mOverlayManager.Register (mConsoleOverlay, GameStateMainMenu); */ LoadFont("AldotheApache.ttf size=20 color=#000000"); button_width = 250; button_height = 40; // This is a simple star field that makes the game so spacy int i; for (i = 0; i < 200; i++) { BackgroundStar star; star.position[0] = rand() / float(RAND_MAX); star.position[1] = rand() / float(RAND_MAX); star.position[2] = rand() / float(RAND_MAX); mBackgroundStars.push_back (star); } mGUIShipSprite.LoadFromPNG("./data/textures/ship.png"); mGUIShipSprite.SetScale (0.1); mAsteroidSprite.LoadFromPNG ("./data/textures/asteroid.png"); mShipSprite.LoadFromPNG ("./data/textures/ship.png"); mShipThrustSprite.LoadFromPNG ("./data/textures/ship_thrust.png"); mShipThrustSprite.SetAnimation (4, 8); mShipPartsSprite.LoadFromPNG ("./data/textures/ship_parts.png"); mShipPartsSprite.SetSubSpriteCount (10); Engine::RegisterListener (this, EventAccelerateStart); Engine::RegisterListener (this, EventAccelerateStop); Engine::RegisterListener (this, EventShipExplode); Engine::RegisterListener (this, EventPlayerDied); Engine::RegisterListener (this, EventGameOver); Engine::RegisterListener (this, EventLevelComplete); PushViewState (ViewStateMainMenu); return 0; } void View::OnDestroy() { mBackgroundStars.clear(); mShipPartsEntityIds.clear(); Engine::ViewBase::OnDestroy(); } bool View::OnReceiveEvent (const Engine::EventBasePtr &event) { switch (event->mEventType) { case EventAccelerateStart: case EventAccelerateStop: if (event->mEventType == EventAccelerateStart) mShipThrustSprite.ResetAnimation(); Engine::LogDebug ("Received Acceleration Event: %d", event->mEventType); return true; break; case EventLevelComplete: PushViewState(ViewStateLevelComplete); GetModel()->SetPlayerEntityId(Engine::NullEntityId); break; case EventGameOver: mFadeTimerSecValue = 2.; PopViewState(); PushViewState(ViewStateGameOver); GetModel()->SetPlayerEntityId(Engine::NullEntityId); break; case EventPlayerDied: mFadeTimerSecValue = 2.; PushViewState(ViewStatePlayerDied); GetModel()->SetPlayerEntityId(Engine::NullEntityId); break; case EventShipExplode: { // This event is fired when the ship explodes. Note: this event is not // sufficient for EventPlayerDied! Engine::LogDebug ("Received PlayerDied Event: %d", event->mEventType); // insert sprits that contains parts of the ship Engine::EntityBase *ship_entity = Engine::GetEntity (event->mEventUnsignedInt); vector3d position = ship_entity->mPhysicState->mPosition; vector3d orientation = ship_entity->mPhysicState->mOrientation; vector3d velocity = ship_entity->mPhysicState->mVelocity; unsigned int i; mShipPartsEntityIds.clear(); for (i = 0; i < mShipPartsSprite.GetSubSpriteCount(); i++) { Engine::EntityBase* part_sprite_particle = Engine::CreateEntity (GameEntityTypeShipPart); part_sprite_particle->mPhysicState->mPosition = position; part_sprite_particle->mPhysicState->mOrientation = orientation; part_sprite_particle->mPhysicState->mVelocity = velocity; part_sprite_particle->mPhysicState->mVelocity = vector3d (velocity[0] * (rand()/float(RAND_MAX)) * 1.7, 0., velocity[2] * (rand()/float(RAND_MAX)) * 1.5); part_sprite_particle->mPhysicState->mAngleVelocity = (rand()/float(RAND_MAX) - 0.5 ) * 100.; mShipPartsEntityIds.push_back(part_sprite_particle->mId); } // We do not need the entity anymore Engine::KillEntity(event->mEventUnsignedInt); return true; break; } default: Engine::LogWarning ("Received Event with type %d but don't know what to do with it!", event->mEventType); break; } return false; } /* * Module specific functions */ void View::UpdateCamera () { mCamera->SetEye ( 0., 9.5, 0. ); mCamera->SetPointOfIntrest ( 0., 0., 0. ); mCamera->SetUp ( 0., 0., -1. ); mCamera->Update (); } void View::DrawStars() { unsigned int i; float world_width, world_height; world_width = static_cast(mModel)->GetWorldWidth(); world_height = static_cast(mModel)->GetWorldHeight(); vector3d velocity (1., 0., 0.); glPushMatrix(); glTranslatef(-world_width * 0.5, 0, -world_height * 0.5); glColor3f (1., 1., 1.); glPointSize(2.); glBegin(GL_POINTS); float z_value; for (i = 0; i < mBackgroundStars.size(); i++) { // glPointSize (2. + 300. *mBackgroundStars.at(i).position[1]); z_value = mBackgroundStars.at(i).position[1] + 0.1; glColor3f (z_value, z_value, z_value); glVertex3f (mBackgroundStars.at(i).position[0] * world_width, -1., mBackgroundStars.at(i).position[2] * world_height); mBackgroundStars.at(i).position -= vector3d(Engine::GetFrameDuration() * 0.7 * mBackgroundStars.at(i).position[1] / world_width, 0., 0.); if (mBackgroundStars.at(i).position[0] < 0.) mBackgroundStars.at(i).position[0] += 1.; if (mBackgroundStars.at(i).position[0] >= 1.) mBackgroundStars.at(i).position[0] -= 1.; } glEnd(); glPopMatrix(); } void View::Draw() { PreDraw(); mFadeTimerSecValue -= GetModel()->GetFrameDuration(); // Actual Drawing UpdateCamera (); if (mDrawGrid) DrawGrid (); /* if (mDrawAxis) DrawAxis (); */ DrawWorld (); DrawUi (); // Perform post-Draw actions PostDraw(); } void View::DrawWorld() { std::map::iterator entity_iterator; Model *game_model = static_cast (mModel); DrawStars (); if ( game_model->GetGameState() != GameStateRunning) { return; } ViewBase::DrawWorld(); for (entity_iterator = game_model->mEntities.begin (); entity_iterator != game_model->mEntities.end(); entity_iterator++) { Engine::EntityBase* entity = entity_iterator->second; // Perform multiple drawing if the entity is at the border Physics* game_physics = (Physics*) game_model->mPhysics; float world_width = game_physics->GetWorldWidth(); float world_height = game_physics->GetWorldHeight(); // Drawing at the original position: glPushMatrix (); glTranslatef (entity->mPhysicState->mPosition[0], entity->mPhysicState->mPosition[1], entity->mPhysicState->mPosition[2]); glRotatef(entity->mPhysicState->mOrientation[0], 0., 0., 1.); glRotatef(entity->mPhysicState->mOrientation[1], 0., 1., 0.); glRotatef(entity->mPhysicState->mOrientation[2], 1., 0., 0.); glColor3f (1., 1., 1.); DrawEntity (entity); glPopMatrix (); // If we move out the right side if (entity->mPhysicState->mPosition[0] + entity->mPhysicState->mRadius * 2 >= world_width * 0.5) { glPushMatrix (); glTranslatef (entity->mPhysicState->mPosition[0] - world_width, entity->mPhysicState->mPosition[1], entity->mPhysicState->mPosition[2]); glRotatef(entity->mPhysicState->mOrientation[0], 0., 0., 1.); glRotatef(entity->mPhysicState->mOrientation[1], 0., 1., 0.); glRotatef(entity->mPhysicState->mOrientation[2], 1., 0., 0.); glColor3f (1., 1., 1.); DrawEntity (entity); glPopMatrix (); } // if we move out the left side if (entity->mPhysicState->mPosition[0] - entity->mPhysicState->mRadius * 2 < - world_width * 0.5) { glPushMatrix (); glTranslatef (entity->mPhysicState->mPosition[0] + world_width, entity->mPhysicState->mPosition[1], entity->mPhysicState->mPosition[2]); glRotatef(entity->mPhysicState->mOrientation[0], 0., 0., 1.); glRotatef(entity->mPhysicState->mOrientation[1], 0., 1., 0.); glRotatef(entity->mPhysicState->mOrientation[2], 1., 0., 0.); glColor3f (1., 1., 1.); DrawEntity (entity); glPopMatrix (); } // If we move out the bottom side if (entity->mPhysicState->mPosition[2] + entity->mPhysicState->mRadius * 2 >= world_height * 0.5) { glPushMatrix (); glTranslatef (entity->mPhysicState->mPosition[0], entity->mPhysicState->mPosition[1], entity->mPhysicState->mPosition[2] - world_height); glRotatef(entity->mPhysicState->mOrientation[0], 0., 0., 1.); glRotatef(entity->mPhysicState->mOrientation[1], 0., 1., 0.); glRotatef(entity->mPhysicState->mOrientation[2], 1., 0., 0.); glColor3f (1., 1., 1.); DrawEntity (entity); glPopMatrix (); } // if we move out the left side if (entity->mPhysicState->mPosition[2] - entity->mPhysicState->mRadius * 2 < - world_height* 0.5) { glPushMatrix (); glTranslatef (entity->mPhysicState->mPosition[0], entity->mPhysicState->mPosition[1], entity->mPhysicState->mPosition[2] + world_height); glRotatef(entity->mPhysicState->mOrientation[0], 0., 0., 1.); glRotatef(entity->mPhysicState->mOrientation[1], 0., 1., 0.); glRotatef(entity->mPhysicState->mOrientation[2], 1., 0., 0.); glColor3f (1., 1., 1.); DrawEntity (entity); glPopMatrix (); } } } /* * Userinterface */ void View::DrawUi () { glClearColor (0.1, 0.1, 0.1, 1.); screen_right = static_cast (Engine::GetWindowWidth()); screen_bottom = static_cast (Engine::GetWindowHeight()); // we switch to orthographic projection and draw the contents of the 2d // overlay on top of the previous drawings glMatrixMode (GL_PROJECTION); glPushMatrix (); glLoadIdentity (); // first we have to get the size of the current viewport to set up the // orthographic projection correctly GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); gluOrtho2D (viewport[0], viewport[2], viewport[3], viewport[1]); glMatrixMode (GL_MODELVIEW); glPushMatrix (); glLoadIdentity (); unsigned int game_state = GetModel()->GetGameState(); SelectFont("console.ttf"); SetFontJustification (Engine::FontJustificationLeft); GetController()->EnableTextinput(true); ViewState current_view_state = GetViewState(); SelectFont ("console.ttf size=12"); Engine::GUI::Label (99999, GetStringViewState(current_view_state), 8, 16); switch (current_view_state) { case ViewStateMainMenu: DrawUiMainMenu(); break; case ViewStateGameRunning: DrawUiGameRunning(); break; case ViewStatePaused: DrawUiGamePaused(); break; case ViewStatePlayerDied: DrawUiPlayerDied(); break; case ViewStateLevelComplete: DrawUiLevelComplete(); break; case ViewStateShowHighscore: DrawUiHighscore(); break; case ViewStateOptions: DrawUiOptions(); break; case ViewStateEnterPlayername: DrawUiEnterPlayername(); break; case ViewStateGameOver: DrawUiGameOver(); break; default: Engine::LogWarning ("Trying to draw unknown ViewState: %s (%d)", GetStringViewState (game_state), game_state); break; } glPopMatrix (); glMatrixMode (GL_PROJECTION); glPopMatrix (); glMatrixMode (GL_MODELVIEW); } void View::DrawPageTitle (const std::string& title) { SelectFont("console.ttf size=46 color=#808080"); float width, height; DrawGLStringMeasure(title.c_str(), &width, &height); float xpos = (screen_right - width) * 0.5 - 40; float ypos = 180; Engine::GUI::Label (4, title.c_str(), xpos - 2, ypos + 2); SelectFont("console.ttf size=46 color=#ffffff"); Engine::GUI::Label (4, title.c_str(), xpos, ypos); } void View::DrawUiMainMenu() { DrawPageTitle ("Asteroids"); SelectFont("console.ttf size=23"); if (Engine::GUI::Button (1, "New Game", screen_right * 0.5 - 100, 200, button_width, button_height)) { PushViewState(ViewStateEnterPlayername); } if (Engine::GUI::Button (2, "Options", screen_right * 0.5 - 100, 250, button_width, button_height)) { PushViewState (ViewStateOptions); } if (Engine::GUI::Button (3, "Highscores", screen_right * 0.5 - 100, 300, button_width, button_height)) { PushViewState(ViewStateShowHighscore); } if (Engine::GUI::Button (4, "Quit", screen_right * 0.5 - 100, 380, button_width, button_height)) { Engine::RunCommand("quit"); } } void View::DrawUiGameRunning() { // We choose a different font and also draw it aligned to the right as this // looks nicer with the points SelectFont ("AldotheApache.ttf size=20 color=#ffffff"); SetFontJustification (Engine::FontJustificationRight); std::ostringstream out_stream; out_stream << GetModel()->GetPlayerLives() << " x "; DrawGLString (screen_right - 64, screen_bottom - 20, out_stream.str().c_str()); mGUIShipSprite.DrawAt2D (screen_right - 32 - 10, screen_bottom - 16); out_stream.str(""); out_stream << GetModel()->GetPoints(); DrawGLString (screen_right - 30, 40, out_stream.str().c_str()); // revert the font justification SetFontJustification (Engine::FontJustificationLeft); if (Engine::GUI::CheckKeyPress(SDLK_ESCAPE)) { PushViewState(ViewStatePaused); GetModel()->SetGameState(GameStatePaused); } } void View::DrawUiGameOver() { DrawPageTitle ("Game Over"); SelectFont("console.ttf size=23 color=#ffffff"); if (GetModel()->GetPlayerLives() == 0) { Engine::GUI::LabelCentered(5, "That was pathetic!", screen_right * 0.5, screen_bottom * 0.5 - 8); } else { Engine::GUI::LabelCentered (6, "You earned yourself the", screen_right * 0.5, screen_bottom * 0.5 - 32); Engine::GUI::LabelCentered (7, "You Rock(TM) award.", screen_right * 0.5, screen_bottom * 0.5 + 0); Engine::GUI::LabelCentered (8, "Go tell your friends.", screen_right * 0.5, screen_bottom * 0.5 + 32); } if (mFadeTimerSecValue > 0.) return; if (Engine::GUI::Button (2, "Continue...", (screen_right - button_width) * 0.5, screen_bottom * 0.5 + 80, button_width, button_height)) { // We do not want the background to show the remaining bits of the game GetModel()->SetGameState(GameStatePaused); PopViewState(); PushViewState(ViewStateShowHighscore); } } void View::DrawUiLevelComplete() { DrawPageTitle ("Level Complete!"); SelectFont ("console.ttf size=23"); if(Engine::GUI::Button (1, "Next level ...", (screen_right - button_width) * 0.5, screen_bottom * 0.5 + 60, button_width, button_height)) { PopViewState(); GetModel()->ProceedToNextLevel(); } } void View::DrawUiGamePaused() { DrawPageTitle ("Game Paused"); SelectFont ("console.ttf size=23"); if (Engine::GUI::Button (1, "Resume Game", screen_right * 0.5 - 100, 200, button_width, button_height)) { PopViewState(); GetModel()->SetGameState(GameStateRunning); } if (Engine::GUI::Button (2, "Options", screen_right * 0.5 - 100, 250, button_width, button_height)) { PushViewState (ViewStateOptions); } if (Engine::GUI::Button (3, "Abort Game", screen_right * 0.5 - 100, 300, button_width, button_height)) { ResetViewState(); PushViewState(ViewStateMainMenu); GetModel()->SetGameState(GameStatePaused); } if (Engine::GUI::Button (4, "Quit", screen_right * 0.5 - 100, 380, button_width, button_height)) { Engine::RunCommand("quit"); } } void View::DrawUiPlayerDied() { DrawPageTitle ("You died!"); SelectFont ("console.ttf size=23"); if (mFadeTimerSecValue > 0.) return; if (Engine::GUI::Button (1, "Continue", screen_right * 0.5 - 100, 380, button_width, button_height)) { PopViewState(); GetModel()->ReloadLevel(); GetModel()->SetGameState(GameStateRunning); } } /** \brief Draws an entry in the form .... * * The amount of dots is also computed within this function. Not necessarily * fast, however it works for now. */ void View::DrawHighscoreEntry (float x, float y, float entry_width, const std::string &name, unsigned int points) { const float width_buffer = 30.; float width, height; float name_width, points_value_width; // Measure and draw the name DrawGLStringMeasure (name.c_str(), &name_width, &height); name_width = fabsf(name_width); Engine::DrawGLString ( x, y, name.c_str()); // Compute the width of the points value when it is printed out std::ostringstream points_value_stream; points_value_stream << points; DrawGLStringMeasure (points_value_stream.str().c_str(), &points_value_width, &height); points_value_width = fabsf(points_value_width); // Compute the amount of dots. This is done by iteratively adding a dot '.' // to a stream and check whether it has the desired width. float dots_width = 0.; std::ostringstream dots_stream; while (ceil(name_width) + dots_width + points_value_width + width_buffer < entry_width) { dots_stream << "."; DrawGLStringMeasure (dots_stream.str().c_str(), &dots_width, &height); dots_width = fabsf(dots_width); } // Drawing of the dots and the points value Engine::DrawGLString ( x + entry_width - points_value_width - dots_width - 10, y, dots_stream.str().c_str()); Engine::DrawGLString ( x + entry_width - points_value_width, y, points_value_stream.str().c_str()); } void View::DrawUiHighscore() { DrawPageTitle ("Highscores"); SelectFont ("console.ttf size=23"); unsigned int entry_length = 32; float char_width, height; DrawGLStringMeasure ("M", &char_width, &height); height = fabsf(height); float entry_width = fabsf(char_width * entry_length); float x = screen_right * 0.5 - entry_width * 0.5; float y = screen_bottom * 0.5 - 8 - 128; // then we do the drawings y += 60; std::list::iterator highscore_iter = GetModel()->mHighscoreList.begin(); SetFontJustification(Engine::FontJustificationLeft); unsigned int i = 0; while (highscore_iter != GetModel()->mHighscoreList.end()) { // Check whether we have to highlight an entry (such as when entering // the name) if (GetModel()->mNewestHighscoreEntryIndex < GetModel()->mHighscoreList.size() && GetModel()->mNewestHighscoreEntryIndex == i) { // we highlight the newest entry SelectFont("console.ttf color=#e8d500 size=23"); } else { SelectFont("console.ttf color=#ffffff size=23"); } DrawHighscoreEntry ( x, y, entry_width, highscore_iter->name, highscore_iter->points); // go down one line y += height * 1.3; i++; highscore_iter++; } if (Engine::GUI::Button (1, "Back to Menu", screen_right * 0.5 - 250 * 0.5, y + 16, button_width, button_height)) { PopViewState(); } } void View::DrawUiOptions() { DrawPageTitle ("Options"); SelectFont ("console.ttf size=23"); Engine::GUI::Label (10, "Effects Volume: ", screen_right * 0.5 - 150, 240); float effects_volume = Engine::GetEffectsVolume(); if (Engine::GUI::VerticalSlider (1, screen_right * 0.5 - 100, 250, 250, 16, 0., 1., effects_volume)) { Engine::LogDebug ("Setting effects volume to: %f", effects_volume); Engine::SetEffectsVolume(effects_volume); } Engine::GUI::Label (11, "Music Volume: ", screen_right * 0.5 - 150, 300); float music_volume = Engine::GetMusicVolume(); if (Engine::GUI::VerticalSlider (2, screen_right * 0.5 - 100, 310, 250, 16, 0., 1., music_volume)) { Engine::LogDebug ("Setting music volume to: %f", music_volume); Engine::SetMusicVolume(music_volume); } if (Engine::GUI::Button (5, "Back", screen_right * 0.5 - 100, 380, button_width, button_height)) { PopViewState(); } } void View::DrawUiEnterPlayername() { DrawPageTitle ("Asteroids"); SelectFont ("console.ttf size=23"); // Enter your name std::string player_name = GetModel()->GetPlayerName(); SelectFont("console.ttf size=23"); Engine::GUI::Label (1, "Enter your name: ", screen_right * 0.5 - 220, 250); if (Engine::GUI::LineEdit (2, screen_right * 0.5 + 20, 238, player_name, 16)) { GetModel()->SetPlayerName(player_name); } if (Engine::GUI::Button (3, "Start Game", screen_right - 180 - 20, 500, 180, 40)) { PopViewState(); PushViewState(ViewStateGameRunning); GetModel()->OnNewGame(); GetModel()->SetGameState(GameStateRunning); } if (Engine::GUI::Button (5, "Back", 20, 500, 180, 40)) { PopViewState(); } } /* * Entities */ void View::DrawEntity (Engine::EntityBase *entity) { if (entity->mType == GameEntityTypeAsteroid) DrawAsteroid ((AsteroidEntity*) entity); else if (entity->mType == GameEntityTypeShip) DrawShip ((ShipEntity*) entity); else if (entity->mType == GameEntityTypeRocket) DrawRocket ((RocketEntity*) entity); else if (entity->mType == GameEntityTypeShipPart) DrawShipPart (entity); else { Engine::LogError ("Cannot draw entity: unknown type '%d'", entity->mType); } } /// \todo: Update of the animation ?? void View::DrawShip (ShipEntity *ship) { if (!ship->mAlive) return; mShipSprite.SetScale (2. * ship->mPhysicState->mRadius / mShipSprite.GetHeight()); mShipThrustSprite.SetScale (2. * ship->mPhysicState->mRadius / mShipSprite.GetHeight()); if (ship->mState == ShipEntity::Accelerating) { mShipThrustSprite.UpdateAnimation (Engine::GetFrameDuration()); mShipThrustSprite.DrawAt(-0.5, 0., 0.); } mShipSprite.DrawAt(0., 0., 0.); #ifdef DRAW_BOUNDARIES glColor3f (1., 1., 1.); DrawCircle (ship->mPhysicState->mRadius, 20); #endif } void View::DrawAsteroid (AsteroidEntity *asteroid) { mAsteroidSprite.SetScale (2. * asteroid->mPhysicState->mRadius / mAsteroidSprite.GetWidth()); mAsteroidSprite.DrawAt(0., 0., 0.); #ifdef DRAW_BOUNDARIES glColor3f (1., 1., 1.); DrawCircle (asteroid->mPhysicState->mRadius, 20); #endif } void View::DrawRocket (RocketEntity *rocket) { glColor3f (1., 1., 1.); glBegin (GL_QUADS); glVertex3f (-0.25, 0., 0.05); glVertex3f (0.05, 0., 0.05); glVertex3f (0.05, 0., -0.05); glVertex3f (-0.25, 0., -0.05); glEnd (); } void View::DrawShipPart (Engine::EntityBase *entity) { unsigned int i; mShipPartsSprite.SetScale (1. / mShipSprite.GetHeight()); for (i = 0; i < mShipPartsEntityIds.size(); i++) { if (mShipPartsEntityIds.at(i) == entity->mId) { mShipPartsSprite.DrawSubAt (i, 0., 0., 0.); } } #ifdef DRAW_BOUNDARIES glColor3f (1., 1., 1.); DrawCircle (entity->mPhysicState->mRadius, 20); #endif } }